diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000000..57aaca2df0 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,9 @@ +# These are supported funding model platforms + +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: Ryujinx +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +custom: # Replace with a single custom sponsorship URL diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000000..cca6c60806 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,27 @@ +name: "Build job" +on: + push: + branches: + - master + pull_request: + branches: + - '*' +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macOS-latest, windows-latest] + dotnet: ['3.1.100'] + environment: ['Debug', 'Release', 'Profile Debug', 'Profile Release'] + name: ${{ matrix.environment }} build (Dotnet ${{ matrix.dotnet }}, OS ${{ matrix.os }}) + steps: + - uses: actions/checkout@master + - name: Setup dotnet + uses: actions/setup-dotnet@v1 + with: + dotnet-version: ${{ matrix.dotnet }} + - name: Build + run: dotnet build -c "${{ matrix.environment }}" + - name: Test + run: dotnet test -c "${{ matrix.environment }}" \ No newline at end of file diff --git a/.gitignore b/.gitignore index 82d9719b59..c0af260973 100644 --- a/.gitignore +++ b/.gitignore @@ -158,3 +158,9 @@ $RECYCLE.BIN/ # Mac desktop service store files .DS_Store + +# VS Launch Settings +launchSettings.json + +# NetCore Publishing Profiles +PublishProfiles/ diff --git a/ChocolArm64/ChocolArm64.csproj b/ARMeilleure/ARMeilleure.csproj similarity index 64% rename from ChocolArm64/ChocolArm64.csproj rename to ARMeilleure/ARMeilleure.csproj index 1156e361f5..4f55243fed 100644 --- a/ChocolArm64/ChocolArm64.csproj +++ b/ARMeilleure/ARMeilleure.csproj @@ -1,8 +1,8 @@ - netcoreapp2.1 - win10-x64;osx-x64;linux-x64 + netcoreapp3.0 + win-x64;osx-x64;linux-x64 @@ -14,7 +14,7 @@ - + diff --git a/ARMeilleure/CodeGen/CompiledFunction.cs b/ARMeilleure/CodeGen/CompiledFunction.cs new file mode 100644 index 0000000000..61e89c2401 --- /dev/null +++ b/ARMeilleure/CodeGen/CompiledFunction.cs @@ -0,0 +1,17 @@ +using ARMeilleure.CodeGen.Unwinding; + +namespace ARMeilleure.CodeGen +{ + struct CompiledFunction + { + public byte[] Code { get; } + + public UnwindInfo UnwindInfo { get; } + + public CompiledFunction(byte[] code, UnwindInfo unwindInfo) + { + Code = code; + UnwindInfo = unwindInfo; + } + } +} \ No newline at end of file diff --git a/ARMeilleure/CodeGen/Optimizations/ConstantFolding.cs b/ARMeilleure/CodeGen/Optimizations/ConstantFolding.cs new file mode 100644 index 0000000000..84eedee0e5 --- /dev/null +++ b/ARMeilleure/CodeGen/Optimizations/ConstantFolding.cs @@ -0,0 +1,258 @@ +using ARMeilleure.IntermediateRepresentation; +using System; + +using static ARMeilleure.IntermediateRepresentation.OperandHelper; + +namespace ARMeilleure.CodeGen.Optimizations +{ + static class ConstantFolding + { + public static void RunPass(Operation operation) + { + if (operation.Destination == null || operation.SourcesCount == 0) + { + return; + } + + if (!AreAllSourcesConstant(operation)) + { + return; + } + + OperandType type = operation.Destination.Type; + + switch (operation.Instruction) + { + case Instruction.Add: + 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.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.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++) + { + if (operation.GetSource(index).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/ARMeilleure/CodeGen/Optimizations/Optimizer.cs b/ARMeilleure/CodeGen/Optimizations/Optimizer.cs new file mode 100644 index 0000000000..c01a8f1e7f --- /dev/null +++ b/ARMeilleure/CodeGen/Optimizations/Optimizer.cs @@ -0,0 +1,126 @@ +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace ARMeilleure.CodeGen.Optimizations +{ + static class Optimizer + { + public static void RunPass(ControlFlowGraph cfg) + { + bool modified; + + do + { + modified = false; + + foreach (BasicBlock block in cfg.Blocks) + { + LinkedListNode node = block.Operations.First; + + while (node != null) + { + LinkedListNode nextNode = node.Next; + + bool isUnused = IsUnused(node.Value); + + if (!(node.Value is Operation operation) || isUnused) + { + if (isUnused) + { + RemoveNode(block, node); + + modified = true; + } + + node = nextNode; + + continue; + } + + ConstantFolding.RunPass(operation); + + Simplification.RunPass(operation); + + if (DestIsLocalVar(operation) && IsPropagableCopy(operation)) + { + PropagateCopy(operation); + + RemoveNode(block, node); + + modified = true; + } + + node = nextNode; + } + } + } + while (modified); + } + + private static void PropagateCopy(Operation copyOp) + { + // Propagate copy source operand to all uses of the destination operand. + Operand dest = copyOp.Destination; + Operand source = copyOp.GetSource(0); + + Node[] uses = dest.Uses.ToArray(); + + foreach (Node 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, LinkedListNode llNode) + { + // 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(llNode); + + Node node = llNode.Value; + + for (int index = 0; index < node.SourcesCount; index++) + { + node.SetSource(index, null); + } + + Debug.Assert(node.Destination == null || node.Destination.Uses.Count == 0); + + node.Destination = null; + } + + private static bool IsUnused(Node node) + { + return DestIsLocalVar(node) && node.Destination.Uses.Count == 0 && !HasSideEffects(node); + } + + private static bool DestIsLocalVar(Node node) + { + return node.Destination != null && node.Destination.Kind == OperandKind.LocalVariable; + } + + private static bool HasSideEffects(Node node) + { + return (node is Operation operation) && operation.Instruction == Instruction.Call; + } + + 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/ARMeilleure/CodeGen/Optimizations/Simplification.cs b/ARMeilleure/CodeGen/Optimizations/Simplification.cs new file mode 100644 index 0000000000..cafc025ca7 --- /dev/null +++ b/ARMeilleure/CodeGen/Optimizations/Simplification.cs @@ -0,0 +1,157 @@ +using ARMeilleure.IntermediateRepresentation; +using System; + +using static ARMeilleure.IntermediateRepresentation.OperandHelper; + +namespace ARMeilleure.CodeGen.Optimizations +{ + static class Simplification + { + public static void RunPass(Operation operation) + { + switch (operation.Instruction) + { + case Instruction.Add: + case Instruction.BitwiseExclusiveOr: + TryEliminateBinaryOpComutative(operation, 0); + break; + + case Instruction.BitwiseAnd: + TryEliminateBitwiseAnd(operation); + break; + + case Instruction.BitwiseOr: + TryEliminateBitwiseOr(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(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 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/ARMeilleure/CodeGen/RegisterAllocators/AllocationResult.cs b/ARMeilleure/CodeGen/RegisterAllocators/AllocationResult.cs new file mode 100644 index 0000000000..94ac6991b0 --- /dev/null +++ b/ARMeilleure/CodeGen/RegisterAllocators/AllocationResult.cs @@ -0,0 +1,19 @@ +namespace ARMeilleure.CodeGen.RegisterAllocators +{ + 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/ARMeilleure/CodeGen/RegisterAllocators/CopyResolver.cs b/ARMeilleure/CodeGen/RegisterAllocators/CopyResolver.cs new file mode 100644 index 0000000000..65901e80cc --- /dev/null +++ b/ARMeilleure/CodeGen/RegisterAllocators/CopyResolver.cs @@ -0,0 +1,246 @@ +using ARMeilleure.IntermediateRepresentation; +using System; +using System.Collections.Generic; + +namespace ARMeilleure.CodeGen.RegisterAllocators +{ + class CopyResolver + { + private class ParallelCopy + { + private 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 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(new Operation(Instruction.Copy, x, y)); + } + + private static void EmitXorSwap(List sequence, Operand x, Operand y) + { + sequence.Add(new Operation(Instruction.BitwiseExclusiveOr, x, x, y)); + sequence.Add(new Operation(Instruction.BitwiseExclusiveOr, y, y, x)); + sequence.Add(new Operation(Instruction.BitwiseExclusiveOr, x, x, y)); + } + } + + private Queue _fillQueue = new Queue(); + private Queue _spillQueue = new Queue(); + + private ParallelCopy _parallelCopy; + + public bool HasCopy { get; private set; } + + public CopyResolver() + { + _fillQueue = new Queue(); + _spillQueue = new Queue(); + + _parallelCopy = new ParallelCopy(); + } + + 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) + { + Operand register = GetRegister(right.Register, type); + + Operand offset = new Operand(left.SpillOffset); + + _fillQueue.Enqueue(new Operation(Instruction.Fill, register, offset)); + + HasCopy = true; + } + + private void AddSplitSpill(LiveInterval left, LiveInterval right, OperandType type) + { + Operand offset = new Operand(right.SpillOffset); + + Operand register = GetRegister(left.Register, type); + + _spillQueue.Enqueue(new Operation(Instruction.Spill, null, offset, register)); + + HasCopy = true; + } + + private void AddSplitCopy(LiveInterval left, LiveInterval right, OperandType type) + { + _parallelCopy.AddCopy(right.Register, left.Register, type); + + HasCopy = true; + } + + public Operation[] Sequence() + { + List sequence = new List(); + + while (_spillQueue.TryDequeue(out Operation spillOp)) + { + sequence.Add(spillOp); + } + + _parallelCopy.Sequence(sequence); + + while (_fillQueue.TryDequeue(out Operation fillOp)) + { + sequence.Add(fillOp); + } + + return sequence.ToArray(); + } + + private static Operand GetRegister(Register reg, OperandType type) + { + return new Operand(reg.Index, reg.Type, type); + } + } +} \ No newline at end of file diff --git a/ARMeilleure/CodeGen/RegisterAllocators/HybridAllocator.cs b/ARMeilleure/CodeGen/RegisterAllocators/HybridAllocator.cs new file mode 100644 index 0000000000..9a827420bb --- /dev/null +++ b/ARMeilleure/CodeGen/RegisterAllocators/HybridAllocator.cs @@ -0,0 +1,382 @@ +using ARMeilleure.Common; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System.Collections.Generic; +using System.Diagnostics; + +using static ARMeilleure.IntermediateRepresentation.OperandHelper; + +namespace ARMeilleure.CodeGen.RegisterAllocators +{ + class HybridAllocator : IRegisterAllocator + { + private const int RegistersCount = 16; + private const int MaxIROperands = 4; + + private 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 class LocalInfo + { + public int Uses { get; set; } + public int UseCount { get; set; } + + public bool PreAllocated { get; set; } + public int Register { get; set; } + public int SpillOffset { get; set; } + + public int Sequence { get; set; } + + public Operand Temp { get; set; } + + public OperandType Type { get; } + + private int _first; + private int _last; + + public bool IsBlockLocal => _first == _last; + + public LocalInfo(OperandType type, int uses) + { + Uses = uses; + Type = type; + + _first = -1; + _last = -1; + } + + public void SetBlockIndex(int blkIndex) + { + if (_first == -1 || blkIndex < _first) + { + _first = blkIndex; + } + + if (_last == -1 || blkIndex > _last) + { + _last = blkIndex; + } + } + } + + public AllocationResult RunPass( + ControlFlowGraph cfg, + StackAllocator stackAlloc, + RegisterMasks regMasks) + { + int intUsedRegisters = 0; + int vecUsedRegisters = 0; + + int intFreeRegisters = regMasks.IntAvailableRegisters; + int vecFreeRegisters = regMasks.VecAvailableRegisters; + + BlockInfo[] blockInfo = new BlockInfo[cfg.Blocks.Count]; + + List locInfo = new List(); + + for (int index = cfg.PostOrderBlocks.Length - 1; index >= 0; index--) + { + BasicBlock block = cfg.PostOrderBlocks[index]; + + int intFixedRegisters = 0; + int vecFixedRegisters = 0; + + bool hasCall = false; + + foreach (Node node in block.Operations) + { + if (node is Operation operation && operation.Instruction == Instruction.Call) + { + hasCall = true; + } + + for (int srcIndex = 0; srcIndex < node.SourcesCount; srcIndex++) + { + Operand source = node.GetSource(srcIndex); + + if (source.Kind == OperandKind.LocalVariable) + { + locInfo[source.AsInt32() - 1].SetBlockIndex(block.Index); + } + } + + for (int dstIndex = 0; dstIndex < node.DestinationsCount; dstIndex++) + { + Operand dest = node.GetDestination(dstIndex); + + if (dest.Kind == OperandKind.LocalVariable) + { + LocalInfo info; + + if (dest.Value != 0) + { + info = locInfo[dest.AsInt32() - 1]; + } + else + { + dest.NumberLocal(locInfo.Count + 1); + + info = new LocalInfo(dest.Type, UsesCount(dest)); + + locInfo.Add(info); + } + + info.SetBlockIndex(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]; + + BlockInfo blkInfo = 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 (LinkedListNode llNode = block.Operations.First; llNode != null; llNode = llNode.Next) + { + Node node = llNode.Value; + + int intLocalUse = 0; + int vecLocalUse = 0; + + for (int srcIndex = 0; srcIndex < node.SourcesCount; srcIndex++) + { + Operand source = node.GetSource(srcIndex); + + if (source.Kind != OperandKind.LocalVariable) + { + continue; + } + + LocalInfo info = locInfo[source.AsInt32() - 1]; + + info.UseCount++; + + Debug.Assert(info.UseCount <= info.Uses); + + if (info.Register != -1) + { + node.SetSource(srcIndex, Register(info.Register, source.Type.ToRegisterType(), source.Type)); + + if (info.UseCount == info.Uses && !info.PreAllocated) + { + if (source.Type.IsInteger()) + { + intLocalFreeRegisters |= 1 << info.Register; + } + else + { + vecLocalFreeRegisters |= 1 << info.Register; + } + } + } + else + { + Operand temp = info.Temp; + + if (temp == null || info.Sequence != sequence) + { + temp = source.Type.IsInteger() + ? GetSpillTemp(source, intSpillTempRegisters, ref intLocalUse) + : GetSpillTemp(source, vecSpillTempRegisters, ref vecLocalUse); + + info.Sequence = sequence; + info.Temp = temp; + } + + node.SetSource(srcIndex, temp); + + Operation fillOp = new Operation(Instruction.Fill, temp, Const(info.SpillOffset)); + + block.Operations.AddBefore(llNode, fillOp); + } + } + + int intLocalAsg = 0; + int vecLocalAsg = 0; + + for (int dstIndex = 0; dstIndex < node.DestinationsCount; dstIndex++) + { + Operand dest = node.GetDestination(dstIndex); + + if (dest.Kind != OperandKind.LocalVariable) + { + continue; + } + + LocalInfo info = locInfo[dest.AsInt32() - 1]; + + if (info.UseCount == 0 && !info.PreAllocated) + { + int mask = dest.Type.IsInteger() + ? intLocalFreeRegisters + : vecLocalFreeRegisters; + + if (info.IsBlockLocal && mask != 0) + { + int selectedReg = BitUtils.LowestBitSet(mask); + + info.Register = selectedReg; + + if (dest.Type.IsInteger()) + { + intLocalFreeRegisters &= ~(1 << selectedReg); + intUsedRegisters |= 1 << selectedReg; + } + else + { + vecLocalFreeRegisters &= ~(1 << selectedReg); + vecUsedRegisters |= 1 << selectedReg; + } + } + else + { + info.Register = -1; + info.SpillOffset = stackAlloc.Allocate(dest.Type.GetSizeInBytes()); + } + } + + info.UseCount++; + + Debug.Assert(info.UseCount <= info.Uses); + + if (info.Register != -1) + { + node.SetDestination(dstIndex, Register(info.Register, dest.Type.ToRegisterType(), dest.Type)); + } + else + { + Operand temp = info.Temp; + + if (temp == null || info.Sequence != sequence) + { + temp = dest.Type.IsInteger() + ? GetSpillTemp(dest, intSpillTempRegisters, ref intLocalAsg) + : GetSpillTemp(dest, vecSpillTempRegisters, ref vecLocalAsg); + + info.Sequence = sequence; + info.Temp = temp; + } + + node.SetDestination(dstIndex, temp); + + Operation spillOp = new Operation(Instruction.Spill, null, Const(info.SpillOffset), temp); + + llNode = block.Operations.AddAfter(llNode, 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 = BitUtils.LowestBitSet(freeMask & ~useMask); + + useMask |= 1 << selectedReg; + + return Register(selectedReg, local.Type.ToRegisterType(), local.Type); + } + + private static int UsesCount(Operand local) + { + return local.Assignments.Count + local.Uses.Count; + } + + private static IEnumerable Successors(BasicBlock block) + { + if (block.Next != null) + { + yield return block.Next; + } + + if (block.Branch != null) + { + yield return block.Branch; + } + } + } +} \ No newline at end of file diff --git a/ARMeilleure/CodeGen/RegisterAllocators/IRegisterAllocator.cs b/ARMeilleure/CodeGen/RegisterAllocators/IRegisterAllocator.cs new file mode 100644 index 0000000000..8f236c2533 --- /dev/null +++ b/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/ARMeilleure/CodeGen/RegisterAllocators/LinearScanAllocator.cs b/ARMeilleure/CodeGen/RegisterAllocators/LinearScanAllocator.cs new file mode 100644 index 0000000000..6d5ecc1416 --- /dev/null +++ b/ARMeilleure/CodeGen/RegisterAllocators/LinearScanAllocator.cs @@ -0,0 +1,1019 @@ +using ARMeilleure.Common; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +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 const int RegistersCount = 16; + + private HashSet _blockEdges; + + private LiveRange[] _blockRanges; + + private BitMap[] _blockLiveIn; + + private List _intervals; + + private LiveInterval[] _parentIntervals; + + private List> _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; } + + public AllocationContext(StackAllocator stackAlloc, RegisterMasks masks, int intervalsCount) + { + StackAlloc = stackAlloc; + Masks = masks; + + Active = new BitMap(intervalsCount); + Inactive = new BitMap(intervalsCount); + } + + 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); + + AllocationContext 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.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); + } + + for (int index = 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) + { + // Check active intervals that already ended. + foreach (int iIndex in context.Active) + { + LiveInterval interval = _intervals[iIndex]; + + 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]; + + if (interval.GetEnd() < current.GetStart()) + { + context.Inactive.Clear(iIndex); + } + else if (interval.Overlaps(current.GetStart())) + { + context.MoveInactiveToActive(iIndex); + } + } + + if (!TryAllocateRegWithoutSpill(context, current, cIndex)) + { + AllocateRegWithSpill(context, current, cIndex); + } + } + + private bool TryAllocateRegWithoutSpill(AllocationContext context, LiveInterval current, int cIndex) + { + RegisterType regType = current.Local.Type.ToRegisterType(); + + int availableRegisters = context.Masks.GetAvailableRegisters(regType); + + int[] freePositions = new int[RegistersCount]; + + for (int index = 0; index < RegistersCount; index++) + { + if ((availableRegisters & (1 << index)) != 0) + { + freePositions[index] = int.MaxValue; + } + } + + foreach (int iIndex in context.Active) + { + LiveInterval interval = _intervals[iIndex]; + + if (interval.Register.Type == regType) + { + freePositions[interval.Register.Index] = 0; + } + } + + foreach (int iIndex in context.Inactive) + { + LiveInterval interval = _intervals[iIndex]; + + if (interval.Register.Type == regType) + { + int overlapPosition = interval.GetOverlapPosition(current); + + if (overlapPosition != LiveInterval.NotFound && freePositions[interval.Register.Index] > overlapPosition) + { + freePositions[interval.Register.Index] = 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()) + { + Debug.Assert(selectedNextUse > current.GetStart(), "Trying to split interval at the start."); + + LiveInterval splitChild = current.Split(selectedNextUse); + + if (splitChild.UsesCount != 0) + { + Debug.Assert(splitChild.GetStart() > current.GetStart(), "Split interval has an invalid start position."); + + InsertInterval(splitChild); + } + 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) + { + RegisterType regType = current.Local.Type.ToRegisterType(); + + int availableRegisters = context.Masks.GetAvailableRegisters(regType); + + int[] usePositions = new int[RegistersCount]; + int[] blockedPositions = new int[RegistersCount]; + + for (int index = 0; index < RegistersCount; index++) + { + if ((availableRegisters & (1 << index)) != 0) + { + usePositions[index] = int.MaxValue; + + blockedPositions[index] = int.MaxValue; + } + } + + void SetUsePosition(int index, int position) + { + usePositions[index] = Math.Min(usePositions[index], position); + } + + void SetBlockedPosition(int index, int position) + { + blockedPositions[index] = Math.Min(blockedPositions[index], position); + + SetUsePosition(index, position); + } + + foreach (int iIndex in context.Active) + { + LiveInterval interval = _intervals[iIndex]; + + if (!interval.IsFixed && interval.Register.Type == regType) + { + int nextUse = interval.NextUseAfter(current.GetStart()); + + if (nextUse != -1) + { + SetUsePosition(interval.Register.Index, nextUse); + } + } + } + + foreach (int iIndex in context.Inactive) + { + LiveInterval interval = _intervals[iIndex]; + + if (!interval.IsFixed && interval.Register.Type == regType && interval.Overlaps(current)) + { + int nextUse = interval.NextUseAfter(current.GetStart()); + + if (nextUse != -1) + { + SetUsePosition(interval.Register.Index, nextUse); + } + } + } + + foreach (int iIndex in context.Active) + { + LiveInterval interval = _intervals[iIndex]; + + if (interval.IsFixed && interval.Register.Type == regType) + { + SetBlockedPosition(interval.Register.Index, 0); + } + } + + foreach (int iIndex in context.Inactive) + { + LiveInterval interval = _intervals[iIndex]; + + if (interval.IsFixed && interval.Register.Type == regType) + { + int overlapPosition = interval.GetOverlapPosition(current); + + if (overlapPosition != LiveInterval.NotFound) + { + SetBlockedPosition(interval.Register.Index, overlapPosition); + } + } + } + + 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); + + 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); + + 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); + } + else + { + Spill(context, splitChild); + } + + SplitAndSpillOverlappingIntervals(context, current); + + context.Active.Set(cIndex); + } + } + + private static int GetHighestValueIndex(int[] array) + { + int higuest = array[0]; + + if (higuest == int.MaxValue) + { + return 0; + } + + int selected = 0; + + for (int index = 1; index < array.Length; index++) + { + int current = array[index]; + + if (higuest < current) + { + higuest = current; + selected = index; + + if (current == int.MaxValue) + { + break; + } + } + } + + return selected; + } + + private void SplitAndSpillOverlappingIntervals(AllocationContext context, LiveInterval current) + { + foreach (int iIndex in context.Active) + { + LiveInterval interval = _intervals[iIndex]; + + if (!interval.IsFixed && interval.Register == current.Register) + { + SplitAndSpillOverlappingInterval(context, current, interval); + + 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); + + context.Inactive.Clear(iIndex); + } + } + } + + private void SplitAndSpillOverlappingInterval( + AllocationContext context, + LiveInterval current, + LiveInterval interval) + { + // 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); + } + else + { + Spill(context, splitChild); + } + } + + private void InsertInterval(LiveInterval interval) + { + 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) + { + CopyResolver copyResolver = new CopyResolver(); + + if (copyResolvers.TryAdd(position, copyResolver)) + { + return copyResolver; + } + + return copyResolvers[position]; + } + + foreach (LiveInterval interval in _intervals.Where(x => x.IsSplit)) + { + LiveInterval previous = interval; + + foreach (LiveInterval splitChild in interval.SplitChilds()) + { + 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; + + LinkedListNode node = GetOperationNode(splitPosition); + + Operation[] sequence = copyResolver.Sequence(); + + node = node.List.AddBefore(node, sequence[0]); + + for (int index = 1; index < sequence.Length; index++) + { + node = node.List.AddAfter(node, sequence[index]); + } + } + } + + private void InsertSplitCopiesAtEdges(ControlFlowGraph cfg) + { + int blocksCount = cfg.Blocks.Count; + + bool IsSplitEdgeBlock(BasicBlock block) + { + return block.Index >= blocksCount; + } + + for (LinkedListNode node = cfg.Blocks.First; node != null; node = node.Next) + { + BasicBlock block = node.Value; + + if (IsSplitEdgeBlock(block)) + { + continue; + } + + bool hasSingleOrNoSuccessor = block.Next == null || block.Branch == null; + + foreach (BasicBlock successor in Successors(block)) + { + 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 = Successors(successor).First().Index; + } + + CopyResolver copyResolver = new CopyResolver(); + + 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 != null && right != null && left != right) + { + copyResolver.AddSplit(left, right); + } + } + + if (!copyResolver.HasCopy) + { + continue; + } + + Operation[] sequence = copyResolver.Sequence(); + + if (hasSingleOrNoSuccessor) + { + foreach (Operation operation in sequence) + { + block.Append(operation); + } + } + else if (successor.Predecessors.Count == 1) + { + LinkedListNode prependNode = successor.Operations.AddFirst(sequence[0]); + + for (int index = 1; index < sequence.Length; index++) + { + Operation operation = sequence[index]; + + prependNode = successor.Operations.AddAfter(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()) + { + Node operation = GetOperationNode(usePosition).Value; + + for (int index = 0; index < operation.SourcesCount; index++) + { + Operand source = operation.GetSource(index); + + if (source == current.Local) + { + operation.SetSource(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 new Operand( + interval.Register.Index, + interval.Register.Type, + interval.Local.Type); + } + + private LinkedListNode GetOperationNode(int position) + { + return _operationNodes[position / InstructionGap]; + } + + private void NumberLocals(ControlFlowGraph cfg) + { + _operationNodes = new List>(); + + _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))); + } + + HashSet visited = new HashSet(); + + _operationsCount = 0; + + for (int index = cfg.PostOrderBlocks.Length - 1; index >= 0; index--) + { + BasicBlock block = cfg.PostOrderBlocks[index]; + + for (LinkedListNode node = block.Operations.First; node != null; node = node.Next) + { + _operationNodes.Add(node); + + Node operation = node.Value; + + foreach (Operand dest in Destinations(operation)) + { + if (dest.Kind == OperandKind.LocalVariable && visited.Add(dest)) + { + dest.NumberLocal(_intervals.Count); + + _intervals.Add(new LiveInterval(dest)); + } + } + } + + _operationsCount += block.Operations.Count * InstructionGap; + + if (block.Operations.Count == 0) + { + // Pretend we have a dummy instruction on the empty block. + _operationNodes.Add(null); + + _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. + foreach (BasicBlock block in cfg.Blocks) + { + BitMap liveGen = new BitMap(mapSize); + BitMap liveKill = new BitMap(mapSize); + + foreach (Node node in block.Operations) + { + foreach (Operand source in Sources(node)) + { + int id = GetOperandId(source); + + if (!liveKill.IsSet(id)) + { + liveGen.Set(id); + } + } + + foreach (Operand dest in Destinations(node)) + { + 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(mapSize); + blkLiveOut[index] = new BitMap(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]; + + foreach (BasicBlock successor in Successors(block)) + { + if (liveOut.Set(blkLiveIn[successor.Index])) + { + modified = true; + } + } + + 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; + } + + foreach (Node node in BottomOperations(block)) + { + operationPos -= InstructionGap; + + foreach (Operand dest in Destinations(node)) + { + LiveInterval interval = _intervals[GetOperandId(dest)]; + + interval.SetStart(operationPos + 1); + interval.AddUsePosition(operationPos + 1); + } + + foreach (Operand source in Sources(node)) + { + LiveInterval interval = _intervals[GetOperandId(source)]; + + interval.AddRange(blockStart, operationPos + 1); + interval.AddUsePosition(operationPos); + } + + if (node is Operation operation && operation.Instruction == Instruction.Call) + { + AddIntervalCallerSavedReg(context.Masks.IntCallerSavedRegisters, operationPos, RegisterType.Integer); + AddIntervalCallerSavedReg(context.Masks.VecCallerSavedRegisters, operationPos, RegisterType.Vector); + } + } + } + } + + private void AddIntervalCallerSavedReg(int mask, int operationPos, RegisterType regType) + { + while (mask != 0) + { + int regIndex = BitUtils.LowestBitSet(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.AsInt32(); + } + 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 IEnumerable Successors(BasicBlock block) + { + if (block.Next != null) + { + yield return block.Next; + } + + if (block.Branch != null) + { + yield return block.Branch; + } + } + + private static IEnumerable BottomOperations(BasicBlock block) + { + LinkedListNode node = block.Operations.Last; + + while (node != null && !(node.Value is PhiNode)) + { + yield return node.Value; + + node = node.Previous; + } + } + + private static IEnumerable Destinations(Node node) + { + for (int index = 0; index < node.DestinationsCount; index++) + { + yield return node.GetDestination(index); + } + } + + private static IEnumerable Sources(Node node) + { + for (int index = 0; index < node.SourcesCount; index++) + { + Operand source = node.GetSource(index); + + if (IsLocalOrRegister(source.Kind)) + { + yield return source; + } + } + } + + private static bool IsLocalOrRegister(OperandKind kind) + { + return kind == OperandKind.LocalVariable || + kind == OperandKind.Register; + } + } +} \ No newline at end of file diff --git a/ARMeilleure/CodeGen/RegisterAllocators/LiveInterval.cs b/ARMeilleure/CodeGen/RegisterAllocators/LiveInterval.cs new file mode 100644 index 0000000000..18858a7689 --- /dev/null +++ b/ARMeilleure/CodeGen/RegisterAllocators/LiveInterval.cs @@ -0,0 +1,390 @@ +using ARMeilleure.IntermediateRepresentation; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace ARMeilleure.CodeGen.RegisterAllocators +{ + class LiveInterval : IComparable + { + public const int NotFound = -1; + + private LiveInterval _parent; + + private SortedSet _usePositions; + + public int UsesCount => _usePositions.Count; + + private List _ranges; + + private SortedList _childs; + + public bool IsSplit => _childs.Count != 0; + + public Operand Local { get; } + + public Register Register { get; set; } + + public int SpillOffset { get; private set; } + + public bool IsSpilled => SpillOffset != -1; + public bool IsFixed { get; } + + public bool IsEmpty => _ranges.Count == 0; + + public LiveInterval(Operand local = null, LiveInterval parent = null) + { + Local = local; + _parent = parent ?? this; + + _usePositions = new SortedSet(); + + _ranges = new List(); + + _childs = new SortedList(); + + SpillOffset = -1; + } + + public LiveInterval(Register register) : this() + { + IsFixed = true; + Register = register; + } + + public void SetStart(int position) + { + if (_ranges.Count != 0) + { + Debug.Assert(position != _ranges[0].End); + + _ranges[0] = new LiveRange(position, _ranges[0].End); + } + else + { + _ranges.Add(new LiveRange(position, position + 1)); + } + } + + public int GetStart() + { + if (_ranges.Count == 0) + { + throw new InvalidOperationException("Empty interval."); + } + + return _ranges[0].Start; + } + + public void SetEnd(int position) + { + if (_ranges.Count != 0) + { + int lastIdx = _ranges.Count - 1; + + Debug.Assert(position != _ranges[lastIdx].Start); + + _ranges[lastIdx] = new LiveRange(_ranges[lastIdx].Start, position); + } + else + { + _ranges.Add(new LiveRange(position, position + 1)); + } + } + + public int GetEnd() + { + if (_ranges.Count == 0) + { + throw new InvalidOperationException("Empty interval."); + } + + return _ranges[_ranges.Count - 1].End; + } + + public void AddRange(int start, int end) + { + if (start >= end) + { + throw new ArgumentException("Invalid range start position " + start + ", " + end); + } + + int index = _ranges.BinarySearch(new LiveRange(start, end)); + + if (index >= 0) + { + // New range insersects with an existing range, we need to remove + // all the intersecting ranges before adding the new one. + // We also extend the new range as needed, based on the values of + // the existing ranges being removed. + int lIndex = index; + int rIndex = index; + + while (lIndex > 0 && _ranges[lIndex - 1].End >= start) + { + lIndex--; + } + + while (rIndex + 1 < _ranges.Count && _ranges[rIndex + 1].Start <= end) + { + rIndex++; + } + + if (start > _ranges[lIndex].Start) + { + start = _ranges[lIndex].Start; + } + + if (end < _ranges[rIndex].End) + { + end = _ranges[rIndex].End; + } + + _ranges.RemoveRange(lIndex, (rIndex - lIndex) + 1); + + InsertRange(lIndex, start, end); + } + else + { + InsertRange(~index, start, end); + } + } + + private void InsertRange(int index, int start, int end) + { + // Here we insert a new range on the ranges list. + // If possible, we extend an existing range rather than inserting a new one. + // We can extend an existing range if any of the following conditions are true: + // - The new range starts right after the end of the previous range on the list. + // - The new range ends right before the start of the next range on the list. + // If both cases are true, we can extend either one. We prefer to extend the + // previous range, and then remove the next one, but theres no specific reason + // for that, extending either one will do. + int? extIndex = null; + + if (index > 0 && _ranges[index - 1].End == start) + { + start = _ranges[index - 1].Start; + + extIndex = index - 1; + } + + if (index < _ranges.Count && _ranges[index].Start == end) + { + end = _ranges[index].End; + + if (extIndex.HasValue) + { + _ranges.RemoveAt(index); + } + else + { + extIndex = index; + } + } + + if (extIndex.HasValue) + { + _ranges[extIndex.Value] = new LiveRange(start, end); + } + else + { + _ranges.Insert(index, new LiveRange(start, end)); + } + } + + public void AddUsePosition(int position) + { + _usePositions.Add(position); + } + + public bool Overlaps(int position) + { + return _ranges.BinarySearch(new LiveRange(position, position + 1)) >= 0; + } + + public bool Overlaps(LiveInterval other) + { + foreach (LiveRange range in other._ranges) + { + if (_ranges.BinarySearch(range) >= 0) + { + return true; + } + } + + return false; + } + + public int GetOverlapPosition(LiveInterval other) + { + foreach (LiveRange range in other._ranges) + { + int overlapIndex = _ranges.BinarySearch(range); + + if (overlapIndex >= 0) + { + // It's possible that we have multiple overlaps within a single interval, + // in this case, we pick the one with the lowest start position, since + // we return the first overlap position. + while (overlapIndex > 0 && _ranges[overlapIndex - 1].End > range.Start) + { + overlapIndex--; + } + + LiveRange overlappingRange = _ranges[overlapIndex]; + + return overlappingRange.Start; + } + } + + return NotFound; + } + + public IEnumerable SplitChilds() + { + return _childs.Values; + } + + public IEnumerable UsePositions() + { + return _usePositions; + } + + public int FirstUse() + { + if (_usePositions.Count == 0) + { + return NotFound; + } + + return _usePositions.First(); + } + + public int NextUseAfter(int position) + { + foreach (int usePosition in _usePositions) + { + if (usePosition >= position) + { + return usePosition; + } + } + + return NotFound; + } + + public LiveInterval Split(int position) + { + LiveInterval right = new LiveInterval(Local, _parent); + + int splitIndex = 0; + + for (; splitIndex < _ranges.Count; splitIndex++) + { + LiveRange range = _ranges[splitIndex]; + + if (position > range.Start && position <= range.End) + { + right._ranges.Add(new LiveRange(position, range.End)); + + range = new LiveRange(range.Start, position); + + _ranges[splitIndex++] = range; + + break; + } + + if (range.Start >= position) + { + break; + } + } + + if (splitIndex < _ranges.Count) + { + int count = _ranges.Count - splitIndex; + + right._ranges.AddRange(_ranges.GetRange(splitIndex, count)); + + _ranges.RemoveRange(splitIndex, count); + } + + foreach (int usePosition in _usePositions.Where(x => x >= position)) + { + right._usePositions.Add(usePosition); + } + + _usePositions.RemoveWhere(x => x >= position); + + Debug.Assert(_ranges.Count != 0, "Left interval is empty after split."); + + Debug.Assert(right._ranges.Count != 0, "Right interval is empty after split."); + + AddSplitChild(right); + + return right; + } + + private void AddSplitChild(LiveInterval child) + { + Debug.Assert(!child.IsEmpty, "Trying to insert a empty interval."); + + _parent._childs.Add(child.GetStart(), child); + } + + public LiveInterval GetSplitChild(int position) + { + if (Overlaps(position)) + { + return this; + } + + foreach (LiveInterval splitChild in _childs.Values) + { + if (splitChild.Overlaps(position)) + { + return splitChild; + } + } + + return null; + } + + public bool TrySpillWithSiblingOffset() + { + foreach (LiveInterval splitChild in _parent._childs.Values) + { + if (splitChild.IsSpilled) + { + Spill(splitChild.SpillOffset); + + return true; + } + } + + return false; + } + + public void Spill(int offset) + { + SpillOffset = offset; + } + + public int CompareTo(LiveInterval other) + { + if (_ranges.Count == 0 || other._ranges.Count == 0) + { + return _ranges.Count.CompareTo(other._ranges.Count); + } + + return _ranges[0].Start.CompareTo(other._ranges[0].Start); + } + + public override string ToString() + { + return string.Join("; ", _ranges); + } + } +} \ No newline at end of file diff --git a/ARMeilleure/CodeGen/RegisterAllocators/LiveRange.cs b/ARMeilleure/CodeGen/RegisterAllocators/LiveRange.cs new file mode 100644 index 0000000000..b5faeffd59 --- /dev/null +++ b/ARMeilleure/CodeGen/RegisterAllocators/LiveRange.cs @@ -0,0 +1,31 @@ +using System; + +namespace ARMeilleure.CodeGen.RegisterAllocators +{ + struct LiveRange : IComparable + { + public int Start { get; } + public int End { get; } + + public LiveRange(int start, int end) + { + Start = start; + End = end; + } + + public int CompareTo(LiveRange other) + { + if (Start < other.End && other.Start < End) + { + return 0; + } + + return Start.CompareTo(other.Start); + } + + public override string ToString() + { + return $"[{Start}, {End}["; + } + } +} \ No newline at end of file diff --git a/ARMeilleure/CodeGen/RegisterAllocators/RegisterMasks.cs b/ARMeilleure/CodeGen/RegisterAllocators/RegisterMasks.cs new file mode 100644 index 0000000000..9652224e5f --- /dev/null +++ b/ARMeilleure/CodeGen/RegisterAllocators/RegisterMasks.cs @@ -0,0 +1,47 @@ +using ARMeilleure.IntermediateRepresentation; +using System; + +namespace ARMeilleure.CodeGen.RegisterAllocators +{ + 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 RegisterMasks( + int intAvailableRegisters, + int vecAvailableRegisters, + int intCallerSavedRegisters, + int vecCallerSavedRegisters, + int intCalleeSavedRegisters, + int vecCalleeSavedRegisters) + { + IntAvailableRegisters = intAvailableRegisters; + VecAvailableRegisters = vecAvailableRegisters; + IntCallerSavedRegisters = intCallerSavedRegisters; + VecCallerSavedRegisters = vecCallerSavedRegisters; + IntCalleeSavedRegisters = intCalleeSavedRegisters; + VecCalleeSavedRegisters = vecCalleeSavedRegisters; + } + + 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/ARMeilleure/CodeGen/RegisterAllocators/StackAllocator.cs b/ARMeilleure/CodeGen/RegisterAllocators/StackAllocator.cs new file mode 100644 index 0000000000..a6233d6eef --- /dev/null +++ b/ARMeilleure/CodeGen/RegisterAllocators/StackAllocator.cs @@ -0,0 +1,27 @@ +using ARMeilleure.Common; +using ARMeilleure.IntermediateRepresentation; +using System; + +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/ARMeilleure/CodeGen/Unwinding/UnwindInfo.cs b/ARMeilleure/CodeGen/Unwinding/UnwindInfo.cs new file mode 100644 index 0000000000..4955f1b4a5 --- /dev/null +++ b/ARMeilleure/CodeGen/Unwinding/UnwindInfo.cs @@ -0,0 +1,18 @@ +namespace ARMeilleure.CodeGen.Unwinding +{ + struct UnwindInfo + { + public UnwindPushEntry[] PushEntries { get; } + + public int PrologueSize { get; } + + public int FixedAllocSize { get; } + + public UnwindInfo(UnwindPushEntry[] pushEntries, int prologueSize, int fixedAllocSize) + { + PushEntries = pushEntries; + PrologueSize = prologueSize; + FixedAllocSize = fixedAllocSize; + } + } +} \ No newline at end of file diff --git a/ARMeilleure/CodeGen/Unwinding/UnwindPushEntry.cs b/ARMeilleure/CodeGen/Unwinding/UnwindPushEntry.cs new file mode 100644 index 0000000000..6597e2b4b9 --- /dev/null +++ b/ARMeilleure/CodeGen/Unwinding/UnwindPushEntry.cs @@ -0,0 +1,20 @@ +using ARMeilleure.IntermediateRepresentation; + +namespace ARMeilleure.CodeGen.Unwinding +{ + struct UnwindPushEntry + { + public int Index { get; } + + public RegisterType Type { get; } + + public int StreamEndOffset { get; } + + public UnwindPushEntry(int index, RegisterType type, int streamEndOffset) + { + Index = index; + Type = type; + StreamEndOffset = streamEndOffset; + } + } +} \ No newline at end of file diff --git a/ARMeilleure/CodeGen/X86/Assembler.cs b/ARMeilleure/CodeGen/X86/Assembler.cs new file mode 100644 index 0000000000..4568253ade --- /dev/null +++ b/ARMeilleure/CodeGen/X86/Assembler.cs @@ -0,0 +1,1375 @@ +using ARMeilleure.IntermediateRepresentation; +using System; +using System.Diagnostics; +using System.IO; + +namespace ARMeilleure.CodeGen.X86 +{ + class Assembler + { + private const int BadOp = 0; + private const int OpModRMBits = 24; + + private const byte RexPrefix = 0x40; + private const byte RexWPrefix = 0x48; + private const byte LockPrefix = 0xf0; + + [Flags] + private enum InstructionFlags + { + None = 0, + RegOnly = 1 << 0, + Reg8Src = 1 << 1, + Reg8Dest = 1 << 2, + RexW = 1 << 3, + Vex = 1 << 4, + + PrefixBit = 16, + PrefixMask = 3 << PrefixBit, + Prefix66 = 1 << PrefixBit, + PrefixF3 = 2 << PrefixBit, + PrefixF2 = 3 << PrefixBit + } + + private 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 static InstructionInfo[] _instTable; + + private Stream _stream; + + 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.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.Cmpxchg16b, new InstructionInfo(0x01000fc7, BadOp, BadOp, BadOp, BadOp, InstructionFlags.RexW)); + 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.Cpuid, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fa2, InstructionFlags.RegOnly)); + 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.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.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.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.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.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.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.Vpblendvb, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a4c, InstructionFlags.Vex | 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)); + } + + private static void Add(X86Instruction inst, InstructionInfo info) + { + _instTable[(int)inst] = info; + } + + public Assembler(Stream stream) + { + _stream = stream; + } + + 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, null, dest.Type, X86Instruction.Bswap); + } + + public void Call(Operand dest) + { + WriteInstruction(dest, null, OperandType.None, X86Instruction.Call); + } + + public void Cdq() + { + WriteByte(0x99); + } + + public void Cmovcc(Operand dest, Operand source, OperandType type, X86Condition condition) + { + InstructionInfo info = _instTable[(int)X86Instruction.Cmovcc]; + + WriteOpCode(dest, null, 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 Cmpxchg16b(MemoryOperand memOp) + { + WriteByte(LockPrefix); + + WriteInstruction(memOp, null, OperandType.None, X86Instruction.Cmpxchg16b); + } + + public void Comisd(Operand src1, Operand src2) + { + WriteInstruction(src1, null, src2, X86Instruction.Comisd); + } + + public void Comiss(Operand src1, Operand src2) + { + WriteInstruction(src1, null, src2, X86Instruction.Comiss); + } + + public void Cpuid() + { + WriteInstruction(null, null, OperandType.None, X86Instruction.Cpuid); + } + + 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(null, 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(null, source, source.Type, X86Instruction.Idiv); + } + + public void Imul(Operand source) + { + WriteInstruction(null, 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) + { + InstructionInfo info = _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, null, src1, type, info.Flags, info.OpRMImm8, rrm: true); + + WriteByte(src2.AsByte()); + } + else if (IsImm32(src2.Value, src2.Type) && info.OpRMImm32 != BadOp) + { + WriteOpCode(dest, null, 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, 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 Lea(Operand dest, Operand source, OperandType type) + { + WriteInstruction(dest, source, type, X86Instruction.Lea); + } + + 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) + { + InstructionInfo info = _instTable[(int)X86Instruction.Movd]; + + if (source.Type.IsInteger() || source.Kind == OperandKind.Memory) + { + WriteOpCode(dest, null, source, OperandType.None, info.Flags, info.OpRRM, rrm: true); + } + else + { + WriteOpCode(dest, null, source, OperandType.None, info.Flags, info.OpRMR); + } + } + + public void Movdqu(Operand dest, Operand source) + { + WriteInstruction(dest, null, 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) + { + InstructionInfo info = _instTable[(int)X86Instruction.Movd]; + + InstructionFlags flags = info.Flags | InstructionFlags.RexW; + + if (source.Type.IsInteger() || source.Kind == OperandKind.Memory) + { + WriteOpCode(dest, null, source, OperandType.None, flags, info.OpRRM, rrm: true); + } + else if (dest.Type.IsInteger() || dest.Kind == OperandKind.Memory) + { + WriteOpCode(dest, null, 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(null, 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, null, dest.Type, X86Instruction.Neg); + } + + public void Not(Operand dest) + { + WriteInstruction(dest, null, dest.Type, X86Instruction.Not); + } + + public void Or(Operand dest, Operand source, OperandType type) + { + WriteInstruction(dest, source, type, X86Instruction.Or); + } + + 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, null, source, X86Instruction.Pextrb); + + WriteByte(imm); + } + + public void Pextrd(Operand dest, Operand source, byte imm) + { + WriteInstruction(dest, null, source, X86Instruction.Pextrd); + + WriteByte(imm); + } + + public void Pextrq(Operand dest, Operand source, byte imm) + { + WriteInstruction(dest, null, source, X86Instruction.Pextrq); + + WriteByte(imm); + } + + public void Pextrw(Operand dest, Operand source, byte imm) + { + WriteInstruction(dest, null, 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, null, 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, null, source, X86Instruction.Pshufd); + + WriteByte(imm); + } + + public void Push(Operand source) + { + if (source.Kind == OperandKind.Register) + { + WriteCompactInst(source, 0x50); + } + else + { + WriteInstruction(null, 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) + { + InstructionInfo info = _instTable[(int)X86Instruction.Setcc]; + + WriteOpCode(dest, null, null, OperandType.None, info.Flags, info.OpRRM | (int)condition); + } + + 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, null, 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, null, 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; + + if (shiftReg != X86Register.Rcx) + { + throw new ArgumentException($"Invalid shift register \"{shiftReg}\"."); + } + + source = null; + } + + WriteInstruction(dest, source, type, inst); + } + + private void WriteInstruction(Operand dest, Operand source, OperandType type, X86Instruction inst) + { + InstructionInfo info = _instTable[(int)inst]; + + if (source != null) + { + if (source.Kind == OperandKind.Constant) + { + ulong imm = source.Value; + + if (inst == X86Instruction.Mov8) + { + WriteOpCode(dest, null, null, type, info.Flags, info.OpRMImm8); + + WriteByte((byte)imm); + } + else if (inst == X86Instruction.Mov16) + { + WriteOpCode(dest, null, null, type, info.Flags, info.OpRMImm32); + + WriteInt16((short)imm); + } + else if (IsImm8(imm, type) && info.OpRMImm8 != BadOp) + { + WriteOpCode(dest, null, null, type, info.Flags, info.OpRMImm8); + + WriteByte((byte)imm); + } + else if (IsImm32(imm, type) && info.OpRMImm32 != BadOp) + { + WriteOpCode(dest, null, null, type, info.Flags, info.OpRMImm32); + + WriteInt32((int)imm); + } + else if (dest != null && 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))); + + WriteUInt64(imm); + } + else + { + throw new ArgumentException($"Failed to encode constant 0x{imm:X}."); + } + } + else if (source.Kind == OperandKind.Register && info.OpRMR != BadOp) + { + WriteOpCode(dest, null, source, type, info.Flags, info.OpRMR); + } + else if (info.OpRRM != BadOp) + { + WriteOpCode(dest, null, 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, null, source, type, info.Flags, info.OpRRM, rrm: true); + } + else if (info.OpRMR != BadOp) + { + WriteOpCode(dest, null, 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) + { + InstructionInfo info = _instTable[(int)inst]; + + if (src2 != null) + { + if (src2.Kind == OperandKind.Constant) + { + ulong imm = src2.Value; + + if ((byte)imm == imm && info.OpRMImm8 != BadOp) + { + WriteOpCode(dest, src1, null, 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 = null; + + if (dest != null) + { + 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 as MemoryOperand; + } + else + { + throw new ArgumentException("Invalid destination operand kind \"" + dest.Kind + "\"."); + } + } + + if (src2 != null) + { + 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 && memOp == null) + { + memOp = src2 as MemoryOperand; + } + else + { + throw new ArgumentException("Invalid source operand kind \"" + src2.Kind + "\"."); + } + } + + bool needsSibByte = false; + bool needsDisplacement = false; + + int sib = 0; + + if (memOp != null) + { + // Either source or destination is a memory operand. + Register baseReg = memOp.BaseAddress.GetRegister(); + + X86Register baseRegLow = (X86Register)(baseReg.Index & 0b111); + + needsSibByte = memOp.Index != null || 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) + { + rexPrefix |= RexPrefix | (baseReg.Index >> 3); + } + + if (needsSibByte) + { + sib = (int)baseRegLow; + + if (memOp.Index != null) + { + int indexReg = memOp.Index.GetRegister().Index; + + if (indexReg == (int)X86Register.Rsp) + { + throw new ArgumentException("Using RSP as index register on the memory operand is not allowed."); + } + + if (indexReg >= 8) + { + 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.Vex) != 0 && HardwareCapabilities.SupportsVexEncoding) + { + int vexByte2 = (int)(flags & InstructionFlags.PrefixMask) >> (int)InstructionFlags.PrefixBit; + + if (src1 != null) + { + 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 + { + switch (flags & InstructionFlags.PrefixMask) + { + case InstructionFlags.Prefix66: WriteByte(0x66); break; + case InstructionFlags.PrefixF2: WriteByte(0xf2); break; + case InstructionFlags.PrefixF3: WriteByte(0xf3); break; + } + + if (rexPrefix != 0) + { + WriteByte((byte)rexPrefix); + } + } + + if (dest != null && (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 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 != null && dest.Kind == OperandKind.Register) + { + SetRegisterHighBit(dest.GetRegister(), rrm ? 2 : 0); + } + + if (source != null && source.Kind == OperandKind.Register) + { + SetRegisterHighBit(source.GetRegister(), rrm ? 0 : 2); + } + + return rexPrefix; + } + + 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); + } + + public 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)); + } + } + + public 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/ARMeilleure/CodeGen/X86/CallConvName.cs b/ARMeilleure/CodeGen/X86/CallConvName.cs new file mode 100644 index 0000000000..be36762820 --- /dev/null +++ b/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/ARMeilleure/CodeGen/X86/CallingConvention.cs b/ARMeilleure/CodeGen/X86/CallingConvention.cs new file mode 100644 index 0000000000..2769fd93e1 --- /dev/null +++ b/ARMeilleure/CodeGen/X86/CallingConvention.cs @@ -0,0 +1,159 @@ +using System; +using System.Runtime.InteropServices; + +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 RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + ? CallConvName.Windows + : CallConvName.SystemV; + } + } +} \ No newline at end of file diff --git a/ARMeilleure/CodeGen/X86/CodeGenContext.cs b/ARMeilleure/CodeGen/X86/CodeGenContext.cs new file mode 100644 index 0000000000..d719b51640 --- /dev/null +++ b/ARMeilleure/CodeGen/X86/CodeGenContext.cs @@ -0,0 +1,305 @@ +using ARMeilleure.CodeGen.RegisterAllocators; +using ARMeilleure.Common; +using ARMeilleure.IntermediateRepresentation; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; + +namespace ARMeilleure.CodeGen.X86 +{ + class CodeGenContext + { + private const int ReservedBytesForJump = 1; + + private Stream _stream; + + 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; } + + private long[] _blockOffsets; + + private struct Jump + { + public bool IsConditional { get; } + + public X86Condition Condition { get; } + + public BasicBlock Target { get; } + + public long JumpPosition { get; } + + public long RelativeOffset { get; set; } + + public int InstSize { get; set; } + + public Jump(BasicBlock target, long jumpPosition) + { + IsConditional = false; + Condition = 0; + Target = target; + JumpPosition = jumpPosition; + + RelativeOffset = 0; + + InstSize = 0; + } + + public Jump(X86Condition condition, BasicBlock target, long jumpPosition) + { + IsConditional = true; + Condition = condition; + Target = target; + JumpPosition = jumpPosition; + + RelativeOffset = 0; + + InstSize = 0; + } + } + + private List _jumps; + + private X86Condition _jNearCondition; + + private long _jNearPosition; + private int _jNearLength; + + public CodeGenContext(Stream stream, AllocationResult allocResult, int maxCallArgs, int blocksCount) + { + _stream = stream; + + AllocResult = allocResult; + + Assembler = new Assembler(stream); + + CallArgsRegionSize = GetCallArgsRegionSize(allocResult, maxCallArgs, out int xmmSaveRegionSize); + XmmSaveRegionSize = xmmSaveRegionSize; + + _blockOffsets = new long[blocksCount]; + + _jumps = new List(); + } + + private 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 = BitUtils.CountBits(vecMask) * 16; + + int calleeSaveRegionSize = BitUtils.CountBits(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; + } + + 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) + { + _blockOffsets[block.Index] = _stream.Position; + + CurrBlock = block; + } + + public void JumpTo(BasicBlock target) + { + _jumps.Add(new Jump(target, _stream.Position)); + + WritePadding(ReservedBytesForJump); + } + + public void JumpTo(X86Condition condition, BasicBlock target) + { + _jumps.Add(new Jump(condition, target, _stream.Position)); + + WritePadding(ReservedBytesForJump); + } + + public void JumpToNear(X86Condition condition) + { + _jNearCondition = condition; + _jNearPosition = _stream.Position; + _jNearLength = Assembler.GetJccLength(0); + + _stream.Seek(_jNearLength, SeekOrigin.Current); + } + + public void JumpHere() + { + long currentPosition = _stream.Position; + + _stream.Seek(_jNearPosition, SeekOrigin.Begin); + + long offset = currentPosition - (_jNearPosition + _jNearLength); + + Debug.Assert(_jNearLength == Assembler.GetJccLength(offset), "Relative offset doesn't fit on near jump."); + + Assembler.Jcc(_jNearCondition, offset); + + _stream.Seek(currentPosition, SeekOrigin.Begin); + } + + private void WritePadding(int size) + { + while (size-- > 0) + { + _stream.WriteByte(0); + } + } + + public byte[] GetCode() + { + // Write jump relative offsets. + bool modified; + + do + { + modified = false; + + for (int index = 0; index < _jumps.Count; index++) + { + Jump jump = _jumps[index]; + + long jumpTarget = _blockOffsets[jump.Target.Index]; + + long offset = jumpTarget - jump.JumpPosition; + + if (offset < 0) + { + for (int index2 = index - 1; index2 >= 0; index2--) + { + Jump jump2 = _jumps[index2]; + + if (jump2.JumpPosition < jumpTarget) + { + break; + } + + offset -= jump2.InstSize - ReservedBytesForJump; + } + } + else + { + for (int index2 = index + 1; index2 < _jumps.Count; index2++) + { + Jump jump2 = _jumps[index2]; + + if (jump2.JumpPosition >= jumpTarget) + { + break; + } + + offset += jump2.InstSize - ReservedBytesForJump; + } + + offset -= ReservedBytesForJump; + } + + if (jump.IsConditional) + { + jump.InstSize = Assembler.GetJccLength(offset); + } + else + { + jump.InstSize = Assembler.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 know), + // we now need to compensate the offset with the jump instruction size. + // It's also worth to note 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.RelativeOffset != offset) + { + modified = true; + } + + jump.RelativeOffset = offset; + + _jumps[index] = jump; + } + } + while (modified); + + // Write the code, ignoring the dummy bytes after jumps, into a new stream. + _stream.Seek(0, SeekOrigin.Begin); + + using (MemoryStream codeStream = new MemoryStream()) + { + Assembler assembler = new Assembler(codeStream); + + byte[] buffer; + + for (int index = 0; index < _jumps.Count; index++) + { + Jump jump = _jumps[index]; + + buffer = new byte[jump.JumpPosition - _stream.Position]; + + _stream.Read(buffer, 0, buffer.Length); + _stream.Seek(ReservedBytesForJump, SeekOrigin.Current); + + codeStream.Write(buffer); + + if (jump.IsConditional) + { + assembler.Jcc(jump.Condition, jump.RelativeOffset); + } + else + { + assembler.Jmp(jump.RelativeOffset); + } + } + + buffer = new byte[_stream.Length - _stream.Position]; + + _stream.Read(buffer, 0, buffer.Length); + + codeStream.Write(buffer); + + return codeStream.ToArray(); + } + } + } +} \ No newline at end of file diff --git a/ARMeilleure/CodeGen/X86/CodeGenerator.cs b/ARMeilleure/CodeGen/X86/CodeGenerator.cs new file mode 100644 index 0000000000..0268665cb0 --- /dev/null +++ b/ARMeilleure/CodeGen/X86/CodeGenerator.cs @@ -0,0 +1,1703 @@ +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.IO; + +namespace ARMeilleure.CodeGen.X86 +{ + static class CodeGenerator + { + private const int PageSize = 0x1000; + private const int StackGuardSize = 0x2000; + + 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.Branch, GenerateBranch); + Add(Instruction.BranchIfFalse, GenerateBranchIfFalse); + Add(Instruction.BranchIfTrue, GenerateBranchIfTrue); + Add(Instruction.ByteSwap, GenerateByteSwap); + Add(Instruction.Call, GenerateCall); + Add(Instruction.Clobber, GenerateClobber); + Add(Instruction.CompareAndSwap128, GenerateCompareAndSwap128); + Add(Instruction.CompareEqual, GenerateCompareEqual); + Add(Instruction.CompareGreater, GenerateCompareGreater); + Add(Instruction.CompareGreaterOrEqual, GenerateCompareGreaterOrEqual); + Add(Instruction.CompareGreaterOrEqualUI, GenerateCompareGreaterOrEqualUI); + Add(Instruction.CompareGreaterUI, GenerateCompareGreaterUI); + Add(Instruction.CompareLess, GenerateCompareLess); + Add(Instruction.CompareLessOrEqual, GenerateCompareLessOrEqual); + Add(Instruction.CompareLessOrEqualUI, GenerateCompareLessOrEqualUI); + Add(Instruction.CompareLessUI, GenerateCompareLessUI); + Add(Instruction.CompareNotEqual, GenerateCompareNotEqual); + Add(Instruction.ConditionalSelect, GenerateConditionalSelect); + Add(Instruction.ConvertI64ToI32, GenerateConvertI64ToI32); + Add(Instruction.ConvertToFP, GenerateConvertToFP); + Add(Instruction.Copy, GenerateCopy); + Add(Instruction.CountLeadingZeros, GenerateCountLeadingZeros); + Add(Instruction.CpuId, GenerateCpuId); + 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.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.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); + } + + private 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 & CompilerOptions.SsaForm) != 0 && + (cctx.Options & CompilerOptions.Optimize) != 0) + { + Optimizer.RunPass(cfg); + } + + Logger.EndPass(PassName.Optimization, cfg); + + Logger.StartPass(PassName.PreAllocation); + + StackAllocator stackAlloc = new StackAllocator(); + + PreAllocator.RunPass(cctx, stackAlloc, out int maxCallArgs); + + Logger.EndPass(PassName.PreAllocation, cfg); + + Logger.StartPass(PassName.RegisterAllocation); + + if ((cctx.Options & CompilerOptions.SsaForm) != 0) + { + Ssa.Deconstruct(cfg); + } + + IRegisterAllocator regAlloc; + + if ((cctx.Options & CompilerOptions.Lsra) != 0) + { + regAlloc = new LinearScanAllocator(); + } + else + { + regAlloc = new HybridAllocator(); + } + + RegisterMasks regMasks = new RegisterMasks( + CallingConvention.GetIntAvailableRegisters(), + CallingConvention.GetVecAvailableRegisters(), + CallingConvention.GetIntCallerSavedRegisters(), + CallingConvention.GetVecCallerSavedRegisters(), + CallingConvention.GetIntCalleeSavedRegisters(), + CallingConvention.GetVecCalleeSavedRegisters()); + + AllocationResult allocResult = regAlloc.RunPass(cfg, stackAlloc, regMasks); + + Logger.EndPass(PassName.RegisterAllocation, cfg); + + Logger.StartPass(PassName.CodeGeneration); + + using (MemoryStream stream = new MemoryStream()) + { + CodeGenContext context = new CodeGenContext(stream, allocResult, maxCallArgs, cfg.Blocks.Count); + + UnwindInfo unwindInfo = WritePrologue(context); + + foreach (BasicBlock block in cfg.Blocks) + { + context.EnterBlock(block); + + foreach (Node node in block.Operations) + { + if (node is Operation operation) + { + GenerateOperation(context, operation); + } + } + } + + Logger.EndPass(PassName.CodeGeneration); + + return new CompiledFunction(context.GetCode(), unwindInfo); + } + } + + private static void GenerateOperation(CodeGenContext context, Operation operation) + { + if (operation.Instruction == Instruction.Extended) + { + IntrinsicOperation intrinOp = (IntrinsicOperation)operation; + + IntrinsicInfo info = IntrinsicTable.GetInfo(intrinOp.Intrinsic); + + switch (info.Type) + { + case IntrinsicType.Comis_: + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + switch (intrinOp.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.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 (intrinOp.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.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; + } + } + } + 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); + + ValidateBinOp(dest, src1, src2); + + if (dest.Type.IsInteger()) + { + context.Assembler.Add(dest, src2, dest.Type); + } + else 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()); + + 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 GenerateBranch(CodeGenContext context, Operation operation) + { + context.JumpTo(context.CurrBlock.Branch); + } + + private static void GenerateBranchIfFalse(CodeGenContext context, Operation operation) + { + Operand source = operation.GetSource(0); + + context.Assembler.Test(source, source, source.Type); + + context.JumpTo(X86Condition.Equal, context.CurrBlock.Branch); + } + + private static void GenerateBranchIfTrue(CodeGenContext context, Operation operation) + { + Operand source = operation.GetSource(0); + + context.Assembler.Test(source, source, source.Type); + + context.JumpTo(X86Condition.NotEqual, context.CurrBlock.Branch); + } + + 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 GenerateCompareAndSwap128(CodeGenContext context, Operation operation) + { + Operand source = operation.GetSource(0); + + MemoryOperand memOp = new MemoryOperand(OperandType.I64, source); + + context.Assembler.Cmpxchg16b(memOp); + } + + private static void GenerateCompareEqual(CodeGenContext context, Operation operation) + { + GenerateCompare(context, operation, X86Condition.Equal); + } + + private static void GenerateCompareGreater(CodeGenContext context, Operation operation) + { + GenerateCompare(context, operation, X86Condition.Greater); + } + + private static void GenerateCompareGreaterOrEqual(CodeGenContext context, Operation operation) + { + GenerateCompare(context, operation, X86Condition.GreaterOrEqual); + } + + private static void GenerateCompareGreaterOrEqualUI(CodeGenContext context, Operation operation) + { + GenerateCompare(context, operation, X86Condition.AboveOrEqual); + } + + private static void GenerateCompareGreaterUI(CodeGenContext context, Operation operation) + { + GenerateCompare(context, operation, X86Condition.Above); + } + + private static void GenerateCompareLess(CodeGenContext context, Operation operation) + { + GenerateCompare(context, operation, X86Condition.Less); + } + + private static void GenerateCompareLessOrEqual(CodeGenContext context, Operation operation) + { + GenerateCompare(context, operation, X86Condition.LessOrEqual); + } + + private static void GenerateCompareLessOrEqualUI(CodeGenContext context, Operation operation) + { + GenerateCompare(context, operation, X86Condition.BelowOrEqual); + } + + private static void GenerateCompareLessUI(CodeGenContext context, Operation operation) + { + GenerateCompare(context, operation, X86Condition.Below); + } + + private static void GenerateCompareNotEqual(CodeGenContext context, Operation operation) + { + GenerateCompare(context, operation, X86Condition.NotEqual); + } + + private static void GenerateCompare(CodeGenContext context, Operation operation, X86Condition condition) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + EnsureSameType(src1, src2); + + Debug.Assert(dest.Type == OperandType.I32); + + context.Assembler.Cmp(src1, src2, src1.Type); + context.Assembler.Setcc(dest, condition); + context.Assembler.Movzx8(dest, dest, OperandType.I32); + } + + 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. + context.JumpToNear(X86Condition.NotEqual); + + context.Assembler.Mov(dest, new Operand(operandSize | operandMask), OperandType.I32); + + context.JumpHere(); + + // 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, new Operand(operandMask), OperandType.I32); + } + + private static void GenerateCpuId(CodeGenContext context, Operation operation) + { + context.Assembler.Cpuid(); + } + + 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); + + MemoryOperand memOp = new MemoryOperand(dest.Type, rsp, null, 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 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); + + MemoryOperand memOp = new MemoryOperand(source.Type, rsp, null, 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); + + MemoryOperand memOp = new MemoryOperand(OperandType.I64, rsp, null, 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 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(); + + if (dest.Type == OperandType.I32) + { + Debug.Assert(index < 4); + + if (HardwareCapabilities.SupportsSse41) + { + context.Assembler.Pextrd(dest, src1, index); + } + else + { + if (index != 0) + { + 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 + { + context.Assembler.Movd(dest, src1); + } + } + } + else if (dest.Type == OperandType.I64) + { + Debug.Assert(index < 2); + + if (HardwareCapabilities.SupportsSse41) + { + context.Assembler.Pextrq(dest, src1, index); + } + else + { + if (index != 0) + { + 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 + { + context.Assembler.Movq(dest, src1); + } + } + } + else + { + Debug.Assert(index < (dest.Type == OperandType.FP32 ? 4 : 2)); + + // 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, new Operand(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, new Operand(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()); + + 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 void ValidateUnOp(Operand dest, Operand source) + { +#if DEBUG + EnsureSameReg (dest, source); + EnsureSameType(dest, source); +#endif + } + + private static void ValidateBinOp(Operand dest, Operand src1, Operand src2) + { +#if DEBUG + EnsureSameReg (dest, src1); + EnsureSameType(dest, src1, src2); +#endif + } + + private static void ValidateShift(Operand dest, Operand src1, Operand src2) + { +#if DEBUG + EnsureSameReg (dest, src1); + EnsureSameType(dest, src1); + + Debug.Assert(dest.Type.IsInteger() && src2.Type == OperandType.I32); +#endif + } + + 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 = BitUtils.LowestBitSet(mask); + + context.Assembler.Push(Register((X86Register)bit)); + + pushEntries.Add(new UnwindPushEntry(bit, RegisterType.Integer, context.StreamOffset)); + + 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, new Operand(reservedStackSize), OperandType.I64); + } + + int offset = reservedStackSize; + + mask = CallingConvention.GetVecCalleeSavedRegisters() & context.AllocResult.VecUsedRegisters; + + while (mask != 0) + { + int bit = BitUtils.LowestBitSet(mask); + + offset -= 16; + + MemoryOperand memOp = new MemoryOperand(OperandType.V128, rsp, null, Multiplier.x1, offset); + + context.Assembler.Movdqu(memOp, Xmm((X86Register)bit)); + + pushEntries.Add(new UnwindPushEntry(bit, RegisterType.Vector, context.StreamOffset)); + + mask &= ~(1 << bit); + } + + return new UnwindInfo(pushEntries.ToArray(), context.StreamOffset, reservedStackSize); + } + + 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 = BitUtils.LowestBitSet(mask); + + offset -= 16; + + MemoryOperand memOp = new MemoryOperand(OperandType.V128, rsp, null, Multiplier.x1, offset); + + context.Assembler.Movdqu(Xmm((X86Register)bit), memOp); + + mask &= ~(1 << bit); + } + + if (reservedStackSize != 0) + { + context.Assembler.Add(rsp, new Operand(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 = new MemoryOperand(OperandType.I32, rsp, null, Multiplier.x1, -offset); + + context.Assembler.Mov(temp, memOp, OperandType.I32); + } + } + + private static MemoryOperand Memory(Operand operand, OperandType type) + { + if (operand.Kind == OperandKind.Memory) + { + return operand as MemoryOperand; + } + + return new MemoryOperand(type, operand); + } + + private static Operand Register(X86Register register, OperandType type = OperandType.I64) + { + return new Operand((int)register, RegisterType.Integer, type); + } + + private static Operand Xmm(X86Register register) + { + return new Operand((int)register, RegisterType.Vector, OperandType.V128); + } + } +} \ No newline at end of file diff --git a/ARMeilleure/CodeGen/X86/HardwareCapabilities.cs b/ARMeilleure/CodeGen/X86/HardwareCapabilities.cs new file mode 100644 index 0000000000..ed81482928 --- /dev/null +++ b/ARMeilleure/CodeGen/X86/HardwareCapabilities.cs @@ -0,0 +1,52 @@ +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; + +namespace ARMeilleure.CodeGen.X86 +{ + static class HardwareCapabilities + { + private delegate ulong GetFeatureInfo(); + + private static ulong _featureInfo; + + public static bool SupportsSse3 => (_featureInfo & (1UL << 0)) != 0; + public static bool SupportsPclmulqdq => (_featureInfo & (1UL << 1)) != 0; + public static bool SupportsSsse3 => (_featureInfo & (1UL << 9)) != 0; + public static bool SupportsFma => (_featureInfo & (1UL << 12)) != 0; + public static bool SupportsCx16 => (_featureInfo & (1UL << 13)) != 0; + public static bool SupportsSse41 => (_featureInfo & (1UL << 19)) != 0; + public static bool SupportsSse42 => (_featureInfo & (1UL << 20)) != 0; + public static bool SupportsPopcnt => (_featureInfo & (1UL << 23)) != 0; + public static bool SupportsAesni => (_featureInfo & (1UL << 25)) != 0; + public static bool SupportsAvx => (_featureInfo & (1UL << 28)) != 0; + public static bool SupportsF16c => (_featureInfo & (1UL << 29)) != 0; + + public static bool SupportsSse => (_featureInfo & (1UL << 32 + 25)) != 0; + public static bool SupportsSse2 => (_featureInfo & (1UL << 32 + 26)) != 0; + + public static bool ForceLegacySse { get; set; } + + public static bool SupportsVexEncoding => SupportsAvx && !ForceLegacySse; + + static HardwareCapabilities() + { + EmitterContext context = new EmitterContext(); + + Operand featureInfo = context.CpuId(); + + context.Return(featureInfo); + + ControlFlowGraph cfg = context.GetControlFlowGraph(); + + OperandType[] argTypes = new OperandType[0]; + + GetFeatureInfo getFeatureInfo = Compiler.Compile( + cfg, + argTypes, + OperandType.I64, + CompilerOptions.HighCq); + + _featureInfo = getFeatureInfo(); + } + } +} \ No newline at end of file diff --git a/ARMeilleure/CodeGen/X86/IntrinsicInfo.cs b/ARMeilleure/CodeGen/X86/IntrinsicInfo.cs new file mode 100644 index 0000000000..b1af352bc3 --- /dev/null +++ b/ARMeilleure/CodeGen/X86/IntrinsicInfo.cs @@ -0,0 +1,14 @@ +namespace ARMeilleure.CodeGen.X86 +{ + 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/ARMeilleure/CodeGen/X86/IntrinsicTable.cs b/ARMeilleure/CodeGen/X86/IntrinsicTable.cs new file mode 100644 index 0000000000..fd3b691d4f --- /dev/null +++ b/ARMeilleure/CodeGen/X86/IntrinsicTable.cs @@ -0,0 +1,168 @@ +using ARMeilleure.Common; +using ARMeilleure.IntermediateRepresentation; + +namespace ARMeilleure.CodeGen.X86 +{ + static class IntrinsicTable + { + private const int BadOp = 0; + + 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.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.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.X86Haddpd, new IntrinsicInfo(X86Instruction.Haddpd, IntrinsicType.Binary)); + Add(Intrinsic.X86Haddps, new IntrinsicInfo(X86Instruction.Haddps, IntrinsicType.Binary)); + 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.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.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.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.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.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.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.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/ARMeilleure/CodeGen/X86/IntrinsicType.cs b/ARMeilleure/CodeGen/X86/IntrinsicType.cs new file mode 100644 index 0000000000..41c52b59db --- /dev/null +++ b/ARMeilleure/CodeGen/X86/IntrinsicType.cs @@ -0,0 +1,15 @@ +namespace ARMeilleure.CodeGen.X86 +{ + enum IntrinsicType + { + Comis_, + PopCount, + Unary, + UnaryToGpr, + Binary, + BinaryGpr, + BinaryImm, + Ternary, + TernaryImm + } +} \ No newline at end of file diff --git a/ARMeilleure/CodeGen/X86/PreAllocator.cs b/ARMeilleure/CodeGen/X86/PreAllocator.cs new file mode 100644 index 0000000000..034a87ac25 --- /dev/null +++ b/ARMeilleure/CodeGen/X86/PreAllocator.cs @@ -0,0 +1,1283 @@ +using ARMeilleure.CodeGen.RegisterAllocators; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System.Collections.Generic; +using System.Diagnostics; + +using static ARMeilleure.IntermediateRepresentation.OperandHelper; + +namespace ARMeilleure.CodeGen.X86 +{ + using LLNode = LinkedListNode; + + static class PreAllocator + { + public static void RunPass(CompilerContext cctx, StackAllocator stackAlloc, out int maxCallArgs) + { + maxCallArgs = -1; + + CallConvName callConv = CallingConvention.GetCurrentCallConv(); + + Operand[] preservedArgs = new Operand[CallingConvention.GetArgumentsOnRegsCount()]; + + foreach (BasicBlock block in cctx.Cfg.Blocks) + { + LLNode nextNode; + + for (LLNode node = block.Operations.First; node != null; node = nextNode) + { + nextNode = node.Next; + + if (!(node.Value is Operation operation)) + { + continue; + } + + HandleConstantCopy(node, operation); + + HandleSameDestSrc1Copy(node, operation); + + HandleFixedRegisterCopy(node, operation); + + switch (operation.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 = operation.SourcesCount - 1; + + if (operation.Destination != null && operation.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) + { + node = HandleCallWindowsAbi(stackAlloc, node, operation); + } + else /* if (callConv == CallConvName.SystemV) */ + { + node = HandleCallSystemVAbi(node, operation); + } + break; + + case Instruction.ConvertToFPUI: + HandleConvertToFPUI(node, operation); + break; + + case Instruction.LoadArgument: + if (callConv == CallConvName.Windows) + { + HandleLoadArgumentWindowsAbi(cctx, node, preservedArgs, operation); + } + else /* if (callConv == CallConvName.SystemV) */ + { + HandleLoadArgumentSystemVAbi(cctx, node, preservedArgs, operation); + } + break; + + case Instruction.Negate: + if (!operation.GetSource(0).Type.IsInteger()) + { + node = HandleNegate(node, operation); + } + break; + + case Instruction.Return: + if (callConv == CallConvName.Windows) + { + HandleReturnWindowsAbi(cctx, node, preservedArgs, operation); + } + else /* if (callConv == CallConvName.SystemV) */ + { + HandleReturnSystemVAbi(node, operation); + } + break; + + case Instruction.VectorInsert8: + if (!HardwareCapabilities.SupportsSse41) + { + node = HandleVectorInsert8(node, operation); + } + break; + } + } + } + } + + private static void HandleConstantCopy(LLNode node, Operation operation) + { + if (operation.SourcesCount == 0 || IsIntrinsic(operation.Instruction)) + { + return; + } + + Instruction inst = operation.Instruction; + + Operand src1 = operation.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(node, src1); + + operation.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(inst)) + { + src2 = operation.GetSource(1); + + Operand temp = src1; + + src1 = src2; + src2 = temp; + + operation.SetSource(0, src1); + operation.SetSource(1, src2); + } + + if (src1.Kind == OperandKind.Constant) + { + src1 = AddCopy(node, src1); + + operation.SetSource(0, src1); + } + } + } + + if (operation.SourcesCount < 2) + { + return; + } + + src2 = operation.GetSource(1); + + if (src2.Kind == OperandKind.Constant) + { + if (!src2.Type.IsInteger()) + { + src2 = AddXmmCopy(node, src2); + + operation.SetSource(1, src2); + } + else if (!HasConstSrc2(inst) || IsLongConst(src2)) + { + src2 = AddCopy(node, src2); + + operation.SetSource(1, src2); + } + } + } + + private static LLNode HandleFixedRegisterCopy(LLNode node, Operation operation) + { + Operand dest = operation.Destination; + + LinkedList nodes = node.List; + + switch (operation.Instruction) + { + case Instruction.CompareAndSwap128: + { + // 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, new Operation(Instruction.VectorExtract, lr, source, Const(0))); + nodes.AddBefore(node, new 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(operation.GetSource(1), rax, rdx); + SplitOperand(operation.GetSource(2), rbx, rcx); + + node = nodes.AddAfter(node, new Operation(Instruction.VectorCreateScalar, dest, rax)); + node = nodes.AddAfter(node, new 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 }); + + break; + } + + case Instruction.CpuId: + { + // Handle the many restrictions of the CPU Id instruction: + // - EAX controls the information returned by this instruction. + // - When EAX is 1, feature information is returned. + // - The information is written to registers EAX, EBX, ECX and EDX. + Debug.Assert(dest.Type == OperandType.I64); + + Operand eax = Gpr(X86Register.Rax, OperandType.I32); + Operand ebx = Gpr(X86Register.Rbx, OperandType.I32); + Operand ecx = Gpr(X86Register.Rcx, OperandType.I32); + Operand edx = Gpr(X86Register.Rdx, OperandType.I32); + + // Value 0x01 = Version, family and feature information. + nodes.AddBefore(node, new Operation(Instruction.Copy, eax, Const(1))); + + // Copy results to the destination register. + // The values are split into 2 32-bits registers, we merge them + // into a single 64-bits register. + Operand rcx = Gpr(X86Register.Rcx, OperandType.I64); + + node = nodes.AddAfter(node, new Operation(Instruction.ZeroExtend32, dest, edx)); + node = nodes.AddAfter(node, new Operation(Instruction.ShiftLeft, dest, dest, Const(32))); + node = nodes.AddAfter(node, new Operation(Instruction.BitwiseOr, dest, dest, rcx)); + + operation.SetDestinations(new Operand[] { eax, ebx, ecx, edx }); + + operation.SetSources(new Operand[] { eax }); + + 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 = operation.GetSource(0); + + Operand rax = Gpr(X86Register.Rax, src1.Type); + Operand rdx = Gpr(X86Register.Rdx, src1.Type); + + nodes.AddBefore(node, new Operation(Instruction.Copy, rax, src1)); + nodes.AddBefore(node, new Operation(Instruction.Clobber, rdx)); + + node = nodes.AddAfter(node, new Operation(Instruction.Copy, dest, rax)); + + operation.SetDestinations(new Operand[] { rdx, rax }); + + operation.SetSources(new Operand[] { rdx, rax, operation.GetSource(1) }); + + operation.Destination = rax; + } + + break; + } + + case Instruction.Extended: + { + IntrinsicOperation intrinOp = (IntrinsicOperation)operation; + + // BLENDVPD, BLENDVPS, PBLENDVB last operand is always implied to be XMM0 when VEX is not supported. + if ((intrinOp.Intrinsic == Intrinsic.X86Blendvpd || + intrinOp.Intrinsic == Intrinsic.X86Blendvps || + intrinOp.Intrinsic == Intrinsic.X86Pblendvb) && + !HardwareCapabilities.SupportsVexEncoding) + { + Operand xmm0 = Xmm(X86Register.Xmm0, OperandType.V128); + + nodes.AddBefore(node, new Operation(Instruction.Copy, xmm0, operation.GetSource(2))); + + operation.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 = operation.GetSource(0); + + Operand rax = Gpr(X86Register.Rax, src1.Type); + Operand rdx = Gpr(X86Register.Rdx, src1.Type); + + nodes.AddBefore(node, new Operation(Instruction.Copy, rax, src1)); + + operation.SetSource(0, rax); + + node = nodes.AddAfter(node, new Operation(Instruction.Copy, dest, rdx)); + + operation.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 (operation.GetSource(1).Kind == OperandKind.LocalVariable) + { + Operand rcx = Gpr(X86Register.Rcx, OperandType.I32); + + nodes.AddBefore(node, new Operation(Instruction.Copy, rcx, operation.GetSource(1))); + + operation.SetSource(1, rcx); + } + + break; + } + } + + return node; + } + + private static LLNode HandleSameDestSrc1Copy(LLNode node, Operation operation) + { + if (operation.Destination == null || operation.SourcesCount == 0) + { + return node; + } + + Instruction inst = operation.Instruction; + + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + + LinkedList nodes = node.List; + + // 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 && operation.GetSource(1).Kind == OperandKind.Constant; + + if (IsSameOperandDestSrc1(operation) && src1.Kind == OperandKind.LocalVariable && !threeOperandForm) + { + bool useNewLocal = false; + + for (int srcIndex = 1; srcIndex < operation.SourcesCount; srcIndex++) + { + if (operation.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, new Operation(Instruction.Copy, temp, src1)); + + operation.SetSource(0, temp); + + node = nodes.AddAfter(node, new Operation(Instruction.Copy, dest, temp)); + + operation.Destination = temp; + } + else + { + nodes.AddBefore(node, new Operation(Instruction.Copy, dest, src1)); + + operation.SetSource(0, dest); + } + } + else if (inst == Instruction.ConditionalSelect) + { + Operand src2 = operation.GetSource(1); + Operand src3 = operation.GetSource(2); + + if (src1 == dest || src2 == dest) + { + Operand temp = Local(dest.Type); + + nodes.AddBefore(node, new Operation(Instruction.Copy, temp, src3)); + + operation.SetSource(2, temp); + + node = nodes.AddAfter(node, new Operation(Instruction.Copy, dest, temp)); + + operation.Destination = temp; + } + else + { + nodes.AddBefore(node, new Operation(Instruction.Copy, dest, src3)); + + operation.SetSource(2, dest); + } + } + + return node; + } + + private static LLNode HandleConvertToFPUI(LLNode node, Operation operation) + { + // 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 = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(source.Type.IsInteger(), $"Invalid source type \"{source.Type}\"."); + + LinkedList nodes = node.List; + + LLNode 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, new Operation(Instruction.ZeroExtend32, zex, source)); + node = nodes.AddAfter(node, new 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, new Operation(Instruction.Copy, lsb, source)); + node = nodes.AddAfter(node, new Operation(Instruction.Copy, half, source)); + + node = nodes.AddAfter(node, new Operation(Instruction.BitwiseAnd, lsb, lsb, Const(1L))); + node = nodes.AddAfter(node, new Operation(Instruction.ShiftRightUI, half, half, Const(1))); + + node = nodes.AddAfter(node, new Operation(Instruction.ConvertToFP, lsbF, lsb)); + node = nodes.AddAfter(node, new Operation(Instruction.ConvertToFP, dest, half)); + + node = nodes.AddAfter(node, new Operation(Instruction.Add, dest, dest, dest)); + node = nodes.AddAfter(node, new Operation(Instruction.Add, dest, dest, lsbF)); + } + + Delete(currentNode, operation); + + return node; + } + + private static LLNode HandleNegate(LLNode node, Operation operation) + { + // 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 = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type == OperandType.FP32 || + dest.Type == OperandType.FP64, $"Invalid destination type \"{dest.Type}\"."); + + LinkedList nodes = node.List; + + LLNode currentNode = node; + + Operand res = Local(dest.Type); + + node = nodes.AddAfter(node, new Operation(Instruction.VectorOne, res)); + + if (dest.Type == OperandType.FP32) + { + node = nodes.AddAfter(node, new IntrinsicOperation(Intrinsic.X86Pslld, res, res, Const(31))); + } + else /* if (dest.Type == OperandType.FP64) */ + { + node = nodes.AddAfter(node, new IntrinsicOperation(Intrinsic.X86Psllq, res, res, Const(63))); + } + + node = nodes.AddAfter(node, new IntrinsicOperation(Intrinsic.X86Xorps, res, res, source)); + + node = nodes.AddAfter(node, new Operation(Instruction.Copy, dest, res)); + + Delete(currentNode, operation); + + return node; + } + + private static LLNode HandleVectorInsert8(LLNode node, Operation operation) + { + // Handle vector insertion, when SSE 4.1 is not supported. + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); // Vector + Operand src2 = operation.GetSource(1); // Value + Operand src3 = operation.GetSource(2); // Index + + Debug.Assert(src3.Kind == OperandKind.Constant); + + byte index = src3.AsByte(); + + Debug.Assert(index < 16); + + LinkedList nodes = node.List; + + LLNode currentNode = node; + + Operand temp1 = Local(OperandType.I32); + Operand temp2 = Local(OperandType.I32); + + node = nodes.AddAfter(node, new Operation(Instruction.Copy, temp2, src2)); + + Operation vextOp = new Operation(Instruction.VectorExtract16, temp1, src1, Const(index >> 1)); + + node = nodes.AddAfter(node, vextOp); + + if ((index & 1) != 0) + { + node = nodes.AddAfter(node, new Operation(Instruction.ZeroExtend8, temp1, temp1)); + node = nodes.AddAfter(node, new Operation(Instruction.ShiftLeft, temp2, temp2, Const(8))); + node = nodes.AddAfter(node, new Operation(Instruction.BitwiseOr, temp1, temp1, temp2)); + } + else + { + node = nodes.AddAfter(node, new Operation(Instruction.ZeroExtend8, temp2, temp2)); + node = nodes.AddAfter(node, new Operation(Instruction.BitwiseAnd, temp1, temp1, Const(0xff00))); + node = nodes.AddAfter(node, new Operation(Instruction.BitwiseOr, temp1, temp1, temp2)); + } + + Operation vinsOp = new Operation(Instruction.VectorInsert16, dest, src1, temp1, Const(index >> 1)); + + node = nodes.AddAfter(node, vinsOp); + + Delete(currentNode, operation); + + return node; + } + + private static LLNode HandleCallWindowsAbi(StackAllocator stackAlloc, LLNode node, Operation operation) + { + Operand dest = operation.Destination; + + LinkedList nodes = node.List; + + // 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 = null; + + if (dest != null && dest.Type == OperandType.V128) + { + int stackOffset = AllocateOnStack(dest.Type.GetSizeInBytes()); + + arg0Reg = Gpr(CallingConvention.GetIntArgumentRegister(0), OperandType.I64); + + Operation allocOp = new Operation(Instruction.StackAlloc, arg0Reg, Const(stackOffset)); + + nodes.AddBefore(node, allocOp); + + retArgs = 1; + } + + int argsCount = operation.SourcesCount - 1; + + int maxArgs = CallingConvention.GetArgumentsOnRegsCount() - retArgs; + + if (argsCount > maxArgs) + { + argsCount = maxArgs; + } + + Operand[] sources = new Operand[1 + retArgs + argsCount]; + + sources[0] = operation.GetSource(0); + + if (arg0Reg != null) + { + sources[1] = arg0Reg; + } + + for (int index = 1; index < operation.SourcesCount; index++) + { + Operand source = operation.GetSource(index); + + if (source.Type == OperandType.V128) + { + Operand stackAddr = Local(OperandType.I64); + + int stackOffset = AllocateOnStack(source.Type.GetSizeInBytes()); + + nodes.AddBefore(node, new Operation(Instruction.StackAlloc, stackAddr, Const(stackOffset))); + + Operation storeOp = new Operation(Instruction.Store, null, stackAddr, source); + + HandleConstantCopy(nodes.AddBefore(node, storeOp), storeOp); + + operation.SetSource(index, stackAddr); + } + } + + // Handle arguments passed on registers. + for (int index = 0; index < argsCount; index++) + { + Operand source = operation.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 = new Operation(Instruction.Copy, argReg, source); + + HandleConstantCopy(nodes.AddBefore(node, copyOp), 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 < operation.SourcesCount - 1; index++) + { + Operand source = operation.GetSource(index + 1); + + Operand offset = new Operand((index + retArgs) * 8); + + Operation spillOp = new Operation(Instruction.SpillArg, null, offset, source); + + HandleConstantCopy(nodes.AddBefore(node, spillOp), spillOp); + } + + if (dest != null) + { + if (dest.Type == OperandType.V128) + { + Operand retValueAddr = Local(OperandType.I64); + + nodes.AddBefore(node, new Operation(Instruction.Copy, retValueAddr, arg0Reg)); + + Operation loadOp = new Operation(Instruction.Load, dest, retValueAddr); + + node = nodes.AddAfter(node, loadOp); + + operation.Destination = null; + } + else + { + Operand retReg = dest.Type.IsInteger() + ? Gpr(CallingConvention.GetIntReturnRegister(), dest.Type) + : Xmm(CallingConvention.GetVecReturnRegister(), dest.Type); + + Operation copyOp = new Operation(Instruction.Copy, dest, retReg); + + node = nodes.AddAfter(node, copyOp); + + operation.Destination = retReg; + } + } + + operation.SetSources(sources); + + return node; + } + + private static LLNode HandleCallSystemVAbi(LLNode node, Operation operation) + { + Operand dest = operation.Destination; + + LinkedList nodes = node.List; + + List sources = new List(); + + sources.Add(operation.GetSource(0)); + + int argsCount = operation.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 = 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, new Operation(Instruction.VectorExtract, argReg, source, Const(0))); + nodes.AddBefore(node, new 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 = new Operation(Instruction.Copy, argReg, source); + + HandleConstantCopy(nodes.AddBefore(node, copyOp), copyOp); + + sources.Add(argReg); + } + else + { + Operand offset = new Operand(stackOffset); + + Operation spillOp = new Operation(Instruction.SpillArg, null, offset, source); + + HandleConstantCopy(nodes.AddBefore(node, spillOp), spillOp); + + stackOffset += source.Type.GetSizeInBytes(); + } + } + + if (dest != null) + { + if (dest.Type == OperandType.V128) + { + Operand retLReg = Gpr(CallingConvention.GetIntReturnRegister(), OperandType.I64); + Operand retHReg = Gpr(CallingConvention.GetIntReturnRegisterHigh(), OperandType.I64); + + node = nodes.AddAfter(node, new Operation(Instruction.VectorCreateScalar, dest, retLReg)); + node = nodes.AddAfter(node, new Operation(Instruction.VectorInsert, dest, dest, retHReg, Const(1))); + + operation.Destination = null; + } + else + { + Operand retReg = dest.Type.IsInteger() + ? Gpr(CallingConvention.GetIntReturnRegister(), dest.Type) + : Xmm(CallingConvention.GetVecReturnRegister(), dest.Type); + + Operation copyOp = new Operation(Instruction.Copy, dest, retReg); + + node = nodes.AddAfter(node, copyOp); + + operation.Destination = retReg; + } + } + + operation.SetSources(sources.ToArray()); + + return node; + } + + private static void HandleLoadArgumentWindowsAbi( + CompilerContext cctx, + LLNode node, + Operand[] preservedArgs, + Operation operation) + { + Operand source = operation.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 = operation.Destination; + + if (preservedArgs[index] == null) + { + 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 = new Operation(Instruction.Copy, pArg, argReg); + + cctx.Cfg.Entry.Operations.AddFirst(copyOp); + + preservedArgs[index] = pArg; + } + + Operation argCopyOp = new Operation(dest.Type == OperandType.V128 + ? Instruction.Load + : Instruction.Copy, dest, preservedArgs[index]); + + node.List.AddBefore(node, argCopyOp); + + Delete(node, operation); + } + else + { + // TODO: Pass on stack. + } + } + + private static void HandleLoadArgumentSystemVAbi( + CompilerContext cctx, + LLNode node, + Operand[] preservedArgs, + Operation operation) + { + Operand source = operation.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 = operation.Destination; + + if (preservedArgs[index] == null) + { + 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 = new Operation(Instruction.VectorCreateScalar, pArg, argLReg); + Operation copyH = new 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 = new Operation(Instruction.Copy, pArg, argReg); + + cctx.Cfg.Entry.Operations.AddFirst(copyOp); + + preservedArgs[index] = pArg; + } + } + + Operation argCopyOp = new Operation(Instruction.Copy, dest, preservedArgs[index]); + + node.List.AddBefore(node, argCopyOp); + + Delete(node, operation); + } + else + { + // TODO: Pass on stack. + } + } + + private static void HandleReturnWindowsAbi( + CompilerContext cctx, + LLNode node, + Operand[] preservedArgs, + Operation operation) + { + if (operation.SourcesCount == 0) + { + return; + } + + Operand source = operation.GetSource(0); + + Operand retReg; + + if (source.Type.IsInteger()) + { + retReg = Gpr(CallingConvention.GetIntReturnRegister(), source.Type); + } + else if (source.Type == OperandType.V128) + { + if (preservedArgs[0] == null) + { + Operand preservedArg = Local(OperandType.I64); + + Operand arg0 = Gpr(CallingConvention.GetIntArgumentRegister(0), OperandType.I64); + + Operation copyOp = new 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 = new Operation(Instruction.Store, null, retReg, source); + + node.List.AddBefore(node, retStoreOp); + } + else + { + Operation retCopyOp = new Operation(Instruction.Copy, retReg, source); + + node.List.AddBefore(node, retCopyOp); + } + + operation.SetSources(new Operand[0]); + } + + private static void HandleReturnSystemVAbi(LLNode node, Operation operation) + { + if (operation.SourcesCount == 0) + { + return; + } + + Operand source = operation.GetSource(0); + + if (source.Type == OperandType.V128) + { + Operand retLReg = Gpr(CallingConvention.GetIntReturnRegister(), OperandType.I64); + Operand retHReg = Gpr(CallingConvention.GetIntReturnRegisterHigh(), OperandType.I64); + + node.List.AddBefore(node, new Operation(Instruction.VectorExtract, retLReg, source, Const(0))); + node.List.AddBefore(node, new 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 = new Operation(Instruction.Copy, retReg, source); + + node.List.AddBefore(node, retCopyOp); + } + } + + private static Operand AddXmmCopy(LLNode node, Operand source) + { + Operand temp = Local(source.Type); + + Operand intConst = AddCopy(node, GetIntConst(source)); + + Operation copyOp = new Operation(Instruction.VectorCreateScalar, temp, intConst); + + node.List.AddBefore(node, copyOp); + + return temp; + } + + private static Operand AddCopy(LLNode node, Operand source) + { + Operand temp = Local(source.Type); + + Operation copyOp = new Operation(Instruction.Copy, temp, source); + + node.List.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; + } + + private static bool IsLongConst(Operand operand) + { + long value = operand.Type == OperandType.I32 + ? operand.AsInt32() + : operand.AsInt64(); + + return !ConstFitsOnS32(value); + } + + private static bool ConstFitsOnS32(long value) + { + return value == (int)value; + } + + private static void Delete(LLNode node, Operation operation) + { + operation.Destination = null; + + for (int index = 0; index < operation.SourcesCount; index++) + { + operation.SetSource(index, null); + } + + node.List.Remove(node); + } + + private static Operand Gpr(X86Register register, OperandType type) + { + return Register((int)register, RegisterType.Integer, type); + } + + private 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: + 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; + } + + return IsVexSameOperandDestSrc1(operation); + } + + private static bool IsVexSameOperandDestSrc1(Operation operation) + { + if (IsIntrinsic(operation.Instruction)) + { + bool isUnary = operation.SourcesCount < 2; + + bool hasVecDest = operation.Destination != null && operation.Destination.Type == OperandType.V128; + + return !HardwareCapabilities.SupportsVexEncoding && !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.CompareEqual: + case Instruction.CompareGreater: + case Instruction.CompareGreaterOrEqual: + case Instruction.CompareGreaterOrEqualUI: + case Instruction.CompareGreaterUI: + case Instruction.CompareLess: + case Instruction.CompareLessOrEqual: + case Instruction.CompareLessOrEqualUI: + case Instruction.CompareLessUI: + case Instruction.CompareNotEqual: + case Instruction.Multiply: + case Instruction.RotateRight: + case Instruction.ShiftLeft: + case Instruction.ShiftRightSI: + case Instruction.ShiftRightUI: + case Instruction.Subtract: + case Instruction.VectorExtract: + case Instruction.VectorExtract16: + case Instruction.VectorExtract8: + return true; + } + + return false; + } + + private static bool IsCommutative(Instruction inst) + { + switch (inst) + { + case Instruction.Add: + case Instruction.BitwiseAnd: + case Instruction.BitwiseExclusiveOr: + case Instruction.BitwiseOr: + case Instruction.CompareEqual: + case Instruction.CompareNotEqual: + case Instruction.Multiply: + return true; + } + + return false; + } + + private static bool IsIntrinsic(Instruction inst) + { + return inst == Instruction.Extended; + } + } +} \ No newline at end of file diff --git a/ARMeilleure/CodeGen/X86/X86Condition.cs b/ARMeilleure/CodeGen/X86/X86Condition.cs new file mode 100644 index 0000000000..a17c6d6c58 --- /dev/null +++ b/ARMeilleure/CodeGen/X86/X86Condition.cs @@ -0,0 +1,22 @@ +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 + } +} \ No newline at end of file diff --git a/ARMeilleure/CodeGen/X86/X86Instruction.cs b/ARMeilleure/CodeGen/X86/X86Instruction.cs new file mode 100644 index 0000000000..813730f2a3 --- /dev/null +++ b/ARMeilleure/CodeGen/X86/X86Instruction.cs @@ -0,0 +1,197 @@ +namespace ARMeilleure.CodeGen.X86 +{ + enum X86Instruction + { + Add, + Addpd, + Addps, + Addsd, + Addss, + And, + Andnpd, + Andnps, + Andpd, + Andps, + Blendvpd, + Blendvps, + Bsr, + Bswap, + Call, + Cmovcc, + Cmp, + Cmppd, + Cmpps, + Cmpsd, + Cmpss, + Cmpxchg16b, + Comisd, + Comiss, + Cpuid, + Cvtdq2pd, + Cvtdq2ps, + Cvtpd2dq, + Cvtpd2ps, + Cvtps2dq, + Cvtps2pd, + Cvtsd2si, + Cvtsd2ss, + Cvtsi2sd, + Cvtsi2ss, + Cvtss2sd, + Cvtss2si, + Div, + Divpd, + Divps, + Divsd, + Divss, + Haddpd, + Haddps, + Idiv, + Imul, + Imul128, + Insertps, + 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, + Pand, + Pandn, + Pavgb, + Pavgw, + Pblendvb, + 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, + Shl, + Shr, + Shufpd, + Shufps, + Sqrtpd, + Sqrtps, + Sqrtsd, + Sqrtss, + Sub, + Subpd, + Subps, + Subsd, + Subss, + Test, + Unpckhpd, + Unpckhps, + Unpcklpd, + Unpcklps, + Vblendvpd, + Vblendvps, + Vpblendvb, + Xor, + Xorpd, + Xorps, + + Count + } +} \ No newline at end of file diff --git a/ARMeilleure/CodeGen/X86/X86Register.cs b/ARMeilleure/CodeGen/X86/X86Register.cs new file mode 100644 index 0000000000..01f63e311e --- /dev/null +++ b/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/ARMeilleure/Common/BitMap.cs b/ARMeilleure/Common/BitMap.cs new file mode 100644 index 0000000000..9dff271b4c --- /dev/null +++ b/ARMeilleure/Common/BitMap.cs @@ -0,0 +1,138 @@ +using System.Collections; +using System.Collections.Generic; + +namespace ARMeilleure.Common +{ + class BitMap : IEnumerable + { + private const int IntSize = 32; + private const int IntMask = IntSize - 1; + + private List _masks; + + public BitMap(int initialCapacity) + { + int count = (initialCapacity + IntMask) / IntSize; + + _masks = new List(count); + + while (count-- > 0) + { + _masks.Add(0); + } + } + + public bool Set(int bit) + { + EnsureCapacity(bit + 1); + + int wordIndex = bit / IntSize; + int wordBit = bit & IntMask; + + int wordMask = 1 << 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; + + int wordMask = 1 << wordBit; + + _masks[wordIndex] &= ~wordMask; + } + + public bool IsSet(int bit) + { + EnsureCapacity(bit + 1); + + int wordIndex = bit / IntSize; + int wordBit = bit & IntMask; + + return (_masks[wordIndex] & (1 << wordBit)) != 0; + } + + public bool Set(BitMap map) + { + EnsureCapacity(map._masks.Count * IntSize); + + bool modified = false; + + for (int index = 0; index < _masks.Count; index++) + { + int newValue = _masks[index] | map._masks[index]; + + if (_masks[index] != newValue) + { + _masks[index] = newValue; + + modified = true; + } + } + + return modified; + } + + public bool Clear(BitMap map) + { + EnsureCapacity(map._masks.Count * IntSize); + + bool modified = false; + + for (int index = 0; index < _masks.Count; index++) + { + int newValue = _masks[index] & ~map._masks[index]; + + if (_masks[index] != newValue) + { + _masks[index] = newValue; + + modified = true; + } + } + + return modified; + } + + private void EnsureCapacity(int size) + { + while (_masks.Count * IntSize < size) + { + _masks.Add(0); + } + } + + public IEnumerator GetEnumerator() + { + for (int index = 0; index < _masks.Count; index++) + { + int mask = _masks[index]; + + while (mask != 0) + { + int bit = BitUtils.LowestBitSet(mask); + + mask &= ~(1 << bit); + + yield return index * IntSize + bit; + } + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} \ No newline at end of file diff --git a/ARMeilleure/Common/BitUtils.cs b/ARMeilleure/Common/BitUtils.cs new file mode 100644 index 0000000000..7a29dcff7f --- /dev/null +++ b/ARMeilleure/Common/BitUtils.cs @@ -0,0 +1,111 @@ +namespace ARMeilleure.Common +{ + static class BitUtils + { + private const int DeBrujinSequence = 0x77cb531; + + private static readonly int[] DeBrujinLbsLut; + + private static readonly sbyte[] HbsNibbleLut; + + static BitUtils() + { + DeBrujinLbsLut = new int[32]; + + for (int index = 0; index < DeBrujinLbsLut.Length; index++) + { + uint lutIndex = (uint)(DeBrujinSequence * (1 << index)) >> 27; + + DeBrujinLbsLut[lutIndex] = index; + } + + HbsNibbleLut = new sbyte[] { -1, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3 }; + } + + public static int CountBits(int value) + { + int count = 0; + + while (value != 0) + { + value &= ~(value & -value); + + count++; + } + + return count; + } + + public static long FillWithOnes(int bits) + { + return bits == 64 ? -1L : (1L << bits) - 1; + } + + public static int HighestBitSet(int value) + { + if (value == 0) + { + return -1; + } + + for (int bit = 31; bit >= 0; bit--) + { + if (((value >> bit) & 1) != 0) + { + return bit; + } + } + + return -1; + } + + public static int HighestBitSetNibble(int value) + { + return HbsNibbleLut[value]; + } + + public static int LowestBitSet(int value) + { + if (value == 0) + { + return -1; + } + + int lsb = value & -value; + + return DeBrujinLbsLut[(uint)(DeBrujinSequence * lsb) >> 27]; + } + + 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/ARMeilleure/Common/EnumUtils.cs b/ARMeilleure/Common/EnumUtils.cs new file mode 100644 index 0000000000..2a4aa645bb --- /dev/null +++ b/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/ARMeilleure/Decoders/Block.cs b/ARMeilleure/Decoders/Block.cs new file mode 100644 index 0000000000..3d13c2d5e4 --- /dev/null +++ b/ARMeilleure/Decoders/Block.cs @@ -0,0 +1,99 @@ +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 List OpCodes { get; private set; } + + public Block() + { + OpCodes = new List(); + } + + public Block(ulong address) : this() + { + Address = address; + } + + public void Split(Block rightBlock) + { + int splitIndex = BinarySearch(OpCodes, rightBlock.Address); + + if ((ulong)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/ARMeilleure/Decoders/Condition.cs b/ARMeilleure/Decoders/Condition.cs new file mode 100644 index 0000000000..727f897dad --- /dev/null +++ b/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/ChocolArm64/Decoder/ADataOp.cs b/ARMeilleure/Decoders/DataOp.cs similarity index 70% rename from ChocolArm64/Decoder/ADataOp.cs rename to ARMeilleure/Decoders/DataOp.cs index a5601a3abc..464d008989 100644 --- a/ChocolArm64/Decoder/ADataOp.cs +++ b/ARMeilleure/Decoders/DataOp.cs @@ -1,6 +1,6 @@ -namespace ChocolArm64.Decoder +namespace ARMeilleure.Decoders { - enum ADataOp + enum DataOp { Adr = 0, Arithmetic = 1, diff --git a/ARMeilleure/Decoders/Decoder.cs b/ARMeilleure/Decoders/Decoder.cs new file mode 100644 index 0000000000..8eb2a99d6d --- /dev/null +++ b/ARMeilleure/Decoders/Decoder.cs @@ -0,0 +1,365 @@ +using ARMeilleure.Instructions; +using ARMeilleure.Memory; +using ARMeilleure.State; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Reflection.Emit; + +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 = 5000; + + private delegate object MakeOp(InstDescriptor inst, ulong address, int opCode); + + private static ConcurrentDictionary _opActivators; + + static Decoder() + { + _opActivators = new ConcurrentDictionary(); + } + + public static Block[] DecodeBasicBlock(MemoryManager memory, ulong address, ExecutionMode mode) + { + Block block = new Block(address); + + FillBlock(memory, mode, block, ulong.MaxValue); + + return new Block[] { block }; + } + + public static Block[] DecodeFunction(MemoryManager memory, ulong address, ExecutionMode mode) + { + List blocks = new List(); + + Queue workQueue = new Queue(); + + Dictionary visited = new Dictionary(); + + int opsCount = 0; + + Block GetBlock(ulong blkAddress) + { + if (!visited.TryGetValue(blkAddress, out Block block)) + { + if (opsCount > MaxInstsPerFunction) + { + return null; + } + + block = new Block(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."); + } + + nBlock.Split(currBlock); + + blocks.Insert(nBlkIndex + 1, currBlock); + + continue; + } + + // 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; + } + } + + 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 (!IsUnconditionalBranch(lastOp) /*|| isCall*/) + { + 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); + } + } + + return blocks.ToArray(); + } + + private 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( + MemoryManager memory, + ExecutionMode mode, + Block block, + ulong limitAddress) + { + ulong address = block.Address; + + OpCode opCode; + + do + { + if (address >= limitAddress) + { + break; + } + + opCode = DecodeOpCode(memory, address, mode); + + block.OpCodes.Add(opCode); + + address += (ulong)opCode.OpCodeSizeInBytes; + } + 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; + } + + // 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) + { + 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) + { + // TODO (CQ): ARM32 support. + return opCode.Instruction.Name == InstName.Bl || + opCode.Instruction.Name == InstName.Blr; + } + + private static bool IsException(OpCode opCode) + { + return opCode.Instruction.Name == InstName.Brk || + opCode.Instruction.Name == InstName.Svc || + opCode.Instruction.Name == InstName.Und; + } + + public static OpCode DecodeOpCode(MemoryManager memory, ulong address, ExecutionMode mode) + { + int opCode = memory.ReadInt32((long)address); + + InstDescriptor inst; + + Type type; + + if (mode == ExecutionMode.Aarch64) + { + (inst, type) = OpCodeTable.GetInstA64(opCode); + } + else + { + if (mode == ExecutionMode.Aarch32Arm) + { + (inst, type) = OpCodeTable.GetInstA32(opCode); + } + else /* if (mode == ExecutionMode.Aarch32Thumb) */ + { + (inst, type) = OpCodeTable.GetInstT32(opCode); + } + } + + if (type != null) + { + return MakeOpCode(inst, type, address, opCode); + } + else + { + return new OpCode(inst, address, opCode); + } + } + + private static OpCode MakeOpCode(InstDescriptor inst, Type type, ulong address, int opCode) + { + MakeOp createInstance = _opActivators.GetOrAdd(type, CacheOpActivator); + + return (OpCode)createInstance(inst, address, opCode); + } + + private static MakeOp CacheOpActivator(Type type) + { + Type[] argTypes = new Type[] { typeof(InstDescriptor), typeof(ulong), typeof(int) }; + + DynamicMethod mthd = new DynamicMethod($"Make{type.Name}", type, argTypes); + + ILGenerator generator = mthd.GetILGenerator(); + + generator.Emit(OpCodes.Ldarg_0); + generator.Emit(OpCodes.Ldarg_1); + generator.Emit(OpCodes.Ldarg_2); + generator.Emit(OpCodes.Newobj, type.GetConstructor(argTypes)); + generator.Emit(OpCodes.Ret); + + return (MakeOp)mthd.CreateDelegate(typeof(MakeOp)); + } + } +} \ No newline at end of file diff --git a/ARMeilleure/Decoders/DecoderHelper.cs b/ARMeilleure/Decoders/DecoderHelper.cs new file mode 100644 index 0000000000..bc41c61c6a --- /dev/null +++ b/ARMeilleure/Decoders/DecoderHelper.cs @@ -0,0 +1,152 @@ +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 < 256; idx++) + { + tbl[idx] = ExpandImm8ToFP32((uint)idx); + } + + return tbl; + } + + private static ulong[] BuildImm8ToFP64Table() + { + ulong[] tbl = new ulong[256]; + + for (int idx = 0; idx < 256; 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; + } + } +} \ No newline at end of file diff --git a/ARMeilleure/Decoders/IOpCode.cs b/ARMeilleure/Decoders/IOpCode.cs new file mode 100644 index 0000000000..37ba7a4c62 --- /dev/null +++ b/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/ARMeilleure/Decoders/IOpCode32.cs b/ARMeilleure/Decoders/IOpCode32.cs new file mode 100644 index 0000000000..126c106906 --- /dev/null +++ b/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/ARMeilleure/Decoders/IOpCode32Alu.cs b/ARMeilleure/Decoders/IOpCode32Alu.cs new file mode 100644 index 0000000000..72aea30ef1 --- /dev/null +++ b/ARMeilleure/Decoders/IOpCode32Alu.cs @@ -0,0 +1,10 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32Alu : IOpCode32 + { + int Rd { get; } + int Rn { get; } + + bool SetFlags { get; } + } +} \ No newline at end of file diff --git a/ARMeilleure/Decoders/IOpCode32BImm.cs b/ARMeilleure/Decoders/IOpCode32BImm.cs new file mode 100644 index 0000000000..ec7db2c269 --- /dev/null +++ b/ARMeilleure/Decoders/IOpCode32BImm.cs @@ -0,0 +1,4 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32BImm : IOpCode32, IOpCodeBImm { } +} \ No newline at end of file diff --git a/ARMeilleure/Decoders/IOpCode32BReg.cs b/ARMeilleure/Decoders/IOpCode32BReg.cs new file mode 100644 index 0000000000..097ab42756 --- /dev/null +++ b/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/ARMeilleure/Decoders/IOpCode32Mem.cs b/ARMeilleure/Decoders/IOpCode32Mem.cs new file mode 100644 index 0000000000..0585ab53ac --- /dev/null +++ b/ARMeilleure/Decoders/IOpCode32Mem.cs @@ -0,0 +1,12 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32Mem : IOpCode32 + { + int Rt { get; } + int Rn { get; } + + bool WBack { get; } + + bool IsLoad { get; } + } +} \ No newline at end of file diff --git a/ARMeilleure/Decoders/IOpCode32MemMult.cs b/ARMeilleure/Decoders/IOpCode32MemMult.cs new file mode 100644 index 0000000000..18fd3f6bf6 --- /dev/null +++ b/ARMeilleure/Decoders/IOpCode32MemMult.cs @@ -0,0 +1,13 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32MemMult : IOpCode32 + { + int Rn { get; } + + int RegisterMask { get; } + + int PostOffset { get; } + + bool IsLoad { get; } + } +} \ No newline at end of file diff --git a/ARMeilleure/Decoders/IOpCodeAlu.cs b/ARMeilleure/Decoders/IOpCodeAlu.cs new file mode 100644 index 0000000000..b8c28513dd --- /dev/null +++ b/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/ARMeilleure/Decoders/IOpCodeAluImm.cs b/ARMeilleure/Decoders/IOpCodeAluImm.cs new file mode 100644 index 0000000000..02f4c997b9 --- /dev/null +++ b/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/ARMeilleure/Decoders/IOpCodeAluRs.cs b/ARMeilleure/Decoders/IOpCodeAluRs.cs new file mode 100644 index 0000000000..22540b11a3 --- /dev/null +++ b/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/ARMeilleure/Decoders/IOpCodeAluRx.cs b/ARMeilleure/Decoders/IOpCodeAluRx.cs new file mode 100644 index 0000000000..9d16be7878 --- /dev/null +++ b/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/ARMeilleure/Decoders/IOpCodeBImm.cs b/ARMeilleure/Decoders/IOpCodeBImm.cs new file mode 100644 index 0000000000..958bff28d6 --- /dev/null +++ b/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/ARMeilleure/Decoders/IOpCodeCond.cs b/ARMeilleure/Decoders/IOpCodeCond.cs new file mode 100644 index 0000000000..9808f7c080 --- /dev/null +++ b/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/ARMeilleure/Decoders/IOpCodeLit.cs b/ARMeilleure/Decoders/IOpCodeLit.cs new file mode 100644 index 0000000000..74084a4575 --- /dev/null +++ b/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/ARMeilleure/Decoders/IOpCodeSimd.cs b/ARMeilleure/Decoders/IOpCodeSimd.cs new file mode 100644 index 0000000000..056ef045c4 --- /dev/null +++ b/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/ARMeilleure/Decoders/InstDescriptor.cs b/ARMeilleure/Decoders/InstDescriptor.cs new file mode 100644 index 0000000000..ee2b1c2e46 --- /dev/null +++ b/ARMeilleure/Decoders/InstDescriptor.cs @@ -0,0 +1,18 @@ +using ARMeilleure.Instructions; + +namespace ARMeilleure.Decoders +{ + struct InstDescriptor + { + public static InstDescriptor Undefined => new InstDescriptor(InstName.Und, null); + + 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/ARMeilleure/Decoders/InstEmitter.cs b/ARMeilleure/Decoders/InstEmitter.cs new file mode 100644 index 0000000000..a8b5265693 --- /dev/null +++ b/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/ChocolArm64/Decoder/AIntType.cs b/ARMeilleure/Decoders/IntType.cs similarity index 78% rename from ChocolArm64/Decoder/AIntType.cs rename to ARMeilleure/Decoders/IntType.cs index 242fdada14..244e968053 100644 --- a/ChocolArm64/Decoder/AIntType.cs +++ b/ARMeilleure/Decoders/IntType.cs @@ -1,6 +1,6 @@ -namespace ChocolArm64.Decoder +namespace ARMeilleure.Decoders { - enum AIntType + enum IntType { UInt8 = 0, UInt16 = 1, diff --git a/ARMeilleure/Decoders/OpCode.cs b/ARMeilleure/Decoders/OpCode.cs new file mode 100644 index 0000000000..0bfc2456bc --- /dev/null +++ b/ARMeilleure/Decoders/OpCode.cs @@ -0,0 +1,48 @@ +using ARMeilleure.IntermediateRepresentation; +using System; + +namespace ARMeilleure.Decoders +{ + class OpCode : IOpCode + { + public ulong Address { get; private set; } + public int RawOpCode { get; private set; } + + public int OpCodeSizeInBytes { get; protected set; } = 4; + + public InstDescriptor Instruction { get; protected set; } + + public RegisterSize RegisterSize { get; protected set; } + + public OpCode(InstDescriptor inst, ulong address, int opCode) + { + Address = address; + RawOpCode = opCode; + + Instruction = inst; + + 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/ARMeilleure/Decoders/OpCode32.cs b/ARMeilleure/Decoders/OpCode32.cs new file mode 100644 index 0000000000..20927d5e4f --- /dev/null +++ b/ARMeilleure/Decoders/OpCode32.cs @@ -0,0 +1,21 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32 : OpCode + { + public Condition Cond { get; protected set; } + + public OpCode32(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + RegisterSize = RegisterSize.Int32; + + Cond = (Condition)((uint)opCode >> 28); + } + + public uint GetPc() + { + // Due to backwards compatibility and legacy behavior of ARMv4 CPUs pipeline, + // the PC actually points 2 instructions ahead. + return (uint)Address + (uint)OpCodeSizeInBytes * 2; + } + } +} \ No newline at end of file diff --git a/ARMeilleure/Decoders/OpCode32Alu.cs b/ARMeilleure/Decoders/OpCode32Alu.cs new file mode 100644 index 0000000000..8d03baddb8 --- /dev/null +++ b/ARMeilleure/Decoders/OpCode32Alu.cs @@ -0,0 +1,18 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32Alu : OpCode32, IOpCode32Alu + { + public int Rd { get; private set; } + public int Rn { get; private set; } + + public bool SetFlags { get; private set; } + + 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/ARMeilleure/Decoders/OpCode32AluImm.cs b/ARMeilleure/Decoders/OpCode32AluImm.cs new file mode 100644 index 0000000000..bba03e4d8c --- /dev/null +++ b/ARMeilleure/Decoders/OpCode32AluImm.cs @@ -0,0 +1,21 @@ +using ARMeilleure.Common; + +namespace ARMeilleure.Decoders +{ + class OpCode32AluImm : OpCode32Alu + { + public int Immediate { get; private set; } + + public bool IsRotated { get; private set; } + + 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/ARMeilleure/Decoders/OpCode32AluRsImm.cs b/ARMeilleure/Decoders/OpCode32AluRsImm.cs new file mode 100644 index 0000000000..779d6cecf8 --- /dev/null +++ b/ARMeilleure/Decoders/OpCode32AluRsImm.cs @@ -0,0 +1,18 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32AluRsImm : OpCode32Alu + { + public int Rm { get; private set; } + public int Imm { get; private set; } + + public ShiftType ShiftType { get; private set; } + + public OpCode32AluRsImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rm = (opCode >> 0) & 0xf; + Imm = (opCode >> 7) & 0x1f; + + ShiftType = (ShiftType)((opCode >> 5) & 3); + } + } +} \ No newline at end of file diff --git a/ARMeilleure/Decoders/OpCode32BImm.cs b/ARMeilleure/Decoders/OpCode32BImm.cs new file mode 100644 index 0000000000..ea6443bc80 --- /dev/null +++ b/ARMeilleure/Decoders/OpCode32BImm.cs @@ -0,0 +1,27 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32BImm : OpCode32, IOpCode32BImm + { + public long Immediate { get; private set; } + + 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/ARMeilleure/Decoders/OpCode32BReg.cs b/ARMeilleure/Decoders/OpCode32BReg.cs new file mode 100644 index 0000000000..ffb4870704 --- /dev/null +++ b/ARMeilleure/Decoders/OpCode32BReg.cs @@ -0,0 +1,12 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32BReg : OpCode32, IOpCode32BReg + { + public int Rm { get; private set; } + + public OpCode32BReg(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rm = opCode & 0xf; + } + } +} \ No newline at end of file diff --git a/ARMeilleure/Decoders/OpCode32Mem.cs b/ARMeilleure/Decoders/OpCode32Mem.cs new file mode 100644 index 0000000000..f4e88d5924 --- /dev/null +++ b/ARMeilleure/Decoders/OpCode32Mem.cs @@ -0,0 +1,37 @@ +using ARMeilleure.Instructions; + +namespace ARMeilleure.Decoders +{ + class OpCode32Mem : OpCode32, IOpCode32Mem + { + public int Rt { get; private set; } + public int Rn { get; private set; } + + public int Immediate { get; protected set; } + + public bool Index { get; private set; } + public bool Add { get; private set; } + public bool WBack { get; private set; } + public bool Unprivileged { get; private set; } + + public bool IsLoad { get; private set; } + + 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/ARMeilleure/Decoders/OpCode32MemImm.cs b/ARMeilleure/Decoders/OpCode32MemImm.cs new file mode 100644 index 0000000000..f79c63197c --- /dev/null +++ b/ARMeilleure/Decoders/OpCode32MemImm.cs @@ -0,0 +1,10 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32MemImm : OpCode32Mem + { + public OpCode32MemImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Immediate = opCode & 0xfff; + } + } +} \ No newline at end of file diff --git a/ARMeilleure/Decoders/OpCode32MemImm8.cs b/ARMeilleure/Decoders/OpCode32MemImm8.cs new file mode 100644 index 0000000000..08027fb758 --- /dev/null +++ b/ARMeilleure/Decoders/OpCode32MemImm8.cs @@ -0,0 +1,13 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32MemImm8 : OpCode32Mem + { + 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/ARMeilleure/Decoders/OpCode32MemMult.cs b/ARMeilleure/Decoders/OpCode32MemMult.cs new file mode 100644 index 0000000000..b61b50ea83 --- /dev/null +++ b/ARMeilleure/Decoders/OpCode32MemMult.cs @@ -0,0 +1,55 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32MemMult : OpCode32, IOpCode32MemMult + { + public int Rn { get; private set; } + + public int RegisterMask { get; private set; } + public int Offset { get; private set; } + public int PostOffset { get; private set; } + + public bool IsLoad { get; private set; } + + 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 = 0; + + for (int index = 0; index < 16; index++) + { + regsSize += (RegisterMask >> index) & 1; + } + + regsSize *= 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/ARMeilleure/Decoders/OpCodeAdr.cs b/ARMeilleure/Decoders/OpCodeAdr.cs new file mode 100644 index 0000000000..fc8219f6c6 --- /dev/null +++ b/ARMeilleure/Decoders/OpCodeAdr.cs @@ -0,0 +1,17 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeAdr : OpCode + { + public int Rd { get; private set; } + + public long Immediate { get; private set; } + + 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/ARMeilleure/Decoders/OpCodeAlu.cs b/ARMeilleure/Decoders/OpCodeAlu.cs new file mode 100644 index 0000000000..171662a066 --- /dev/null +++ b/ARMeilleure/Decoders/OpCodeAlu.cs @@ -0,0 +1,21 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeAlu : OpCode, IOpCodeAlu + { + public int Rd { get; protected set; } + public int Rn { get; private set; } + + public DataOp DataOp { get; private set; } + + 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/ARMeilleure/Decoders/OpCodeAluBinary.cs b/ARMeilleure/Decoders/OpCodeAluBinary.cs new file mode 100644 index 0000000000..2bdf1d7986 --- /dev/null +++ b/ARMeilleure/Decoders/OpCodeAluBinary.cs @@ -0,0 +1,12 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeAluBinary : OpCodeAlu + { + public int Rm { get; private set; } + + 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/ARMeilleure/Decoders/OpCodeAluImm.cs b/ARMeilleure/Decoders/OpCodeAluImm.cs new file mode 100644 index 0000000000..35c83fcc37 --- /dev/null +++ b/ARMeilleure/Decoders/OpCodeAluImm.cs @@ -0,0 +1,38 @@ +using System; + +namespace ARMeilleure.Decoders +{ + class OpCodeAluImm : OpCodeAlu, IOpCodeAluImm + { + public long Immediate { get; private set; } + + 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/ARMeilleure/Decoders/OpCodeAluRs.cs b/ARMeilleure/Decoders/OpCodeAluRs.cs new file mode 100644 index 0000000000..84fb6ac6d1 --- /dev/null +++ b/ARMeilleure/Decoders/OpCodeAluRs.cs @@ -0,0 +1,27 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeAluRs : OpCodeAlu, IOpCodeAluRs + { + public int Shift { get; private set; } + public int Rm { get; private set; } + + public ShiftType ShiftType { get; private set; } + + 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/ARMeilleure/Decoders/OpCodeAluRx.cs b/ARMeilleure/Decoders/OpCodeAluRx.cs new file mode 100644 index 0000000000..5c8d427e84 --- /dev/null +++ b/ARMeilleure/Decoders/OpCodeAluRx.cs @@ -0,0 +1,17 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeAluRx : OpCodeAlu, IOpCodeAluRx + { + public int Shift { get; private set; } + public int Rm { get; private set; } + + public IntType IntType { get; private set; } + + 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/ARMeilleure/Decoders/OpCodeBImm.cs b/ARMeilleure/Decoders/OpCodeBImm.cs new file mode 100644 index 0000000000..2821a62461 --- /dev/null +++ b/ARMeilleure/Decoders/OpCodeBImm.cs @@ -0,0 +1,9 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeBImm : OpCode, IOpCodeBImm + { + public long Immediate { get; protected set; } + + public OpCodeBImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) { } + } +} \ No newline at end of file diff --git a/ARMeilleure/Decoders/OpCodeBImmAl.cs b/ARMeilleure/Decoders/OpCodeBImmAl.cs new file mode 100644 index 0000000000..94bcea8848 --- /dev/null +++ b/ARMeilleure/Decoders/OpCodeBImmAl.cs @@ -0,0 +1,10 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeBImmAl : OpCodeBImm + { + 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/ARMeilleure/Decoders/OpCodeBImmCmp.cs b/ARMeilleure/Decoders/OpCodeBImmCmp.cs new file mode 100644 index 0000000000..2b7c283414 --- /dev/null +++ b/ARMeilleure/Decoders/OpCodeBImmCmp.cs @@ -0,0 +1,18 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeBImmCmp : OpCodeBImm + { + public int Rt { get; private set; } + + 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/ARMeilleure/Decoders/OpCodeBImmCond.cs b/ARMeilleure/Decoders/OpCodeBImmCond.cs new file mode 100644 index 0000000000..f898821ac8 --- /dev/null +++ b/ARMeilleure/Decoders/OpCodeBImmCond.cs @@ -0,0 +1,23 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeBImmCond : OpCodeBImm, IOpCodeCond + { + public Condition Cond { get; private set; } + + 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/ARMeilleure/Decoders/OpCodeBImmTest.cs b/ARMeilleure/Decoders/OpCodeBImmTest.cs new file mode 100644 index 0000000000..6687c2e7a9 --- /dev/null +++ b/ARMeilleure/Decoders/OpCodeBImmTest.cs @@ -0,0 +1,18 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeBImmTest : OpCodeBImm + { + public int Rt { get; private set; } + public int Bit { get; private set; } + + 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/ARMeilleure/Decoders/OpCodeBReg.cs b/ARMeilleure/Decoders/OpCodeBReg.cs new file mode 100644 index 0000000000..00c51ec71e --- /dev/null +++ b/ARMeilleure/Decoders/OpCodeBReg.cs @@ -0,0 +1,22 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeBReg : OpCode + { + public int Rn { get; private set; } + + 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/ARMeilleure/Decoders/OpCodeBfm.cs b/ARMeilleure/Decoders/OpCodeBfm.cs new file mode 100644 index 0000000000..2ae8edf56e --- /dev/null +++ b/ARMeilleure/Decoders/OpCodeBfm.cs @@ -0,0 +1,27 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeBfm : OpCodeAlu + { + public long WMask { get; private set; } + public long TMask { get; private set; } + public int Pos { get; private set; } + public int Shift { get; private set; } + + 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/ARMeilleure/Decoders/OpCodeCcmp.cs b/ARMeilleure/Decoders/OpCodeCcmp.cs new file mode 100644 index 0000000000..c302f6a32a --- /dev/null +++ b/ARMeilleure/Decoders/OpCodeCcmp.cs @@ -0,0 +1,30 @@ +using ARMeilleure.State; + +namespace ARMeilleure.Decoders +{ + class OpCodeCcmp : OpCodeAlu, IOpCodeCond + { + public int Nzcv { get; private set; } + protected int RmImm; + + public Condition Cond { get; private set; } + + 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/ARMeilleure/Decoders/OpCodeCcmpImm.cs b/ARMeilleure/Decoders/OpCodeCcmpImm.cs new file mode 100644 index 0000000000..4a2d01f46e --- /dev/null +++ b/ARMeilleure/Decoders/OpCodeCcmpImm.cs @@ -0,0 +1,9 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeCcmpImm : OpCodeCcmp, IOpCodeAluImm + { + public long Immediate => RmImm; + + public OpCodeCcmpImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) { } + } +} \ No newline at end of file diff --git a/ARMeilleure/Decoders/OpCodeCcmpReg.cs b/ARMeilleure/Decoders/OpCodeCcmpReg.cs new file mode 100644 index 0000000000..0e2b922cf2 --- /dev/null +++ b/ARMeilleure/Decoders/OpCodeCcmpReg.cs @@ -0,0 +1,13 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeCcmpReg : OpCodeCcmp, IOpCodeAluRs + { + public int Rm => RmImm; + + public int Shift => 0; + + public ShiftType ShiftType => ShiftType.Lsl; + + public OpCodeCcmpReg(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) { } + } +} \ No newline at end of file diff --git a/ARMeilleure/Decoders/OpCodeCsel.cs b/ARMeilleure/Decoders/OpCodeCsel.cs new file mode 100644 index 0000000000..fd07e6fd49 --- /dev/null +++ b/ARMeilleure/Decoders/OpCodeCsel.cs @@ -0,0 +1,15 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeCsel : OpCodeAlu, IOpCodeCond + { + public int Rm { get; private set; } + + public Condition Cond { get; private set; } + + 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/ARMeilleure/Decoders/OpCodeException.cs b/ARMeilleure/Decoders/OpCodeException.cs new file mode 100644 index 0000000000..9781c543bd --- /dev/null +++ b/ARMeilleure/Decoders/OpCodeException.cs @@ -0,0 +1,12 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeException : OpCode + { + public int Id { get; private set; } + + 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/ARMeilleure/Decoders/OpCodeMem.cs b/ARMeilleure/Decoders/OpCodeMem.cs new file mode 100644 index 0000000000..5a7ab482a3 --- /dev/null +++ b/ARMeilleure/Decoders/OpCodeMem.cs @@ -0,0 +1,17 @@ +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 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/ARMeilleure/Decoders/OpCodeMemEx.cs b/ARMeilleure/Decoders/OpCodeMemEx.cs new file mode 100644 index 0000000000..5956f36722 --- /dev/null +++ b/ARMeilleure/Decoders/OpCodeMemEx.cs @@ -0,0 +1,14 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeMemEx : OpCodeMem + { + public int Rt2 { get; private set; } + public int Rs { get; private set; } + + 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/ARMeilleure/Decoders/OpCodeMemImm.cs b/ARMeilleure/Decoders/OpCodeMemImm.cs new file mode 100644 index 0000000000..517434f290 --- /dev/null +++ b/ARMeilleure/Decoders/OpCodeMemImm.cs @@ -0,0 +1,51 @@ +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 set; } + + private enum MemOp + { + Unscaled = 0, + PostIndexed = 1, + Unprivileged = 2, + PreIndexed = 3, + Unsigned + } + + 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/ARMeilleure/Decoders/OpCodeMemLit.cs b/ARMeilleure/Decoders/OpCodeMemLit.cs new file mode 100644 index 0000000000..b80585cb49 --- /dev/null +++ b/ARMeilleure/Decoders/OpCodeMemLit.cs @@ -0,0 +1,26 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeMemLit : OpCode, IOpCodeLit + { + public int Rt { get; private set; } + public long Immediate { get; private set; } + public int Size { get; private set; } + public bool Signed { get; private set; } + public bool Prefetch { get; private set; } + + 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/ARMeilleure/Decoders/OpCodeMemPair.cs b/ARMeilleure/Decoders/OpCodeMemPair.cs new file mode 100644 index 0000000000..ea329a1db7 --- /dev/null +++ b/ARMeilleure/Decoders/OpCodeMemPair.cs @@ -0,0 +1,23 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeMemPair : OpCodeMemImm + { + public int Rt2 { get; private set; } + + 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/ARMeilleure/Decoders/OpCodeMemReg.cs b/ARMeilleure/Decoders/OpCodeMemReg.cs new file mode 100644 index 0000000000..f5c2f9911e --- /dev/null +++ b/ARMeilleure/Decoders/OpCodeMemReg.cs @@ -0,0 +1,18 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeMemReg : OpCodeMem + { + public bool Shift { get; private set; } + public int Rm { get; private set; } + + public IntType IntType { get; private set; } + + 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/ARMeilleure/Decoders/OpCodeMov.cs b/ARMeilleure/Decoders/OpCodeMov.cs new file mode 100644 index 0000000000..b65178cff3 --- /dev/null +++ b/ARMeilleure/Decoders/OpCodeMov.cs @@ -0,0 +1,36 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeMov : OpCode + { + public int Rd { get; private set; } + + public long Immediate { get; private set; } + + public int Bit { get; private set; } + + 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/ARMeilleure/Decoders/OpCodeMul.cs b/ARMeilleure/Decoders/OpCodeMul.cs new file mode 100644 index 0000000000..3eb4dc97c5 --- /dev/null +++ b/ARMeilleure/Decoders/OpCodeMul.cs @@ -0,0 +1,14 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeMul : OpCodeAlu + { + public int Rm { get; private set; } + public int Ra { get; private set; } + + 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/ARMeilleure/Decoders/OpCodeSimd.cs b/ARMeilleure/Decoders/OpCodeSimd.cs new file mode 100644 index 0000000000..a258446c19 --- /dev/null +++ b/ARMeilleure/Decoders/OpCodeSimd.cs @@ -0,0 +1,22 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimd : OpCode, IOpCodeSimd + { + public int Rd { get; private set; } + public int Rn { get; private set; } + public int Opc { get; private set; } + public int Size { get; protected set; } + + 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/ARMeilleure/Decoders/OpCodeSimdCvt.cs b/ARMeilleure/Decoders/OpCodeSimdCvt.cs new file mode 100644 index 0000000000..15658bb89d --- /dev/null +++ b/ARMeilleure/Decoders/OpCodeSimdCvt.cs @@ -0,0 +1,19 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdCvt : OpCodeSimd + { + public int FBits { get; private set; } + + 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/ARMeilleure/Decoders/OpCodeSimdExt.cs b/ARMeilleure/Decoders/OpCodeSimdExt.cs new file mode 100644 index 0000000000..d585449c12 --- /dev/null +++ b/ARMeilleure/Decoders/OpCodeSimdExt.cs @@ -0,0 +1,12 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdExt : OpCodeSimdReg + { + public int Imm4 { get; private set; } + + 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/ARMeilleure/Decoders/OpCodeSimdFcond.cs b/ARMeilleure/Decoders/OpCodeSimdFcond.cs new file mode 100644 index 0000000000..9e7a5f3bf6 --- /dev/null +++ b/ARMeilleure/Decoders/OpCodeSimdFcond.cs @@ -0,0 +1,15 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdFcond : OpCodeSimdReg, IOpCodeCond + { + public int Nzcv { get; private set; } + + public Condition Cond { get; private set; } + + public OpCodeSimdFcond(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Nzcv = (opCode >> 0) & 0xf; + Cond = (Condition)((opCode >> 12) & 0xf); + } + } +} diff --git a/ARMeilleure/Decoders/OpCodeSimdFmov.cs b/ARMeilleure/Decoders/OpCodeSimdFmov.cs new file mode 100644 index 0000000000..f0da03968d --- /dev/null +++ b/ARMeilleure/Decoders/OpCodeSimdFmov.cs @@ -0,0 +1,30 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdFmov : OpCode, IOpCodeSimd + { + public int Rd { get; private set; } + public long Immediate { get; private set; } + public int Size { get; private set; } + + 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/ARMeilleure/Decoders/OpCodeSimdImm.cs b/ARMeilleure/Decoders/OpCodeSimdImm.cs new file mode 100644 index 0000000000..a88e360ee4 --- /dev/null +++ b/ARMeilleure/Decoders/OpCodeSimdImm.cs @@ -0,0 +1,105 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdImm : OpCode, IOpCodeSimd + { + public int Rd { get; private set; } + public long Immediate { get; private set; } + public int Size { get; private set; } + + 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/ARMeilleure/Decoders/OpCodeSimdIns.cs b/ARMeilleure/Decoders/OpCodeSimdIns.cs new file mode 100644 index 0000000000..78328adb56 --- /dev/null +++ b/ARMeilleure/Decoders/OpCodeSimdIns.cs @@ -0,0 +1,34 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdIns : OpCodeSimd + { + public int SrcIndex { get; private set; } + public int DstIndex { get; private set; } + + 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/ARMeilleure/Decoders/OpCodeSimdMemImm.cs b/ARMeilleure/Decoders/OpCodeSimdMemImm.cs new file mode 100644 index 0000000000..6b9e66d935 --- /dev/null +++ b/ARMeilleure/Decoders/OpCodeSimdMemImm.cs @@ -0,0 +1,17 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdMemImm : OpCodeMemImm, IOpCodeSimd + { + public OpCodeSimdMemImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Size |= (opCode >> 21) & 4; + + if (!WBack && !Unscaled && Size >= 4) + { + Immediate <<= 4; + } + + Extend64 = false; + } + } +} \ No newline at end of file diff --git a/ARMeilleure/Decoders/OpCodeSimdMemLit.cs b/ARMeilleure/Decoders/OpCodeSimdMemLit.cs new file mode 100644 index 0000000000..607df1392c --- /dev/null +++ b/ARMeilleure/Decoders/OpCodeSimdMemLit.cs @@ -0,0 +1,29 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdMemLit : OpCode, IOpCodeSimd, IOpCodeLit + { + public int Rt { get; private set; } + public long Immediate { get; private set; } + public int Size { get; private set; } + public bool Signed => false; + public bool Prefetch => false; + + 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/ChocolArm64/Decoder/AOpCodeSimdMemMs.cs b/ARMeilleure/Decoders/OpCodeSimdMemMs.cs similarity index 54% rename from ChocolArm64/Decoder/AOpCodeSimdMemMs.cs rename to ARMeilleure/Decoders/OpCodeSimdMemMs.cs index a54e2360c5..9fa5ff42c4 100644 --- a/ChocolArm64/Decoder/AOpCodeSimdMemMs.cs +++ b/ARMeilleure/Decoders/OpCodeSimdMemMs.cs @@ -1,18 +1,15 @@ -using ChocolArm64.Instruction; -using ChocolArm64.State; - -namespace ChocolArm64.Decoder +namespace ARMeilleure.Decoders { - class AOpCodeSimdMemMs : AOpCodeMemReg, IAOpCodeSimd + class OpCodeSimdMemMs : OpCodeMemReg, IOpCodeSimd { public int Reps { get; private set; } public int SElems { get; private set; } public int Elems { get; private set; } public bool WBack { get; private set; } - public AOpCodeSimdMemMs(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) + public OpCodeSimdMemMs(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) { - switch ((OpCode >> 12) & 0xf) + switch ((opCode >> 12) & 0xf) { case 0b0000: Reps = 1; SElems = 4; break; case 0b0010: Reps = 4; SElems = 1; break; @@ -22,26 +19,26 @@ namespace ChocolArm64.Decoder case 0b1000: Reps = 1; SElems = 2; break; case 0b1010: Reps = 2; SElems = 1; break; - default: Inst = AInst.Undefined; return; + default: Instruction = InstDescriptor.Undefined; return; } - Size = (OpCode >> 10) & 3; - WBack = ((OpCode >> 23) & 1) != 0; + Size = (opCode >> 10) & 3; + WBack = ((opCode >> 23) & 1) != 0; - bool Q = ((OpCode >> 30) & 1) != 0; + bool q = ((opCode >> 30) & 1) != 0; - if (!Q && Size == 3 && SElems != 1) + if (!q && Size == 3 && SElems != 1) { - Inst = AInst.Undefined; + Instruction = InstDescriptor.Undefined; return; } Extend64 = false; - RegisterSize = Q - ? ARegisterSize.SIMD128 - : ARegisterSize.SIMD64; + RegisterSize = q + ? RegisterSize.Simd128 + : RegisterSize.Simd64; Elems = (GetBitsCount() >> 3) >> Size; } diff --git a/ARMeilleure/Decoders/OpCodeSimdMemPair.cs b/ARMeilleure/Decoders/OpCodeSimdMemPair.cs new file mode 100644 index 0000000000..a4af49d022 --- /dev/null +++ b/ARMeilleure/Decoders/OpCodeSimdMemPair.cs @@ -0,0 +1,14 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdMemPair : OpCodeMemPair, IOpCodeSimd + { + 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/ARMeilleure/Decoders/OpCodeSimdMemReg.cs b/ARMeilleure/Decoders/OpCodeSimdMemReg.cs new file mode 100644 index 0000000000..7b783d63df --- /dev/null +++ b/ARMeilleure/Decoders/OpCodeSimdMemReg.cs @@ -0,0 +1,12 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdMemReg : OpCodeMemReg, IOpCodeSimd + { + public OpCodeSimdMemReg(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Size |= (opCode >> 21) & 4; + + Extend64 = false; + } + } +} \ No newline at end of file diff --git a/ARMeilleure/Decoders/OpCodeSimdMemSs.cs b/ARMeilleure/Decoders/OpCodeSimdMemSs.cs new file mode 100644 index 0000000000..302decbcc5 --- /dev/null +++ b/ARMeilleure/Decoders/OpCodeSimdMemSs.cs @@ -0,0 +1,95 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdMemSs : OpCodeMemReg, IOpCodeSimd + { + public int SElems { get; private set; } + public int Index { get; private set; } + public bool Replicate { get; private set; } + public bool WBack { get; private set; } + + 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/ARMeilleure/Decoders/OpCodeSimdReg.cs b/ARMeilleure/Decoders/OpCodeSimdReg.cs new file mode 100644 index 0000000000..d076806a61 --- /dev/null +++ b/ARMeilleure/Decoders/OpCodeSimdReg.cs @@ -0,0 +1,16 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdReg : OpCodeSimd + { + public bool Bit3 { get; private set; } + public int Ra { get; private set; } + public int Rm { get; protected set; } + + 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/ARMeilleure/Decoders/OpCodeSimdRegElem.cs b/ARMeilleure/Decoders/OpCodeSimdRegElem.cs new file mode 100644 index 0000000000..d2f1583d22 --- /dev/null +++ b/ARMeilleure/Decoders/OpCodeSimdRegElem.cs @@ -0,0 +1,29 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdRegElem : OpCodeSimdReg + { + public int Index { get; private set; } + + 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/ARMeilleure/Decoders/OpCodeSimdRegElemF.cs b/ARMeilleure/Decoders/OpCodeSimdRegElemF.cs new file mode 100644 index 0000000000..365b77172a --- /dev/null +++ b/ARMeilleure/Decoders/OpCodeSimdRegElemF.cs @@ -0,0 +1,31 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdRegElemF : OpCodeSimdReg + { + public int Index { get; private set; } + + 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/ARMeilleure/Decoders/OpCodeSimdShImm.cs b/ARMeilleure/Decoders/OpCodeSimdShImm.cs new file mode 100644 index 0000000000..d260c4b3e8 --- /dev/null +++ b/ARMeilleure/Decoders/OpCodeSimdShImm.cs @@ -0,0 +1,16 @@ +using ARMeilleure.Common; + +namespace ARMeilleure.Decoders +{ + class OpCodeSimdShImm : OpCodeSimd + { + public int Imm { get; private set; } + + public OpCodeSimdShImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Imm = (opCode >> 16) & 0x7f; + + Size = BitUtils.HighestBitSetNibble(Imm >> 3); + } + } +} diff --git a/ARMeilleure/Decoders/OpCodeSimdTbl.cs b/ARMeilleure/Decoders/OpCodeSimdTbl.cs new file mode 100644 index 0000000000..14fdd6489e --- /dev/null +++ b/ARMeilleure/Decoders/OpCodeSimdTbl.cs @@ -0,0 +1,10 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdTbl : OpCodeSimdReg + { + 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/ARMeilleure/Decoders/OpCodeSystem.cs b/ARMeilleure/Decoders/OpCodeSystem.cs new file mode 100644 index 0000000000..cf7c5cc159 --- /dev/null +++ b/ARMeilleure/Decoders/OpCodeSystem.cs @@ -0,0 +1,22 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSystem : OpCode + { + public int Rt { get; private set; } + public int Op2 { get; private set; } + public int CRm { get; private set; } + public int CRn { get; private set; } + public int Op1 { get; private set; } + public int Op0 { get; private set; } + + 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/ARMeilleure/Decoders/OpCodeT16.cs b/ARMeilleure/Decoders/OpCodeT16.cs new file mode 100644 index 0000000000..e7b7aff533 --- /dev/null +++ b/ARMeilleure/Decoders/OpCodeT16.cs @@ -0,0 +1,12 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT16 : OpCode32 + { + public OpCodeT16(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Cond = Condition.Al; + + OpCodeSizeInBytes = 2; + } + } +} \ No newline at end of file diff --git a/ARMeilleure/Decoders/OpCodeT16AluImm8.cs b/ARMeilleure/Decoders/OpCodeT16AluImm8.cs new file mode 100644 index 0000000000..197d3b091f --- /dev/null +++ b/ARMeilleure/Decoders/OpCodeT16AluImm8.cs @@ -0,0 +1,20 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT16AluImm8 : OpCodeT16, IOpCode32Alu + { + private int _rdn; + + public int Rd => _rdn; + public int Rn => _rdn; + + public bool SetFlags => false; + + public int Immediate { get; private set; } + + public OpCodeT16AluImm8(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Immediate = (opCode >> 0) & 0xff; + _rdn = (opCode >> 8) & 0x7; + } + } +} \ No newline at end of file diff --git a/ARMeilleure/Decoders/OpCodeT16BReg.cs b/ARMeilleure/Decoders/OpCodeT16BReg.cs new file mode 100644 index 0000000000..1fb3975916 --- /dev/null +++ b/ARMeilleure/Decoders/OpCodeT16BReg.cs @@ -0,0 +1,12 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT16BReg : OpCodeT16, IOpCode32BReg + { + public int Rm { get; private set; } + + public OpCodeT16BReg(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rm = (opCode >> 3) & 0xf; + } + } +} \ No newline at end of file diff --git a/ARMeilleure/Decoders/OpCodeTable.cs b/ARMeilleure/Decoders/OpCodeTable.cs new file mode 100644 index 0000000000..2fa7702d90 --- /dev/null +++ b/ARMeilleure/Decoders/OpCodeTable.cs @@ -0,0 +1,792 @@ +using ARMeilleure.Instructions; +using ARMeilleure.State; +using System; +using System.Collections.Generic; + +namespace ARMeilleure.Decoders +{ + static class OpCodeTable + { + private const int FastLookupSize = 0x1000; + + private struct InstInfo + { + public int Mask { get; } + public int Value { get; } + + public InstDescriptor Inst { get; } + + public Type Type { get; } + + public InstInfo(int mask, int value, InstDescriptor inst, Type type) + { + Mask = mask; + Value = value; + Inst = inst; + Type = type; + } + } + + private static List _allInstA32 = new List(); + private static List _allInstT32 = new List(); + private static List _allInstA64 = new List(); + + 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, typeof(OpCodeAluRs)); + SetA64("x0111010000xxxxx000000xxxxxxxxxx", InstName.Adcs, InstEmit.Adcs, typeof(OpCodeAluRs)); + SetA64("x00100010xxxxxxxxxxxxxxxxxxxxxxx", InstName.Add, InstEmit.Add, typeof(OpCodeAluImm)); + SetA64("00001011<<0xxxxx0xxxxxxxxxxxxxxx", InstName.Add, InstEmit.Add, typeof(OpCodeAluRs)); + SetA64("10001011<<0xxxxxxxxxxxxxxxxxxxxx", InstName.Add, InstEmit.Add, typeof(OpCodeAluRs)); + SetA64("x0001011001xxxxxxxx0xxxxxxxxxxxx", InstName.Add, InstEmit.Add, typeof(OpCodeAluRx)); + SetA64("x0001011001xxxxxxxx100xxxxxxxxxx", InstName.Add, InstEmit.Add, typeof(OpCodeAluRx)); + SetA64("x01100010xxxxxxxxxxxxxxxxxxxxxxx", InstName.Adds, InstEmit.Adds, typeof(OpCodeAluImm)); + SetA64("00101011<<0xxxxx0xxxxxxxxxxxxxxx", InstName.Adds, InstEmit.Adds, typeof(OpCodeAluRs)); + SetA64("10101011<<0xxxxxxxxxxxxxxxxxxxxx", InstName.Adds, InstEmit.Adds, typeof(OpCodeAluRs)); + SetA64("x0101011001xxxxxxxx0xxxxxxxxxxxx", InstName.Adds, InstEmit.Adds, typeof(OpCodeAluRx)); + SetA64("x0101011001xxxxxxxx100xxxxxxxxxx", InstName.Adds, InstEmit.Adds, typeof(OpCodeAluRx)); + SetA64("0xx10000xxxxxxxxxxxxxxxxxxxxxxxx", InstName.Adr, InstEmit.Adr, typeof(OpCodeAdr)); + SetA64("1xx10000xxxxxxxxxxxxxxxxxxxxxxxx", InstName.Adrp, InstEmit.Adrp, typeof(OpCodeAdr)); + SetA64("0001001000xxxxxxxxxxxxxxxxxxxxxx", InstName.And, InstEmit.And, typeof(OpCodeAluImm)); + SetA64("100100100xxxxxxxxxxxxxxxxxxxxxxx", InstName.And, InstEmit.And, typeof(OpCodeAluImm)); + SetA64("00001010xx0xxxxx0xxxxxxxxxxxxxxx", InstName.And, InstEmit.And, typeof(OpCodeAluRs)); + SetA64("10001010xx0xxxxxxxxxxxxxxxxxxxxx", InstName.And, InstEmit.And, typeof(OpCodeAluRs)); + SetA64("0111001000xxxxxxxxxxxxxxxxxxxxxx", InstName.Ands, InstEmit.Ands, typeof(OpCodeAluImm)); + SetA64("111100100xxxxxxxxxxxxxxxxxxxxxxx", InstName.Ands, InstEmit.Ands, typeof(OpCodeAluImm)); + SetA64("01101010xx0xxxxx0xxxxxxxxxxxxxxx", InstName.Ands, InstEmit.Ands, typeof(OpCodeAluRs)); + SetA64("11101010xx0xxxxxxxxxxxxxxxxxxxxx", InstName.Ands, InstEmit.Ands, typeof(OpCodeAluRs)); + SetA64("x0011010110xxxxx001010xxxxxxxxxx", InstName.Asrv, InstEmit.Asrv, typeof(OpCodeAluRs)); + SetA64("000101xxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.B, InstEmit.B, typeof(OpCodeBImmAl)); + SetA64("01010100xxxxxxxxxxxxxxxxxxx0xxxx", InstName.B_Cond, InstEmit.B_Cond, typeof(OpCodeBImmCond)); + SetA64("00110011000xxxxx0xxxxxxxxxxxxxxx", InstName.Bfm, InstEmit.Bfm, typeof(OpCodeBfm)); + SetA64("1011001101xxxxxxxxxxxxxxxxxxxxxx", InstName.Bfm, InstEmit.Bfm, typeof(OpCodeBfm)); + SetA64("00001010xx1xxxxx0xxxxxxxxxxxxxxx", InstName.Bic, InstEmit.Bic, typeof(OpCodeAluRs)); + SetA64("10001010xx1xxxxxxxxxxxxxxxxxxxxx", InstName.Bic, InstEmit.Bic, typeof(OpCodeAluRs)); + SetA64("01101010xx1xxxxx0xxxxxxxxxxxxxxx", InstName.Bics, InstEmit.Bics, typeof(OpCodeAluRs)); + SetA64("11101010xx1xxxxxxxxxxxxxxxxxxxxx", InstName.Bics, InstEmit.Bics, typeof(OpCodeAluRs)); + SetA64("100101xxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Bl, InstEmit.Bl, typeof(OpCodeBImmAl)); + SetA64("1101011000111111000000xxxxx00000", InstName.Blr, InstEmit.Blr, typeof(OpCodeBReg)); + SetA64("1101011000011111000000xxxxx00000", InstName.Br, InstEmit.Br, typeof(OpCodeBReg)); + SetA64("11010100001xxxxxxxxxxxxxxxx00000", InstName.Brk, InstEmit.Brk, typeof(OpCodeException)); + SetA64("x0110101xxxxxxxxxxxxxxxxxxxxxxxx", InstName.Cbnz, InstEmit.Cbnz, typeof(OpCodeBImmCmp)); + SetA64("x0110100xxxxxxxxxxxxxxxxxxxxxxxx", InstName.Cbz, InstEmit.Cbz, typeof(OpCodeBImmCmp)); + SetA64("x0111010010xxxxxxxxx10xxxxx0xxxx", InstName.Ccmn, InstEmit.Ccmn, typeof(OpCodeCcmpImm)); + SetA64("x0111010010xxxxxxxxx00xxxxx0xxxx", InstName.Ccmn, InstEmit.Ccmn, typeof(OpCodeCcmpReg)); + SetA64("x1111010010xxxxxxxxx10xxxxx0xxxx", InstName.Ccmp, InstEmit.Ccmp, typeof(OpCodeCcmpImm)); + SetA64("x1111010010xxxxxxxxx00xxxxx0xxxx", InstName.Ccmp, InstEmit.Ccmp, typeof(OpCodeCcmpReg)); + SetA64("11010101000000110011xxxx01011111", InstName.Clrex, InstEmit.Clrex, typeof(OpCodeSystem)); + SetA64("x101101011000000000101xxxxxxxxxx", InstName.Cls, InstEmit.Cls, typeof(OpCodeAlu)); + SetA64("x101101011000000000100xxxxxxxxxx", InstName.Clz, InstEmit.Clz, typeof(OpCodeAlu)); + SetA64("00011010110xxxxx010000xxxxxxxxxx", InstName.Crc32b, InstEmit.Crc32b, typeof(OpCodeAluBinary)); + SetA64("00011010110xxxxx010001xxxxxxxxxx", InstName.Crc32h, InstEmit.Crc32h, typeof(OpCodeAluBinary)); + SetA64("00011010110xxxxx010010xxxxxxxxxx", InstName.Crc32w, InstEmit.Crc32w, typeof(OpCodeAluBinary)); + SetA64("10011010110xxxxx010011xxxxxxxxxx", InstName.Crc32x, InstEmit.Crc32x, typeof(OpCodeAluBinary)); + SetA64("00011010110xxxxx010100xxxxxxxxxx", InstName.Crc32cb, InstEmit.Crc32cb, typeof(OpCodeAluBinary)); + SetA64("00011010110xxxxx010101xxxxxxxxxx", InstName.Crc32ch, InstEmit.Crc32ch, typeof(OpCodeAluBinary)); + SetA64("00011010110xxxxx010110xxxxxxxxxx", InstName.Crc32cw, InstEmit.Crc32cw, typeof(OpCodeAluBinary)); + SetA64("10011010110xxxxx010111xxxxxxxxxx", InstName.Crc32cx, InstEmit.Crc32cx, typeof(OpCodeAluBinary)); + SetA64("x0011010100xxxxxxxxx00xxxxxxxxxx", InstName.Csel, InstEmit.Csel, typeof(OpCodeCsel)); + SetA64("x0011010100xxxxxxxxx01xxxxxxxxxx", InstName.Csinc, InstEmit.Csinc, typeof(OpCodeCsel)); + SetA64("x1011010100xxxxxxxxx00xxxxxxxxxx", InstName.Csinv, InstEmit.Csinv, typeof(OpCodeCsel)); + SetA64("x1011010100xxxxxxxxx01xxxxxxxxxx", InstName.Csneg, InstEmit.Csneg, typeof(OpCodeCsel)); + SetA64("11010101000000110011xxxx10111111", InstName.Dmb, InstEmit.Dmb, typeof(OpCodeSystem)); + SetA64("11010101000000110011xxxx10011111", InstName.Dsb, InstEmit.Dsb, typeof(OpCodeSystem)); + SetA64("01001010xx1xxxxx0xxxxxxxxxxxxxxx", InstName.Eon, InstEmit.Eon, typeof(OpCodeAluRs)); + SetA64("11001010xx1xxxxxxxxxxxxxxxxxxxxx", InstName.Eon, InstEmit.Eon, typeof(OpCodeAluRs)); + SetA64("0101001000xxxxxxxxxxxxxxxxxxxxxx", InstName.Eor, InstEmit.Eor, typeof(OpCodeAluImm)); + SetA64("110100100xxxxxxxxxxxxxxxxxxxxxxx", InstName.Eor, InstEmit.Eor, typeof(OpCodeAluImm)); + SetA64("01001010xx0xxxxx0xxxxxxxxxxxxxxx", InstName.Eor, InstEmit.Eor, typeof(OpCodeAluRs)); + SetA64("11001010xx0xxxxxxxxxxxxxxxxxxxxx", InstName.Eor, InstEmit.Eor, typeof(OpCodeAluRs)); + SetA64("00010011100xxxxx0xxxxxxxxxxxxxxx", InstName.Extr, InstEmit.Extr, typeof(OpCodeAluRs)); + SetA64("10010011110xxxxxxxxxxxxxxxxxxxxx", InstName.Extr, InstEmit.Extr, typeof(OpCodeAluRs)); + SetA64("11010101000000110010xxxxxxx11111", InstName.Hint, InstEmit.Hint, typeof(OpCodeSystem)); + SetA64("11010101000000110011xxxx11011111", InstName.Isb, InstEmit.Isb, typeof(OpCodeSystem)); + SetA64("xx001000110xxxxx1xxxxxxxxxxxxxxx", InstName.Ldar, InstEmit.Ldar, typeof(OpCodeMemEx)); + SetA64("1x001000011xxxxx1xxxxxxxxxxxxxxx", InstName.Ldaxp, InstEmit.Ldaxp, typeof(OpCodeMemEx)); + SetA64("xx001000010xxxxx1xxxxxxxxxxxxxxx", InstName.Ldaxr, InstEmit.Ldaxr, typeof(OpCodeMemEx)); + SetA64("<<10100xx1xxxxxxxxxxxxxxxxxxxxxx", InstName.Ldp, InstEmit.Ldp, typeof(OpCodeMemPair)); + SetA64("xx111000010xxxxxxxxxxxxxxxxxxxxx", InstName.Ldr, InstEmit.Ldr, typeof(OpCodeMemImm)); + SetA64("xx11100101xxxxxxxxxxxxxxxxxxxxxx", InstName.Ldr, InstEmit.Ldr, typeof(OpCodeMemImm)); + SetA64("xx111000011xxxxxxxxx10xxxxxxxxxx", InstName.Ldr, InstEmit.Ldr, typeof(OpCodeMemReg)); + SetA64("xx011000xxxxxxxxxxxxxxxxxxxxxxxx", InstName.Ldr_Literal, InstEmit.Ldr_Literal, typeof(OpCodeMemLit)); + SetA64("0x1110001x0xxxxxxxxxxxxxxxxxxxxx", InstName.Ldrs, InstEmit.Ldrs, typeof(OpCodeMemImm)); + SetA64("0x1110011xxxxxxxxxxxxxxxxxxxxxxx", InstName.Ldrs, InstEmit.Ldrs, typeof(OpCodeMemImm)); + SetA64("10111000100xxxxxxxxxxxxxxxxxxxxx", InstName.Ldrs, InstEmit.Ldrs, typeof(OpCodeMemImm)); + SetA64("1011100110xxxxxxxxxxxxxxxxxxxxxx", InstName.Ldrs, InstEmit.Ldrs, typeof(OpCodeMemImm)); + SetA64("0x1110001x1xxxxxxxxx10xxxxxxxxxx", InstName.Ldrs, InstEmit.Ldrs, typeof(OpCodeMemReg)); + SetA64("10111000101xxxxxxxxx10xxxxxxxxxx", InstName.Ldrs, InstEmit.Ldrs, typeof(OpCodeMemReg)); + SetA64("xx001000010xxxxx0xxxxxxxxxxxxxxx", InstName.Ldxr, InstEmit.Ldxr, typeof(OpCodeMemEx)); + SetA64("1x001000011xxxxx0xxxxxxxxxxxxxxx", InstName.Ldxp, InstEmit.Ldxp, typeof(OpCodeMemEx)); + SetA64("x0011010110xxxxx001000xxxxxxxxxx", InstName.Lslv, InstEmit.Lslv, typeof(OpCodeAluRs)); + SetA64("x0011010110xxxxx001001xxxxxxxxxx", InstName.Lsrv, InstEmit.Lsrv, typeof(OpCodeAluRs)); + SetA64("x0011011000xxxxx0xxxxxxxxxxxxxxx", InstName.Madd, InstEmit.Madd, typeof(OpCodeMul)); + SetA64("0111001010xxxxxxxxxxxxxxxxxxxxxx", InstName.Movk, InstEmit.Movk, typeof(OpCodeMov)); + SetA64("111100101xxxxxxxxxxxxxxxxxxxxxxx", InstName.Movk, InstEmit.Movk, typeof(OpCodeMov)); + SetA64("0001001010xxxxxxxxxxxxxxxxxxxxxx", InstName.Movn, InstEmit.Movn, typeof(OpCodeMov)); + SetA64("100100101xxxxxxxxxxxxxxxxxxxxxxx", InstName.Movn, InstEmit.Movn, typeof(OpCodeMov)); + SetA64("0101001010xxxxxxxxxxxxxxxxxxxxxx", InstName.Movz, InstEmit.Movz, typeof(OpCodeMov)); + SetA64("110100101xxxxxxxxxxxxxxxxxxxxxxx", InstName.Movz, InstEmit.Movz, typeof(OpCodeMov)); + SetA64("110101010011xxxxxxxxxxxxxxxxxxxx", InstName.Mrs, InstEmit.Mrs, typeof(OpCodeSystem)); + SetA64("110101010001xxxxxxxxxxxxxxxxxxxx", InstName.Msr, InstEmit.Msr, typeof(OpCodeSystem)); + SetA64("x0011011000xxxxx1xxxxxxxxxxxxxxx", InstName.Msub, InstEmit.Msub, typeof(OpCodeMul)); + SetA64("11010101000000110010000000011111", InstName.Nop, InstEmit.Nop, typeof(OpCodeSystem)); + SetA64("00101010xx1xxxxx0xxxxxxxxxxxxxxx", InstName.Orn, InstEmit.Orn, typeof(OpCodeAluRs)); + SetA64("10101010xx1xxxxxxxxxxxxxxxxxxxxx", InstName.Orn, InstEmit.Orn, typeof(OpCodeAluRs)); + SetA64("0011001000xxxxxxxxxxxxxxxxxxxxxx", InstName.Orr, InstEmit.Orr, typeof(OpCodeAluImm)); + SetA64("101100100xxxxxxxxxxxxxxxxxxxxxxx", InstName.Orr, InstEmit.Orr, typeof(OpCodeAluImm)); + SetA64("00101010xx0xxxxx0xxxxxxxxxxxxxxx", InstName.Orr, InstEmit.Orr, typeof(OpCodeAluRs)); + SetA64("10101010xx0xxxxxxxxxxxxxxxxxxxxx", InstName.Orr, InstEmit.Orr, typeof(OpCodeAluRs)); + SetA64("1111100110xxxxxxxxxxxxxxxxxxxxxx", InstName.Pfrm, InstEmit.Pfrm, typeof(OpCodeMemImm)); + SetA64("11111000100xxxxxxxxx00xxxxxxxxxx", InstName.Pfrm, InstEmit.Pfrm, typeof(OpCodeMemImm)); + SetA64("11011000xxxxxxxxxxxxxxxxxxxxxxxx", InstName.Pfrm, InstEmit.Pfrm, typeof(OpCodeMemLit)); + SetA64("x101101011000000000000xxxxxxxxxx", InstName.Rbit, InstEmit.Rbit, typeof(OpCodeAlu)); + SetA64("1101011001011111000000xxxxx00000", InstName.Ret, InstEmit.Ret, typeof(OpCodeBReg)); + SetA64("x101101011000000000001xxxxxxxxxx", InstName.Rev16, InstEmit.Rev16, typeof(OpCodeAlu)); + SetA64("x101101011000000000010xxxxxxxxxx", InstName.Rev32, InstEmit.Rev32, typeof(OpCodeAlu)); + SetA64("1101101011000000000011xxxxxxxxxx", InstName.Rev64, InstEmit.Rev64, typeof(OpCodeAlu)); + SetA64("x0011010110xxxxx001011xxxxxxxxxx", InstName.Rorv, InstEmit.Rorv, typeof(OpCodeAluRs)); + SetA64("x1011010000xxxxx000000xxxxxxxxxx", InstName.Sbc, InstEmit.Sbc, typeof(OpCodeAluRs)); + SetA64("x1111010000xxxxx000000xxxxxxxxxx", InstName.Sbcs, InstEmit.Sbcs, typeof(OpCodeAluRs)); + SetA64("00010011000xxxxx0xxxxxxxxxxxxxxx", InstName.Sbfm, InstEmit.Sbfm, typeof(OpCodeBfm)); + SetA64("1001001101xxxxxxxxxxxxxxxxxxxxxx", InstName.Sbfm, InstEmit.Sbfm, typeof(OpCodeBfm)); + SetA64("x0011010110xxxxx000011xxxxxxxxxx", InstName.Sdiv, InstEmit.Sdiv, typeof(OpCodeAluBinary)); + SetA64("10011011001xxxxx0xxxxxxxxxxxxxxx", InstName.Smaddl, InstEmit.Smaddl, typeof(OpCodeMul)); + SetA64("10011011001xxxxx1xxxxxxxxxxxxxxx", InstName.Smsubl, InstEmit.Smsubl, typeof(OpCodeMul)); + SetA64("10011011010xxxxx0xxxxxxxxxxxxxxx", InstName.Smulh, InstEmit.Smulh, typeof(OpCodeMul)); + SetA64("xx001000100xxxxx1xxxxxxxxxxxxxxx", InstName.Stlr, InstEmit.Stlr, typeof(OpCodeMemEx)); + SetA64("1x001000001xxxxx1xxxxxxxxxxxxxxx", InstName.Stlxp, InstEmit.Stlxp, typeof(OpCodeMemEx)); + SetA64("xx001000000xxxxx1xxxxxxxxxxxxxxx", InstName.Stlxr, InstEmit.Stlxr, typeof(OpCodeMemEx)); + SetA64("x010100xx0xxxxxxxxxxxxxxxxxxxxxx", InstName.Stp, InstEmit.Stp, typeof(OpCodeMemPair)); + SetA64("xx111000000xxxxxxxxxxxxxxxxxxxxx", InstName.Str, InstEmit.Str, typeof(OpCodeMemImm)); + SetA64("xx11100100xxxxxxxxxxxxxxxxxxxxxx", InstName.Str, InstEmit.Str, typeof(OpCodeMemImm)); + SetA64("xx111000001xxxxxxxxx10xxxxxxxxxx", InstName.Str, InstEmit.Str, typeof(OpCodeMemReg)); + SetA64("1x001000001xxxxx0xxxxxxxxxxxxxxx", InstName.Stxp, InstEmit.Stxp, typeof(OpCodeMemEx)); + SetA64("xx001000000xxxxx0xxxxxxxxxxxxxxx", InstName.Stxr, InstEmit.Stxr, typeof(OpCodeMemEx)); + SetA64("x10100010xxxxxxxxxxxxxxxxxxxxxxx", InstName.Sub, InstEmit.Sub, typeof(OpCodeAluImm)); + SetA64("01001011<<0xxxxx0xxxxxxxxxxxxxxx", InstName.Sub, InstEmit.Sub, typeof(OpCodeAluRs)); + SetA64("11001011<<0xxxxxxxxxxxxxxxxxxxxx", InstName.Sub, InstEmit.Sub, typeof(OpCodeAluRs)); + SetA64("x1001011001xxxxxxxx0xxxxxxxxxxxx", InstName.Sub, InstEmit.Sub, typeof(OpCodeAluRx)); + SetA64("x1001011001xxxxxxxx100xxxxxxxxxx", InstName.Sub, InstEmit.Sub, typeof(OpCodeAluRx)); + SetA64("x11100010xxxxxxxxxxxxxxxxxxxxxxx", InstName.Subs, InstEmit.Subs, typeof(OpCodeAluImm)); + SetA64("01101011<<0xxxxx0xxxxxxxxxxxxxxx", InstName.Subs, InstEmit.Subs, typeof(OpCodeAluRs)); + SetA64("11101011<<0xxxxxxxxxxxxxxxxxxxxx", InstName.Subs, InstEmit.Subs, typeof(OpCodeAluRs)); + SetA64("x1101011001xxxxxxxx0xxxxxxxxxxxx", InstName.Subs, InstEmit.Subs, typeof(OpCodeAluRx)); + SetA64("x1101011001xxxxxxxx100xxxxxxxxxx", InstName.Subs, InstEmit.Subs, typeof(OpCodeAluRx)); + SetA64("11010100000xxxxxxxxxxxxxxxx00001", InstName.Svc, InstEmit.Svc, typeof(OpCodeException)); + SetA64("1101010100001xxxxxxxxxxxxxxxxxxx", InstName.Sys, InstEmit.Sys, typeof(OpCodeSystem)); + SetA64("x0110111xxxxxxxxxxxxxxxxxxxxxxxx", InstName.Tbnz, InstEmit.Tbnz, typeof(OpCodeBImmTest)); + SetA64("x0110110xxxxxxxxxxxxxxxxxxxxxxxx", InstName.Tbz, InstEmit.Tbz, typeof(OpCodeBImmTest)); + SetA64("01010011000xxxxx0xxxxxxxxxxxxxxx", InstName.Ubfm, InstEmit.Ubfm, typeof(OpCodeBfm)); + SetA64("1101001101xxxxxxxxxxxxxxxxxxxxxx", InstName.Ubfm, InstEmit.Ubfm, typeof(OpCodeBfm)); + SetA64("x0011010110xxxxx000010xxxxxxxxxx", InstName.Udiv, InstEmit.Udiv, typeof(OpCodeAluBinary)); + SetA64("10011011101xxxxx0xxxxxxxxxxxxxxx", InstName.Umaddl, InstEmit.Umaddl, typeof(OpCodeMul)); + SetA64("10011011101xxxxx1xxxxxxxxxxxxxxx", InstName.Umsubl, InstEmit.Umsubl, typeof(OpCodeMul)); + SetA64("10011011110xxxxx0xxxxxxxxxxxxxxx", InstName.Umulh, InstEmit.Umulh, typeof(OpCodeMul)); + + // FP & SIMD + SetA64("0101111011100000101110xxxxxxxxxx", InstName.Abs_S, InstEmit.Abs_S, typeof(OpCodeSimd)); + SetA64("0>001110<<100000101110xxxxxxxxxx", InstName.Abs_V, InstEmit.Abs_V, typeof(OpCodeSimd)); + SetA64("01011110111xxxxx100001xxxxxxxxxx", InstName.Add_S, InstEmit.Add_S, typeof(OpCodeSimdReg)); + SetA64("0>001110<<1xxxxx100001xxxxxxxxxx", InstName.Add_V, InstEmit.Add_V, typeof(OpCodeSimdReg)); + SetA64("0x001110<<1xxxxx010000xxxxxxxxxx", InstName.Addhn_V, InstEmit.Addhn_V, typeof(OpCodeSimdReg)); + SetA64("0101111011110001101110xxxxxxxxxx", InstName.Addp_S, InstEmit.Addp_S, typeof(OpCodeSimd)); + SetA64("0>001110<<1xxxxx101111xxxxxxxxxx", InstName.Addp_V, InstEmit.Addp_V, typeof(OpCodeSimdReg)); + SetA64("000011100x110001101110xxxxxxxxxx", InstName.Addv_V, InstEmit.Addv_V, typeof(OpCodeSimd)); + SetA64("01001110<<110001101110xxxxxxxxxx", InstName.Addv_V, InstEmit.Addv_V, typeof(OpCodeSimd)); + SetA64("0100111000101000010110xxxxxxxxxx", InstName.Aesd_V, InstEmit.Aesd_V, typeof(OpCodeSimd)); + SetA64("0100111000101000010010xxxxxxxxxx", InstName.Aese_V, InstEmit.Aese_V, typeof(OpCodeSimd)); + SetA64("0100111000101000011110xxxxxxxxxx", InstName.Aesimc_V, InstEmit.Aesimc_V, typeof(OpCodeSimd)); + SetA64("0100111000101000011010xxxxxxxxxx", InstName.Aesmc_V, InstEmit.Aesmc_V, typeof(OpCodeSimd)); + SetA64("0x001110001xxxxx000111xxxxxxxxxx", InstName.And_V, InstEmit.And_V, typeof(OpCodeSimdReg)); + SetA64("0x001110011xxxxx000111xxxxxxxxxx", InstName.Bic_V, InstEmit.Bic_V, typeof(OpCodeSimdReg)); + SetA64("0x10111100000xxx0xx101xxxxxxxxxx", InstName.Bic_Vi, InstEmit.Bic_Vi, typeof(OpCodeSimdImm)); + SetA64("0x10111100000xxx10x101xxxxxxxxxx", InstName.Bic_Vi, InstEmit.Bic_Vi, typeof(OpCodeSimdImm)); + SetA64("0x101110111xxxxx000111xxxxxxxxxx", InstName.Bif_V, InstEmit.Bif_V, typeof(OpCodeSimdReg)); + SetA64("0x101110101xxxxx000111xxxxxxxxxx", InstName.Bit_V, InstEmit.Bit_V, typeof(OpCodeSimdReg)); + SetA64("0x101110011xxxxx000111xxxxxxxxxx", InstName.Bsl_V, InstEmit.Bsl_V, typeof(OpCodeSimdReg)); + SetA64("0x001110<<100000010010xxxxxxxxxx", InstName.Cls_V, InstEmit.Cls_V, typeof(OpCodeSimd)); + SetA64("0x101110<<100000010010xxxxxxxxxx", InstName.Clz_V, InstEmit.Clz_V, typeof(OpCodeSimd)); + SetA64("01111110111xxxxx100011xxxxxxxxxx", InstName.Cmeq_S, InstEmit.Cmeq_S, typeof(OpCodeSimdReg)); + SetA64("0101111011100000100110xxxxxxxxxx", InstName.Cmeq_S, InstEmit.Cmeq_S, typeof(OpCodeSimd)); + SetA64("0>101110<<1xxxxx100011xxxxxxxxxx", InstName.Cmeq_V, InstEmit.Cmeq_V, typeof(OpCodeSimdReg)); + SetA64("0>001110<<100000100110xxxxxxxxxx", InstName.Cmeq_V, InstEmit.Cmeq_V, typeof(OpCodeSimd)); + SetA64("01011110111xxxxx001111xxxxxxxxxx", InstName.Cmge_S, InstEmit.Cmge_S, typeof(OpCodeSimdReg)); + SetA64("0111111011100000100010xxxxxxxxxx", InstName.Cmge_S, InstEmit.Cmge_S, typeof(OpCodeSimd)); + SetA64("0>001110<<1xxxxx001111xxxxxxxxxx", InstName.Cmge_V, InstEmit.Cmge_V, typeof(OpCodeSimdReg)); + SetA64("0>101110<<100000100010xxxxxxxxxx", InstName.Cmge_V, InstEmit.Cmge_V, typeof(OpCodeSimd)); + SetA64("01011110111xxxxx001101xxxxxxxxxx", InstName.Cmgt_S, InstEmit.Cmgt_S, typeof(OpCodeSimdReg)); + SetA64("0101111011100000100010xxxxxxxxxx", InstName.Cmgt_S, InstEmit.Cmgt_S, typeof(OpCodeSimd)); + SetA64("0>001110<<1xxxxx001101xxxxxxxxxx", InstName.Cmgt_V, InstEmit.Cmgt_V, typeof(OpCodeSimdReg)); + SetA64("0>001110<<100000100010xxxxxxxxxx", InstName.Cmgt_V, InstEmit.Cmgt_V, typeof(OpCodeSimd)); + SetA64("01111110111xxxxx001101xxxxxxxxxx", InstName.Cmhi_S, InstEmit.Cmhi_S, typeof(OpCodeSimdReg)); + SetA64("0>101110<<1xxxxx001101xxxxxxxxxx", InstName.Cmhi_V, InstEmit.Cmhi_V, typeof(OpCodeSimdReg)); + SetA64("01111110111xxxxx001111xxxxxxxxxx", InstName.Cmhs_S, InstEmit.Cmhs_S, typeof(OpCodeSimdReg)); + SetA64("0>101110<<1xxxxx001111xxxxxxxxxx", InstName.Cmhs_V, InstEmit.Cmhs_V, typeof(OpCodeSimdReg)); + SetA64("0111111011100000100110xxxxxxxxxx", InstName.Cmle_S, InstEmit.Cmle_S, typeof(OpCodeSimd)); + SetA64("0>101110<<100000100110xxxxxxxxxx", InstName.Cmle_V, InstEmit.Cmle_V, typeof(OpCodeSimd)); + SetA64("0101111011100000101010xxxxxxxxxx", InstName.Cmlt_S, InstEmit.Cmlt_S, typeof(OpCodeSimd)); + SetA64("0>001110<<100000101010xxxxxxxxxx", InstName.Cmlt_V, InstEmit.Cmlt_V, typeof(OpCodeSimd)); + SetA64("01011110111xxxxx100011xxxxxxxxxx", InstName.Cmtst_S, InstEmit.Cmtst_S, typeof(OpCodeSimdReg)); + SetA64("0>001110<<1xxxxx100011xxxxxxxxxx", InstName.Cmtst_V, InstEmit.Cmtst_V, typeof(OpCodeSimdReg)); + SetA64("0x00111000100000010110xxxxxxxxxx", InstName.Cnt_V, InstEmit.Cnt_V, typeof(OpCodeSimd)); + SetA64("0>001110000x<>>>000011xxxxxxxxxx", InstName.Dup_Gp, InstEmit.Dup_Gp, typeof(OpCodeSimdIns)); + SetA64("01011110000xxxxx000001xxxxxxxxxx", InstName.Dup_S, InstEmit.Dup_S, typeof(OpCodeSimdIns)); + SetA64("0>001110000x<>>>000001xxxxxxxxxx", InstName.Dup_V, InstEmit.Dup_V, typeof(OpCodeSimdIns)); + SetA64("0x101110001xxxxx000111xxxxxxxxxx", InstName.Eor_V, InstEmit.Eor_V, typeof(OpCodeSimdReg)); + SetA64("0>101110000xxxxx01011101<1xxxxx110101xxxxxxxxxx", InstName.Fabd_V, InstEmit.Fabd_V, typeof(OpCodeSimdReg)); + SetA64("000111100x100000110000xxxxxxxxxx", InstName.Fabs_S, InstEmit.Fabs_S, typeof(OpCodeSimd)); + SetA64("0>0011101<100000111110xxxxxxxxxx", InstName.Fabs_V, InstEmit.Fabs_V, typeof(OpCodeSimd)); + SetA64("000111100x1xxxxx001010xxxxxxxxxx", InstName.Fadd_S, InstEmit.Fadd_S, typeof(OpCodeSimdReg)); + SetA64("0>0011100<1xxxxx110101xxxxxxxxxx", InstName.Fadd_V, InstEmit.Fadd_V, typeof(OpCodeSimdReg)); + SetA64("011111100x110000110110xxxxxxxxxx", InstName.Faddp_S, InstEmit.Faddp_S, typeof(OpCodeSimd)); + SetA64("0>1011100<1xxxxx110101xxxxxxxxxx", InstName.Faddp_V, InstEmit.Faddp_V, typeof(OpCodeSimdReg)); + SetA64("000111100x1xxxxxxxxx01xxxxx0xxxx", InstName.Fccmp_S, InstEmit.Fccmp_S, typeof(OpCodeSimdFcond)); + SetA64("000111100x1xxxxxxxxx01xxxxx1xxxx", InstName.Fccmpe_S, InstEmit.Fccmpe_S, typeof(OpCodeSimdFcond)); + SetA64("010111100x1xxxxx111001xxxxxxxxxx", InstName.Fcmeq_S, InstEmit.Fcmeq_S, typeof(OpCodeSimdReg)); + SetA64("010111101x100000110110xxxxxxxxxx", InstName.Fcmeq_S, InstEmit.Fcmeq_S, typeof(OpCodeSimd)); + SetA64("0>0011100<1xxxxx111001xxxxxxxxxx", InstName.Fcmeq_V, InstEmit.Fcmeq_V, typeof(OpCodeSimdReg)); + SetA64("0>0011101<100000110110xxxxxxxxxx", InstName.Fcmeq_V, InstEmit.Fcmeq_V, typeof(OpCodeSimd)); + SetA64("011111100x1xxxxx111001xxxxxxxxxx", InstName.Fcmge_S, InstEmit.Fcmge_S, typeof(OpCodeSimdReg)); + SetA64("011111101x100000110010xxxxxxxxxx", InstName.Fcmge_S, InstEmit.Fcmge_S, typeof(OpCodeSimd)); + SetA64("0>1011100<1xxxxx111001xxxxxxxxxx", InstName.Fcmge_V, InstEmit.Fcmge_V, typeof(OpCodeSimdReg)); + SetA64("0>1011101<100000110010xxxxxxxxxx", InstName.Fcmge_V, InstEmit.Fcmge_V, typeof(OpCodeSimd)); + SetA64("011111101x1xxxxx111001xxxxxxxxxx", InstName.Fcmgt_S, InstEmit.Fcmgt_S, typeof(OpCodeSimdReg)); + SetA64("010111101x100000110010xxxxxxxxxx", InstName.Fcmgt_S, InstEmit.Fcmgt_S, typeof(OpCodeSimd)); + SetA64("0>1011101<1xxxxx111001xxxxxxxxxx", InstName.Fcmgt_V, InstEmit.Fcmgt_V, typeof(OpCodeSimdReg)); + SetA64("0>0011101<100000110010xxxxxxxxxx", InstName.Fcmgt_V, InstEmit.Fcmgt_V, typeof(OpCodeSimd)); + SetA64("011111101x100000110110xxxxxxxxxx", InstName.Fcmle_S, InstEmit.Fcmle_S, typeof(OpCodeSimd)); + SetA64("0>1011101<100000110110xxxxxxxxxx", InstName.Fcmle_V, InstEmit.Fcmle_V, typeof(OpCodeSimd)); + SetA64("010111101x100000111010xxxxxxxxxx", InstName.Fcmlt_S, InstEmit.Fcmlt_S, typeof(OpCodeSimd)); + SetA64("0>0011101<100000111010xxxxxxxxxx", InstName.Fcmlt_V, InstEmit.Fcmlt_V, typeof(OpCodeSimd)); + SetA64("000111100x1xxxxx001000xxxxx0x000", InstName.Fcmp_S, InstEmit.Fcmp_S, typeof(OpCodeSimdReg)); + SetA64("000111100x1xxxxx001000xxxxx1x000", InstName.Fcmpe_S, InstEmit.Fcmpe_S, typeof(OpCodeSimdReg)); + SetA64("000111100x1xxxxxxxxx11xxxxxxxxxx", InstName.Fcsel_S, InstEmit.Fcsel_S, typeof(OpCodeSimdFcond)); + SetA64("00011110xx10001xx10000xxxxxxxxxx", InstName.Fcvt_S, InstEmit.Fcvt_S, typeof(OpCodeSimd)); + SetA64("x00111100x100100000000xxxxxxxxxx", InstName.Fcvtas_Gp, InstEmit.Fcvtas_Gp, typeof(OpCodeSimdCvt)); + SetA64("x00111100x100101000000xxxxxxxxxx", InstName.Fcvtau_Gp, InstEmit.Fcvtau_Gp, typeof(OpCodeSimdCvt)); + SetA64("0x0011100x100001011110xxxxxxxxxx", InstName.Fcvtl_V, InstEmit.Fcvtl_V, typeof(OpCodeSimd)); + SetA64("x00111100x110000000000xxxxxxxxxx", InstName.Fcvtms_Gp, InstEmit.Fcvtms_Gp, typeof(OpCodeSimdCvt)); + SetA64("x00111100x110001000000xxxxxxxxxx", InstName.Fcvtmu_Gp, InstEmit.Fcvtmu_Gp, typeof(OpCodeSimdCvt)); + SetA64("0x0011100x100001011010xxxxxxxxxx", InstName.Fcvtn_V, InstEmit.Fcvtn_V, typeof(OpCodeSimd)); + SetA64("010111100x100001101010xxxxxxxxxx", InstName.Fcvtns_S, InstEmit.Fcvtns_S, typeof(OpCodeSimd)); + SetA64("0>0011100<100001101010xxxxxxxxxx", InstName.Fcvtns_V, InstEmit.Fcvtns_V, typeof(OpCodeSimd)); + SetA64("011111100x100001101010xxxxxxxxxx", InstName.Fcvtnu_S, InstEmit.Fcvtnu_S, typeof(OpCodeSimd)); + SetA64("0>1011100<100001101010xxxxxxxxxx", InstName.Fcvtnu_V, InstEmit.Fcvtnu_V, typeof(OpCodeSimd)); + SetA64("x00111100x101000000000xxxxxxxxxx", InstName.Fcvtps_Gp, InstEmit.Fcvtps_Gp, typeof(OpCodeSimdCvt)); + SetA64("x00111100x101001000000xxxxxxxxxx", InstName.Fcvtpu_Gp, InstEmit.Fcvtpu_Gp, typeof(OpCodeSimdCvt)); + SetA64("x00111100x111000000000xxxxxxxxxx", InstName.Fcvtzs_Gp, InstEmit.Fcvtzs_Gp, typeof(OpCodeSimdCvt)); + SetA64(">00111100x011000>xxxxxxxxxxxxxxx", InstName.Fcvtzs_Gp_Fixed, InstEmit.Fcvtzs_Gp_Fixed, typeof(OpCodeSimdCvt)); + SetA64("010111101x100001101110xxxxxxxxxx", InstName.Fcvtzs_S, InstEmit.Fcvtzs_S, typeof(OpCodeSimd)); + SetA64("0>0011101<100001101110xxxxxxxxxx", InstName.Fcvtzs_V, InstEmit.Fcvtzs_V, typeof(OpCodeSimd)); + SetA64("0x001111001xxxxx111111xxxxxxxxxx", InstName.Fcvtzs_V_Fixed, InstEmit.Fcvtzs_V_Fixed, typeof(OpCodeSimdShImm)); + SetA64("0100111101xxxxxx111111xxxxxxxxxx", InstName.Fcvtzs_V_Fixed, InstEmit.Fcvtzs_V_Fixed, typeof(OpCodeSimdShImm)); + SetA64("x00111100x111001000000xxxxxxxxxx", InstName.Fcvtzu_Gp, InstEmit.Fcvtzu_Gp, typeof(OpCodeSimdCvt)); + SetA64(">00111100x011001>xxxxxxxxxxxxxxx", InstName.Fcvtzu_Gp_Fixed, InstEmit.Fcvtzu_Gp_Fixed, typeof(OpCodeSimdCvt)); + SetA64("011111101x100001101110xxxxxxxxxx", InstName.Fcvtzu_S, InstEmit.Fcvtzu_S, typeof(OpCodeSimd)); + SetA64("0>1011101<100001101110xxxxxxxxxx", InstName.Fcvtzu_V, InstEmit.Fcvtzu_V, typeof(OpCodeSimd)); + SetA64("0x101111001xxxxx111111xxxxxxxxxx", InstName.Fcvtzu_V_Fixed, InstEmit.Fcvtzu_V_Fixed, typeof(OpCodeSimdShImm)); + SetA64("0110111101xxxxxx111111xxxxxxxxxx", InstName.Fcvtzu_V_Fixed, InstEmit.Fcvtzu_V_Fixed, typeof(OpCodeSimdShImm)); + SetA64("000111100x1xxxxx000110xxxxxxxxxx", InstName.Fdiv_S, InstEmit.Fdiv_S, typeof(OpCodeSimdReg)); + SetA64("0>1011100<1xxxxx111111xxxxxxxxxx", InstName.Fdiv_V, InstEmit.Fdiv_V, typeof(OpCodeSimdReg)); + SetA64("000111110x0xxxxx0xxxxxxxxxxxxxxx", InstName.Fmadd_S, InstEmit.Fmadd_S, typeof(OpCodeSimdReg)); + SetA64("000111100x1xxxxx010010xxxxxxxxxx", InstName.Fmax_S, InstEmit.Fmax_S, typeof(OpCodeSimdReg)); + SetA64("0>0011100<1xxxxx111101xxxxxxxxxx", InstName.Fmax_V, InstEmit.Fmax_V, typeof(OpCodeSimdReg)); + SetA64("000111100x1xxxxx011010xxxxxxxxxx", InstName.Fmaxnm_S, InstEmit.Fmaxnm_S, typeof(OpCodeSimdReg)); + SetA64("0>0011100<1xxxxx110001xxxxxxxxxx", InstName.Fmaxnm_V, InstEmit.Fmaxnm_V, typeof(OpCodeSimdReg)); + SetA64("0>1011100<1xxxxx111101xxxxxxxxxx", InstName.Fmaxp_V, InstEmit.Fmaxp_V, typeof(OpCodeSimdReg)); + SetA64("000111100x1xxxxx010110xxxxxxxxxx", InstName.Fmin_S, InstEmit.Fmin_S, typeof(OpCodeSimdReg)); + SetA64("0>0011101<1xxxxx111101xxxxxxxxxx", InstName.Fmin_V, InstEmit.Fmin_V, typeof(OpCodeSimdReg)); + SetA64("000111100x1xxxxx011110xxxxxxxxxx", InstName.Fminnm_S, InstEmit.Fminnm_S, typeof(OpCodeSimdReg)); + SetA64("0>0011101<1xxxxx110001xxxxxxxxxx", InstName.Fminnm_V, InstEmit.Fminnm_V, typeof(OpCodeSimdReg)); + SetA64("0>1011101<1xxxxx111101xxxxxxxxxx", InstName.Fminp_V, InstEmit.Fminp_V, typeof(OpCodeSimdReg)); + SetA64("010111111xxxxxxx0001x0xxxxxxxxxx", InstName.Fmla_Se, InstEmit.Fmla_Se, typeof(OpCodeSimdRegElemF)); + SetA64("0>0011100<1xxxxx110011xxxxxxxxxx", InstName.Fmla_V, InstEmit.Fmla_V, typeof(OpCodeSimdReg)); + SetA64("0>00111110011101<1xxxxx110011xxxxxxxxxx", InstName.Fmls_V, InstEmit.Fmls_V, typeof(OpCodeSimdReg)); + SetA64("0>00111111011100<1xxxxx110111xxxxxxxxxx", InstName.Fmul_V, InstEmit.Fmul_V, typeof(OpCodeSimdReg)); + SetA64("0>00111110011100<1xxxxx110111xxxxxxxxxx", InstName.Fmulx_V, InstEmit.Fmulx_V, typeof(OpCodeSimdReg)); + SetA64("0>10111111011101<100000111110xxxxxxxxxx", InstName.Fneg_V, InstEmit.Fneg_V, typeof(OpCodeSimd)); + SetA64("000111110x1xxxxx0xxxxxxxxxxxxxxx", InstName.Fnmadd_S, InstEmit.Fnmadd_S, typeof(OpCodeSimdReg)); + SetA64("000111110x1xxxxx1xxxxxxxxxxxxxxx", InstName.Fnmsub_S, InstEmit.Fnmsub_S, typeof(OpCodeSimdReg)); + SetA64("000111100x1xxxxx100010xxxxxxxxxx", InstName.Fnmul_S, InstEmit.Fnmul_S, typeof(OpCodeSimdReg)); + SetA64("010111101x100001110110xxxxxxxxxx", InstName.Frecpe_S, InstEmit.Frecpe_S, typeof(OpCodeSimd)); + SetA64("0>0011101<100001110110xxxxxxxxxx", InstName.Frecpe_V, InstEmit.Frecpe_V, typeof(OpCodeSimd)); + SetA64("010111100x1xxxxx111111xxxxxxxxxx", InstName.Frecps_S, InstEmit.Frecps_S, typeof(OpCodeSimdReg)); + SetA64("0>0011100<1xxxxx111111xxxxxxxxxx", InstName.Frecps_V, InstEmit.Frecps_V, typeof(OpCodeSimdReg)); + SetA64("010111101x100001111110xxxxxxxxxx", InstName.Frecpx_S, InstEmit.Frecpx_S, typeof(OpCodeSimd)); + SetA64("000111100x100110010000xxxxxxxxxx", InstName.Frinta_S, InstEmit.Frinta_S, typeof(OpCodeSimd)); + SetA64("0>1011100<100001100010xxxxxxxxxx", InstName.Frinta_V, InstEmit.Frinta_V, typeof(OpCodeSimd)); + SetA64("000111100x100111110000xxxxxxxxxx", InstName.Frinti_S, InstEmit.Frinti_S, typeof(OpCodeSimd)); + SetA64("0>1011101<100001100110xxxxxxxxxx", InstName.Frinti_V, InstEmit.Frinti_V, typeof(OpCodeSimd)); + SetA64("000111100x100101010000xxxxxxxxxx", InstName.Frintm_S, InstEmit.Frintm_S, typeof(OpCodeSimd)); + SetA64("0>0011100<100001100110xxxxxxxxxx", InstName.Frintm_V, InstEmit.Frintm_V, typeof(OpCodeSimd)); + SetA64("000111100x100100010000xxxxxxxxxx", InstName.Frintn_S, InstEmit.Frintn_S, typeof(OpCodeSimd)); + SetA64("0>0011100<100001100010xxxxxxxxxx", InstName.Frintn_V, InstEmit.Frintn_V, typeof(OpCodeSimd)); + SetA64("000111100x100100110000xxxxxxxxxx", InstName.Frintp_S, InstEmit.Frintp_S, typeof(OpCodeSimd)); + SetA64("0>0011101<100001100010xxxxxxxxxx", InstName.Frintp_V, InstEmit.Frintp_V, typeof(OpCodeSimd)); + SetA64("000111100x100111010000xxxxxxxxxx", InstName.Frintx_S, InstEmit.Frintx_S, typeof(OpCodeSimd)); + SetA64("0>1011100<100001100110xxxxxxxxxx", InstName.Frintx_V, InstEmit.Frintx_V, typeof(OpCodeSimd)); + SetA64("000111100x100101110000xxxxxxxxxx", InstName.Frintz_S, InstEmit.Frintz_S, typeof(OpCodeSimd)); + SetA64("0>0011101<100001100110xxxxxxxxxx", InstName.Frintz_V, InstEmit.Frintz_V, typeof(OpCodeSimd)); + SetA64("011111101x100001110110xxxxxxxxxx", InstName.Frsqrte_S, InstEmit.Frsqrte_S, typeof(OpCodeSimd)); + SetA64("0>1011101<100001110110xxxxxxxxxx", InstName.Frsqrte_V, InstEmit.Frsqrte_V, typeof(OpCodeSimd)); + SetA64("010111101x1xxxxx111111xxxxxxxxxx", InstName.Frsqrts_S, InstEmit.Frsqrts_S, typeof(OpCodeSimdReg)); + SetA64("0>0011101<1xxxxx111111xxxxxxxxxx", InstName.Frsqrts_V, InstEmit.Frsqrts_V, typeof(OpCodeSimdReg)); + SetA64("000111100x100001110000xxxxxxxxxx", InstName.Fsqrt_S, InstEmit.Fsqrt_S, typeof(OpCodeSimd)); + SetA64("0>1011101<100001111110xxxxxxxxxx", InstName.Fsqrt_V, InstEmit.Fsqrt_V, typeof(OpCodeSimd)); + SetA64("000111100x1xxxxx001110xxxxxxxxxx", InstName.Fsub_S, InstEmit.Fsub_S, typeof(OpCodeSimdReg)); + SetA64("0>0011101<1xxxxx110101xxxxxxxxxx", InstName.Fsub_V, InstEmit.Fsub_V, typeof(OpCodeSimdReg)); + SetA64("01001110000xxxxx000111xxxxxxxxxx", InstName.Ins_Gp, InstEmit.Ins_Gp, typeof(OpCodeSimdIns)); + SetA64("01101110000xxxxx0xxxx1xxxxxxxxxx", InstName.Ins_V, InstEmit.Ins_V, typeof(OpCodeSimdIns)); + SetA64("0x00110001000000xxxxxxxxxxxxxxxx", InstName.Ld__Vms, InstEmit.Ld__Vms, typeof(OpCodeSimdMemMs)); + SetA64("0x001100110xxxxxxxxxxxxxxxxxxxxx", InstName.Ld__Vms, InstEmit.Ld__Vms, typeof(OpCodeSimdMemMs)); + SetA64("0x00110101x00000xxxxxxxxxxxxxxxx", InstName.Ld__Vss, InstEmit.Ld__Vss, typeof(OpCodeSimdMemSs)); + SetA64("0x00110111xxxxxxxxxxxxxxxxxxxxxx", InstName.Ld__Vss, InstEmit.Ld__Vss, typeof(OpCodeSimdMemSs)); + SetA64("xx10110xx1xxxxxxxxxxxxxxxxxxxxxx", InstName.Ldp, InstEmit.Ldp, typeof(OpCodeSimdMemPair)); + SetA64("xx111100x10xxxxxxxxx00xxxxxxxxxx", InstName.Ldr, InstEmit.Ldr, typeof(OpCodeSimdMemImm)); + SetA64("xx111100x10xxxxxxxxx01xxxxxxxxxx", InstName.Ldr, InstEmit.Ldr, typeof(OpCodeSimdMemImm)); + SetA64("xx111100x10xxxxxxxxx11xxxxxxxxxx", InstName.Ldr, InstEmit.Ldr, typeof(OpCodeSimdMemImm)); + SetA64("xx111101x1xxxxxxxxxxxxxxxxxxxxxx", InstName.Ldr, InstEmit.Ldr, typeof(OpCodeSimdMemImm)); + SetA64("xx111100x11xxxxxxxxx10xxxxxxxxxx", InstName.Ldr, InstEmit.Ldr, typeof(OpCodeSimdMemReg)); + SetA64("xx011100xxxxxxxxxxxxxxxxxxxxxxxx", InstName.Ldr_Literal, InstEmit.Ldr_Literal, typeof(OpCodeSimdMemLit)); + SetA64("0x001110<<1xxxxx100101xxxxxxxxxx", InstName.Mla_V, InstEmit.Mla_V, typeof(OpCodeSimdReg)); + SetA64("0x101111xxxxxxxx0000x0xxxxxxxxxx", InstName.Mla_Ve, InstEmit.Mla_Ve, typeof(OpCodeSimdRegElem)); + SetA64("0x101110<<1xxxxx100101xxxxxxxxxx", InstName.Mls_V, InstEmit.Mls_V, typeof(OpCodeSimdReg)); + SetA64("0x101111xxxxxxxx0100x0xxxxxxxxxx", InstName.Mls_Ve, InstEmit.Mls_Ve, typeof(OpCodeSimdRegElem)); + SetA64("0x00111100000xxx0xx001xxxxxxxxxx", InstName.Movi_V, InstEmit.Movi_V, typeof(OpCodeSimdImm)); + SetA64("0x00111100000xxx10x001xxxxxxxxxx", InstName.Movi_V, InstEmit.Movi_V, typeof(OpCodeSimdImm)); + SetA64("0x00111100000xxx110x01xxxxxxxxxx", InstName.Movi_V, InstEmit.Movi_V, typeof(OpCodeSimdImm)); + SetA64("0xx0111100000xxx111001xxxxxxxxxx", InstName.Movi_V, InstEmit.Movi_V, typeof(OpCodeSimdImm)); + SetA64("0x001110<<1xxxxx100111xxxxxxxxxx", InstName.Mul_V, InstEmit.Mul_V, typeof(OpCodeSimdReg)); + SetA64("0x001111xxxxxxxx1000x0xxxxxxxxxx", InstName.Mul_Ve, InstEmit.Mul_Ve, typeof(OpCodeSimdRegElem)); + SetA64("0x10111100000xxx0xx001xxxxxxxxxx", InstName.Mvni_V, InstEmit.Mvni_V, typeof(OpCodeSimdImm)); + SetA64("0x10111100000xxx10x001xxxxxxxxxx", InstName.Mvni_V, InstEmit.Mvni_V, typeof(OpCodeSimdImm)); + SetA64("0x10111100000xxx110x01xxxxxxxxxx", InstName.Mvni_V, InstEmit.Mvni_V, typeof(OpCodeSimdImm)); + SetA64("0111111011100000101110xxxxxxxxxx", InstName.Neg_S, InstEmit.Neg_S, typeof(OpCodeSimd)); + SetA64("0>101110<<100000101110xxxxxxxxxx", InstName.Neg_V, InstEmit.Neg_V, typeof(OpCodeSimd)); + SetA64("0x10111000100000010110xxxxxxxxxx", InstName.Not_V, InstEmit.Not_V, typeof(OpCodeSimd)); + SetA64("0x001110111xxxxx000111xxxxxxxxxx", InstName.Orn_V, InstEmit.Orn_V, typeof(OpCodeSimdReg)); + SetA64("0x001110101xxxxx000111xxxxxxxxxx", InstName.Orr_V, InstEmit.Orr_V, typeof(OpCodeSimdReg)); + SetA64("0x00111100000xxx0xx101xxxxxxxxxx", InstName.Orr_Vi, InstEmit.Orr_Vi, typeof(OpCodeSimdImm)); + SetA64("0x00111100000xxx10x101xxxxxxxxxx", InstName.Orr_Vi, InstEmit.Orr_Vi, typeof(OpCodeSimdImm)); + SetA64("0x101110<<1xxxxx010000xxxxxxxxxx", InstName.Raddhn_V, InstEmit.Raddhn_V, typeof(OpCodeSimdReg)); + SetA64("0x10111001100000010110xxxxxxxxxx", InstName.Rbit_V, InstEmit.Rbit_V, typeof(OpCodeSimd)); + SetA64("0x00111000100000000110xxxxxxxxxx", InstName.Rev16_V, InstEmit.Rev16_V, typeof(OpCodeSimd)); + SetA64("0x1011100x100000000010xxxxxxxxxx", InstName.Rev32_V, InstEmit.Rev32_V, typeof(OpCodeSimd)); + SetA64("0x001110<<100000000010xxxxxxxxxx", InstName.Rev64_V, InstEmit.Rev64_V, typeof(OpCodeSimd)); + SetA64("0x00111100>>>xxx100011xxxxxxxxxx", InstName.Rshrn_V, InstEmit.Rshrn_V, typeof(OpCodeSimdShImm)); + SetA64("0x101110<<1xxxxx011000xxxxxxxxxx", InstName.Rsubhn_V, InstEmit.Rsubhn_V, typeof(OpCodeSimdReg)); + SetA64("0x001110<<1xxxxx011111xxxxxxxxxx", InstName.Saba_V, InstEmit.Saba_V, typeof(OpCodeSimdReg)); + SetA64("0x001110<<1xxxxx010100xxxxxxxxxx", InstName.Sabal_V, InstEmit.Sabal_V, typeof(OpCodeSimdReg)); + SetA64("0x001110<<1xxxxx011101xxxxxxxxxx", InstName.Sabd_V, InstEmit.Sabd_V, typeof(OpCodeSimdReg)); + SetA64("0x001110<<1xxxxx011100xxxxxxxxxx", InstName.Sabdl_V, InstEmit.Sabdl_V, typeof(OpCodeSimdReg)); + SetA64("0x001110<<100000011010xxxxxxxxxx", InstName.Sadalp_V, InstEmit.Sadalp_V, typeof(OpCodeSimd)); + SetA64("0x001110<<1xxxxx000000xxxxxxxxxx", InstName.Saddl_V, InstEmit.Saddl_V, typeof(OpCodeSimdReg)); + SetA64("0x001110<<100000001010xxxxxxxxxx", InstName.Saddlp_V, InstEmit.Saddlp_V, typeof(OpCodeSimd)); + SetA64("000011100x110000001110xxxxxxxxxx", InstName.Saddlv_V, InstEmit.Saddlv_V, typeof(OpCodeSimd)); + SetA64("01001110<<110000001110xxxxxxxxxx", InstName.Saddlv_V, InstEmit.Saddlv_V, typeof(OpCodeSimd)); + SetA64("0x001110<<1xxxxx000100xxxxxxxxxx", InstName.Saddw_V, InstEmit.Saddw_V, typeof(OpCodeSimdReg)); + SetA64("x00111100x100010000000xxxxxxxxxx", InstName.Scvtf_Gp, InstEmit.Scvtf_Gp, typeof(OpCodeSimdCvt)); + SetA64(">00111100x000010>xxxxxxxxxxxxxxx", InstName.Scvtf_Gp_Fixed, InstEmit.Scvtf_Gp_Fixed, typeof(OpCodeSimdCvt)); + SetA64("010111100x100001110110xxxxxxxxxx", InstName.Scvtf_S, InstEmit.Scvtf_S, typeof(OpCodeSimd)); + SetA64("0>0011100<100001110110xxxxxxxxxx", InstName.Scvtf_V, InstEmit.Scvtf_V, typeof(OpCodeSimd)); + SetA64("0x001111001xxxxx111001xxxxxxxxxx", InstName.Scvtf_V_Fixed, InstEmit.Scvtf_V_Fixed, typeof(OpCodeSimdShImm)); + SetA64("0100111101xxxxxx111001xxxxxxxxxx", InstName.Scvtf_V_Fixed, InstEmit.Scvtf_V_Fixed, typeof(OpCodeSimdShImm)); + SetA64("01011110000xxxxx000000xxxxxxxxxx", InstName.Sha1c_V, InstEmit.Sha1c_V, typeof(OpCodeSimdReg)); + SetA64("0101111000101000000010xxxxxxxxxx", InstName.Sha1h_V, InstEmit.Sha1h_V, typeof(OpCodeSimd)); + SetA64("01011110000xxxxx001000xxxxxxxxxx", InstName.Sha1m_V, InstEmit.Sha1m_V, typeof(OpCodeSimdReg)); + SetA64("01011110000xxxxx000100xxxxxxxxxx", InstName.Sha1p_V, InstEmit.Sha1p_V, typeof(OpCodeSimdReg)); + SetA64("01011110000xxxxx001100xxxxxxxxxx", InstName.Sha1su0_V, InstEmit.Sha1su0_V, typeof(OpCodeSimdReg)); + SetA64("0101111000101000000110xxxxxxxxxx", InstName.Sha1su1_V, InstEmit.Sha1su1_V, typeof(OpCodeSimd)); + SetA64("01011110000xxxxx010000xxxxxxxxxx", InstName.Sha256h_V, InstEmit.Sha256h_V, typeof(OpCodeSimdReg)); + SetA64("01011110000xxxxx010100xxxxxxxxxx", InstName.Sha256h2_V, InstEmit.Sha256h2_V, typeof(OpCodeSimdReg)); + SetA64("0101111000101000001010xxxxxxxxxx", InstName.Sha256su0_V, InstEmit.Sha256su0_V, typeof(OpCodeSimd)); + SetA64("01011110000xxxxx011000xxxxxxxxxx", InstName.Sha256su1_V, InstEmit.Sha256su1_V, typeof(OpCodeSimdReg)); + SetA64("0x001110<<1xxxxx000001xxxxxxxxxx", InstName.Shadd_V, InstEmit.Shadd_V, typeof(OpCodeSimdReg)); + SetA64("0101111101xxxxxx010101xxxxxxxxxx", InstName.Shl_S, InstEmit.Shl_S, typeof(OpCodeSimdShImm)); + SetA64("0x00111100>>>xxx010101xxxxxxxxxx", InstName.Shl_V, InstEmit.Shl_V, typeof(OpCodeSimdShImm)); + SetA64("0100111101xxxxxx010101xxxxxxxxxx", InstName.Shl_V, InstEmit.Shl_V, typeof(OpCodeSimdShImm)); + SetA64("0x101110<<100001001110xxxxxxxxxx", InstName.Shll_V, InstEmit.Shll_V, typeof(OpCodeSimd)); + SetA64("0x00111100>>>xxx100001xxxxxxxxxx", InstName.Shrn_V, InstEmit.Shrn_V, typeof(OpCodeSimdShImm)); + SetA64("0x001110<<1xxxxx001001xxxxxxxxxx", InstName.Shsub_V, InstEmit.Shsub_V, typeof(OpCodeSimdReg)); + SetA64("0111111101xxxxxx010101xxxxxxxxxx", InstName.Sli_S, InstEmit.Sli_S, typeof(OpCodeSimdShImm)); + SetA64("0x10111100>>>xxx010101xxxxxxxxxx", InstName.Sli_V, InstEmit.Sli_V, typeof(OpCodeSimdShImm)); + SetA64("0110111101xxxxxx010101xxxxxxxxxx", InstName.Sli_V, InstEmit.Sli_V, typeof(OpCodeSimdShImm)); + SetA64("0x001110<<1xxxxx011001xxxxxxxxxx", InstName.Smax_V, InstEmit.Smax_V, typeof(OpCodeSimdReg)); + SetA64("0x001110<<1xxxxx101001xxxxxxxxxx", InstName.Smaxp_V, InstEmit.Smaxp_V, typeof(OpCodeSimdReg)); + SetA64("000011100x110000101010xxxxxxxxxx", InstName.Smaxv_V, InstEmit.Smaxv_V, typeof(OpCodeSimd)); + SetA64("01001110<<110000101010xxxxxxxxxx", InstName.Smaxv_V, InstEmit.Smaxv_V, typeof(OpCodeSimd)); + SetA64("0x001110<<1xxxxx011011xxxxxxxxxx", InstName.Smin_V, InstEmit.Smin_V, typeof(OpCodeSimdReg)); + SetA64("0x001110<<1xxxxx101011xxxxxxxxxx", InstName.Sminp_V, InstEmit.Sminp_V, typeof(OpCodeSimdReg)); + SetA64("000011100x110001101010xxxxxxxxxx", InstName.Sminv_V, InstEmit.Sminv_V, typeof(OpCodeSimd)); + SetA64("01001110<<110001101010xxxxxxxxxx", InstName.Sminv_V, InstEmit.Sminv_V, typeof(OpCodeSimd)); + SetA64("0x001110<<1xxxxx100000xxxxxxxxxx", InstName.Smlal_V, InstEmit.Smlal_V, typeof(OpCodeSimdReg)); + SetA64("0x001111xxxxxxxx0010x0xxxxxxxxxx", InstName.Smlal_Ve, InstEmit.Smlal_Ve, typeof(OpCodeSimdRegElem)); + SetA64("0x001110<<1xxxxx101000xxxxxxxxxx", InstName.Smlsl_V, InstEmit.Smlsl_V, typeof(OpCodeSimdReg)); + SetA64("0x001111xxxxxxxx0110x0xxxxxxxxxx", InstName.Smlsl_Ve, InstEmit.Smlsl_Ve, typeof(OpCodeSimdRegElem)); + SetA64("0x001110000xxxxx001011xxxxxxxxxx", InstName.Smov_S, InstEmit.Smov_S, typeof(OpCodeSimdIns)); + SetA64("0x001110<<1xxxxx110000xxxxxxxxxx", InstName.Smull_V, InstEmit.Smull_V, typeof(OpCodeSimdReg)); + SetA64("0x001111xxxxxxxx1010x0xxxxxxxxxx", InstName.Smull_Ve, InstEmit.Smull_Ve, typeof(OpCodeSimdRegElem)); + SetA64("01011110xx100000011110xxxxxxxxxx", InstName.Sqabs_S, InstEmit.Sqabs_S, typeof(OpCodeSimd)); + SetA64("0>001110<<100000011110xxxxxxxxxx", InstName.Sqabs_V, InstEmit.Sqabs_V, typeof(OpCodeSimd)); + SetA64("01011110xx1xxxxx000011xxxxxxxxxx", InstName.Sqadd_S, InstEmit.Sqadd_S, typeof(OpCodeSimdReg)); + SetA64("0>001110<<1xxxxx000011xxxxxxxxxx", InstName.Sqadd_V, InstEmit.Sqadd_V, typeof(OpCodeSimdReg)); + SetA64("01011110011xxxxx101101xxxxxxxxxx", InstName.Sqdmulh_S, InstEmit.Sqdmulh_S, typeof(OpCodeSimdReg)); + SetA64("01011110101xxxxx101101xxxxxxxxxx", InstName.Sqdmulh_S, InstEmit.Sqdmulh_S, typeof(OpCodeSimdReg)); + SetA64("0x001110011xxxxx101101xxxxxxxxxx", InstName.Sqdmulh_V, InstEmit.Sqdmulh_V, typeof(OpCodeSimdReg)); + SetA64("0x001110101xxxxx101101xxxxxxxxxx", InstName.Sqdmulh_V, InstEmit.Sqdmulh_V, typeof(OpCodeSimdReg)); + SetA64("01111110xx100000011110xxxxxxxxxx", InstName.Sqneg_S, InstEmit.Sqneg_S, typeof(OpCodeSimd)); + SetA64("0>101110<<100000011110xxxxxxxxxx", InstName.Sqneg_V, InstEmit.Sqneg_V, typeof(OpCodeSimd)); + SetA64("01111110011xxxxx101101xxxxxxxxxx", InstName.Sqrdmulh_S, InstEmit.Sqrdmulh_S, typeof(OpCodeSimdReg)); + SetA64("01111110101xxxxx101101xxxxxxxxxx", InstName.Sqrdmulh_S, InstEmit.Sqrdmulh_S, typeof(OpCodeSimdReg)); + SetA64("0x101110011xxxxx101101xxxxxxxxxx", InstName.Sqrdmulh_V, InstEmit.Sqrdmulh_V, typeof(OpCodeSimdReg)); + SetA64("0x101110101xxxxx101101xxxxxxxxxx", InstName.Sqrdmulh_V, InstEmit.Sqrdmulh_V, typeof(OpCodeSimdReg)); + SetA64("0>001110<<1xxxxx010111xxxxxxxxxx", InstName.Sqrshl_V, InstEmit.Sqrshl_V, typeof(OpCodeSimdReg)); + SetA64("0101111100>>>xxx100111xxxxxxxxxx", InstName.Sqrshrn_S, InstEmit.Sqrshrn_S, typeof(OpCodeSimdShImm)); + SetA64("0x00111100>>>xxx100111xxxxxxxxxx", InstName.Sqrshrn_V, InstEmit.Sqrshrn_V, typeof(OpCodeSimdShImm)); + SetA64("0111111100>>>xxx100011xxxxxxxxxx", InstName.Sqrshrun_S, InstEmit.Sqrshrun_S, typeof(OpCodeSimdShImm)); + SetA64("0x10111100>>>xxx100011xxxxxxxxxx", InstName.Sqrshrun_V, InstEmit.Sqrshrun_V, typeof(OpCodeSimdShImm)); + SetA64("0>001110<<1xxxxx010011xxxxxxxxxx", InstName.Sqshl_V, InstEmit.Sqshl_V, typeof(OpCodeSimdReg)); + SetA64("0101111100>>>xxx100101xxxxxxxxxx", InstName.Sqshrn_S, InstEmit.Sqshrn_S, typeof(OpCodeSimdShImm)); + SetA64("0x00111100>>>xxx100101xxxxxxxxxx", InstName.Sqshrn_V, InstEmit.Sqshrn_V, typeof(OpCodeSimdShImm)); + SetA64("0111111100>>>xxx100001xxxxxxxxxx", InstName.Sqshrun_S, InstEmit.Sqshrun_S, typeof(OpCodeSimdShImm)); + SetA64("0x10111100>>>xxx100001xxxxxxxxxx", InstName.Sqshrun_V, InstEmit.Sqshrun_V, typeof(OpCodeSimdShImm)); + SetA64("01011110xx1xxxxx001011xxxxxxxxxx", InstName.Sqsub_S, InstEmit.Sqsub_S, typeof(OpCodeSimdReg)); + SetA64("0>001110<<1xxxxx001011xxxxxxxxxx", InstName.Sqsub_V, InstEmit.Sqsub_V, typeof(OpCodeSimdReg)); + SetA64("01011110<<100001010010xxxxxxxxxx", InstName.Sqxtn_S, InstEmit.Sqxtn_S, typeof(OpCodeSimd)); + SetA64("0x001110<<100001010010xxxxxxxxxx", InstName.Sqxtn_V, InstEmit.Sqxtn_V, typeof(OpCodeSimd)); + SetA64("01111110<<100001001010xxxxxxxxxx", InstName.Sqxtun_S, InstEmit.Sqxtun_S, typeof(OpCodeSimd)); + SetA64("0x101110<<100001001010xxxxxxxxxx", InstName.Sqxtun_V, InstEmit.Sqxtun_V, typeof(OpCodeSimd)); + SetA64("0x001110<<1xxxxx000101xxxxxxxxxx", InstName.Srhadd_V, InstEmit.Srhadd_V, typeof(OpCodeSimdReg)); + SetA64("0111111101xxxxxx010001xxxxxxxxxx", InstName.Sri_S, InstEmit.Sri_S, typeof(OpCodeSimdShImm)); + SetA64("0x10111100>>>xxx010001xxxxxxxxxx", InstName.Sri_V, InstEmit.Sri_V, typeof(OpCodeSimdShImm)); + SetA64("0110111101xxxxxx010001xxxxxxxxxx", InstName.Sri_V, InstEmit.Sri_V, typeof(OpCodeSimdShImm)); + SetA64("0>001110<<1xxxxx010101xxxxxxxxxx", InstName.Srshl_V, InstEmit.Srshl_V, typeof(OpCodeSimdReg)); + SetA64("0101111101xxxxxx001001xxxxxxxxxx", InstName.Srshr_S, InstEmit.Srshr_S, typeof(OpCodeSimdShImm)); + SetA64("0x00111100>>>xxx001001xxxxxxxxxx", InstName.Srshr_V, InstEmit.Srshr_V, typeof(OpCodeSimdShImm)); + SetA64("0100111101xxxxxx001001xxxxxxxxxx", InstName.Srshr_V, InstEmit.Srshr_V, typeof(OpCodeSimdShImm)); + SetA64("0101111101xxxxxx001101xxxxxxxxxx", InstName.Srsra_S, InstEmit.Srsra_S, typeof(OpCodeSimdShImm)); + SetA64("0x00111100>>>xxx001101xxxxxxxxxx", InstName.Srsra_V, InstEmit.Srsra_V, typeof(OpCodeSimdShImm)); + SetA64("0100111101xxxxxx001101xxxxxxxxxx", InstName.Srsra_V, InstEmit.Srsra_V, typeof(OpCodeSimdShImm)); + SetA64("0>001110<<1xxxxx010001xxxxxxxxxx", InstName.Sshl_V, InstEmit.Sshl_V, typeof(OpCodeSimdReg)); + SetA64("0x00111100>>>xxx101001xxxxxxxxxx", InstName.Sshll_V, InstEmit.Sshll_V, typeof(OpCodeSimdShImm)); + SetA64("0101111101xxxxxx000001xxxxxxxxxx", InstName.Sshr_S, InstEmit.Sshr_S, typeof(OpCodeSimdShImm)); + SetA64("0x00111100>>>xxx000001xxxxxxxxxx", InstName.Sshr_V, InstEmit.Sshr_V, typeof(OpCodeSimdShImm)); + SetA64("0100111101xxxxxx000001xxxxxxxxxx", InstName.Sshr_V, InstEmit.Sshr_V, typeof(OpCodeSimdShImm)); + SetA64("0101111101xxxxxx000101xxxxxxxxxx", InstName.Ssra_S, InstEmit.Ssra_S, typeof(OpCodeSimdShImm)); + SetA64("0x00111100>>>xxx000101xxxxxxxxxx", InstName.Ssra_V, InstEmit.Ssra_V, typeof(OpCodeSimdShImm)); + SetA64("0100111101xxxxxx000101xxxxxxxxxx", InstName.Ssra_V, InstEmit.Ssra_V, typeof(OpCodeSimdShImm)); + SetA64("0x001110<<1xxxxx001000xxxxxxxxxx", InstName.Ssubl_V, InstEmit.Ssubl_V, typeof(OpCodeSimdReg)); + SetA64("0x001110<<1xxxxx001100xxxxxxxxxx", InstName.Ssubw_V, InstEmit.Ssubw_V, typeof(OpCodeSimdReg)); + SetA64("0x00110000000000xxxxxxxxxxxxxxxx", InstName.St__Vms, InstEmit.St__Vms, typeof(OpCodeSimdMemMs)); + SetA64("0x001100100xxxxxxxxxxxxxxxxxxxxx", InstName.St__Vms, InstEmit.St__Vms, typeof(OpCodeSimdMemMs)); + SetA64("0x00110100x00000xxxxxxxxxxxxxxxx", InstName.St__Vss, InstEmit.St__Vss, typeof(OpCodeSimdMemSs)); + SetA64("0x00110110xxxxxxxxxxxxxxxxxxxxxx", InstName.St__Vss, InstEmit.St__Vss, typeof(OpCodeSimdMemSs)); + SetA64("xx10110xx0xxxxxxxxxxxxxxxxxxxxxx", InstName.Stp, InstEmit.Stp, typeof(OpCodeSimdMemPair)); + SetA64("xx111100x00xxxxxxxxx00xxxxxxxxxx", InstName.Str, InstEmit.Str, typeof(OpCodeSimdMemImm)); + SetA64("xx111100x00xxxxxxxxx01xxxxxxxxxx", InstName.Str, InstEmit.Str, typeof(OpCodeSimdMemImm)); + SetA64("xx111100x00xxxxxxxxx11xxxxxxxxxx", InstName.Str, InstEmit.Str, typeof(OpCodeSimdMemImm)); + SetA64("xx111101x0xxxxxxxxxxxxxxxxxxxxxx", InstName.Str, InstEmit.Str, typeof(OpCodeSimdMemImm)); + SetA64("xx111100x01xxxxxxxxx10xxxxxxxxxx", InstName.Str, InstEmit.Str, typeof(OpCodeSimdMemReg)); + SetA64("01111110111xxxxx100001xxxxxxxxxx", InstName.Sub_S, InstEmit.Sub_S, typeof(OpCodeSimdReg)); + SetA64("0>101110<<1xxxxx100001xxxxxxxxxx", InstName.Sub_V, InstEmit.Sub_V, typeof(OpCodeSimdReg)); + SetA64("0x001110<<1xxxxx011000xxxxxxxxxx", InstName.Subhn_V, InstEmit.Subhn_V, typeof(OpCodeSimdReg)); + SetA64("01011110xx100000001110xxxxxxxxxx", InstName.Suqadd_S, InstEmit.Suqadd_S, typeof(OpCodeSimd)); + SetA64("0>001110<<100000001110xxxxxxxxxx", InstName.Suqadd_V, InstEmit.Suqadd_V, typeof(OpCodeSimd)); + SetA64("0x001110000xxxxx0xx000xxxxxxxxxx", InstName.Tbl_V, InstEmit.Tbl_V, typeof(OpCodeSimdTbl)); + SetA64("0x001110000xxxxx0xx100xxxxxxxxxx", InstName.Tbx_V, InstEmit.Tbx_V, typeof(OpCodeSimdTbl)); + SetA64("0>001110<<0xxxxx001010xxxxxxxxxx", InstName.Trn1_V, InstEmit.Trn1_V, typeof(OpCodeSimdReg)); + SetA64("0>001110<<0xxxxx011010xxxxxxxxxx", InstName.Trn2_V, InstEmit.Trn2_V, typeof(OpCodeSimdReg)); + SetA64("0x101110<<1xxxxx011111xxxxxxxxxx", InstName.Uaba_V, InstEmit.Uaba_V, typeof(OpCodeSimdReg)); + SetA64("0x101110<<1xxxxx010100xxxxxxxxxx", InstName.Uabal_V, InstEmit.Uabal_V, typeof(OpCodeSimdReg)); + SetA64("0x101110<<1xxxxx011101xxxxxxxxxx", InstName.Uabd_V, InstEmit.Uabd_V, typeof(OpCodeSimdReg)); + SetA64("0x101110<<1xxxxx011100xxxxxxxxxx", InstName.Uabdl_V, InstEmit.Uabdl_V, typeof(OpCodeSimdReg)); + SetA64("0x101110<<100000011010xxxxxxxxxx", InstName.Uadalp_V, InstEmit.Uadalp_V, typeof(OpCodeSimd)); + SetA64("0x101110<<1xxxxx000000xxxxxxxxxx", InstName.Uaddl_V, InstEmit.Uaddl_V, typeof(OpCodeSimdReg)); + SetA64("0x101110<<100000001010xxxxxxxxxx", InstName.Uaddlp_V, InstEmit.Uaddlp_V, typeof(OpCodeSimd)); + SetA64("001011100x110000001110xxxxxxxxxx", InstName.Uaddlv_V, InstEmit.Uaddlv_V, typeof(OpCodeSimd)); + SetA64("01101110<<110000001110xxxxxxxxxx", InstName.Uaddlv_V, InstEmit.Uaddlv_V, typeof(OpCodeSimd)); + SetA64("0x101110<<1xxxxx000100xxxxxxxxxx", InstName.Uaddw_V, InstEmit.Uaddw_V, typeof(OpCodeSimdReg)); + SetA64("x00111100x100011000000xxxxxxxxxx", InstName.Ucvtf_Gp, InstEmit.Ucvtf_Gp, typeof(OpCodeSimdCvt)); + SetA64(">00111100x000011>xxxxxxxxxxxxxxx", InstName.Ucvtf_Gp_Fixed, InstEmit.Ucvtf_Gp_Fixed, typeof(OpCodeSimdCvt)); + SetA64("011111100x100001110110xxxxxxxxxx", InstName.Ucvtf_S, InstEmit.Ucvtf_S, typeof(OpCodeSimd)); + SetA64("0>1011100<100001110110xxxxxxxxxx", InstName.Ucvtf_V, InstEmit.Ucvtf_V, typeof(OpCodeSimd)); + SetA64("0x101111001xxxxx111001xxxxxxxxxx", InstName.Ucvtf_V_Fixed, InstEmit.Ucvtf_V_Fixed, typeof(OpCodeSimdShImm)); + SetA64("0110111101xxxxxx111001xxxxxxxxxx", InstName.Ucvtf_V_Fixed, InstEmit.Ucvtf_V_Fixed, typeof(OpCodeSimdShImm)); + SetA64("0x101110<<1xxxxx000001xxxxxxxxxx", InstName.Uhadd_V, InstEmit.Uhadd_V, typeof(OpCodeSimdReg)); + SetA64("0x101110<<1xxxxx001001xxxxxxxxxx", InstName.Uhsub_V, InstEmit.Uhsub_V, typeof(OpCodeSimdReg)); + SetA64("0x101110<<1xxxxx011001xxxxxxxxxx", InstName.Umax_V, InstEmit.Umax_V, typeof(OpCodeSimdReg)); + SetA64("0x101110<<1xxxxx101001xxxxxxxxxx", InstName.Umaxp_V, InstEmit.Umaxp_V, typeof(OpCodeSimdReg)); + SetA64("001011100x110000101010xxxxxxxxxx", InstName.Umaxv_V, InstEmit.Umaxv_V, typeof(OpCodeSimd)); + SetA64("01101110<<110000101010xxxxxxxxxx", InstName.Umaxv_V, InstEmit.Umaxv_V, typeof(OpCodeSimd)); + SetA64("0x101110<<1xxxxx011011xxxxxxxxxx", InstName.Umin_V, InstEmit.Umin_V, typeof(OpCodeSimdReg)); + SetA64("0x101110<<1xxxxx101011xxxxxxxxxx", InstName.Uminp_V, InstEmit.Uminp_V, typeof(OpCodeSimdReg)); + SetA64("001011100x110001101010xxxxxxxxxx", InstName.Uminv_V, InstEmit.Uminv_V, typeof(OpCodeSimd)); + SetA64("01101110<<110001101010xxxxxxxxxx", InstName.Uminv_V, InstEmit.Uminv_V, typeof(OpCodeSimd)); + SetA64("0x101110<<1xxxxx100000xxxxxxxxxx", InstName.Umlal_V, InstEmit.Umlal_V, typeof(OpCodeSimdReg)); + SetA64("0x101111xxxxxxxx0010x0xxxxxxxxxx", InstName.Umlal_Ve, InstEmit.Umlal_Ve, typeof(OpCodeSimdRegElem)); + SetA64("0x101110<<1xxxxx101000xxxxxxxxxx", InstName.Umlsl_V, InstEmit.Umlsl_V, typeof(OpCodeSimdReg)); + SetA64("0x101111xxxxxxxx0110x0xxxxxxxxxx", InstName.Umlsl_Ve, InstEmit.Umlsl_Ve, typeof(OpCodeSimdRegElem)); + SetA64("0x001110000xxxxx001111xxxxxxxxxx", InstName.Umov_S, InstEmit.Umov_S, typeof(OpCodeSimdIns)); + SetA64("0x101110<<1xxxxx110000xxxxxxxxxx", InstName.Umull_V, InstEmit.Umull_V, typeof(OpCodeSimdReg)); + SetA64("0x101111xxxxxxxx1010x0xxxxxxxxxx", InstName.Umull_Ve, InstEmit.Umull_Ve, typeof(OpCodeSimdRegElem)); + SetA64("01111110xx1xxxxx000011xxxxxxxxxx", InstName.Uqadd_S, InstEmit.Uqadd_S, typeof(OpCodeSimdReg)); + SetA64("0>101110<<1xxxxx000011xxxxxxxxxx", InstName.Uqadd_V, InstEmit.Uqadd_V, typeof(OpCodeSimdReg)); + SetA64("0>101110<<1xxxxx010111xxxxxxxxxx", InstName.Uqrshl_V, InstEmit.Uqrshl_V, typeof(OpCodeSimdReg)); + SetA64("0111111100>>>xxx100111xxxxxxxxxx", InstName.Uqrshrn_S, InstEmit.Uqrshrn_S, typeof(OpCodeSimdShImm)); + SetA64("0x10111100>>>xxx100111xxxxxxxxxx", InstName.Uqrshrn_V, InstEmit.Uqrshrn_V, typeof(OpCodeSimdShImm)); + SetA64("0>101110<<1xxxxx010011xxxxxxxxxx", InstName.Uqshl_V, InstEmit.Uqshl_V, typeof(OpCodeSimdReg)); + SetA64("0111111100>>>xxx100101xxxxxxxxxx", InstName.Uqshrn_S, InstEmit.Uqshrn_S, typeof(OpCodeSimdShImm)); + SetA64("0x10111100>>>xxx100101xxxxxxxxxx", InstName.Uqshrn_V, InstEmit.Uqshrn_V, typeof(OpCodeSimdShImm)); + SetA64("01111110xx1xxxxx001011xxxxxxxxxx", InstName.Uqsub_S, InstEmit.Uqsub_S, typeof(OpCodeSimdReg)); + SetA64("0>101110<<1xxxxx001011xxxxxxxxxx", InstName.Uqsub_V, InstEmit.Uqsub_V, typeof(OpCodeSimdReg)); + SetA64("01111110<<100001010010xxxxxxxxxx", InstName.Uqxtn_S, InstEmit.Uqxtn_S, typeof(OpCodeSimd)); + SetA64("0x101110<<100001010010xxxxxxxxxx", InstName.Uqxtn_V, InstEmit.Uqxtn_V, typeof(OpCodeSimd)); + SetA64("0x101110<<1xxxxx000101xxxxxxxxxx", InstName.Urhadd_V, InstEmit.Urhadd_V, typeof(OpCodeSimdReg)); + SetA64("0>101110<<1xxxxx010101xxxxxxxxxx", InstName.Urshl_V, InstEmit.Urshl_V, typeof(OpCodeSimdReg)); + SetA64("0111111101xxxxxx001001xxxxxxxxxx", InstName.Urshr_S, InstEmit.Urshr_S, typeof(OpCodeSimdShImm)); + SetA64("0x10111100>>>xxx001001xxxxxxxxxx", InstName.Urshr_V, InstEmit.Urshr_V, typeof(OpCodeSimdShImm)); + SetA64("0110111101xxxxxx001001xxxxxxxxxx", InstName.Urshr_V, InstEmit.Urshr_V, typeof(OpCodeSimdShImm)); + SetA64("0111111101xxxxxx001101xxxxxxxxxx", InstName.Ursra_S, InstEmit.Ursra_S, typeof(OpCodeSimdShImm)); + SetA64("0x10111100>>>xxx001101xxxxxxxxxx", InstName.Ursra_V, InstEmit.Ursra_V, typeof(OpCodeSimdShImm)); + SetA64("0110111101xxxxxx001101xxxxxxxxxx", InstName.Ursra_V, InstEmit.Ursra_V, typeof(OpCodeSimdShImm)); + SetA64("0>101110<<1xxxxx010001xxxxxxxxxx", InstName.Ushl_V, InstEmit.Ushl_V, typeof(OpCodeSimdReg)); + SetA64("0x10111100>>>xxx101001xxxxxxxxxx", InstName.Ushll_V, InstEmit.Ushll_V, typeof(OpCodeSimdShImm)); + SetA64("0111111101xxxxxx000001xxxxxxxxxx", InstName.Ushr_S, InstEmit.Ushr_S, typeof(OpCodeSimdShImm)); + SetA64("0x10111100>>>xxx000001xxxxxxxxxx", InstName.Ushr_V, InstEmit.Ushr_V, typeof(OpCodeSimdShImm)); + SetA64("0110111101xxxxxx000001xxxxxxxxxx", InstName.Ushr_V, InstEmit.Ushr_V, typeof(OpCodeSimdShImm)); + SetA64("01111110xx100000001110xxxxxxxxxx", InstName.Usqadd_S, InstEmit.Usqadd_S, typeof(OpCodeSimd)); + SetA64("0>101110<<100000001110xxxxxxxxxx", InstName.Usqadd_V, InstEmit.Usqadd_V, typeof(OpCodeSimd)); + SetA64("0111111101xxxxxx000101xxxxxxxxxx", InstName.Usra_S, InstEmit.Usra_S, typeof(OpCodeSimdShImm)); + SetA64("0x10111100>>>xxx000101xxxxxxxxxx", InstName.Usra_V, InstEmit.Usra_V, typeof(OpCodeSimdShImm)); + SetA64("0110111101xxxxxx000101xxxxxxxxxx", InstName.Usra_V, InstEmit.Usra_V, typeof(OpCodeSimdShImm)); + SetA64("0x101110<<1xxxxx001000xxxxxxxxxx", InstName.Usubl_V, InstEmit.Usubl_V, typeof(OpCodeSimdReg)); + SetA64("0x101110<<1xxxxx001100xxxxxxxxxx", InstName.Usubw_V, InstEmit.Usubw_V, typeof(OpCodeSimdReg)); + SetA64("0>001110<<0xxxxx000110xxxxxxxxxx", InstName.Uzp1_V, InstEmit.Uzp1_V, typeof(OpCodeSimdReg)); + SetA64("0>001110<<0xxxxx010110xxxxxxxxxx", InstName.Uzp2_V, InstEmit.Uzp2_V, typeof(OpCodeSimdReg)); + SetA64("0x001110<<100001001010xxxxxxxxxx", InstName.Xtn_V, InstEmit.Xtn_V, typeof(OpCodeSimd)); + SetA64("0>001110<<0xxxxx001110xxxxxxxxxx", InstName.Zip1_V, InstEmit.Zip1_V, typeof(OpCodeSimdReg)); + SetA64("0>001110<<0xxxxx011110xxxxxxxxxx", InstName.Zip2_V, InstEmit.Zip2_V, typeof(OpCodeSimdReg)); +#endregion + +#region "OpCode Table (AArch32)" + // Base + SetA32("<<<<0010100xxxxxxxxxxxxxxxxxxxxx", InstName.Add, InstEmit32.Add, typeof(OpCode32AluImm)); + SetA32("<<<<0000100xxxxxxxxxxxxxxxx0xxxx", InstName.Add, InstEmit32.Add, typeof(OpCode32AluRsImm)); + SetA32("<<<<1010xxxxxxxxxxxxxxxxxxxxxxxx", InstName.B, InstEmit32.B, typeof(OpCode32BImm)); + SetA32("<<<<1011xxxxxxxxxxxxxxxxxxxxxxxx", InstName.Bl, InstEmit32.Bl, typeof(OpCode32BImm)); + SetA32("1111101xxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Blx, InstEmit32.Blx, typeof(OpCode32BImm)); + SetA32("<<<<000100101111111111110001xxxx", InstName.Bx, InstEmit32.Bx, typeof(OpCode32BReg)); + SetT32("xxxxxxxxxxxxxxxx010001110xxxx000", InstName.Bx, InstEmit32.Bx, typeof(OpCodeT16BReg)); + SetA32("<<<<00110101xxxx0000xxxxxxxxxxxx", InstName.Cmp, InstEmit32.Cmp, typeof(OpCode32AluImm)); + SetA32("<<<<00010101xxxx0000xxxxxxx0xxxx", InstName.Cmp, InstEmit32.Cmp, typeof(OpCode32AluRsImm)); + SetA32("<<<<100xx0x1xxxxxxxxxxxxxxxxxxxx", InstName.Ldm, InstEmit32.Ldm, typeof(OpCode32MemMult)); + SetA32("<<<<010xx0x1xxxxxxxxxxxxxxxxxxxx", InstName.Ldr, InstEmit32.Ldr, typeof(OpCode32MemImm)); + SetA32("<<<<010xx1x1xxxxxxxxxxxxxxxxxxxx", InstName.Ldrb, InstEmit32.Ldrb, typeof(OpCode32MemImm)); + SetA32("<<<<000xx1x0xxxxxxxxxxxx1101xxxx", InstName.Ldrd, InstEmit32.Ldrd, typeof(OpCode32MemImm8)); + SetA32("<<<<000xx1x1xxxxxxxxxxxx1011xxxx", InstName.Ldrh, InstEmit32.Ldrh, typeof(OpCode32MemImm8)); + SetA32("<<<<000xx1x1xxxxxxxxxxxx1101xxxx", InstName.Ldrsb, InstEmit32.Ldrsb, typeof(OpCode32MemImm8)); + SetA32("<<<<000xx1x1xxxxxxxxxxxx1111xxxx", InstName.Ldrsh, InstEmit32.Ldrsh, typeof(OpCode32MemImm8)); + SetA32("<<<<0011101x0000xxxxxxxxxxxxxxxx", InstName.Mov, InstEmit32.Mov, typeof(OpCode32AluImm)); + SetA32("<<<<0001101x0000xxxxxxxxxxx0xxxx", InstName.Mov, InstEmit32.Mov, typeof(OpCode32AluRsImm)); + SetT32("xxxxxxxxxxxxxxxx00100xxxxxxxxxxx", InstName.Mov, InstEmit32.Mov, typeof(OpCodeT16AluImm8)); + SetA32("<<<<100xx0x0xxxxxxxxxxxxxxxxxxxx", InstName.Stm, InstEmit32.Stm, typeof(OpCode32MemMult)); + SetA32("<<<<010xx0x0xxxxxxxxxxxxxxxxxxxx", InstName.Str, InstEmit32.Str, typeof(OpCode32MemImm)); + SetA32("<<<<010xx1x0xxxxxxxxxxxxxxxxxxxx", InstName.Strb, InstEmit32.Strb, typeof(OpCode32MemImm)); + SetA32("<<<<000xx1x0xxxxxxxxxxxx1111xxxx", InstName.Strd, InstEmit32.Strd, typeof(OpCode32MemImm8)); + SetA32("<<<<000xx1x0xxxxxxxxxxxx1011xxxx", InstName.Strh, InstEmit32.Strh, typeof(OpCode32MemImm8)); + SetA32("<<<<0010010xxxxxxxxxxxxxxxxxxxxx", InstName.Sub, InstEmit32.Sub, typeof(OpCode32AluImm)); + SetA32("<<<<0000010xxxxxxxxxxxxxxxx0xxxx", InstName.Sub, InstEmit32.Sub, typeof(OpCode32AluRsImm)); +#endregion + + FillFastLookupTable(_instA32FastLookup, _allInstA32); + FillFastLookupTable(_instT32FastLookup, _allInstT32); + FillFastLookupTable(_instA64FastLookup, _allInstA64); + } + + private static void FillFastLookupTable(InstInfo[][] table, List allInsts) + { + List[] temp = new List[FastLookupSize]; + + for (int index = 0; index < FastLookupSize; 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 < FastLookupSize; index++) + { + if ((index & mask) == value) + { + temp[index].Add(inst); + } + } + } + + for (int index = 0; index < FastLookupSize; index++) + { + table[index] = temp[index].ToArray(); + } + } + + private static void SetA32(string encoding, InstName name, InstEmitter emitter, Type type) + { + Set(encoding, ExecutionMode.Aarch32Arm, new InstDescriptor(name, emitter), type); + } + + private static void SetT32(string encoding, InstName name, InstEmitter emitter, Type type) + { + Set(encoding, ExecutionMode.Aarch32Thumb, new InstDescriptor(name, emitter), type); + } + + private static void SetA64(string encoding, InstName name, InstEmitter emitter, Type type) + { + Set(encoding, ExecutionMode.Aarch64, new InstDescriptor(name, emitter), type); + } + + private static void Set(string encoding, ExecutionMode mode, InstDescriptor inst, Type type) + { + 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) + { + InsertInst(new InstInfo(xMask, value, inst, type), mode); + + 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) + { + InsertInst(new InstInfo(xMask, value | mask, inst, type), mode); + } + } + } + + private static void InsertInst(InstInfo info, ExecutionMode mode) + { + switch (mode) + { + case ExecutionMode.Aarch32Arm: _allInstA32.Add(info); break; + case ExecutionMode.Aarch32Thumb: _allInstT32.Add(info); break; + case ExecutionMode.Aarch64: _allInstA64.Add(info); break; + } + } + + public static (InstDescriptor inst, Type type) GetInstA32(int opCode) + { + return GetInstFromList(_instA32FastLookup[ToFastLookupIndex(opCode)], opCode); + } + + public static (InstDescriptor inst, Type type) GetInstT32(int opCode) + { + return GetInstFromList(_instT32FastLookup[ToFastLookupIndex(opCode)], opCode); + } + + public static (InstDescriptor inst, Type type) GetInstA64(int opCode) + { + return GetInstFromList(_instA64FastLookup[ToFastLookupIndex(opCode)], opCode); + } + + private static (InstDescriptor inst, Type type) GetInstFromList(InstInfo[] insts, int opCode) + { + foreach (InstInfo info in insts) + { + if ((opCode & info.Mask) == info.Value) + { + return (info.Inst, info.Type); + } + } + + return (new InstDescriptor(InstName.Und, InstEmit.Und), typeof(OpCode)); + } + + private static int ToFastLookupIndex(int value) + { + return ((value >> 10) & 0x00F) | ((value >> 18) & 0xFF0); + } + } +} diff --git a/ARMeilleure/Decoders/RegisterSize.cs b/ARMeilleure/Decoders/RegisterSize.cs new file mode 100644 index 0000000000..c9cea03ed2 --- /dev/null +++ b/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/ARMeilleure/Decoders/ShiftType.cs b/ARMeilleure/Decoders/ShiftType.cs new file mode 100644 index 0000000000..8583f16ad0 --- /dev/null +++ b/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/ARMeilleure/Diagnostics/IRDumper.cs b/ARMeilleure/Diagnostics/IRDumper.cs new file mode 100644 index 0000000000..55d5b493e3 --- /dev/null +++ b/ARMeilleure/Diagnostics/IRDumper.cs @@ -0,0 +1,168 @@ +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System; +using System.Collections.Generic; +using System.Text; + +namespace ARMeilleure.Diagnostics +{ + static class IRDumper + { + private const string Indentation = " "; + + public static string GetDump(ControlFlowGraph cfg) + { + StringBuilder sb = new StringBuilder(); + + Dictionary localNames = new Dictionary(); + + string indentation = string.Empty; + + void IncreaseIndentation() + { + indentation += Indentation; + } + + void DecreaseIndentation() + { + indentation = indentation.Substring(0, indentation.Length - Indentation.Length); + } + + void AppendLine(string text) + { + sb.AppendLine(indentation + text); + } + + IncreaseIndentation(); + + foreach (BasicBlock block in cfg.Blocks) + { + string blockName = GetBlockName(block); + + if (block.Next != null) + { + blockName += $" (next {GetBlockName(block.Next)})"; + } + + if (block.Branch != null) + { + blockName += $" (branch {GetBlockName(block.Branch)})"; + } + + blockName += ":"; + + AppendLine(blockName); + + IncreaseIndentation(); + + foreach (Node node in block.Operations) + { + string[] sources = new string[node.SourcesCount]; + + string instName = string.Empty; + + if (node is PhiNode phi) + { + for (int index = 0; index < sources.Length; index++) + { + string phiBlockName = GetBlockName(phi.GetBlock(index)); + + string operName = GetOperandName(phi.GetSource(index), localNames); + + sources[index] = $"({phiBlockName}: {operName})"; + } + + instName = "Phi"; + } + else if (node is Operation operation) + { + for (int index = 0; index < sources.Length; index++) + { + sources[index] = GetOperandName(operation.GetSource(index), localNames); + } + + instName = operation.Instruction.ToString(); + } + + string allSources = string.Join(", ", sources); + + string line = instName + " " + allSources; + + if (node.Destination != null) + { + line = GetOperandName(node.Destination, localNames) + " = " + line; + } + + AppendLine(line); + } + + DecreaseIndentation(); + } + + return sb.ToString(); + } + + private static string GetBlockName(BasicBlock block) + { + return $"block{block.Index}"; + } + + private static string GetOperandName(Operand operand, Dictionary localNames) + { + if (operand == null) + { + return ""; + } + + string name = string.Empty; + + if (operand.Kind == OperandKind.LocalVariable) + { + if (!localNames.TryGetValue(operand, out string localName)) + { + localName = "%" + localNames.Count; + + localNames.Add(operand, localName); + } + + name = localName; + } + else if (operand.Kind == OperandKind.Register) + { + Register reg = operand.GetRegister(); + + switch (reg.Type) + { + case RegisterType.Flag: name = "b" + reg.Index; break; + case RegisterType.Integer: name = "r" + reg.Index; break; + case RegisterType.Vector: name = "v" + reg.Index; break; + } + } + else if (operand.Kind == OperandKind.Constant) + { + name = "0x" + operand.Value.ToString("X"); + } + else + { + name = operand.Kind.ToString().ToLower(); + } + + return GetTypeName(operand.Type) + " " + name; + } + + private static string GetTypeName(OperandType type) + { + switch (type) + { + case OperandType.FP32: return "f32"; + case OperandType.FP64: return "f64"; + case OperandType.I32: return "i32"; + case OperandType.I64: return "i64"; + case OperandType.None: return "none"; + case OperandType.V128: return "v128"; + } + + throw new ArgumentException($"Invalid operand type \"{type}\"."); + } + } +} \ No newline at end of file diff --git a/ARMeilleure/Diagnostics/Logger.cs b/ARMeilleure/Diagnostics/Logger.cs new file mode 100644 index 0000000000..29d9c79b93 --- /dev/null +++ b/ARMeilleure/Diagnostics/Logger.cs @@ -0,0 +1,59 @@ +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]; + } + + public static void StartPass(PassName name) + { +#if M_DEBUG + WriteOutput(name + " pass started..."); + + _startTime = Stopwatch.GetTimestamp(); +#endif + } + + public static void EndPass(PassName name, ControlFlowGraph cfg) + { +#if M_DEBUG + EndPass(name); + + WriteOutput("IR after " + name + " pass:"); + + WriteOutput(IRDumper.GetDump(cfg)); +#endif + } + + public static void EndPass(PassName name) + { +#if M_DEBUG + long elapsedTime = Stopwatch.GetTimestamp() - _startTime; + + _accumulatedTime[(int)name] += elapsedTime; + + WriteOutput($"{name} pass ended after {GetMilliseconds(_accumulatedTime[(int)name])} ms..."); +#endif + } + + 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/ARMeilleure/Diagnostics/PassName.cs b/ARMeilleure/Diagnostics/PassName.cs new file mode 100644 index 0000000000..e37439855e --- /dev/null +++ b/ARMeilleure/Diagnostics/PassName.cs @@ -0,0 +1,17 @@ +namespace ARMeilleure.Diagnostics +{ + enum PassName + { + Decoding, + Translation, + RegisterUsage, + Dominance, + SsaConstruction, + Optimization, + PreAllocation, + RegisterAllocation, + CodeGeneration, + + Count + } +} \ No newline at end of file diff --git a/ARMeilleure/Instructions/CryptoHelper.cs b/ARMeilleure/Instructions/CryptoHelper.cs new file mode 100644 index 0000000000..b6b4a62d36 --- /dev/null +++ b/ARMeilleure/Instructions/CryptoHelper.cs @@ -0,0 +1,279 @@ +// https://www.intel.com/content/dam/doc/white-paper/advanced-encryption-standard-new-instructions-set-paper.pdf + +using ARMeilleure.State; + +namespace ARMeilleure.Instructions +{ + static class CryptoHelper + { +#region "LookUp Tables" + private static readonly byte[] _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 readonly byte[] _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 readonly byte[] _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 readonly byte[] _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 readonly byte[] _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 readonly byte[] _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 readonly byte[] _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 readonly byte[] _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 readonly byte[] _srPerm = new byte[] + { + 0, 13, 10, 7, 4, 1, 14, 11, 8, 5, 2, 15, 12, 9, 6, 3 + }; + + private static readonly byte[] _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/ARMeilleure/Instructions/DelegateTypes.cs b/ARMeilleure/Instructions/DelegateTypes.cs new file mode 100644 index 0000000000..424203ffae --- /dev/null +++ b/ARMeilleure/Instructions/DelegateTypes.cs @@ -0,0 +1,84 @@ +using ARMeilleure.State; +using System; + +namespace ARMeilleure.Instructions +{ + delegate double _F64_F64(double a1); + delegate double _F64_F64_F64(double a1, double a2); + delegate double _F64_F64_F64_F64(double a1, double a2, double a3); + delegate double _F64_F64_MidpointRounding(double a1, MidpointRounding a2); + + delegate float _F32_F32(float a1); + delegate float _F32_F32_F32(float a1, float a2); + delegate float _F32_F32_F32_F32(float a1, float a2, float a3); + delegate float _F32_F32_MidpointRounding(float a1, MidpointRounding a2); + delegate float _F32_U16(ushort a1); + + delegate int _S32_F32(float a1); + delegate int _S32_F32_F32_Bool(float a1, float a2, bool a3); + delegate int _S32_F64(double a1); + delegate int _S32_F64_F64_Bool(double a1, double a2, bool a3); + delegate int _S32_U64_U16(ulong a1, ushort a2); + delegate int _S32_U64_U32(ulong a1, uint a2); + delegate int _S32_U64_U64(ulong a1, ulong a2); + delegate int _S32_U64_U8(ulong a1, byte a2); + delegate int _S32_U64_V128(ulong a1, V128 a2); + + delegate long _S64_F32(float a1); + delegate long _S64_F64(double a1); + delegate long _S64_S64(long a1); + delegate long _S64_S64_S32(long a1, int a2); + delegate long _S64_S64_S64(long a1, long a2); + delegate long _S64_S64_S64_Bool_S32(long a1, long a2, bool a3, int a4); + delegate long _S64_S64_S64_S32(long a1, long a2, int a3); + delegate long _S64_U64_S32(ulong a1, int a2); + delegate long _S64_U64_S64(ulong a1, long a2); + + delegate ushort _U16_F32(float a1); + delegate ushort _U16_U64(ulong a1); + + delegate uint _U32_F32(float a1); + delegate uint _U32_F64(double a1); + delegate uint _U32_U32(uint a1); + delegate uint _U32_U32_U16(uint a1, ushort a2); + delegate uint _U32_U32_U32(uint a1, uint a2); + delegate uint _U32_U32_U64(uint a1, ulong a2); + delegate uint _U32_U32_U8(uint a1, byte a2); + delegate uint _U32_U64(ulong a1); + + delegate ulong _U64(); + delegate ulong _U64_F32(float a1); + delegate ulong _U64_F64(double a1); + delegate ulong _U64_S64_S32(long a1, int a2); + delegate ulong _U64_S64_U64(long a1, ulong a2); + delegate ulong _U64_U64(ulong a1); + delegate ulong _U64_U64_S32(ulong a1, int a2); + delegate ulong _U64_U64_S64_S32(ulong a1, long a2, int a3); + delegate ulong _U64_U64_U64(ulong a1, ulong a2); + delegate ulong _U64_U64_U64_Bool_S32(ulong a1, ulong a2, bool a3, int a4); + + delegate byte _U8_U64(ulong a1); + + delegate V128 _V128_U64(ulong a1); + delegate V128 _V128_V128(V128 a1); + delegate V128 _V128_V128_S32_V128(V128 a1, int a2, V128 a3); + delegate V128 _V128_V128_S32_V128_V128(V128 a1, int a2, V128 a3, V128 a4); + delegate V128 _V128_V128_S32_V128_V128_V128(V128 a1, int a2, V128 a3, V128 a4, V128 a5); + delegate V128 _V128_V128_S32_V128_V128_V128_V128(V128 a1, int a2, V128 a3, V128 a4, V128 a5, V128 a6); + delegate V128 _V128_V128_U32_V128(V128 a1, uint a2, V128 a3); + delegate V128 _V128_V128_V128(V128 a1, V128 a2); + delegate V128 _V128_V128_V128_S32_V128(V128 a1, V128 a2, int a3, V128 a4); + delegate V128 _V128_V128_V128_S32_V128_V128(V128 a1, V128 a2, int a3, V128 a4, V128 a5); + delegate V128 _V128_V128_V128_S32_V128_V128_V128(V128 a1, V128 a2, int a3, V128 a4, V128 a5, V128 a6); + delegate V128 _V128_V128_V128_S32_V128_V128_V128_V128(V128 a1, V128 a2, int a3, V128 a4, V128 a5, V128 a6, V128 a7); + delegate V128 _V128_V128_V128_V128(V128 a1, V128 a2, V128 a3); + + delegate void _Void(); + delegate void _Void_U64(ulong a1); + delegate void _Void_U64_S32(ulong a1, int a2); + delegate void _Void_U64_U16(ulong a1, ushort a2); + delegate void _Void_U64_U32(ulong a1, uint a2); + delegate void _Void_U64_U64(ulong a1, ulong a2); + delegate void _Void_U64_U8(ulong a1, byte a2); + delegate void _Void_U64_V128(ulong a1, V128 a2); +} diff --git a/ARMeilleure/Instructions/InstEmitAlu.cs b/ARMeilleure/Instructions/InstEmitAlu.cs new file mode 100644 index 0000000000..ed1faae417 --- /dev/null +++ b/ARMeilleure/Instructions/InstEmitAlu.cs @@ -0,0 +1,434 @@ +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.OperandHelper; + +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 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))); + } + + 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); + } + + private 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 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 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/ARMeilleure/Instructions/InstEmitAlu32.cs b/ARMeilleure/Instructions/InstEmitAlu32.cs new file mode 100644 index 0000000000..79b0abbc32 --- /dev/null +++ b/ARMeilleure/Instructions/InstEmitAlu32.cs @@ -0,0 +1,129 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; + +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.Instructions.InstEmitAluHelper; +using static ARMeilleure.IntermediateRepresentation.OperandHelper; + +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 (op.SetFlags) + { + EmitNZFlagsCheck(context, res); + + EmitAddsCCheck(context, n, res); + EmitAddsVCheck(context, n, m, res); + } + + EmitAluStore(context, res); + } + + public static void Cmp(ArmEmitterContext context) + { + IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; + + 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 Mov(ArmEmitterContext context) + { + IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; + + Operand m = GetAluM(context); + + if (op.SetFlags) + { + EmitNZFlagsCheck(context, m); + } + + EmitAluStore(context, m); + } + + 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 (op.SetFlags) + { + EmitNZFlagsCheck(context, res); + + EmitSubsCCheck(context, n, res); + EmitSubsVCheck(context, n, m, res); + } + + EmitAluStore(context, res); + } + + private static void EmitAluStore(ArmEmitterContext context, Operand value) + { + IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; + + if (op.Rd == RegisterAlias.Aarch32Pc) + { + if (op.SetFlags) + { + // TODO: Load SPSR etc. + Operand isThumb = GetFlag(PState.TFlag); + + Operand lblThumb = Label(); + + context.BranchIfTrue(lblThumb, isThumb); + + context.Return(context.ZeroExtend32(OperandType.I64, context.BitwiseAnd(value, Const(~3)))); + + context.MarkLabel(lblThumb); + + context.Return(context.ZeroExtend32(OperandType.I64, context.BitwiseAnd(value, Const(~1)))); + } + else + { + EmitAluWritePc(context, value); + } + } + else + { + SetIntA32(context, op.Rd, value); + } + } + + private static void EmitAluWritePc(ArmEmitterContext context, Operand value) + { + context.StoreToContext(); + + if (IsThumb(context.CurrOp)) + { + context.Return(context.ZeroExtend32(OperandType.I64, context.BitwiseAnd(value, Const(~1)))); + } + else + { + EmitBxWritePc(context, value); + } + } + } +} \ No newline at end of file diff --git a/ARMeilleure/Instructions/InstEmitAluHelper.cs b/ARMeilleure/Instructions/InstEmitAluHelper.cs new file mode 100644 index 0000000000..d032b32e87 --- /dev/null +++ b/ARMeilleure/Instructions/InstEmitAluHelper.cs @@ -0,0 +1,351 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; +using System; + +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.IntermediateRepresentation.OperandHelper; + +namespace ARMeilleure.Instructions +{ + static class InstEmitAluHelper + { + 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 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 OpCode32AluImm op: + { + if (op.SetFlags && op.IsRotated) + { + SetFlag(context, PState.CFlag, Const((uint)op.Immediate >> 31)); + } + + return Const(op.Immediate); + } + + case OpCode32AluRsImm op: return GetMShiftedByImmediate(context, op, setCarry); + + case OpCodeT16AluImm8 op: return Const(op.Immediate); + + // 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. + private static Operand GetMShiftedByImmediate(ArmEmitterContext context, OpCode32AluRsImm op, bool setCarry) + { + Operand m = GetIntA32(context, op.Rm); + + int shift = op.Imm; + + 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 &= op.SetFlags; + + 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.Imm != 0) + { + m = GetRorC(context, m, setCarry, shift); + } + else + { + m = GetRrxC(context, m, setCarry); + } + break; + } + } + + return m; + } + + private static Operand GetLslC(ArmEmitterContext context, Operand m, bool setCarry, int shift) + { + 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)); + } + } + + private static Operand GetLsrC(ArmEmitterContext context, Operand m, bool setCarry, int shift) + { + 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); + } + + private static Operand GetAsrC(ArmEmitterContext context, Operand m, bool setCarry, int shift) + { + 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)); + } + } + + private static Operand GetRorC(ArmEmitterContext context, Operand m, bool setCarry, int shift) + { + shift &= 0x1f; + + m = context.RotateRight(m, Const(shift)); + + if (setCarry) + { + SetCarryMMsb(context, m); + } + + return m; + } + + private static Operand GetRrxC(ArmEmitterContext context, Operand m, bool setCarry) + { + // 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) + { + SetFlag(context, PState.CFlag, context.BitwiseAnd(m, Const(1))); + } + + private static void SetCarryMMsb(ArmEmitterContext context, Operand m) + { + SetFlag(context, PState.CFlag, context.ShiftRightUI(m, Const(31))); + } + + private static void SetCarryMShrOut(ArmEmitterContext context, Operand m, int shift) + { + Operand cOut = context.ShiftRightUI(m, Const(shift - 1)); + + cOut = context.BitwiseAnd(cOut, Const(1)); + + SetFlag(context, PState.CFlag, cOut); + } + } +} diff --git a/ARMeilleure/Instructions/InstEmitBfm.cs b/ARMeilleure/Instructions/InstEmitBfm.cs new file mode 100644 index 0000000000..8fdbf6cfd0 --- /dev/null +++ b/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.OperandHelper; + +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/ARMeilleure/Instructions/InstEmitCcmp.cs b/ARMeilleure/Instructions/InstEmitCcmp.cs new file mode 100644 index 0000000000..b1b0a2a1c8 --- /dev/null +++ b/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.OperandHelper; + +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/ARMeilleure/Instructions/InstEmitCsel.cs b/ARMeilleure/Instructions/InstEmitCsel.cs new file mode 100644 index 0000000000..60baf0bc29 --- /dev/null +++ b/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.OperandHelper; + +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/ARMeilleure/Instructions/InstEmitDiv.cs b/ARMeilleure/Instructions/InstEmitDiv.cs new file mode 100644 index 0000000000..0c21dd1bac --- /dev/null +++ b/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.OperandHelper; + +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/ARMeilleure/Instructions/InstEmitException.cs b/ARMeilleure/Instructions/InstEmitException.cs new file mode 100644 index 0000000000..6f7b6fd51f --- /dev/null +++ b/ARMeilleure/Instructions/InstEmitException.cs @@ -0,0 +1,55 @@ +using ARMeilleure.Decoders; +using ARMeilleure.Translation; +using System; + +using static ARMeilleure.IntermediateRepresentation.OperandHelper; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit + { + public static void Brk(ArmEmitterContext context) + { + EmitExceptionCall(context, NativeInterface.Break); + } + + public static void Svc(ArmEmitterContext context) + { + EmitExceptionCall(context, NativeInterface.SupervisorCall); + } + + private static void EmitExceptionCall(ArmEmitterContext context, _Void_U64_S32 func) + { + OpCodeException op = (OpCodeException)context.CurrOp; + + context.StoreToContext(); + + context.Call(func, Const(op.Address), Const(op.Id)); + + context.LoadFromContext(); + + if (context.CurrBlock.Next == null) + { + context.Return(Const(op.Address + 4)); + } + } + + public static void Und(ArmEmitterContext context) + { + OpCode op = context.CurrOp; + + Delegate dlg = new _Void_U64_S32(NativeInterface.Undefined); + + context.StoreToContext(); + + context.Call(dlg, Const(op.Address), Const(op.RawOpCode)); + + context.LoadFromContext(); + + if (context.CurrBlock.Next == null) + { + context.Return(Const(op.Address + 4)); + } + } + } +} \ No newline at end of file diff --git a/ARMeilleure/Instructions/InstEmitFlow.cs b/ARMeilleure/Instructions/InstEmitFlow.cs new file mode 100644 index 0000000000..93d36e1b94 --- /dev/null +++ b/ARMeilleure/Instructions/InstEmitFlow.cs @@ -0,0 +1,159 @@ +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.OperandHelper; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit + { + public static void B(ArmEmitterContext context) + { + OpCodeBImmAl op = (OpCodeBImmAl)context.CurrOp; + + if (context.CurrBlock.Branch != null) + { + context.Branch(context.GetLabel((ulong)op.Immediate)); + } + else + { + context.Return(Const(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)); + } + + 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) + { + context.Return(context.BitwiseOr(GetIntOrZR(context, RegisterAlias.Lr), Const(CallFlag))); + } + + 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; + + if (context.CurrBlock.Branch != null) + { + EmitCondBranch(context, context.GetLabel((ulong)op.Immediate), cond); + + if (context.CurrBlock.Next == null) + { + context.Return(Const(op.Address + 4)); + } + } + else + { + Operand lblTaken = Label(); + + EmitCondBranch(context, lblTaken, cond); + + context.Return(Const(op.Address + 4)); + + context.MarkLabel(lblTaken); + + context.Return(Const(op.Immediate)); + } + } + + private static void EmitBranch(ArmEmitterContext context, Operand value, bool onNotZero) + { + OpCodeBImm op = (OpCodeBImm)context.CurrOp; + + if (context.CurrBlock.Branch != null) + { + Operand lblTarget = context.GetLabel((ulong)op.Immediate); + + if (onNotZero) + { + context.BranchIfTrue(lblTarget, value); + } + else + { + context.BranchIfFalse(lblTarget, value); + } + + if (context.CurrBlock.Next == null) + { + context.Return(Const(op.Address + 4)); + } + } + else + { + Operand lblTaken = Label(); + + if (onNotZero) + { + context.BranchIfTrue(lblTaken, value); + } + else + { + context.BranchIfFalse(lblTaken, value); + } + + context.Return(Const(op.Address + 4)); + + context.MarkLabel(lblTaken); + + context.Return(Const(op.Immediate)); + } + } + } +} \ No newline at end of file diff --git a/ARMeilleure/Instructions/InstEmitFlow32.cs b/ARMeilleure/Instructions/InstEmitFlow32.cs new file mode 100644 index 0000000000..27addc78e3 --- /dev/null +++ b/ARMeilleure/Instructions/InstEmitFlow32.cs @@ -0,0 +1,71 @@ +using ARMeilleure.Decoders; +using ARMeilleure.State; +using ARMeilleure.Translation; + +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.IntermediateRepresentation.OperandHelper; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit32 + { + public static void B(ArmEmitterContext context) + { + IOpCode32BImm op = (IOpCode32BImm)context.CurrOp; + + if (context.CurrBlock.Branch != null) + { + context.Branch(context.GetLabel((ulong)op.Immediate)); + } + else + { + context.StoreToContext(); + + context.Return(Const(op.Immediate)); + } + } + + public static void Bl(ArmEmitterContext context) + { + Blx(context, x: false); + } + + public static void Blx(ArmEmitterContext context) + { + Blx(context, x: true); + } + + public static void Bx(ArmEmitterContext context) + { + IOpCode32BReg op = (IOpCode32BReg)context.CurrOp; + + context.StoreToContext(); + + EmitBxWritePc(context, GetIntA32(context, op.Rm)); + } + + private static void Blx(ArmEmitterContext context, bool x) + { + IOpCode32BImm op = (IOpCode32BImm)context.CurrOp; + + uint pc = op.GetPc(); + + bool isThumb = IsThumb(context.CurrOp); + + uint currentPc = isThumb + ? op.GetPc() | 1 + : op.GetPc() - 4; + + SetIntOrSP(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)); + } + + InstEmitFlowHelper.EmitCall(context, (ulong)op.Immediate); + } + } +} \ No newline at end of file diff --git a/ARMeilleure/Instructions/InstEmitFlowHelper.cs b/ARMeilleure/Instructions/InstEmitFlowHelper.cs new file mode 100644 index 0000000000..a8eb21d33f --- /dev/null +++ b/ARMeilleure/Instructions/InstEmitFlowHelper.cs @@ -0,0 +1,192 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; + +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.IntermediateRepresentation.OperandHelper; + +namespace ARMeilleure.Instructions +{ + static class InstEmitFlowHelper + { + public const ulong CallFlag = 1; + + 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 != null) + { + 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) + { + context.Return(Const(immediate | CallFlag)); + } + + public static void EmitVirtualCall(ArmEmitterContext context, Operand target) + { + EmitVirtualCallOrJump(context, target, isJump: false); + } + + public static void EmitVirtualJump(ArmEmitterContext context, Operand target) + { + EmitVirtualCallOrJump(context, target, isJump: true); + } + + private static void EmitVirtualCallOrJump(ArmEmitterContext context, Operand target, bool isJump) + { + context.Return(context.BitwiseOr(target, Const(target.Type, (long)CallFlag))); + } + + private static void EmitContinueOrReturnCheck(ArmEmitterContext context, Operand retVal) + { + // Note: The return value of the called method will be placed + // at the Stack, the return value is always a Int64 with the + // return address of the function. We check if the address is + // correct, if it isn't we keep returning until we reach the dispatcher. + ulong nextAddr = GetNextOpAddress(context.CurrOp); + + if (context.CurrBlock.Next != null) + { + Operand lblContinue = Label(); + + context.BranchIfTrue(lblContinue, context.ICompareEqual(retVal, Const(nextAddr))); + + context.Return(Const(nextAddr)); + + context.MarkLabel(lblContinue); + } + else + { + context.Return(Const(nextAddr)); + } + } + + private static ulong GetNextOpAddress(OpCode op) + { + return op.Address + (ulong)op.OpCodeSizeInBytes; + } + } +} diff --git a/ARMeilleure/Instructions/InstEmitHash.cs b/ARMeilleure/Instructions/InstEmitHash.cs new file mode 100644 index 0000000000..0be8458e20 --- /dev/null +++ b/ARMeilleure/Instructions/InstEmitHash.cs @@ -0,0 +1,64 @@ +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 Crc32b(ArmEmitterContext context) + { + EmitCrc32Call(context, new _U32_U32_U8(SoftFallback.Crc32b)); + } + + public static void Crc32h(ArmEmitterContext context) + { + EmitCrc32Call(context, new _U32_U32_U16(SoftFallback.Crc32h)); + } + + public static void Crc32w(ArmEmitterContext context) + { + EmitCrc32Call(context, new _U32_U32_U32(SoftFallback.Crc32w)); + } + + public static void Crc32x(ArmEmitterContext context) + { + EmitCrc32Call(context, new _U32_U32_U64(SoftFallback.Crc32x)); + } + + public static void Crc32cb(ArmEmitterContext context) + { + EmitCrc32Call(context, new _U32_U32_U8(SoftFallback.Crc32cb)); + } + + public static void Crc32ch(ArmEmitterContext context) + { + EmitCrc32Call(context, new _U32_U32_U16(SoftFallback.Crc32ch)); + } + + public static void Crc32cw(ArmEmitterContext context) + { + EmitCrc32Call(context, new _U32_U32_U32(SoftFallback.Crc32cw)); + } + + public static void Crc32cx(ArmEmitterContext context) + { + EmitCrc32Call(context, new _U32_U32_U64(SoftFallback.Crc32cx)); + } + + private static void EmitCrc32Call(ArmEmitterContext context, Delegate dlg) + { + OpCodeAluBinary op = (OpCodeAluBinary)context.CurrOp; + + Operand n = GetIntOrZR(context, op.Rn); + Operand m = GetIntOrZR(context, op.Rm); + + Operand d = context.Call(dlg, n, m); + + SetIntOrZR(context, op.Rd, d); + } + } +} diff --git a/ARMeilleure/Instructions/InstEmitHelper.cs b/ARMeilleure/Instructions/InstEmitHelper.cs new file mode 100644 index 0000000000..02e104a4ff --- /dev/null +++ b/ARMeilleure/Instructions/InstEmitHelper.cs @@ -0,0 +1,218 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; +using System; + +using static ARMeilleure.IntermediateRepresentation.OperandHelper; + +namespace ARMeilleure.Instructions +{ + static class InstEmitHelper + { + public static bool IsThumb(OpCode op) + { + return op is OpCodeT16; + } + + 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 GetIntOrSP(context, GetRegisterAlias(context.Mode, regIndex)); + } + } + + public static void SetIntA32(ArmEmitterContext context, int regIndex, Operand value) + { + if (regIndex == RegisterAlias.Aarch32Pc) + { + context.StoreToContext(); + + EmitBxWritePc(context, value); + } + else + { + SetIntOrSP(context, GetRegisterAlias(context.Mode, regIndex), 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 void EmitBxWritePc(ArmEmitterContext context, Operand pc) + { + Operand mode = context.BitwiseAnd(pc, Const(1)); + + SetFlag(context, PState.TFlag, mode); + + Operand lblArmMode = Label(); + + context.BranchIfTrue(lblArmMode, mode); + + context.Return(context.ZeroExtend32(OperandType.I64, context.BitwiseAnd(pc, Const(~1)))); + + context.MarkLabel(lblArmMode); + + context.Return(context.ZeroExtend32(OperandType.I64, context.BitwiseAnd(pc, Const(~3)))); + } + + 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 void SetFlag(ArmEmitterContext context, PState stateFlag, Operand value) + { + context.Copy(GetFlag(stateFlag), value); + + context.MarkFlagSet(stateFlag); + } + } +} diff --git a/ARMeilleure/Instructions/InstEmitMemory.cs b/ARMeilleure/Instructions/InstEmitMemory.cs new file mode 100644 index 0000000000..1d5953fb24 --- /dev/null +++ b/ARMeilleure/Instructions/InstEmitMemory.cs @@ -0,0 +1,177 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; + +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.Instructions.InstEmitMemoryHelper; +using static ARMeilleure.IntermediateRepresentation.OperandHelper; + +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 = context.Add(address, Const(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); + + InstEmitMemoryHelper.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 = context.Add(address, Const(1L << op.Size)); + + InstEmitMemoryHelper.EmitStore(context, address, op.Rt, op.Size); + InstEmitMemoryHelper.EmitStore(context, address2, op.Rt2, op.Size); + + EmitWBackIfNeeded(context, address); + } + + private static Operand GetAddress(ArmEmitterContext context) + { + Operand address = null; + + 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)); + } + + 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); + + 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/ARMeilleure/Instructions/InstEmitMemory32.cs b/ARMeilleure/Instructions/InstEmitMemory32.cs new file mode 100644 index 0000000000..002d2c5c65 --- /dev/null +++ b/ARMeilleure/Instructions/InstEmitMemory32.cs @@ -0,0 +1,256 @@ +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.OperandHelper; + +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, + + LoadZx = Load, + LoadSx = Load | Signed, + } + + public static void Ldm(ArmEmitterContext context) + { + OpCode32MemMult op = (OpCode32MemMult)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) + { + OpCode32MemMult op = (OpCode32MemMult)context.CurrOp; + + Operand n = 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) + { + OpCode32Mem op = (OpCode32Mem)context.CurrOp; + + Operand n = context.Copy(GetIntA32(context, op.Rn)); + + Operand temp = null; + + if (op.Index || op.WBack) + { + temp = op.Add + ? context.Add (n, Const(op.Immediate)) + : context.Subtract(n, Const(op.Immediate)); + } + + 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.Rt | 1, 4, WordSizeLog2); + + context.Branch(lblEnd); + + context.MarkLabel(lblBigEndian); + + Load(op.Rt | 1, 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.Rt | 1, 4, WordSizeLog2); + + context.Branch(lblEnd); + + context.MarkLabel(lblBigEndian); + + Store(op.Rt | 1, 0, WordSizeLog2); + Store(op.Rt, 4, WordSizeLog2); + + context.MarkLabel(lblEnd); + } + else + { + Store(op.Rt, 0, size); + } + } + } + } +} \ No newline at end of file diff --git a/ARMeilleure/Instructions/InstEmitMemoryEx.cs b/ARMeilleure/Instructions/InstEmitMemoryEx.cs new file mode 100644 index 0000000000..bcca7619d2 --- /dev/null +++ b/ARMeilleure/Instructions/InstEmitMemoryEx.cs @@ -0,0 +1,261 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System; +using System.Diagnostics; + +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.IntermediateRepresentation.OperandHelper; + +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) + { + context.Call(new _Void(NativeInterface.ClearExclusive)); + } + + 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 = EmitLoad(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 = EmitLoad(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 = EmitLoad(context, address, exclusive, op.Size); + + SetIntOrZR(context, op.Rt, value); + } + } + + private static Operand EmitLoad( + ArmEmitterContext context, + Operand address, + bool exclusive, + int size) + { + Delegate fallbackMethodDlg = null; + + if (exclusive) + { + switch (size) + { + case 0: fallbackMethodDlg = new _U8_U64 (NativeInterface.ReadByteExclusive); break; + case 1: fallbackMethodDlg = new _U16_U64 (NativeInterface.ReadUInt16Exclusive); break; + case 2: fallbackMethodDlg = new _U32_U64 (NativeInterface.ReadUInt32Exclusive); break; + case 3: fallbackMethodDlg = new _U64_U64 (NativeInterface.ReadUInt64Exclusive); break; + case 4: fallbackMethodDlg = new _V128_U64(NativeInterface.ReadVector128Exclusive); break; + } + } + else + { + switch (size) + { + case 0: fallbackMethodDlg = new _U8_U64 (NativeInterface.ReadByte); break; + case 1: fallbackMethodDlg = new _U16_U64 (NativeInterface.ReadUInt16); break; + case 2: fallbackMethodDlg = new _U32_U64 (NativeInterface.ReadUInt32); break; + case 3: fallbackMethodDlg = new _U64_U64 (NativeInterface.ReadUInt64); break; + case 4: fallbackMethodDlg = new _V128_U64(NativeInterface.ReadVector128); break; + } + } + + return context.Call(fallbackMethodDlg, address); + } + + public static void Pfrm(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; + + if (ordered) + { + EmitBarrier(context); + } + + Operand address = context.Copy(GetIntOrSP(context, op.Rn)); + + Operand t = GetIntOrZR(context, op.Rt); + + Operand s = null; + + 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); + } + + s = EmitStore(context, address, value, exclusive, op.Size + 1); + } + else + { + s = EmitStore(context, address, t, exclusive, op.Size); + } + + if (s != null) + { + // This is only needed for exclusive stores. The function returns 0 + // when the store is successful, and 1 otherwise. + SetIntOrZR(context, op.Rs, s); + } + } + + private static Operand EmitStore( + ArmEmitterContext context, + Operand address, + Operand value, + bool exclusive, + int size) + { + if (size < 3) + { + value = context.ConvertI64ToI32(value); + } + + Delegate fallbackMethodDlg = null; + + if (exclusive) + { + switch (size) + { + case 0: fallbackMethodDlg = new _S32_U64_U8 (NativeInterface.WriteByteExclusive); break; + case 1: fallbackMethodDlg = new _S32_U64_U16 (NativeInterface.WriteUInt16Exclusive); break; + case 2: fallbackMethodDlg = new _S32_U64_U32 (NativeInterface.WriteUInt32Exclusive); break; + case 3: fallbackMethodDlg = new _S32_U64_U64 (NativeInterface.WriteUInt64Exclusive); break; + case 4: fallbackMethodDlg = new _S32_U64_V128(NativeInterface.WriteVector128Exclusive); break; + } + + return context.Call(fallbackMethodDlg, address, value); + } + else + { + switch (size) + { + case 0: fallbackMethodDlg = new _Void_U64_U8 (NativeInterface.WriteByte); break; + case 1: fallbackMethodDlg = new _Void_U64_U16 (NativeInterface.WriteUInt16); break; + case 2: fallbackMethodDlg = new _Void_U64_U32 (NativeInterface.WriteUInt32); break; + case 3: fallbackMethodDlg = new _Void_U64_U64 (NativeInterface.WriteUInt64); break; + case 4: fallbackMethodDlg = new _Void_U64_V128(NativeInterface.WriteVector128); break; + } + + context.Call(fallbackMethodDlg, address, value); + + return null; + } + } + + 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. + } + } +} \ No newline at end of file diff --git a/ARMeilleure/Instructions/InstEmitMemoryHelper.cs b/ARMeilleure/Instructions/InstEmitMemoryHelper.cs new file mode 100644 index 0000000000..e0b44353b4 --- /dev/null +++ b/ARMeilleure/Instructions/InstEmitMemoryHelper.cs @@ -0,0 +1,509 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Memory; +using ARMeilleure.Translation; +using System; + +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.IntermediateRepresentation.OperandHelper; + +namespace ARMeilleure.Instructions +{ + static class InstEmitMemoryHelper + { + 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) + { + Operand value = GetIntOrZR(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; + } + } + + SetIntOrZR(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); + } + + private static void EmitReadInt(ArmEmitterContext context, Operand address, int rt, int size) + { + Operand isUnalignedAddr = EmitAddressCheck(context, address, size); + + Operand lblFastPath = Label(); + Operand lblSlowPath = Label(); + Operand lblEnd = Label(); + + context.BranchIfFalse(lblFastPath, isUnalignedAddr); + + context.MarkLabel(lblSlowPath); + + EmitReadIntFallback(context, address, rt, size); + + context.Branch(lblEnd); + + context.MarkLabel(lblFastPath); + + Operand physAddr = EmitPtPointerLoad(context, address, lblSlowPath); + + Operand value = null; + + 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); + + context.MarkLabel(lblEnd); + } + + private static void EmitReadVector( + ArmEmitterContext context, + Operand address, + Operand vector, + int rt, + int elem, + int size) + { + Operand isUnalignedAddr = EmitAddressCheck(context, address, size); + + Operand lblFastPath = Label(); + Operand lblSlowPath = Label(); + Operand lblEnd = Label(); + + context.BranchIfFalse(lblFastPath, isUnalignedAddr); + + context.MarkLabel(lblSlowPath); + + EmitReadVectorFallback(context, address, vector, rt, elem, size); + + context.Branch(lblEnd); + + context.MarkLabel(lblFastPath); + + Operand physAddr = EmitPtPointerLoad(context, address, lblSlowPath); + + Operand value = null; + + 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); + + 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 isUnalignedAddr = EmitAddressCheck(context, address, size); + + Operand lblFastPath = Label(); + Operand lblSlowPath = Label(); + Operand lblEnd = Label(); + + context.BranchIfFalse(lblFastPath, isUnalignedAddr); + + context.MarkLabel(lblSlowPath); + + EmitWriteIntFallback(context, address, rt, size); + + context.Branch(lblEnd); + + context.MarkLabel(lblFastPath); + + Operand physAddr = EmitPtPointerLoad(context, address, lblSlowPath); + + 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; + } + + context.MarkLabel(lblEnd); + } + + private static void EmitWriteVector( + ArmEmitterContext context, + Operand address, + int rt, + int elem, + int size) + { + Operand isUnalignedAddr = EmitAddressCheck(context, address, size); + + Operand lblFastPath = Label(); + Operand lblSlowPath = Label(); + Operand lblEnd = Label(); + + context.BranchIfFalse(lblFastPath, isUnalignedAddr); + + context.MarkLabel(lblSlowPath); + + EmitWriteVectorFallback(context, address, rt, elem, size); + + context.Branch(lblEnd); + + context.MarkLabel(lblFastPath); + + Operand physAddr = EmitPtPointerLoad(context, address, lblSlowPath); + + 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.FP32, value, elem)); + break; + + case 3: + context.Store(physAddr, context.VectorExtract(OperandType.FP64, value, elem)); + break; + + case 4: + context.Store(physAddr, value); + break; + } + + context.MarkLabel(lblEnd); + } + + private static Operand EmitAddressCheck(ArmEmitterContext context, Operand address, int size) + { + long addressCheckMask = ~(context.Memory.AddressSpaceSize - 1); + + addressCheckMask |= (1u << size) - 1; + + return context.BitwiseAnd(address, Const(address.Type, addressCheckMask)); + } + + private static Operand EmitPtPointerLoad(ArmEmitterContext context, Operand address, Operand lblFallbackPath) + { + Operand pte = Const(context.Memory.PageTable.ToInt64()); + + int bit = MemoryManager.PageBits; + + do + { + Operand addrPart = context.ShiftRightUI(address, Const(bit)); + + bit += context.Memory.PtLevelBits; + + if (bit < context.Memory.AddressSpaceBits) + { + addrPart = context.BitwiseAnd(addrPart, Const(addrPart.Type, context.Memory.PtLevelMask)); + } + + Operand pteOffset = context.ShiftLeft(addrPart, Const(3)); + + if (pteOffset.Type == OperandType.I32) + { + pteOffset = context.ZeroExtend32(OperandType.I64, pteOffset); + } + + Operand pteAddress = context.Add(pte, pteOffset); + + pte = context.Load(OperandType.I64, pteAddress); + } + while (bit < context.Memory.AddressSpaceBits); + + Operand hasFlagSet = context.BitwiseAnd(pte, Const((long)MemoryManager.PteFlagsMask)); + + context.BranchIfTrue(lblFallbackPath, hasFlagSet); + + Operand pageOffset = context.BitwiseAnd(address, Const(address.Type, MemoryManager.PageMask)); + + if (pageOffset.Type == OperandType.I32) + { + pageOffset = context.ZeroExtend32(OperandType.I64, pageOffset); + } + + Operand physAddr = context.Add(pte, pageOffset); + + return physAddr; + } + + private static void EmitReadIntFallback(ArmEmitterContext context, Operand address, int rt, int size) + { + Delegate fallbackMethodDlg = null; + + switch (size) + { + case 0: fallbackMethodDlg = new _U8_U64 (NativeInterface.ReadByte); break; + case 1: fallbackMethodDlg = new _U16_U64(NativeInterface.ReadUInt16); break; + case 2: fallbackMethodDlg = new _U32_U64(NativeInterface.ReadUInt32); break; + case 3: fallbackMethodDlg = new _U64_U64(NativeInterface.ReadUInt64); break; + } + + SetInt(context, rt, context.Call(fallbackMethodDlg, address)); + } + + private static void EmitReadVectorFallback( + ArmEmitterContext context, + Operand address, + Operand vector, + int rt, + int elem, + int size) + { + Delegate fallbackMethodDlg = null; + + switch (size) + { + case 0: fallbackMethodDlg = new _U8_U64 (NativeInterface.ReadByte); break; + case 1: fallbackMethodDlg = new _U16_U64 (NativeInterface.ReadUInt16); break; + case 2: fallbackMethodDlg = new _U32_U64 (NativeInterface.ReadUInt32); break; + case 3: fallbackMethodDlg = new _U64_U64 (NativeInterface.ReadUInt64); break; + case 4: fallbackMethodDlg = new _V128_U64(NativeInterface.ReadVector128); break; + } + + Operand value = context.Call(fallbackMethodDlg, 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) + { + Delegate fallbackMethodDlg = null; + + switch (size) + { + case 0: fallbackMethodDlg = new _Void_U64_U8 (NativeInterface.WriteByte); break; + case 1: fallbackMethodDlg = new _Void_U64_U16(NativeInterface.WriteUInt16); break; + case 2: fallbackMethodDlg = new _Void_U64_U32(NativeInterface.WriteUInt32); break; + case 3: fallbackMethodDlg = new _Void_U64_U64(NativeInterface.WriteUInt64); break; + } + + Operand value = GetInt(context, rt); + + if (size < 3 && value.Type == OperandType.I64) + { + value = context.ConvertI64ToI32(value); + } + + context.Call(fallbackMethodDlg, address, value); + } + + private static void EmitWriteVectorFallback( + ArmEmitterContext context, + Operand address, + int rt, + int elem, + int size) + { + Delegate fallbackMethodDlg = null; + + switch (size) + { + case 0: fallbackMethodDlg = new _Void_U64_U8 (NativeInterface.WriteByte); break; + case 1: fallbackMethodDlg = new _Void_U64_U16 (NativeInterface.WriteUInt16); break; + case 2: fallbackMethodDlg = new _Void_U64_U32 (NativeInterface.WriteUInt32); break; + case 3: fallbackMethodDlg = new _Void_U64_U64 (NativeInterface.WriteUInt64); break; + case 4: fallbackMethodDlg = new _Void_U64_V128(NativeInterface.WriteVector128); break; + } + + Operand value = null; + + 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(fallbackMethodDlg, 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); + } + } + } +} \ No newline at end of file diff --git a/ARMeilleure/Instructions/InstEmitMove.cs b/ARMeilleure/Instructions/InstEmitMove.cs new file mode 100644 index 0000000000..bf051f3294 --- /dev/null +++ b/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.OperandHelper; + +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/ARMeilleure/Instructions/InstEmitMul.cs b/ARMeilleure/Instructions/InstEmitMul.cs new file mode 100644 index 0000000000..65d11b30df --- /dev/null +++ b/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/ARMeilleure/Instructions/InstEmitSimdArithmetic.cs b/ARMeilleure/Instructions/InstEmitSimdArithmetic.cs new file mode 100644 index 0000000000..4603ae0b17 --- /dev/null +++ b/ARMeilleure/Instructions/InstEmitSimdArithmetic.cs @@ -0,0 +1,3359 @@ +// 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 static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.Instructions.InstEmitSimdHelper; +using static ARMeilleure.IntermediateRepresentation.OperandHelper; + +namespace ARMeilleure.Instructions +{ + using Func2I = Func; + + static partial class InstEmit + { + public static void Abs_S(ArmEmitterContext context) + { + EmitScalarUnaryOpSx(context, (op1) => EmitAbs(context, op1)); + } + + public static void Abs_V(ArmEmitterContext context) + { + EmitVectorUnaryOpSx(context, (op1) => EmitAbs(context, op1)); + } + + public static void Add_S(ArmEmitterContext context) + { + EmitScalarBinaryOpZx(context, (op1, op2) => context.Add(op1, op2)); + } + + public static void Add_V(ArmEmitterContext context) + { + 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) + { + EmitHighNarrow(context, (op1, op2) => context.Add(op1, op2), round: false); + } + + public static void Addp_S(ArmEmitterContext context) + { + 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.UseSsse3) + { + EmitSsse3VectorPairwiseOp(context, X86PaddInstruction); + } + else + { + EmitVectorPairwiseOpZx(context, (op1, op2) => context.Add(op1, op2)); + } + } + + public static void Addv_V(ArmEmitterContext context) + { + EmitVectorAcrossVectorOpZx(context, (op1, op2) => context.Add(op1, op2)); + } + + public static void Cls_V(ArmEmitterContext context) + { + 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(new _U64_U64_S32(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) + { + 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; + + if (eSize == 64) + { + de = context.CountLeadingZeros(ne); + } + else + { + de = context.Call(new _U64_U64_S32(SoftFallback.CountLeadingZeros), ne, Const(eSize)); + } + + res = EmitVectorInsert(context, res, de, index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void Cnt_V(ArmEmitterContext context) + { + 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 = context.Call(new _U64_U64(SoftFallback.CountSetBits8), ne); + } + + res = EmitVectorInsert(context, res, de, index, 0); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void Fabd_S(ArmEmitterContext context) + { + 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)); + + Operand mask = X86GetScalar(context, -0f); + + res = context.AddIntrinsic(Intrinsic.X86Andnps, mask, res); + + context.Copy(GetVec(op.Rd), context.VectorZeroUpper96(res)); + } + else /* if (sizeF == 1) */ + { + Operand res = context.AddIntrinsic(Intrinsic.X86Subsd, GetVec(op.Rn), GetVec(op.Rm)); + + Operand mask = X86GetScalar(context, -0d); + + res = context.AddIntrinsic(Intrinsic.X86Andnpd, mask, res); + + context.Copy(GetVec(op.Rd), context.VectorZeroUpper64(res)); + } + } + else + { + EmitScalarBinaryOpF(context, (op1, op2) => + { + Operand res = EmitSoftFloatCall(context, SoftFloat32.FPSub, SoftFloat64.FPSub, op1, op2); + + return EmitUnaryMathCall(context, MathF.Abs, Math.Abs, res); + }); + } + } + + public static void Fabd_V(ArmEmitterContext context) + { + 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)); + + Operand mask = X86GetAllElements(context, -0f); + + res = context.AddIntrinsic(Intrinsic.X86Andnps, mask, res); + + 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)); + + Operand mask = X86GetAllElements(context, -0d); + + res = context.AddIntrinsic(Intrinsic.X86Andnpd, mask, res); + + context.Copy(GetVec(op.Rd), res); + } + } + else + { + EmitVectorBinaryOpF(context, (op1, op2) => + { + Operand res = EmitSoftFloatCall(context, SoftFloat32.FPSub, SoftFloat64.FPSub, op1, op2); + + return EmitUnaryMathCall(context, MathF.Abs, Math.Abs, res); + }); + } + } + + public static void Fabs_S(ArmEmitterContext context) + { + if (Optimizations.UseSse2) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + if (op.Size == 0) + { + Operand mask = X86GetScalar(context, -0f); + + Operand res = context.AddIntrinsic(Intrinsic.X86Andnps, 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.X86Andnpd, mask, GetVec(op.Rn)); + + context.Copy(GetVec(op.Rd), context.VectorZeroUpper64(res)); + } + } + else + { + EmitScalarUnaryOpF(context, (op1) => + { + return EmitUnaryMathCall(context, MathF.Abs, Math.Abs, op1); + }); + } + } + + public static void Fabs_V(ArmEmitterContext context) + { + 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.X86Andnps, 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.X86Andnpd, mask, GetVec(op.Rn)); + + context.Copy(GetVec(op.Rd), res); + } + } + else + { + EmitVectorUnaryOpF(context, (op1) => + { + return EmitUnaryMathCall(context, MathF.Abs, Math.Abs, op1); + }); + } + } + + public static void Fadd_S(ArmEmitterContext context) + { + 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, SoftFloat32.FPAdd, SoftFloat64.FPAdd, op1, op2); + }); + } + } + + public static void Fadd_V(ArmEmitterContext context) + { + 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, SoftFloat32.FPAdd, SoftFloat64.FPAdd, op1, op2); + }); + } + } + + public static void Faddp_S(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + int sizeF = op.Size & 1; + + if (Optimizations.FastFP && Optimizations.UseSse3) + { + if (sizeF == 0) + { + Operand res = context.AddIntrinsic(Intrinsic.X86Haddps, GetVec(op.Rn), GetVec(op.Rn)); + + context.Copy(GetVec(op.Rd), context.VectorZeroUpper96(res)); + } + else /* if (sizeF == 1) */ + { + Operand res = context.AddIntrinsic(Intrinsic.X86Haddpd, GetVec(op.Rn), GetVec(op.Rn)); + + context.Copy(GetVec(op.Rd), context.VectorZeroUpper64(res)); + } + } + else + { + OperandType type = sizeF != 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 = EmitSoftFloatCall(context, SoftFloat32.FPAdd, SoftFloat64.FPAdd, ne0, ne1); + + context.Copy(GetVec(op.Rd), context.VectorInsert(context.VectorZero(), res, 0)); + } + } + + public static void Faddp_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitSse2VectorPairwiseOpF(context, Intrinsic.X86Addps, Intrinsic.X86Addpd); + } + else + { + EmitVectorPairwiseOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, SoftFloat32.FPAdd, SoftFloat64.FPAdd, op1, op2); + }); + } + } + + public static void Fdiv_S(ArmEmitterContext context) + { + 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, SoftFloat32.FPDiv, SoftFloat64.FPDiv, op1, op2); + }); + } + } + + public static void Fdiv_V(ArmEmitterContext context) + { + 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, SoftFloat32.FPDiv, SoftFloat64.FPDiv, op1, op2); + }); + } + } + + public static void Fmadd_S(ArmEmitterContext context) // Fused. + { + 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); + + if (op.Size == 0) + { + Operand 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) */ + { + Operand 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, SoftFloat32.FPMulAdd, SoftFloat64.FPMulAdd, op1, op2, op3); + }); + } + } + + public static void Fmax_S(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarBinaryOpF(context, Intrinsic.X86Maxss, Intrinsic.X86Maxsd); + } + else + { + EmitScalarBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, SoftFloat32.FPMax, SoftFloat64.FPMax, op1, op2); + }); + } + } + + public static void Fmax_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitVectorBinaryOpF(context, Intrinsic.X86Maxps, Intrinsic.X86Maxpd); + } + else + { + EmitVectorBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, SoftFloat32.FPMax, SoftFloat64.FPMax, op1, op2); + }); + } + } + + public static void Fmaxnm_S(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse41MaxMinNumOpF(context, isMaxNum: true, scalar: true); + } + else + { + EmitScalarBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, SoftFloat32.FPMaxNum, SoftFloat64.FPMaxNum, op1, op2); + }); + } + } + + public static void Fmaxnm_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse41MaxMinNumOpF(context, isMaxNum: true, scalar: false); + } + else + { + EmitVectorBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, SoftFloat32.FPMaxNum, SoftFloat64.FPMaxNum, op1, op2); + }); + } + } + + public static void Fmaxp_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitSse2VectorPairwiseOpF(context, Intrinsic.X86Maxps, Intrinsic.X86Maxpd); + } + else + { + EmitVectorPairwiseOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, SoftFloat32.FPMax, SoftFloat64.FPMax, op1, op2); + }); + } + } + + public static void Fmin_S(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarBinaryOpF(context, Intrinsic.X86Minss, Intrinsic.X86Minsd); + } + else + { + EmitScalarBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, SoftFloat32.FPMin, SoftFloat64.FPMin, op1, op2); + }); + } + } + + public static void Fmin_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitVectorBinaryOpF(context, Intrinsic.X86Minps, Intrinsic.X86Minpd); + } + else + { + EmitVectorBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, SoftFloat32.FPMin, SoftFloat64.FPMin, op1, op2); + }); + } + } + + public static void Fminnm_S(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse41MaxMinNumOpF(context, isMaxNum: false, scalar: true); + } + else + { + EmitScalarBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, SoftFloat32.FPMinNum, SoftFloat64.FPMinNum, op1, op2); + }); + } + } + + public static void Fminnm_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse41MaxMinNumOpF(context, isMaxNum: false, scalar: false); + } + else + { + EmitVectorBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, SoftFloat32.FPMinNum, SoftFloat64.FPMinNum, op1, op2); + }); + } + } + + public static void Fminp_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitSse2VectorPairwiseOpF(context, Intrinsic.X86Minps, Intrinsic.X86Minpd); + } + else + { + EmitVectorPairwiseOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, SoftFloat32.FPMin, SoftFloat64.FPMin, op1, op2); + }); + } + } + + public static void Fmla_Se(ArmEmitterContext context) // Fused. + { + EmitScalarTernaryOpByElemF(context, (op1, op2, op3) => + { + return context.Add(op1, context.Multiply(op2, op3)); + }); + } + + public static void Fmla_V(ArmEmitterContext context) // Fused. + { + 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; + + if (sizeF == 0) + { + Operand 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) */ + { + Operand 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, SoftFloat32.FPMulAdd, SoftFloat64.FPMulAdd, op1, op2, op3); + }); + } + } + + public static void Fmla_Ve(ArmEmitterContext context) // Fused. + { + 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)); + + 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)); + + 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, SoftFloat32.FPMulAdd, SoftFloat64.FPMulAdd, op1, op2, op3); + }); + } + } + + public static void Fmls_Se(ArmEmitterContext context) // Fused. + { + EmitScalarTernaryOpByElemF(context, (op1, op2, op3) => + { + return context.Subtract(op1, context.Multiply(op2, op3)); + }); + } + + public static void Fmls_V(ArmEmitterContext context) // Fused. + { + 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; + + if (sizeF == 0) + { + Operand 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) */ + { + Operand 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, SoftFloat32.FPMulSub, SoftFloat64.FPMulSub, op1, op2, op3); + }); + } + } + + public static void Fmls_Ve(ArmEmitterContext context) // Fused. + { + 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)); + + 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)); + + 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, SoftFloat32.FPMulSub, SoftFloat64.FPMulSub, op1, op2, op3); + }); + } + } + + public static void Fmsub_S(ArmEmitterContext context) // Fused. + { + 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); + + if (op.Size == 0) + { + Operand 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) */ + { + Operand 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, SoftFloat32.FPMulSub, SoftFloat64.FPMulSub, op1, op2, op3); + }); + } + } + + public static void Fmul_S(ArmEmitterContext context) + { + 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, SoftFloat32.FPMul, SoftFloat64.FPMul, op1, op2); + }); + } + } + + public static void Fmul_Se(ArmEmitterContext context) + { + EmitScalarBinaryOpByElemF(context, (op1, op2) => context.Multiply(op1, op2)); + } + + public static void Fmul_V(ArmEmitterContext context) + { + 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, SoftFloat32.FPMul, SoftFloat64.FPMul, op1, op2); + }); + } + } + + public static void Fmul_Ve(ArmEmitterContext context) + { + 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, SoftFloat32.FPMul, SoftFloat64.FPMul, op1, op2); + }); + } + } + + public static void Fmulx_S(ArmEmitterContext context) + { + EmitScalarBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, SoftFloat32.FPMulX, SoftFloat64.FPMulX, op1, op2); + }); + } + + public static void Fmulx_Se(ArmEmitterContext context) + { + EmitScalarBinaryOpByElemF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, SoftFloat32.FPMulX, SoftFloat64.FPMulX, op1, op2); + }); + } + + public static void Fmulx_V(ArmEmitterContext context) + { + EmitVectorBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, SoftFloat32.FPMulX, SoftFloat64.FPMulX, op1, op2); + }); + } + + public static void Fmulx_Ve(ArmEmitterContext context) + { + EmitVectorBinaryOpByElemF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, SoftFloat32.FPMulX, SoftFloat64.FPMulX, op1, op2); + }); + } + + public static void Fneg_S(ArmEmitterContext context) + { + 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.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.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); + + if (op.Size == 0) + { + Operand mask = X86GetScalar(context, -0f); + + Operand aNeg = context.AddIntrinsic(Intrinsic.X86Xorps, mask, a); + + Operand 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) */ + { + Operand mask = X86GetScalar(context, -0d); + + Operand aNeg = context.AddIntrinsic(Intrinsic.X86Xorpd, mask, a); + + Operand 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, SoftFloat32.FPNegMulAdd, SoftFloat64.FPNegMulAdd, op1, op2, op3); + }); + } + } + + public static void Fnmsub_S(ArmEmitterContext context) // Fused. + { + 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); + + if (op.Size == 0) + { + Operand mask = X86GetScalar(context, -0f); + + Operand aNeg = context.AddIntrinsic(Intrinsic.X86Xorps, mask, a); + + Operand 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) */ + { + Operand mask = X86GetScalar(context, -0d); + + Operand aNeg = context.AddIntrinsic(Intrinsic.X86Xorpd, mask, a); + + Operand 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, SoftFloat32.FPNegMulSub, SoftFloat64.FPNegMulSub, op1, op2, op3); + }); + } + } + + public static void Fnmul_S(ArmEmitterContext context) + { + 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.FastFP && Optimizations.UseSse && sizeF == 0) + { + EmitScalarUnaryOpF(context, Intrinsic.X86Rcpss, 0); + } + else + { + EmitScalarUnaryOpF(context, (op1) => + { + return EmitSoftFloatCall(context, SoftFloat32.FPRecipEstimate, SoftFloat64.FPRecipEstimate, op1); + }); + } + } + + public static void Frecpe_V(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + int sizeF = op.Size & 1; + + if (Optimizations.FastFP && Optimizations.UseSse && sizeF == 0) + { + EmitVectorUnaryOpF(context, Intrinsic.X86Rcpps, 0); + } + else + { + EmitVectorUnaryOpF(context, (op1) => + { + return EmitSoftFloatCall(context, SoftFloat32.FPRecipEstimate, SoftFloat64.FPRecipEstimate, op1); + }); + } + } + + public static void Frecps_S(ArmEmitterContext context) // Fused. + { + if (Optimizations.FastFP && Optimizations.UseSse2) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + int sizeF = op.Size & 1; + + if (sizeF == 0) + { + Operand mask = X86GetScalar(context, 2f); + + Operand res = context.AddIntrinsic(Intrinsic.X86Mulss, GetVec(op.Rn), GetVec(op.Rm)); + + res = context.AddIntrinsic(Intrinsic.X86Subss, mask, res); + + context.Copy(GetVec(op.Rd), context.VectorZeroUpper96(res)); + } + else /* if (sizeF == 1) */ + { + Operand mask = X86GetScalar(context, 2d); + + Operand res = context.AddIntrinsic(Intrinsic.X86Mulsd, GetVec(op.Rn), GetVec(op.Rm)); + + res = context.AddIntrinsic(Intrinsic.X86Subsd, mask, res); + + context.Copy(GetVec(op.Rd), context.VectorZeroUpper64(res)); + } + } + else + { + EmitScalarBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, SoftFloat32.FPRecipStepFused, SoftFloat64.FPRecipStepFused, op1, op2); + }); + } + } + + public static void Frecps_V(ArmEmitterContext context) // Fused. + { + if (Optimizations.FastFP && Optimizations.UseSse2) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + int sizeF = op.Size & 1; + + if (sizeF == 0) + { + Operand mask = X86GetAllElements(context, 2f); + + Operand res = context.AddIntrinsic(Intrinsic.X86Mulps, GetVec(op.Rn), GetVec(op.Rm)); + + res = context.AddIntrinsic(Intrinsic.X86Subps, mask, res); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else /* if (sizeF == 1) */ + { + Operand mask = X86GetAllElements(context, 2d); + + Operand res = context.AddIntrinsic(Intrinsic.X86Mulpd, GetVec(op.Rn), GetVec(op.Rm)); + + res = context.AddIntrinsic(Intrinsic.X86Subpd, mask, res); + + context.Copy(GetVec(op.Rd), res); + } + } + else + { + EmitVectorBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, SoftFloat32.FPRecipStepFused, SoftFloat64.FPRecipStepFused, op1, op2); + }); + } + } + + public static void Frecpx_S(ArmEmitterContext context) + { + EmitScalarUnaryOpF(context, (op1) => + { + return EmitSoftFloatCall(context, SoftFloat32.FPRecpX, SoftFloat64.FPRecpX, op1); + }); + } + + public static void Frinta_S(ArmEmitterContext context) + { + EmitScalarUnaryOpF(context, (op1) => + { + return EmitRoundMathCall(context, MidpointRounding.AwayFromZero, op1); + }); + } + + public static void Frinta_V(ArmEmitterContext context) + { + EmitVectorUnaryOpF(context, (op1) => + { + return EmitRoundMathCall(context, MidpointRounding.AwayFromZero, op1); + }); + } + + public static void Frinti_S(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + EmitScalarUnaryOpF(context, (op1) => + { + if (op.Size == 0) + { + return context.Call(new _F32_F32(SoftFallback.RoundF), op1); + } + else /* if (op.Size == 1) */ + { + return context.Call(new _F64_F64(SoftFallback.Round), op1); + } + }); + } + + public static void Frinti_V(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + int sizeF = op.Size & 1; + + EmitVectorUnaryOpF(context, (op1) => + { + if (sizeF == 0) + { + return context.Call(new _F32_F32(SoftFallback.RoundF), op1); + } + else /* if (sizeF == 1) */ + { + return context.Call(new _F64_F64(SoftFallback.Round), op1); + } + }); + } + + public static void Frintm_S(ArmEmitterContext context) + { + if (Optimizations.UseSse41) + { + EmitScalarRoundOpF(context, FPRoundingMode.TowardsMinusInfinity); + } + else + { + EmitScalarUnaryOpF(context, (op1) => + { + return EmitUnaryMathCall(context, MathF.Floor, Math.Floor, op1); + }); + } + } + + public static void Frintm_V(ArmEmitterContext context) + { + if (Optimizations.UseSse41) + { + EmitVectorRoundOpF(context, FPRoundingMode.TowardsMinusInfinity); + } + else + { + EmitVectorUnaryOpF(context, (op1) => + { + return EmitUnaryMathCall(context, MathF.Floor, Math.Floor, op1); + }); + } + } + + public static void Frintn_S(ArmEmitterContext context) + { + if (Optimizations.UseSse41) + { + EmitScalarRoundOpF(context, FPRoundingMode.ToNearest); + } + else + { + EmitScalarUnaryOpF(context, (op1) => + { + return EmitRoundMathCall(context, MidpointRounding.ToEven, op1); + }); + } + } + + public static void Frintn_V(ArmEmitterContext context) + { + if (Optimizations.UseSse41) + { + EmitVectorRoundOpF(context, FPRoundingMode.ToNearest); + } + else + { + EmitVectorUnaryOpF(context, (op1) => + { + return EmitRoundMathCall(context, MidpointRounding.ToEven, op1); + }); + } + } + + public static void Frintp_S(ArmEmitterContext context) + { + if (Optimizations.UseSse41) + { + EmitScalarRoundOpF(context, FPRoundingMode.TowardsPlusInfinity); + } + else + { + EmitScalarUnaryOpF(context, (op1) => + { + return EmitUnaryMathCall(context, MathF.Ceiling, Math.Ceiling, op1); + }); + } + } + + public static void Frintp_V(ArmEmitterContext context) + { + if (Optimizations.UseSse41) + { + EmitVectorRoundOpF(context, FPRoundingMode.TowardsPlusInfinity); + } + else + { + EmitVectorUnaryOpF(context, (op1) => + { + return EmitUnaryMathCall(context, MathF.Ceiling, Math.Ceiling, op1); + }); + } + } + + public static void Frintx_S(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + EmitScalarUnaryOpF(context, (op1) => + { + if (op.Size == 0) + { + return context.Call(new _F32_F32(SoftFallback.RoundF), op1); + } + else /* if (op.Size == 1) */ + { + return context.Call(new _F64_F64(SoftFallback.Round), op1); + } + }); + } + + public static void Frintx_V(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + int sizeF = op.Size & 1; + + EmitVectorUnaryOpF(context, (op1) => + { + if (sizeF == 0) + { + return context.Call(new _F32_F32(SoftFallback.RoundF), op1); + } + else /* if (sizeF == 1) */ + { + return context.Call(new _F64_F64(SoftFallback.Round), op1); + } + }); + } + + public static void Frintz_S(ArmEmitterContext context) + { + if (Optimizations.UseSse41) + { + EmitScalarRoundOpF(context, FPRoundingMode.TowardsZero); + } + else + { + EmitScalarUnaryOpF(context, (op1) => + { + return EmitUnaryMathCall(context, MathF.Truncate, Math.Truncate, op1); + }); + } + } + + public static void Frintz_V(ArmEmitterContext context) + { + if (Optimizations.UseSse41) + { + EmitVectorRoundOpF(context, FPRoundingMode.TowardsZero); + } + else + { + EmitVectorUnaryOpF(context, (op1) => + { + return EmitUnaryMathCall(context, MathF.Truncate, Math.Truncate, op1); + }); + } + } + + public static void Frsqrte_S(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + int sizeF = op.Size & 1; + + if (Optimizations.FastFP && Optimizations.UseSse && sizeF == 0) + { + EmitScalarUnaryOpF(context, Intrinsic.X86Rsqrtss, 0); + } + else + { + EmitScalarUnaryOpF(context, (op1) => + { + return EmitSoftFloatCall(context, SoftFloat32.FPRSqrtEstimate, SoftFloat64.FPRSqrtEstimate, op1); + }); + } + } + + public static void Frsqrte_V(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + int sizeF = op.Size & 1; + + if (Optimizations.FastFP && Optimizations.UseSse && sizeF == 0) + { + EmitVectorUnaryOpF(context, Intrinsic.X86Rsqrtps, 0); + } + else + { + EmitVectorUnaryOpF(context, (op1) => + { + return EmitSoftFloatCall(context, SoftFloat32.FPRSqrtEstimate, SoftFloat64.FPRSqrtEstimate, op1); + }); + } + } + + public static void Frsqrts_S(ArmEmitterContext context) // Fused. + { + if (Optimizations.FastFP && Optimizations.UseSse2) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + int sizeF = op.Size & 1; + + if (sizeF == 0) + { + Operand maskHalf = X86GetScalar(context, 0.5f); + Operand maskThree = X86GetScalar(context, 3f); + + Operand res = context.AddIntrinsic(Intrinsic.X86Mulss, GetVec(op.Rn), GetVec(op.Rm)); + + res = context.AddIntrinsic(Intrinsic.X86Subss, maskThree, res); + res = context.AddIntrinsic(Intrinsic.X86Mulss, maskHalf, res); + + context.Copy(GetVec(op.Rd), context.VectorZeroUpper96(res)); + } + else /* if (sizeF == 1) */ + { + Operand maskHalf = X86GetScalar(context, 0.5d); + Operand maskThree = X86GetScalar(context, 3d); + + Operand res = context.AddIntrinsic(Intrinsic.X86Mulsd, GetVec(op.Rn), GetVec(op.Rm)); + + res = context.AddIntrinsic(Intrinsic.X86Subsd, maskThree, res); + res = context.AddIntrinsic(Intrinsic.X86Mulsd, maskHalf, res); + + context.Copy(GetVec(op.Rd), context.VectorZeroUpper64(res)); + } + } + else + { + EmitScalarBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, SoftFloat32.FPRSqrtStepFused, SoftFloat64.FPRSqrtStepFused, op1, op2); + }); + } + } + + public static void Frsqrts_V(ArmEmitterContext context) // Fused. + { + if (Optimizations.FastFP && Optimizations.UseSse2) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + int sizeF = op.Size & 1; + + if (sizeF == 0) + { + Operand maskHalf = X86GetAllElements(context, 0.5f); + Operand maskThree = X86GetAllElements(context, 3f); + + Operand res = context.AddIntrinsic(Intrinsic.X86Mulps, GetVec(op.Rn), GetVec(op.Rm)); + + res = context.AddIntrinsic(Intrinsic.X86Subps, maskThree, res); + res = context.AddIntrinsic(Intrinsic.X86Mulps, maskHalf, res); + + 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 res = context.AddIntrinsic(Intrinsic.X86Mulpd, GetVec(op.Rn), GetVec(op.Rm)); + + res = context.AddIntrinsic(Intrinsic.X86Subpd, maskThree, res); + res = context.AddIntrinsic(Intrinsic.X86Mulpd, maskHalf, res); + + context.Copy(GetVec(op.Rd), res); + } + } + else + { + EmitVectorBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, SoftFloat32.FPRSqrtStepFused, SoftFloat64.FPRSqrtStepFused, op1, op2); + }); + } + } + + public static void Fsqrt_S(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarUnaryOpF(context, Intrinsic.X86Sqrtss, Intrinsic.X86Sqrtsd); + } + else + { + EmitScalarUnaryOpF(context, (op1) => + { + return EmitSoftFloatCall(context, SoftFloat32.FPSqrt, SoftFloat64.FPSqrt, op1); + }); + } + } + + public static void Fsqrt_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitVectorUnaryOpF(context, Intrinsic.X86Sqrtps, Intrinsic.X86Sqrtpd); + } + else + { + EmitVectorUnaryOpF(context, (op1) => + { + return EmitSoftFloatCall(context, SoftFloat32.FPSqrt, SoftFloat64.FPSqrt, op1); + }); + } + } + + public static void Fsub_S(ArmEmitterContext context) + { + 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, SoftFloat32.FPSub, SoftFloat64.FPSub, op1, op2); + }); + } + } + + public static void Fsub_V(ArmEmitterContext context) + { + 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, SoftFloat32.FPSub, SoftFloat64.FPSub, op1, op2); + }); + } + } + + public static void Mla_V(ArmEmitterContext context) + { + if (Optimizations.UseSse41) + { + EmitSse41Mul_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) + { + EmitVectorTernaryOpByElemZx(context, (op1, op2, op3) => + { + return context.Add(op1, context.Multiply(op2, op3)); + }); + } + + public static void Mls_V(ArmEmitterContext context) + { + if (Optimizations.UseSse41) + { + EmitSse41Mul_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) + { + EmitVectorTernaryOpByElemZx(context, (op1, op2, op3) => + { + return context.Subtract(op1, context.Multiply(op2, op3)); + }); + } + + public static void Mul_V(ArmEmitterContext context) + { + if (Optimizations.UseSse41) + { + EmitSse41Mul_AddSub(context, AddSub.None); + } + else + { + EmitVectorBinaryOpZx(context, (op1, op2) => context.Multiply(op1, op2)); + } + } + + public static void Mul_Ve(ArmEmitterContext context) + { + EmitVectorBinaryOpByElemZx(context, (op1, op2) => context.Multiply(op1, op2)); + } + + public static void Neg_S(ArmEmitterContext context) + { + EmitScalarUnaryOpSx(context, (op1) => context.Negate(op1)); + } + + public static void Neg_V(ArmEmitterContext context) + { + 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 Raddhn_V(ArmEmitterContext context) + { + EmitHighNarrow(context, (op1, op2) => context.Add(op1, op2), round: true); + } + + public static void Rsubhn_V(ArmEmitterContext context) + { + EmitHighNarrow(context, (op1, op2) => context.Subtract(op1, op2), round: true); + } + + public static void Saba_V(ArmEmitterContext context) + { + EmitVectorTernaryOpSx(context, (op1, op2, op3) => + { + return context.Add(op1, EmitAbs(context, context.Subtract(op2, op3))); + }); + } + + public static void Sabal_V(ArmEmitterContext context) + { + EmitVectorWidenRnRmTernaryOpSx(context, (op1, op2, op3) => + { + return context.Add(op1, EmitAbs(context, context.Subtract(op2, op3))); + }); + } + + public static void Sabd_V(ArmEmitterContext context) + { + if (Optimizations.UseSse2) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + EmitSse41Sabd(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.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); + + EmitSse41Sabd(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) + { + EmitAddLongPairwise(context, signed: true, accumulate: true); + } + + public static void Saddl_V(ArmEmitterContext context) + { + 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) + { + EmitAddLongPairwise(context, signed: true, accumulate: false); + } + + public static void Saddlv_V(ArmEmitterContext context) + { + EmitVectorLongAcrossVectorOpSx(context, (op1, op2) => context.Add(op1, op2)); + } + + public static void Saddw_V(ArmEmitterContext context) + { + 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.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.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.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 + { + Delegate dlg = new _S64_S64_S64(Math.Max); + + EmitVectorBinaryOpSx(context, (op1, op2) => context.Call(dlg, op1, op2)); + } + } + + public static void Smaxp_V(ArmEmitterContext context) + { + if (Optimizations.UseSsse3) + { + EmitSsse3VectorPairwiseOp(context, X86PmaxsInstruction); + } + else + { + Delegate dlg = new _S64_S64_S64(Math.Max); + + EmitVectorPairwiseOpSx(context, (op1, op2) => context.Call(dlg, op1, op2)); + } + } + + public static void Smaxv_V(ArmEmitterContext context) + { + Delegate dlg = new _S64_S64_S64(Math.Max); + + EmitVectorAcrossVectorOpSx(context, (op1, op2) => context.Call(dlg, op1, op2)); + } + + public static void Smin_V(ArmEmitterContext context) + { + 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 + { + Delegate dlg = new _S64_S64_S64(Math.Min); + + EmitVectorBinaryOpSx(context, (op1, op2) => context.Call(dlg, op1, op2)); + } + } + + public static void Sminp_V(ArmEmitterContext context) + { + if (Optimizations.UseSsse3) + { + EmitSsse3VectorPairwiseOp(context, X86PminsInstruction); + } + else + { + Delegate dlg = new _S64_S64_S64(Math.Min); + + EmitVectorPairwiseOpSx(context, (op1, op2) => context.Call(dlg, op1, op2)); + } + } + + public static void Sminv_V(ArmEmitterContext context) + { + Delegate dlg = new _S64_S64_S64(Math.Min); + + EmitVectorAcrossVectorOpSx(context, (op1, op2) => context.Call(dlg, op1, op2)); + } + + public static void Smlal_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + 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) + { + 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.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) + { + EmitVectorWidenTernaryOpByElemSx(context, (op1, op2, op3) => + { + return context.Subtract(op1, context.Multiply(op2, op3)); + }); + } + + public static void Smull_V(ArmEmitterContext context) + { + EmitVectorWidenRnRmBinaryOpSx(context, (op1, op2) => context.Multiply(op1, op2)); + } + + public static void Smull_Ve(ArmEmitterContext context) + { + EmitVectorWidenBinaryOpByElemSx(context, (op1, op2) => context.Multiply(op1, op2)); + } + + public static void Sqabs_S(ArmEmitterContext context) + { + EmitScalarSaturatingUnaryOpSx(context, (op1) => EmitAbs(context, op1)); + } + + public static void Sqabs_V(ArmEmitterContext context) + { + EmitVectorSaturatingUnaryOpSx(context, (op1) => EmitAbs(context, op1)); + } + + public static void Sqadd_S(ArmEmitterContext context) + { + EmitScalarSaturatingBinaryOpSx(context, SaturatingFlags.Add); + } + + public static void Sqadd_V(ArmEmitterContext context) + { + EmitVectorSaturatingBinaryOpSx(context, SaturatingFlags.Add); + } + + public static void Sqdmulh_S(ArmEmitterContext context) + { + EmitSaturatingBinaryOp(context, (op1, op2) => EmitDoublingMultiplyHighHalf(context, op1, op2, round: false), SaturatingFlags.ScalarSx); + } + + public static void Sqdmulh_V(ArmEmitterContext context) + { + EmitSaturatingBinaryOp(context, (op1, op2) => EmitDoublingMultiplyHighHalf(context, op1, op2, round: false), SaturatingFlags.VectorSx); + } + + public static void Sqneg_S(ArmEmitterContext context) + { + EmitScalarSaturatingUnaryOpSx(context, (op1) => context.Negate(op1)); + } + + public static void Sqneg_V(ArmEmitterContext context) + { + EmitVectorSaturatingUnaryOpSx(context, (op1) => context.Negate(op1)); + } + + public static void Sqrdmulh_S(ArmEmitterContext context) + { + EmitSaturatingBinaryOp(context, (op1, op2) => EmitDoublingMultiplyHighHalf(context, op1, op2, round: true), SaturatingFlags.ScalarSx); + } + + public static void Sqrdmulh_V(ArmEmitterContext context) + { + EmitSaturatingBinaryOp(context, (op1, op2) => EmitDoublingMultiplyHighHalf(context, op1, op2, round: true), SaturatingFlags.VectorSx); + } + + public static void Sqsub_S(ArmEmitterContext context) + { + EmitScalarSaturatingBinaryOpSx(context, SaturatingFlags.Sub); + } + + public static void Sqsub_V(ArmEmitterContext context) + { + EmitVectorSaturatingBinaryOpSx(context, SaturatingFlags.Sub); + } + + public static void Sqxtn_S(ArmEmitterContext context) + { + EmitSaturatingNarrowOp(context, SaturatingNarrowFlags.ScalarSxSx); + } + + public static void Sqxtn_V(ArmEmitterContext context) + { + EmitSaturatingNarrowOp(context, SaturatingNarrowFlags.VectorSxSx); + } + + public static void Sqxtun_S(ArmEmitterContext context) + { + EmitSaturatingNarrowOp(context, SaturatingNarrowFlags.ScalarSxZx); + } + + public static void Sqxtun_V(ArmEmitterContext context) + { + EmitSaturatingNarrowOp(context, SaturatingNarrowFlags.VectorSxZx); + } + + public static void Srhadd_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + 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.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.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) + { + EmitScalarBinaryOpZx(context, (op1, op2) => context.Subtract(op1, op2)); + } + + public static void Sub_V(ArmEmitterContext context) + { + 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) + { + EmitHighNarrow(context, (op1, op2) => context.Subtract(op1, op2), round: false); + } + + public static void Suqadd_S(ArmEmitterContext context) + { + EmitScalarSaturatingBinaryOpSx(context, SaturatingFlags.Accumulate); + } + + public static void Suqadd_V(ArmEmitterContext context) + { + EmitVectorSaturatingBinaryOpSx(context, SaturatingFlags.Accumulate); + } + + public static void Uaba_V(ArmEmitterContext context) + { + EmitVectorTernaryOpZx(context, (op1, op2, op3) => + { + return context.Add(op1, EmitAbs(context, context.Subtract(op2, op3))); + }); + } + + public static void Uabal_V(ArmEmitterContext context) + { + EmitVectorWidenRnRmTernaryOpZx(context, (op1, op2, op3) => + { + return context.Add(op1, EmitAbs(context, context.Subtract(op2, op3))); + }); + } + + public static void Uabd_V(ArmEmitterContext context) + { + if (Optimizations.UseSse41) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + EmitSse41Uabd(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.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); + + EmitSse41Uabd(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) + { + EmitAddLongPairwise(context, signed: false, accumulate: true); + } + + public static void Uaddl_V(ArmEmitterContext context) + { + 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) + { + EmitAddLongPairwise(context, signed: false, accumulate: false); + } + + public static void Uaddlv_V(ArmEmitterContext context) + { + EmitVectorLongAcrossVectorOpZx(context, (op1, op2) => context.Add(op1, op2)); + } + + public static void Uaddw_V(ArmEmitterContext context) + { + 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.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.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.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 + { + Delegate dlg = new _U64_U64_U64(Math.Max); + + EmitVectorBinaryOpZx(context, (op1, op2) => context.Call(dlg, op1, op2)); + } + } + + public static void Umaxp_V(ArmEmitterContext context) + { + if (Optimizations.UseSsse3) + { + EmitSsse3VectorPairwiseOp(context, X86PmaxuInstruction); + } + else + { + Delegate dlg = new _U64_U64_U64(Math.Max); + + EmitVectorPairwiseOpZx(context, (op1, op2) => context.Call(dlg, op1, op2)); + } + } + + public static void Umaxv_V(ArmEmitterContext context) + { + Delegate dlg = new _U64_U64_U64(Math.Max); + + EmitVectorAcrossVectorOpZx(context, (op1, op2) => context.Call(dlg, op1, op2)); + } + + public static void Umin_V(ArmEmitterContext context) + { + 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 + { + Delegate dlg = new _U64_U64_U64(Math.Min); + + EmitVectorBinaryOpZx(context, (op1, op2) => context.Call(dlg, op1, op2)); + } + } + + public static void Uminp_V(ArmEmitterContext context) + { + if (Optimizations.UseSsse3) + { + EmitSsse3VectorPairwiseOp(context, X86PminuInstruction); + } + else + { + Delegate dlg = new _U64_U64_U64(Math.Min); + + EmitVectorPairwiseOpZx(context, (op1, op2) => context.Call(dlg, op1, op2)); + } + } + + public static void Uminv_V(ArmEmitterContext context) + { + Delegate dlg = new _U64_U64_U64(Math.Min); + + EmitVectorAcrossVectorOpZx(context, (op1, op2) => context.Call(dlg, op1, op2)); + } + + public static void Umlal_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + 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) + { + 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.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) + { + EmitVectorWidenTernaryOpByElemZx(context, (op1, op2, op3) => + { + return context.Subtract(op1, context.Multiply(op2, op3)); + }); + } + + public static void Umull_V(ArmEmitterContext context) + { + EmitVectorWidenRnRmBinaryOpZx(context, (op1, op2) => context.Multiply(op1, op2)); + } + + public static void Umull_Ve(ArmEmitterContext context) + { + EmitVectorWidenBinaryOpByElemZx(context, (op1, op2) => context.Multiply(op1, op2)); + } + + public static void Uqadd_S(ArmEmitterContext context) + { + EmitScalarSaturatingBinaryOpZx(context, SaturatingFlags.Add); + } + + public static void Uqadd_V(ArmEmitterContext context) + { + EmitVectorSaturatingBinaryOpZx(context, SaturatingFlags.Add); + } + + public static void Uqsub_S(ArmEmitterContext context) + { + EmitScalarSaturatingBinaryOpZx(context, SaturatingFlags.Sub); + } + + public static void Uqsub_V(ArmEmitterContext context) + { + EmitVectorSaturatingBinaryOpZx(context, SaturatingFlags.Sub); + } + + public static void Uqxtn_S(ArmEmitterContext context) + { + EmitSaturatingNarrowOp(context, SaturatingNarrowFlags.ScalarZxZx); + } + + public static void Uqxtn_V(ArmEmitterContext context) + { + EmitSaturatingNarrowOp(context, SaturatingNarrowFlags.VectorZxZx); + } + + public static void Urhadd_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + 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) + { + EmitScalarSaturatingBinaryOpZx(context, SaturatingFlags.Accumulate); + } + + public static void Usqadd_V(ArmEmitterContext context) + { + EmitVectorSaturatingBinaryOpZx(context, SaturatingFlags.Accumulate); + } + + public static void Usubl_V(ArmEmitterContext context) + { + 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.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); + } + + public static void EmitScalarRoundOpF(ArmEmitterContext context, FPRoundingMode roundMode) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + Intrinsic inst = (op.Size & 1) != 0 ? Intrinsic.X86Roundsd : Intrinsic.X86Roundss; + + Operand res = context.AddIntrinsic(inst, n, Const(X86GetRoundControl(roundMode))); + + if ((op.Size & 1) != 0) + { + res = context.VectorZeroUpper64(res); + } + else + { + res = context.VectorZeroUpper96(res); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorRoundOpF(ArmEmitterContext context, FPRoundingMode roundMode) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + Intrinsic inst = (op.Size & 1) != 0 ? Intrinsic.X86Roundpd : Intrinsic.X86Roundps; + + Operand res = context.AddIntrinsic(inst, n, Const(X86GetRoundControl(roundMode))); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + + private static Operand EmitSse2VectorIsQNaNOpF(ArmEmitterContext context, Operand opF) + { + 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)); + + return context.AddIntrinsic(Intrinsic.X86Andps, mask1, mask2); + } + 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)); + + return context.AddIntrinsic(Intrinsic.X86Andpd, mask1, mask2); + } + } + + private static void EmitSse41MaxMinNumOpF(ArmEmitterContext context, bool isMaxNum, bool scalar) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Operand nNum = context.Copy(n); + Operand mNum = context.Copy(m); + + Operand nQNaNMask = EmitSse2VectorIsQNaNOpF(context, nNum); + Operand mQNaNMask = EmitSse2VectorIsQNaNOpF(context, mNum); + + 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); + + Operand res = context.AddIntrinsic(isMaxNum ? Intrinsic.X86Maxps : Intrinsic.X86Minps, nNum, mNum); + + if (scalar) + { + res = context.VectorZeroUpper96(res); + } + else if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(d, res); + } + 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); + + Operand res = context.AddIntrinsic(isMaxNum ? Intrinsic.X86Maxpd : Intrinsic.X86Minpd, nNum, mNum); + + if (scalar) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(d, res); + } + } + + private enum AddSub + { + None, + Add, + Subtract + } + + private static void EmitSse41Mul_AddSub(ArmEmitterContext context, AddSub addSub) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Operand res = null; + + 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) + { + switch (op.Size) + { + case 0: res = context.AddIntrinsic(Intrinsic.X86Paddb, d, res); break; + case 1: res = context.AddIntrinsic(Intrinsic.X86Paddw, d, res); break; + case 2: res = context.AddIntrinsic(Intrinsic.X86Paddd, d, res); break; + case 3: res = context.AddIntrinsic(Intrinsic.X86Paddq, d, res); break; + } + } + else if (addSub == AddSub.Subtract) + { + switch (op.Size) + { + case 0: res = context.AddIntrinsic(Intrinsic.X86Psubb, d, res); break; + case 1: res = context.AddIntrinsic(Intrinsic.X86Psubw, d, res); break; + case 2: res = context.AddIntrinsic(Intrinsic.X86Psubd, d, res); break; + case 3: res = context.AddIntrinsic(Intrinsic.X86Psubq, d, res); break; + } + } + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(d, res); + } + + private static void EmitSse41Sabd( + 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 EmitSse41Uabd( + 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); + } + } +} diff --git a/ARMeilleure/Instructions/InstEmitSimdCmp.cs b/ARMeilleure/Instructions/InstEmitSimdCmp.cs new file mode 100644 index 0000000000..e70f56a0a3 --- /dev/null +++ b/ARMeilleure/Instructions/InstEmitSimdCmp.cs @@ -0,0 +1,717 @@ +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.OperandHelper; + +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 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) + { + EmitSse2CmpOpF(context, CmpCondition.Equal, scalar: true); + } + else + { + EmitCmpOpF(context, SoftFloat32.FPCompareEQ, SoftFloat64.FPCompareEQ, scalar: true); + } + } + + public static void Fcmeq_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitSse2CmpOpF(context, CmpCondition.Equal, scalar: false); + } + else + { + EmitCmpOpF(context, SoftFloat32.FPCompareEQ, SoftFloat64.FPCompareEQ, scalar: false); + } + } + + public static void Fcmge_S(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAvx) + { + EmitSse2CmpOpF(context, CmpCondition.GreaterThanOrEqual, scalar: true); + } + else + { + EmitCmpOpF(context, SoftFloat32.FPCompareGE, SoftFloat64.FPCompareGE, scalar: true); + } + } + + public static void Fcmge_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAvx) + { + EmitSse2CmpOpF(context, CmpCondition.GreaterThanOrEqual, scalar: false); + } + else + { + EmitCmpOpF(context, SoftFloat32.FPCompareGE, SoftFloat64.FPCompareGE, scalar: false); + } + } + + public static void Fcmgt_S(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAvx) + { + EmitSse2CmpOpF(context, CmpCondition.GreaterThan, scalar: true); + } + else + { + EmitCmpOpF(context, SoftFloat32.FPCompareGT, SoftFloat64.FPCompareGT, scalar: true); + } + } + + public static void Fcmgt_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAvx) + { + EmitSse2CmpOpF(context, CmpCondition.GreaterThan, scalar: false); + } + else + { + EmitCmpOpF(context, SoftFloat32.FPCompareGT, SoftFloat64.FPCompareGT, scalar: false); + } + } + + public static void Fcmle_S(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitSse2CmpOpF(context, CmpCondition.LessThanOrEqual, scalar: true); + } + else + { + EmitCmpOpF(context, SoftFloat32.FPCompareLE, SoftFloat64.FPCompareLE, scalar: true); + } + } + + public static void Fcmle_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitSse2CmpOpF(context, CmpCondition.LessThanOrEqual, scalar: false); + } + else + { + EmitCmpOpF(context, SoftFloat32.FPCompareLE, SoftFloat64.FPCompareLE, scalar: false); + } + } + + public static void Fcmlt_S(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitSse2CmpOpF(context, CmpCondition.LessThan, scalar: true); + } + else + { + EmitCmpOpF(context, SoftFloat32.FPCompareLT, SoftFloat64.FPCompareLT, scalar: true); + } + } + + public static void Fcmlt_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitSse2CmpOpF(context, CmpCondition.LessThan, scalar: false); + } + else + { + EmitCmpOpF(context, SoftFloat32.FPCompareLT, SoftFloat64.FPCompareLT, scalar: false); + } + } + + public static void Fcmp_S(ArmEmitterContext context) + { + EmitFcmpOrFcmpe(context, signalNaNs: false); + } + + public static void Fcmpe_S(ArmEmitterContext context) + { + 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 cf = context.AddIntrinsicInt(Intrinsic.X86Comissge, n, m); + Operand zf = context.AddIntrinsicInt(Intrinsic.X86Comisseq, n, m); + Operand nf = context.AddIntrinsicInt(Intrinsic.X86Comisslt, n, m); + + 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 cf = context.AddIntrinsicInt(Intrinsic.X86Comisdge, n, m); + Operand zf = context.AddIntrinsicInt(Intrinsic.X86Comisdeq, n, m); + Operand nf = context.AddIntrinsicInt(Intrinsic.X86Comisdlt, n, m); + + 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); + } + + Delegate dlg = op.Size != 0 + ? (Delegate)new _S32_F64_F64_Bool(SoftFloat64.FPCompare) + : (Delegate)new _S32_F32_F32_Bool(SoftFloat32.FPCompare); + + Operand nzcv = context.Call(dlg, 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, + _F32_F32_F32 f32, + _F64_F64_F64 f64, + bool scalar) + { + 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); + } + + Operand e = EmitSoftFloatCall(context, f32, f64, ne, me); + + res = context.VectorInsert(res, e, index); + } + + context.Copy(GetVec(op.Rd), res); + } + + private static void EmitSse2CmpOpF(ArmEmitterContext context, CmpCondition cond, bool scalar) + { + 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) + { + 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) */ + { + 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/ARMeilleure/Instructions/InstEmitSimdCrypto.cs b/ARMeilleure/Instructions/InstEmitSimdCrypto.cs new file mode 100644 index 0000000000..2b61fadac1 --- /dev/null +++ b/ARMeilleure/Instructions/InstEmitSimdCrypto.cs @@ -0,0 +1,49 @@ +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); + + context.Copy(d, context.Call(new _V128_V128_V128(SoftFallback.Decrypt), d, n)); + } + + public static void Aese_V(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + context.Copy(d, context.Call(new _V128_V128_V128(SoftFallback.Encrypt), d, n)); + } + + public static void Aesimc_V(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + context.Copy(GetVec(op.Rd), context.Call(new _V128_V128(SoftFallback.InverseMixColumns), n)); + } + + public static void Aesmc_V(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + context.Copy(GetVec(op.Rd), context.Call(new _V128_V128(SoftFallback.MixColumns), n)); + } + } +} diff --git a/ARMeilleure/Instructions/InstEmitSimdCvt.cs b/ARMeilleure/Instructions/InstEmitSimdCvt.cs new file mode 100644 index 0000000000..30c1bd2089 --- /dev/null +++ b/ARMeilleure/Instructions/InstEmitSimdCvt.cs @@ -0,0 +1,1483 @@ +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.OperandHelper; + +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. + { + Operand ne = context.VectorExtract(OperandType.FP32, GetVec(op.Rn), 0); + + Delegate dlg = new _U16_F32(SoftFloat32_16.FPConvert); + + Operand res = context.Call(dlg, ne); + + 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. + { + Operand ne = EmitVectorExtractZx(context, op.Rn, 0, 1); + + Delegate dlg = new _F32_U16(SoftFloat16_32.FPConvert); + + Operand res = context.Call(dlg, ne); + + context.Copy(GetVec(op.Rd), context.VectorInsert(context.VectorZero(), res, 0)); + } + else if (op.Size == 1 && op.Opc == 3) // Double -> Half. + { + throw new NotImplementedException("Double-precision to half-precision."); + } + else if (op.Size == 3 && op.Opc == 1) // Double -> Half. + { + throw new NotImplementedException("Half-precision to double-precision."); + } + else // Invalid encoding. + { + Debug.Assert(false, $"type == {op.Size} && opc == {op.Opc}"); + } + } + + public static void Fcvtas_Gp(ArmEmitterContext context) + { + EmitFcvt_s_Gp(context, (op1) => EmitRoundMathCall(context, MidpointRounding.AwayFromZero, op1)); + } + + public static void Fcvtau_Gp(ArmEmitterContext context) + { + EmitFcvt_u_Gp(context, (op1) => EmitRoundMathCall(context, MidpointRounding.AwayFromZero, op1)); + } + + public static void Fcvtl_V(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + int sizeF = op.Size & 1; + + if (Optimizations.UseSse2 && sizeF == 1) + { + Operand n = GetVec(op.Rn); + Operand res; + + if (op.RegisterSize == RegisterSize.Simd128) + { + res = context.AddIntrinsic(Intrinsic.X86Movhlps, n, n); + } + else + { + res = n; + } + + res = context.AddIntrinsic(Intrinsic.X86Cvtps2pd, 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); + + Delegate dlg = new _F32_U16(SoftFloat16_32.FPConvert); + + Operand e = context.Call(dlg, ne); + + 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.UseSse41) + { + EmitSse41Fcvts_Gp(context, FPRoundingMode.TowardsMinusInfinity, isFixed: false); + } + else + { + EmitFcvt_s_Gp(context, (op1) => EmitUnaryMathCall(context, MathF.Floor, Math.Floor, op1)); + } + } + + public static void Fcvtmu_Gp(ArmEmitterContext context) + { + if (Optimizations.UseSse41) + { + EmitSse41Fcvtu_Gp(context, FPRoundingMode.TowardsMinusInfinity, isFixed: false); + } + else + { + EmitFcvt_u_Gp(context, (op1) => EmitUnaryMathCall(context, MathF.Floor, Math.Floor, op1)); + } + } + + public static void Fcvtn_V(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + int sizeF = op.Size & 1; + + if (Optimizations.UseSse2 && sizeF == 1) + { + Operand d = GetVec(op.Rd); + + Operand res = context.VectorZeroUpper64(d); + + Operand nInt = context.AddIntrinsic(Intrinsic.X86Cvtpd2ps, GetVec(op.Rn)); + + nInt = context.AddIntrinsic(Intrinsic.X86Movlhps, nInt, nInt); + + Intrinsic movInst = op.RegisterSize == RegisterSize.Simd128 + ? Intrinsic.X86Movlhps + : Intrinsic.X86Movhlps; + + 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), 0); + + if (sizeF == 0) + { + Delegate dlg = new _U16_F32(SoftFloat32_16.FPConvert); + + Operand e = context.Call(dlg, ne); + + e = context.ZeroExtend16(OperandType.I64, e); + + 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_S(ArmEmitterContext context) + { + if (Optimizations.UseSse41) + { + EmitSse41Fcvts(context, FPRoundingMode.ToNearest, scalar: true); + } + else + { + EmitFcvtn(context, signed: true, scalar: true); + } + } + + public static void Fcvtns_V(ArmEmitterContext context) + { + if (Optimizations.UseSse41) + { + EmitSse41Fcvts(context, FPRoundingMode.ToNearest, scalar: false); + } + else + { + EmitFcvtn(context, signed: true, scalar: false); + } + } + + public static void Fcvtnu_S(ArmEmitterContext context) + { + if (Optimizations.UseSse41) + { + EmitSse41Fcvtu(context, FPRoundingMode.ToNearest, scalar: true); + } + else + { + EmitFcvtn(context, signed: false, scalar: true); + } + } + + public static void Fcvtnu_V(ArmEmitterContext context) + { + if (Optimizations.UseSse41) + { + EmitSse41Fcvtu(context, FPRoundingMode.ToNearest, scalar: false); + } + else + { + EmitFcvtn(context, signed: false, scalar: false); + } + } + + public static void Fcvtps_Gp(ArmEmitterContext context) + { + if (Optimizations.UseSse41) + { + EmitSse41Fcvts_Gp(context, FPRoundingMode.TowardsPlusInfinity, isFixed: false); + } + else + { + EmitFcvt_s_Gp(context, (op1) => EmitUnaryMathCall(context, MathF.Ceiling, Math.Ceiling, op1)); + } + } + + public static void Fcvtpu_Gp(ArmEmitterContext context) + { + if (Optimizations.UseSse41) + { + EmitSse41Fcvtu_Gp(context, FPRoundingMode.TowardsPlusInfinity, isFixed: false); + } + else + { + EmitFcvt_u_Gp(context, (op1) => EmitUnaryMathCall(context, MathF.Ceiling, Math.Ceiling, op1)); + } + } + + public static void Fcvtzs_Gp(ArmEmitterContext context) + { + 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.UseSse41) + { + EmitSse41Fcvts_Gp(context, FPRoundingMode.TowardsZero, isFixed: true); + } + else + { + EmitFcvtzs_Gp_Fixed(context); + } + } + + public static void Fcvtzs_S(ArmEmitterContext context) + { + if (Optimizations.UseSse41) + { + EmitSse41Fcvts(context, FPRoundingMode.TowardsZero, scalar: true); + } + else + { + EmitFcvtz(context, signed: true, scalar: true); + } + } + + public static void Fcvtzs_V(ArmEmitterContext context) + { + if (Optimizations.UseSse41) + { + EmitSse41Fcvts(context, FPRoundingMode.TowardsZero, scalar: false); + } + else + { + EmitFcvtz(context, signed: true, scalar: false); + } + } + + public static void Fcvtzs_V_Fixed(ArmEmitterContext context) + { + if (Optimizations.UseSse41) + { + EmitSse41Fcvts(context, FPRoundingMode.TowardsZero, scalar: false); + } + else + { + EmitFcvtz(context, signed: true, scalar: false); + } + } + + public static void Fcvtzu_Gp(ArmEmitterContext context) + { + 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.UseSse41) + { + EmitSse41Fcvtu_Gp(context, FPRoundingMode.TowardsZero, isFixed: true); + } + else + { + EmitFcvtzu_Gp_Fixed(context); + } + } + + public static void Fcvtzu_S(ArmEmitterContext context) + { + if (Optimizations.UseSse41) + { + EmitSse41Fcvtu(context, FPRoundingMode.TowardsZero, scalar: true); + } + else + { + EmitFcvtz(context, signed: false, scalar: true); + } + } + + public static void Fcvtzu_V(ArmEmitterContext context) + { + if (Optimizations.UseSse41) + { + EmitSse41Fcvtu(context, FPRoundingMode.TowardsZero, scalar: false); + } + else + { + EmitFcvtz(context, signed: false, scalar: false); + } + } + + public static void Fcvtzu_V_Fixed(ArmEmitterContext context) + { + if (Optimizations.UseSse41) + { + EmitSse41Fcvtu(context, FPRoundingMode.TowardsZero, scalar: false); + } + else + { + EmitFcvtz(context, signed: false, scalar: false); + } + } + + public static void Scvtf_Gp(ArmEmitterContext context) + { + 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; + + 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.UseSse2) + { + EmitSse2Scvtf(context, scalar: true); + } + else + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + int sizeF = op.Size & 1; + + Operand res = EmitVectorLongExtract(context, op.Rn, 0, sizeF + 2); + + res = EmitFPConvert(context, res, op.Size, signed: true); + + context.Copy(GetVec(op.Rd), context.VectorInsert(context.VectorZero(), res, 0)); + } + } + + public static void Scvtf_V(ArmEmitterContext context) + { + if (Optimizations.UseSse2) + { + EmitSse2Scvtf(context, scalar: false); + } + else + { + EmitVectorCvtf(context, signed: true); + } + } + + public static void Scvtf_V_Fixed(ArmEmitterContext context) + { + if (Optimizations.UseSse2) + { + EmitSse2Scvtf(context, scalar: false); + } + else + { + EmitVectorCvtf(context, signed: true); + } + } + + public static void Ucvtf_Gp(ArmEmitterContext context) + { + 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; + + 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.UseSse2) + { + EmitSse2Ucvtf(context, scalar: true); + } + else + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + int sizeF = op.Size & 1; + + Operand ne = EmitVectorLongExtract(context, op.Rn, 0, sizeF + 2); + + Operand res = EmitFPConvert(context, ne, sizeF, signed: false); + + context.Copy(GetVec(op.Rd), context.VectorInsert(context.VectorZero(), res, 0)); + } + } + + public static void Ucvtf_V(ArmEmitterContext context) + { + if (Optimizations.UseSse2) + { + EmitSse2Ucvtf(context, scalar: false); + } + else + { + EmitVectorCvtf(context, signed: false); + } + } + + public static void Ucvtf_V_Fixed(ArmEmitterContext context) + { + if (Optimizations.UseSse2) + { + EmitSse2Ucvtf(context, scalar: false); + } + else + { + EmitVectorCvtf(context, signed: false); + } + } + + private static void EmitFcvtn(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 elems = !scalar ? op.GetBytesCount() >> sizeI : 1; + + for (int index = 0; index < elems; index++) + { + Operand ne = context.VectorExtract(type, n, index); + + Operand e = EmitRoundMathCall(context, MidpointRounding.ToEven, ne); + + if (sizeF == 0) + { + Delegate dlg = signed + ? (Delegate)new _S32_F32(SoftFallback.SatF32ToS32) + : (Delegate)new _U32_F32(SoftFallback.SatF32ToU32); + + e = context.Call(dlg, e); + + e = context.ZeroExtend32(OperandType.I64, e); + } + else /* if (sizeF == 1) */ + { + Delegate dlg = signed + ? (Delegate)new _S64_F64(SoftFallback.SatF64ToS64) + : (Delegate)new _U64_F64(SoftFallback.SatF64ToU64); + + e = context.Call(dlg, 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) + { + Delegate dlg = signed + ? (Delegate)new _S32_F32(SoftFallback.SatF32ToS32) + : (Delegate)new _U32_F32(SoftFallback.SatF32ToU32); + + e = context.Call(dlg, e); + + e = context.ZeroExtend32(OperandType.I64, e); + } + else /* if (sizeF == 1) */ + { + Delegate dlg = signed + ? (Delegate)new _S64_F64(SoftFallback.SatF64ToS64) + : (Delegate)new _U64_F64(SoftFallback.SatF64ToU64); + + e = context.Call(dlg, 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 EmitVectorCvtf(ArmEmitterContext context, bool signed) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand res = context.VectorZero(); + + int sizeF = op.Size & 1; + int sizeI = sizeF + 2; + + int fBits = GetFBits(context); + + int elems = op.GetBytesCount() >> sizeI; + + 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); + + if (context.CurrOp.RegisterSize == RegisterSize.Int32) + { + Delegate dlg = value.Type == OperandType.FP32 + ? (Delegate)new _S32_F32(SoftFallback.SatF32ToS32) + : (Delegate)new _S32_F64(SoftFallback.SatF64ToS32); + + return context.Call(dlg, value); + } + else + { + Delegate dlg = value.Type == OperandType.FP32 + ? (Delegate)new _S64_F32(SoftFallback.SatF32ToS64) + : (Delegate)new _S64_F64(SoftFallback.SatF64ToS64); + + return context.Call(dlg, 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); + + if (context.CurrOp.RegisterSize == RegisterSize.Int32) + { + Delegate dlg = value.Type == OperandType.FP32 + ? (Delegate)new _U32_F32(SoftFallback.SatF32ToU32) + : (Delegate)new _U32_F64(SoftFallback.SatF64ToU32); + + return context.Call(dlg, value); + } + else + { + Delegate dlg = value.Type == OperandType.FP32 + ? (Delegate)new _U64_F32(SoftFallback.SatF32ToU64) + : (Delegate)new _U64_F64(SoftFallback.SatF64ToU64); + + return context.Call(dlg, 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))); + } + } + + private 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 EmitSse2Scvtf(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 EmitSse2Ucvtf(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 EmitSse41Fcvts(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); + } + + nRes = context.AddIntrinsic(Intrinsic.X86Roundps, nRes, Const(X86GetRoundControl(roundMode))); + + 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); + } + + nRes = context.AddIntrinsic(Intrinsic.X86Roundpd, nRes, Const(X86GetRoundControl(roundMode))); + + 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 EmitSse41Fcvtu(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); + } + + nRes = context.AddIntrinsic(Intrinsic.X86Roundps, nRes, Const(X86GetRoundControl(roundMode))); + + 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); + } + + nRes = context.AddIntrinsic(Intrinsic.X86Roundpd, nRes, Const(X86GetRoundControl(roundMode))); + + 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); + } + + nRes = context.AddIntrinsic(Intrinsic.X86Roundss, nRes, Const(X86GetRoundControl(roundMode))); + + 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); + } + + nRes = context.AddIntrinsic(Intrinsic.X86Roundsd, nRes, Const(X86GetRoundControl(roundMode))); + + 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); + } + + nRes = context.AddIntrinsic(Intrinsic.X86Roundss, nRes, Const(X86GetRoundControl(roundMode))); + + 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); + } + + nRes = context.AddIntrinsic(Intrinsic.X86Roundsd, nRes, Const(X86GetRoundControl(roundMode))); + + 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/ARMeilleure/Instructions/InstEmitSimdHash.cs b/ARMeilleure/Instructions/InstEmitSimdHash.cs new file mode 100644 index 0000000000..4ed960612f --- /dev/null +++ b/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(new _V128_V128_U32_V128(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(new _U32_U32(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(new _V128_V128_U32_V128(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(new _V128_V128_U32_V128(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(new _V128_V128_V128_V128(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(new _V128_V128_V128(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 = context.Call(new _V128_V128_V128_V128(SoftFallback.HashLower), d, n, m); + + 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 = context.Call(new _V128_V128_V128_V128(SoftFallback.HashUpper), d, n, m); + + 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 = context.Call(new _V128_V128_V128(SoftFallback.Sha256SchedulePart1), 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 = context.Call(new _V128_V128_V128_V128(SoftFallback.Sha256SchedulePart2), d, n, m); + + context.Copy(GetVec(op.Rd), res); + } +#endregion + } +} diff --git a/ARMeilleure/Instructions/InstEmitSimdHelper.cs b/ARMeilleure/Instructions/InstEmitSimdHelper.cs new file mode 100644 index 0000000000..fce1bed5cb --- /dev/null +++ b/ARMeilleure/Instructions/InstEmitSimdHelper.cs @@ -0,0 +1,1560 @@ +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.OperandHelper; + +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 + }; + + private static readonly long _zeroMask = 128L << 56 | 128L << 48 | 128L << 40 | 128L << 32 | 128L << 24 | 128L << 16 | 128L << 8 | 128L << 0; +#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 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, 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) + { + 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 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, _F32_F32 f32, _F64_F64 f64, Operand n) + { + IOpCodeSimd op = (IOpCodeSimd)context.CurrOp; + + return (op.Size & 1) == 0 ? context.Call(f32, n) : context.Call(f64, n); + } + + public static Operand EmitRoundMathCall(ArmEmitterContext context, MidpointRounding roundMode, Operand n) + { + IOpCodeSimd op = (IOpCodeSimd)context.CurrOp; + + Delegate dlg; + + if ((op.Size & 1) == 0) + { + dlg = new _F32_F32_MidpointRounding(MathF.Round); + } + else /* if ((op.Size & 1) == 1) */ + { + dlg = new _F64_F64_MidpointRounding(Math.Round); + } + + return context.Call(dlg, n, Const((int)roundMode)); + } + + public static Operand EmitSoftFloatCall( + ArmEmitterContext context, + _F32_F32 f32, + _F64_F64 f64, + params Operand[] callArgs) + { + IOpCodeSimd op = (IOpCodeSimd)context.CurrOp; + + Delegate dlg = (op.Size & 1) == 0 ? (Delegate)f32 : (Delegate)f64; + + return context.Call(dlg, callArgs); + } + + public static Operand EmitSoftFloatCall( + ArmEmitterContext context, + _F32_F32_F32 f32, + _F64_F64_F64 f64, + params Operand[] callArgs) + { + IOpCodeSimd op = (IOpCodeSimd)context.CurrOp; + + Delegate dlg = (op.Size & 1) == 0 ? (Delegate)f32 : (Delegate)f64; + + return context.Call(dlg, callArgs); + } + + public static Operand EmitSoftFloatCall( + ArmEmitterContext context, + _F32_F32_F32_F32 f32, + _F64_F64_F64_F64 f64, + params Operand[] callArgs) + { + IOpCodeSimd op = (IOpCodeSimd)context.CurrOp; + + Delegate dlg = (op.Size & 1) == 0 ? (Delegate)f32 : (Delegate)f64; + + return context.Call(dlg, callArgs); + } + + 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 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, Intrinsic inst32, Intrinsic inst64) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + int sizeF = op.Size & 1; + + if (sizeF == 0) + { + if (op.RegisterSize == RegisterSize.Simd64) + { + Operand unpck = context.AddIntrinsic(Intrinsic.X86Unpcklps, n, m); + + 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), context.AddIntrinsic(inst32, 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, n, m, Const(sm0)); + Operand part1 = context.AddIntrinsic(Intrinsic.X86Shufps, n, m, Const(sm1)); + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst32, part0, part1)); + } + } + else /* if (sizeF == 1) */ + { + Operand part0 = context.AddIntrinsic(Intrinsic.X86Unpcklpd, n, m); + Operand part1 = context.AddIntrinsic(Intrinsic.X86Unpckhpd, n, m); + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst64, 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 + { + Scalar = 1 << 0, + Signed = 1 << 1, + + Add = 1 << 2, + Sub = 1 << 3, + + Accumulate = 1 << 4, + + ScalarSx = Scalar | Signed, + ScalarZx = Scalar, + + VectorSx = Signed, + VectorZx = 0 + } + + public static void EmitScalarSaturatingUnaryOpSx(ArmEmitterContext context, Func1I emit) + { + EmitSaturatingUnaryOpSx(context, emit, SaturatingFlags.ScalarSx); + } + + public static void EmitVectorSaturatingUnaryOpSx(ArmEmitterContext context, Func1I emit) + { + EmitSaturatingUnaryOpSx(context, emit, SaturatingFlags.VectorSx); + } + + private 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 = EmitSatQ(context, emit(ne), op.Size, signedSrc: true, 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, SaturatingFlags flags) + { + EmitSaturatingBinaryOp(context, null, SaturatingFlags.ScalarSx | flags); + } + + public static void EmitScalarSaturatingBinaryOpZx(ArmEmitterContext context, SaturatingFlags flags) + { + EmitSaturatingBinaryOp(context, null, SaturatingFlags.ScalarZx | flags); + } + + public static void EmitVectorSaturatingBinaryOpSx(ArmEmitterContext context, SaturatingFlags flags) + { + EmitSaturatingBinaryOp(context, null, SaturatingFlags.VectorSx | flags); + } + + public static void EmitVectorSaturatingBinaryOpZx(ArmEmitterContext context, SaturatingFlags flags) + { + EmitSaturatingBinaryOp(context, null, SaturatingFlags.VectorZx | flags); + } + + public static void EmitSaturatingBinaryOp(ArmEmitterContext context, Func2I emit, SaturatingFlags flags) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand res = context.VectorZero(); + + 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) + { + OpCodeSimdReg opReg = (OpCodeSimdReg)op; + + for (int index = 0; index < elems; index++) + { + Operand de; + Operand ne = EmitVectorExtract(context, opReg.Rn, index, op.Size, signed); + Operand me = EmitVectorExtract(context, opReg.Rm, index, op.Size, signed); + + if (op.Size <= 2) + { + Operand temp = add ? context.Add(ne, me) : context.Subtract(ne, me); + + de = EmitSatQ(context, temp, op.Size, signedSrc: true, signedDst: signed); + } + else if (add) /* if (op.Size == 3) */ + { + de = EmitBinarySatQAdd(context, ne, me, signed); + } + else /* if (sub) */ + { + de = EmitBinarySatQSub(context, ne, me, signed); + } + + 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 = EmitSatQ(context, temp, op.Size, signedSrc: true, signedDst: signed); + } + else /* if (op.Size == 3) */ + { + de = EmitBinarySatQAccumulate(context, ne, me, signed); + } + + res = EmitVectorInsert(context, res, de, index, op.Size); + } + } + else + { + OpCodeSimdReg opReg = (OpCodeSimdReg)op; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtract(context, opReg.Rn, index, op.Size, signed); + Operand me = EmitVectorExtract(context, opReg.Rm, index, op.Size, signed); + + Operand de = EmitSatQ(context, emit(ne, me), op.Size, true, 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 = EmitSatQ(context, ne, op.Size, signedSrc, signedDst); + + res = EmitVectorInsert(context, res, temp, part + index, op.Size); + } + + context.Copy(d, res); + } + + // TSrc (16bit, 32bit, 64bit; signed, unsigned) > TDst (8bit, 16bit, 32bit; signed, unsigned). + public static Operand EmitSatQ(ArmEmitterContext context, Operand op, int sizeDst, bool signedSrc, bool signedDst) + { + if ((uint)sizeDst > 2u) + { + throw new ArgumentOutOfRangeException(nameof(sizeDst)); + } + + Delegate dlg; + + if (signedSrc) + { + dlg = signedDst + ? (Delegate)new _S64_S64_S32(SoftFallback.SignedSrcSignedDstSatQ) + : (Delegate)new _U64_S64_S32(SoftFallback.SignedSrcUnsignedDstSatQ); + } + else + { + dlg = signedDst + ? (Delegate)new _S64_U64_S32(SoftFallback.UnsignedSrcSignedDstSatQ) + : (Delegate)new _U64_U64_S32(SoftFallback.UnsignedSrcUnsignedDstSatQ); + } + + return context.Call(dlg, op, Const(sizeDst)); + } + + // TSrc (64bit) == TDst (64bit); signed. + public static Operand EmitUnarySignedSatQAbsOrNeg(ArmEmitterContext context, Operand op) + { + Debug.Assert(((OpCodeSimd)context.CurrOp).Size == 3, "Invalid element size."); + + return context.Call(new _S64_S64(SoftFallback.UnarySignedSatQAbsOrNeg), op); + } + + // TSrcs (64bit) == TDst (64bit); signed, unsigned. + public static Operand EmitBinarySatQAdd(ArmEmitterContext context, Operand op1, Operand op2, bool signed) + { + Debug.Assert(((OpCodeSimd)context.CurrOp).Size == 3, "Invalid element size."); + + Delegate dlg = signed + ? (Delegate)new _S64_S64_S64(SoftFallback.BinarySignedSatQAdd) + : (Delegate)new _U64_U64_U64(SoftFallback.BinaryUnsignedSatQAdd); + + return context.Call(dlg, op1, op2); + } + + // TSrcs (64bit) == TDst (64bit); signed, unsigned. + public static Operand EmitBinarySatQSub(ArmEmitterContext context, Operand op1, Operand op2, bool signed) + { + Debug.Assert(((OpCodeSimd)context.CurrOp).Size == 3, "Invalid element size."); + + Delegate dlg = signed + ? (Delegate)new _S64_S64_S64(SoftFallback.BinarySignedSatQSub) + : (Delegate)new _U64_U64_U64(SoftFallback.BinaryUnsignedSatQSub); + + return context.Call(dlg, op1, op2); + } + + // TSrcs (64bit) == TDst (64bit); signed, unsigned. + public static Operand EmitBinarySatQAccumulate(ArmEmitterContext context, Operand op1, Operand op2, bool signed) + { + Debug.Assert(((OpCodeSimd)context.CurrOp).Size == 3, "Invalid element size."); + + Delegate dlg = signed + ? (Delegate)new _S64_U64_S64(SoftFallback.BinarySignedSatQAcc) + : (Delegate)new _U64_S64_U64(SoftFallback.BinaryUnsignedSatQAcc); + + return context.Call(dlg, op1, op2); + } + + 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 = null; + + 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 = 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; + } + + private 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/ARMeilleure/Instructions/InstEmitSimdLogical.cs b/ARMeilleure/Instructions/InstEmitSimdLogical.cs new file mode 100644 index 0000000000..362296f7ff --- /dev/null +++ b/ARMeilleure/Instructions/InstEmitSimdLogical.cs @@ -0,0 +1,467 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System.Diagnostics; + +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.Instructions.InstEmitSimdHelper; +using static ARMeilleure.IntermediateRepresentation.OperandHelper; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit + { + public static void And_V(ArmEmitterContext context) + { + 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.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) + { + EmitVectorImmBinaryOp(context, (op1, op2) => + { + return context.BitwiseAnd(op1, context.BitwiseNot(op2)); + }); + } + + public static void Bif_V(ArmEmitterContext context) + { + EmitBifBit(context, notRm: true); + } + + public static void Bit_V(ArmEmitterContext context) + { + 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.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.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.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.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.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) + { + EmitVectorImmBinaryOp(context, (op1, op2) => context.BitwiseOr(op1, op2)); + } + + public static void Rbit_V(ArmEmitterContext context) + { + 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 = 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/ARMeilleure/Instructions/InstEmitSimdMemory.cs b/ARMeilleure/Instructions/InstEmitSimdMemory.cs new file mode 100644 index 0000000000..22e9ef7a80 --- /dev/null +++ b/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.OperandHelper; + +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/ARMeilleure/Instructions/InstEmitSimdMove.cs b/ARMeilleure/Instructions/InstEmitSimdMove.cs new file mode 100644 index 0000000000..a1a4635f97 --- /dev/null +++ b/ARMeilleure/Instructions/InstEmitSimdMove.cs @@ -0,0 +1,839 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System; +using System.Collections.Generic; + +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.Instructions.InstEmitSimdHelper; +using static ARMeilleure.IntermediateRepresentation.OperandHelper; + +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 (op.Size == 0) + { + context.Copy(GetVec(op.Rd), X86GetScalar(context, (int)op.Immediate)); + } + else + { + context.Copy(GetVec(op.Rd), X86GetScalar(context, op.Immediate)); + } + } + + 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) + { + EmitSse2MoviMvni(context, not: false); + } + else + { + EmitVectorImmUnaryOp(context, (op1) => op1); + } + } + + public static void Mvni_V(ArmEmitterContext context) + { + if (Optimizations.UseSse2) + { + EmitSse2MoviMvni(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 EmitSse2MoviMvni(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)); + } + + Delegate dlg = null; + + switch (op.Size) + { + case 1: dlg = isTbl + ? (Delegate)new _V128_V128_S32_V128 (SoftFallback.Tbl1) + : (Delegate)new _V128_V128_V128_S32_V128(SoftFallback.Tbx1); + break; + + case 2: dlg = isTbl + ? (Delegate)new _V128_V128_S32_V128_V128 (SoftFallback.Tbl2) + : (Delegate)new _V128_V128_V128_S32_V128_V128(SoftFallback.Tbx2); + break; + + case 3: dlg = isTbl + ? (Delegate)new _V128_V128_S32_V128_V128_V128 (SoftFallback.Tbl3) + : (Delegate)new _V128_V128_V128_S32_V128_V128_V128(SoftFallback.Tbx3); + break; + + case 4: dlg = isTbl + ? (Delegate)new _V128_V128_S32_V128_V128_V128_V128 (SoftFallback.Tbl4) + : (Delegate)new _V128_V128_V128_S32_V128_V128_V128_V128(SoftFallback.Tbx4); + break; + } + + context.Copy(d, context.Call(dlg, args.ToArray())); + } + } + + private static void EmitVectorTranspose(ArmEmitterContext context, int part) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + if (Optimizations.UseSsse3) + { + Operand mask = null; + + 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 = null; + + 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/ARMeilleure/Instructions/InstEmitSimdShift.cs b/ARMeilleure/Instructions/InstEmitSimdShift.cs new file mode 100644 index 0000000000..19c0a74d28 --- /dev/null +++ b/ARMeilleure/Instructions/InstEmitSimdShift.cs @@ -0,0 +1,1177 @@ +// 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 static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.Instructions.InstEmitSimdHelper; +using static ARMeilleure.IntermediateRepresentation.OperandHelper; + +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.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 = null; + + 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); + + EmitScalarUnaryOpZx(context, (op1) => context.ShiftLeft(op1, Const(shift))); + } + + public static void Shl_V(ArmEmitterContext context) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShl(op); + + 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.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.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) + { + EmitSli(context, scalar: true); + } + + public static void Sli_V(ArmEmitterContext context) + { + EmitSli(context, scalar: false); + } + + public static void Sqrshl_V(ArmEmitterContext context) + { + 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); + + Operand e = context.Call(new _S64_S64_S64_Bool_S32(SoftFallback.SignedShlRegSatQ), ne, me, Const(1), Const(op.Size)); + + res = EmitVectorInsert(context, res, e, index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void Sqrshrn_S(ArmEmitterContext context) + { + EmitRoundShrImmSaturatingNarrowOp(context, ShrImmSaturatingNarrowFlags.ScalarSxSx); + } + + public static void Sqrshrn_V(ArmEmitterContext context) + { + EmitRoundShrImmSaturatingNarrowOp(context, ShrImmSaturatingNarrowFlags.VectorSxSx); + } + + public static void Sqrshrun_S(ArmEmitterContext context) + { + EmitRoundShrImmSaturatingNarrowOp(context, ShrImmSaturatingNarrowFlags.ScalarSxZx); + } + + public static void Sqrshrun_V(ArmEmitterContext context) + { + EmitRoundShrImmSaturatingNarrowOp(context, ShrImmSaturatingNarrowFlags.VectorSxZx); + } + + public static void Sqshl_V(ArmEmitterContext context) + { + 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); + + Operand e = context.Call(new _S64_S64_S64_Bool_S32(SoftFallback.SignedShlRegSatQ), ne, me, Const(0), Const(op.Size)); + + res = EmitVectorInsert(context, res, e, index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void Sqshrn_S(ArmEmitterContext context) + { + EmitShrImmSaturatingNarrowOp(context, ShrImmSaturatingNarrowFlags.ScalarSxSx); + } + + public static void Sqshrn_V(ArmEmitterContext context) + { + EmitShrImmSaturatingNarrowOp(context, ShrImmSaturatingNarrowFlags.VectorSxSx); + } + + public static void Sqshrun_S(ArmEmitterContext context) + { + EmitShrImmSaturatingNarrowOp(context, ShrImmSaturatingNarrowFlags.ScalarSxZx); + } + + public static void Sqshrun_V(ArmEmitterContext context) + { + EmitShrImmSaturatingNarrowOp(context, ShrImmSaturatingNarrowFlags.VectorSxZx); + } + + public static void Sri_S(ArmEmitterContext context) + { + EmitSri(context, scalar: true); + } + + public static void Sri_V(ArmEmitterContext context) + { + EmitSri(context, scalar: false); + } + + public static void Srshl_V(ArmEmitterContext context) + { + 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); + + Operand e = context.Call(new _S64_S64_S64_Bool_S32(SoftFallback.SignedShlReg), ne, me, Const(1), Const(op.Size)); + + res = EmitVectorInsert(context, res, e, index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void Srshr_S(ArmEmitterContext context) + { + EmitScalarShrImmOpSx(context, ShrImmFlags.Round); + } + + public static void Srshr_V(ArmEmitterContext context) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + 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) + { + EmitScalarShrImmOpSx(context, ShrImmFlags.Round | ShrImmFlags.Accumulate); + } + + public static void Srsra_V(ArmEmitterContext context) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + 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_V(ArmEmitterContext context) + { + 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); + + Operand e = context.Call(new _S64_S64_S64_Bool_S32(SoftFallback.SignedShlReg), ne, me, Const(0), Const(op.Size)); + + res = EmitVectorInsert(context, res, e, index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void Sshll_V(ArmEmitterContext context) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShl(op); + + 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) + { + EmitShrImmOp(context, ShrImmFlags.ScalarSx); + } + + public static void Sshr_V(ArmEmitterContext context) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + if (Optimizations.UseSse2 && op.Size > 0 && op.Size < 3) + { + int shift = GetImmShr(op); + + 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) + { + EmitScalarShrImmOpSx(context, ShrImmFlags.Accumulate); + } + + public static void Ssra_V(ArmEmitterContext context) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + 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) + { + 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); + + Operand e = context.Call(new _U64_U64_U64_Bool_S32(SoftFallback.UnsignedShlRegSatQ), ne, me, Const(1), Const(op.Size)); + + res = EmitVectorInsert(context, res, e, index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void Uqrshrn_S(ArmEmitterContext context) + { + EmitRoundShrImmSaturatingNarrowOp(context, ShrImmSaturatingNarrowFlags.ScalarZxZx); + } + + public static void Uqrshrn_V(ArmEmitterContext context) + { + EmitRoundShrImmSaturatingNarrowOp(context, ShrImmSaturatingNarrowFlags.VectorZxZx); + } + + public static void Uqshl_V(ArmEmitterContext context) + { + 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); + + Operand e = context.Call(new _U64_U64_U64_Bool_S32(SoftFallback.UnsignedShlRegSatQ), ne, me, Const(0), Const(op.Size)); + + res = EmitVectorInsert(context, res, e, index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void Uqshrn_S(ArmEmitterContext context) + { + EmitShrImmSaturatingNarrowOp(context, ShrImmSaturatingNarrowFlags.ScalarZxZx); + } + + public static void Uqshrn_V(ArmEmitterContext context) + { + EmitShrImmSaturatingNarrowOp(context, ShrImmSaturatingNarrowFlags.VectorZxZx); + } + + public static void Urshl_V(ArmEmitterContext context) + { + 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); + + Operand e = context.Call(new _U64_U64_U64_Bool_S32(SoftFallback.UnsignedShlReg), ne, me, Const(1), Const(op.Size)); + + res = EmitVectorInsert(context, res, e, index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void Urshr_S(ArmEmitterContext context) + { + EmitScalarShrImmOpZx(context, ShrImmFlags.Round); + } + + public static void Urshr_V(ArmEmitterContext context) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + 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) + { + EmitScalarShrImmOpZx(context, ShrImmFlags.Round | ShrImmFlags.Accumulate); + } + + public static void Ursra_V(ArmEmitterContext context) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + 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_V(ArmEmitterContext context) + { + 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 = EmitVectorExtractSx(context, op.Rm, index << op.Size, 0); + + Operand e = EmitUnsignedShlRegOp(context, ne, context.ConvertI64ToI32(me), op.Size); + + res = EmitVectorInsert(context, res, e, index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void Ushll_V(ArmEmitterContext context) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShl(op); + + 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) + { + EmitShrImmOp(context, ShrImmFlags.ScalarZx); + } + + public static void Ushr_V(ArmEmitterContext context) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + 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) + { + EmitScalarShrImmOpZx(context, ShrImmFlags.Accumulate); + } + + public static void Usra_V(ArmEmitterContext context) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + 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 Operand EmitUnsignedShlRegOp(ArmEmitterContext context, Operand op, Operand shiftLsB, int size) + { + Debug.Assert(op.Type == OperandType.I64); + Debug.Assert(shiftLsB.Type == OperandType.I32); + 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 = context.ShiftRightUI(op, negShiftLsB); + + Operand res = context.ConditionalSelect(isPositive, shl, shr); + + Operand isOutOfRange = context.BitwiseOr( + context.ICompareGreaterOrEqual(shiftLsB, Const(8 << size)), + context.ICompareGreaterOrEqual(negShiftLsB, Const(8 << size))); + + return context.ConditionalSelect(isOutOfRange, Const(0UL), 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 = EmitSatQ(context, e, op.Size, signedSrc, 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) + { + Delegate dlg = signed + ? (Delegate)new _S64_S64_S64_S32(SoftFallback.SignedShrImm64) + : (Delegate)new _U64_U64_S64_S32(SoftFallback.UnsignedShrImm64); + + return context.Call(dlg, 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); + + ulong mask = shift != 0 ? ulong.MaxValue >> (64 - shift) : 0UL; + + 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 (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); + } + } + } +} diff --git a/ARMeilleure/Instructions/InstEmitSystem.cs b/ARMeilleure/Instructions/InstEmitSystem.cs new file mode 100644 index 0000000000..49973404fa --- /dev/null +++ b/ARMeilleure/Instructions/InstEmitSystem.cs @@ -0,0 +1,156 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; +using System; + +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.IntermediateRepresentation.OperandHelper; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit + { + private const int DczSizeLog2 = 4; + + public static void Hint(ArmEmitterContext context) + { + // Execute as no-op. + } + + public static void Isb(ArmEmitterContext context) + { + // Execute as no-op. + } + + public static void Mrs(ArmEmitterContext context) + { + OpCodeSystem op = (OpCodeSystem)context.CurrOp; + + Delegate dlg; + + switch (GetPackedId(op)) + { + case 0b11_011_0000_0000_001: dlg = new _U64(NativeInterface.GetCtrEl0); break; + case 0b11_011_0000_0000_111: dlg = new _U64(NativeInterface.GetDczidEl0); break; + case 0b11_011_0100_0010_000: EmitGetNzcv(context); return; + case 0b11_011_0100_0100_000: dlg = new _U64(NativeInterface.GetFpcr); break; + case 0b11_011_0100_0100_001: dlg = new _U64(NativeInterface.GetFpsr); break; + case 0b11_011_1101_0000_010: dlg = new _U64(NativeInterface.GetTpidrEl0); break; + case 0b11_011_1101_0000_011: dlg = new _U64(NativeInterface.GetTpidr); break; + case 0b11_011_1110_0000_000: dlg = new _U64(NativeInterface.GetCntfrqEl0); break; + case 0b11_011_1110_0000_001: dlg = new _U64(NativeInterface.GetCntpctEl0); break; + + default: throw new NotImplementedException($"Unknown MRS 0x{op.RawOpCode:X8} at 0x{op.Address:X16}."); + } + + SetIntOrZR(context, op.Rt, context.Call(dlg)); + } + + public static void Msr(ArmEmitterContext context) + { + OpCodeSystem op = (OpCodeSystem)context.CurrOp; + + Delegate dlg; + + switch (GetPackedId(op)) + { + case 0b11_011_0100_0010_000: EmitSetNzcv(context); return; + case 0b11_011_0100_0100_000: dlg = new _Void_U64(NativeInterface.SetFpcr); break; + case 0b11_011_0100_0100_001: dlg = new _Void_U64(NativeInterface.SetFpsr); break; + case 0b11_011_1101_0000_010: dlg = new _Void_U64(NativeInterface.SetTpidrEl0); break; + + default: throw new NotImplementedException($"Unknown MSR 0x{op.RawOpCode:X8} at 0x{op.Address:X16}."); + } + + context.Call(dlg, GetIntOrZR(context, op.Rt)); + } + + 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 < (4 << DczSizeLog2); offset += 8) + { + Operand address = context.Add(t, Const(offset)); + + context.Call(new _Void_U64_U64(NativeInterface.WriteUInt64), address, Const(0L)); + } + + break; + } + + // No-op + case 0b11_011_0111_1110_001: //DC CIVAC + 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 vSh = context.ShiftLeft(GetFlag(PState.VFlag), Const((int)PState.VFlag)); + Operand cSh = context.ShiftLeft(GetFlag(PState.CFlag), Const((int)PState.CFlag)); + Operand zSh = context.ShiftLeft(GetFlag(PState.ZFlag), Const((int)PState.ZFlag)); + Operand nSh = context.ShiftLeft(GetFlag(PState.NFlag), Const((int)PState.NFlag)); + + Operand nzcvSh = context.BitwiseOr(context.BitwiseOr(nSh, zSh), context.BitwiseOr(cSh, vSh)); + + SetIntOrZR(context, op.Rt, nzcvSh); + } + + private static void EmitSetNzcv(ArmEmitterContext context) + { + OpCodeSystem op = (OpCodeSystem)context.CurrOp; + + Operand t = GetIntOrZR(context, op.Rt); + t = context.ConvertI64ToI32(t); + + Operand v = context.ShiftRightUI(t, Const((int)PState.VFlag)); + v = context.BitwiseAnd (v, Const(1)); + + Operand c = context.ShiftRightUI(t, Const((int)PState.CFlag)); + c = context.BitwiseAnd (c, Const(1)); + + Operand z = context.ShiftRightUI(t, Const((int)PState.ZFlag)); + z = context.BitwiseAnd (z, Const(1)); + + Operand n = context.ShiftRightUI(t, Const((int)PState.NFlag)); + n = context.BitwiseAnd (n, Const(1)); + + SetFlag(context, PState.VFlag, v); + SetFlag(context, PState.CFlag, c); + SetFlag(context, PState.ZFlag, z); + SetFlag(context, PState.NFlag, n); + } + } +} diff --git a/ARMeilleure/Instructions/InstName.cs b/ARMeilleure/Instructions/InstName.cs new file mode 100644 index 0000000000..c81484a6f4 --- /dev/null +++ b/ARMeilleure/Instructions/InstName.cs @@ -0,0 +1,463 @@ +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, + Csel, + Csinc, + Csinv, + Csneg, + Dmb, + Dsb, + Eon, + Eor, + Extr, + Hint, + Isb, + Ldar, + Ldaxp, + Ldaxr, + Ldp, + Ldr, + Ldr_Literal, + Ldrs, + Ldxr, + Ldxp, + Lslv, + Lsrv, + Madd, + Movk, + Movn, + Movz, + Mrs, + Msr, + Msub, + Nop, + Orn, + Orr, + Pfrm, + Rbit, + Ret, + Rev16, + Rev32, + Rev64, + Rorv, + Sbc, + Sbcs, + Sbfm, + Sdiv, + Smaddl, + Smsubl, + Smulh, + Stlr, + Stlxp, + Stlxr, + Stp, + Str, + Stxp, + Stxr, + Sub, + Subs, + Svc, + Sys, + Tbnz, + Tbz, + Ubfm, + Udiv, + Umaddl, + Umsubl, + Umulh, + Und, + + // 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, + 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, + Fcvtau_Gp, + Fcvtl_V, + Fcvtms_Gp, + Fcvtmu_Gp, + Fcvtn_V, + 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, + Fmaxp_V, + Fmin_S, + Fmin_V, + Fminnm_S, + Fminnm_V, + Fminp_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, + 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_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, + Sqneg_S, + Sqneg_V, + Sqrdmulh_S, + Sqrdmulh_V, + 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_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_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_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) + Blx, + Bx, + Cmp, + Ldm, + Ldrb, + Ldrd, + Ldrh, + Ldrsb, + Ldrsh, + Mov, + Stm, + Strb, + Strd, + Strh + } +} diff --git a/ARMeilleure/Instructions/NativeInterface.cs b/ARMeilleure/Instructions/NativeInterface.cs new file mode 100644 index 0000000000..3a1e91c8ed --- /dev/null +++ b/ARMeilleure/Instructions/NativeInterface.cs @@ -0,0 +1,367 @@ +using ARMeilleure.Memory; +using ARMeilleure.State; +using System; + +namespace ARMeilleure.Instructions +{ + static class NativeInterface + { + private const int ErgSizeLog2 = 4; + + private class ThreadContext + { + public ExecutionContext Context { get; } + public MemoryManager Memory { get; } + + public ulong ExclusiveAddress { get; set; } + public ulong ExclusiveValueLow { get; set; } + public ulong ExclusiveValueHigh { get; set; } + + public ThreadContext(ExecutionContext context, MemoryManager memory) + { + Context = context; + Memory = memory; + + ExclusiveAddress = ulong.MaxValue; + } + } + + [ThreadStatic] + private static ThreadContext _context; + + public static void RegisterThread(ExecutionContext context, MemoryManager memory) + { + _context = new ThreadContext(context, memory); + } + + 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 GetFpcr() + { + return (ulong)GetContext().Fpcr; + } + + public static ulong GetFpsr() + { + return (ulong)GetContext().Fpsr; + } + + public static ulong GetTpidrEl0() + { + return (ulong)GetContext().TpidrEl0; + } + + public static ulong GetTpidr() + { + return (ulong)GetContext().Tpidr; + } + + public static ulong GetCntfrqEl0() + { + return GetContext().CntfrqEl0; + } + + public static ulong GetCntpctEl0() + { + return GetContext().CntpctEl0; + } + + public static void SetFpcr(ulong value) + { + GetContext().Fpcr = (FPCR)value; + } + + public static void SetFpsr(ulong value) + { + GetContext().Fpsr = (FPSR)value; + } + + public static void SetTpidrEl0(ulong value) + { + GetContext().TpidrEl0 = (long)value; + } +#endregion + +#region "Read" + public static byte ReadByte(ulong address) + { + return GetMemoryManager().ReadByte((long)address); + } + + public static ushort ReadUInt16(ulong address) + { + return GetMemoryManager().ReadUInt16((long)address); + } + + public static uint ReadUInt32(ulong address) + { + return GetMemoryManager().ReadUInt32((long)address); + } + + public static ulong ReadUInt64(ulong address) + { + return GetMemoryManager().ReadUInt64((long)address); + } + + public static V128 ReadVector128(ulong address) + { + return GetMemoryManager().ReadVector128((long)address); + } +#endregion + +#region "Read exclusive" + public static byte ReadByteExclusive(ulong address) + { + byte value = _context.Memory.ReadByte((long)address); + + _context.ExclusiveAddress = GetMaskedExclusiveAddress(address); + _context.ExclusiveValueLow = value; + _context.ExclusiveValueHigh = 0; + + return value; + } + + public static ushort ReadUInt16Exclusive(ulong address) + { + ushort value = _context.Memory.ReadUInt16((long)address); + + _context.ExclusiveAddress = GetMaskedExclusiveAddress(address); + _context.ExclusiveValueLow = value; + _context.ExclusiveValueHigh = 0; + + return value; + } + + public static uint ReadUInt32Exclusive(ulong address) + { + uint value = _context.Memory.ReadUInt32((long)address); + + _context.ExclusiveAddress = GetMaskedExclusiveAddress(address); + _context.ExclusiveValueLow = value; + _context.ExclusiveValueHigh = 0; + + return value; + } + + public static ulong ReadUInt64Exclusive(ulong address) + { + ulong value = _context.Memory.ReadUInt64((long)address); + + _context.ExclusiveAddress = GetMaskedExclusiveAddress(address); + _context.ExclusiveValueLow = value; + _context.ExclusiveValueHigh = 0; + + return value; + } + + public static V128 ReadVector128Exclusive(ulong address) + { + V128 value = _context.Memory.AtomicLoadInt128((long)address); + + _context.ExclusiveAddress = GetMaskedExclusiveAddress(address); + _context.ExclusiveValueLow = value.GetUInt64(0); + _context.ExclusiveValueHigh = value.GetUInt64(1); + + return value; + } +#endregion + +#region "Write" + public static void WriteByte(ulong address, byte value) + { + GetMemoryManager().WriteByte((long)address, value); + } + + public static void WriteUInt16(ulong address, ushort value) + { + GetMemoryManager().WriteUInt16((long)address, value); + } + + public static void WriteUInt32(ulong address, uint value) + { + GetMemoryManager().WriteUInt32((long)address, value); + } + + public static void WriteUInt64(ulong address, ulong value) + { + GetMemoryManager().WriteUInt64((long)address, value); + } + + public static void WriteVector128(ulong address, V128 value) + { + GetMemoryManager().WriteVector128((long)address, value); + } +#endregion + +#region "Write exclusive" + public static int WriteByteExclusive(ulong address, byte value) + { + bool success = _context.ExclusiveAddress == GetMaskedExclusiveAddress(address); + + if (success) + { + success = _context.Memory.AtomicCompareExchangeByte( + (long)address, + (byte)_context.ExclusiveValueLow, + (byte)value); + + if (success) + { + ClearExclusive(); + } + } + + return success ? 0 : 1; + } + + public static int WriteUInt16Exclusive(ulong address, ushort value) + { + bool success = _context.ExclusiveAddress == GetMaskedExclusiveAddress(address); + + if (success) + { + success = _context.Memory.AtomicCompareExchangeInt16( + (long)address, + (short)_context.ExclusiveValueLow, + (short)value); + + if (success) + { + ClearExclusive(); + } + } + + return success ? 0 : 1; + } + + public static int WriteUInt32Exclusive(ulong address, uint value) + { + bool success = _context.ExclusiveAddress == GetMaskedExclusiveAddress(address); + + if (success) + { + success = _context.Memory.AtomicCompareExchangeInt32( + (long)address, + (int)_context.ExclusiveValueLow, + (int)value); + + if (success) + { + ClearExclusive(); + } + } + + return success ? 0 : 1; + } + + public static int WriteUInt64Exclusive(ulong address, ulong value) + { + bool success = _context.ExclusiveAddress == GetMaskedExclusiveAddress(address); + + if (success) + { + success = _context.Memory.AtomicCompareExchangeInt64( + (long)address, + (long)_context.ExclusiveValueLow, + (long)value); + + if (success) + { + ClearExclusive(); + } + } + + return success ? 0 : 1; + } + + public static int WriteVector128Exclusive(ulong address, V128 value) + { + bool success = _context.ExclusiveAddress == GetMaskedExclusiveAddress(address); + + if (success) + { + V128 expected = new V128(_context.ExclusiveValueLow, _context.ExclusiveValueHigh); + + success = _context.Memory.AtomicCompareExchangeInt128((long)address, expected, value); + + if (success) + { + ClearExclusive(); + } + } + + return success ? 0 : 1; + } +#endregion + + private static ulong GetMaskedExclusiveAddress(ulong address) + { + return address & ~((4UL << ErgSizeLog2) - 1); + } + + public static void ClearExclusive() + { + _context.ExclusiveAddress = ulong.MaxValue; + } + + public static void CheckSynchronization() + { + Statistics.PauseTimer(); + + GetContext().CheckInterrupt(); + + Statistics.ResumeTimer(); + } + + public static ExecutionContext GetContext() + { + return _context.Context; + } + + public static MemoryManager GetMemoryManager() + { + return _context.Memory; + } + } +} \ No newline at end of file diff --git a/ARMeilleure/Instructions/SoftFallback.cs b/ARMeilleure/Instructions/SoftFallback.cs new file mode 100644 index 0000000000..10bb47df54 --- /dev/null +++ b/ARMeilleure/Instructions/SoftFallback.cs @@ -0,0 +1,1244 @@ +using ARMeilleure.State; +using System; + +namespace ARMeilleure.Instructions +{ + static class SoftFallback + { +#region "ShlReg" + public static long SignedShlReg(long value, long shift, bool round, int size) + { + int eSize = 8 << size; + + int shiftLsB = (sbyte)shift; + + if (shiftLsB < 0) + { + return SignedShrReg(value, -shiftLsB, round, eSize); + } + else if (shiftLsB > 0) + { + if (shiftLsB >= eSize) + { + return 0L; + } + + return value << shiftLsB; + } + else /* if (shiftLsB == 0) */ + { + return value; + } + } + + public static ulong UnsignedShlReg(ulong value, ulong shift, bool round, int size) + { + int eSize = 8 << size; + + int shiftLsB = (sbyte)shift; + + if (shiftLsB < 0) + { + return UnsignedShrReg(value, -shiftLsB, round, eSize); + } + else if (shiftLsB > 0) + { + if (shiftLsB >= eSize) + { + return 0UL; + } + + return value << shiftLsB; + } + else /* if (shiftLsB == 0) */ + { + return value; + } + } + + public static long SignedShlRegSatQ(long value, long shift, bool round, int size) + { + ExecutionContext context = NativeInterface.GetContext(); + + int eSize = 8 << size; + + int shiftLsB = (sbyte)shift; + + if (shiftLsB < 0) + { + return SignedShrReg(value, -shiftLsB, round, eSize); + } + else if (shiftLsB > 0) + { + if (shiftLsB >= eSize) + { + return SignedSignSatQ(value, eSize, context); + } + + if (eSize == 64) + { + long shl = value << shiftLsB; + long shr = shl >> shiftLsB; + + if (shr != value) + { + return SignedSignSatQ(value, eSize, context); + } + else /* if (shr == value) */ + { + return shl; + } + } + else /* if (eSize != 64) */ + { + return SignedSrcSignedDstSatQ(value << shiftLsB, size); + } + } + else /* if (shiftLsB == 0) */ + { + return value; + } + } + + public static ulong UnsignedShlRegSatQ(ulong value, ulong shift, bool round, int size) + { + ExecutionContext context = NativeInterface.GetContext(); + + int eSize = 8 << size; + + int shiftLsB = (sbyte)shift; + + if (shiftLsB < 0) + { + return UnsignedShrReg(value, -shiftLsB, round, eSize); + } + else if (shiftLsB > 0) + { + if (shiftLsB >= eSize) + { + return UnsignedSignSatQ(value, eSize, context); + } + + if (eSize == 64) + { + ulong shl = value << shiftLsB; + ulong shr = shl >> shiftLsB; + + if (shr != value) + { + return UnsignedSignSatQ(value, eSize, context); + } + else /* if (shr == value) */ + { + return shl; + } + } + else /* if (eSize != 64) */ + { + return UnsignedSrcUnsignedDstSatQ(value << shiftLsB, size); + } + } + else /* if (shiftLsB == 0) */ + { + return value; + } + } + + private static long SignedShrReg(long value, int shift, bool round, int eSize) // shift := [1, 128]; eSize := {8, 16, 32, 64}. + { + if (round) + { + if (shift >= eSize) + { + return 0L; + } + + long roundConst = 1L << (shift - 1); + + long add = value + roundConst; + + if (eSize == 64) + { + if ((~value & (value ^ add)) < 0L) + { + return (long)((ulong)add >> shift); + } + else + { + return add >> shift; + } + } + else /* if (eSize != 64) */ + { + return add >> shift; + } + } + else /* if (!round) */ + { + if (shift >= eSize) + { + if (value < 0L) + { + return -1L; + } + else /* if (value >= 0L) */ + { + return 0L; + } + } + + return value >> shift; + } + } + + private static ulong UnsignedShrReg(ulong value, int shift, bool round, int eSize) // shift := [1, 128]; eSize := {8, 16, 32, 64}. + { + if (round) + { + if (shift > 64) + { + return 0UL; + } + + ulong roundConst = 1UL << (shift - 1); + + ulong add = value + roundConst; + + if (eSize == 64) + { + if ((add < value) && (add < roundConst)) + { + if (shift == 64) + { + return 1UL; + } + + return (add >> shift) | (0x8000000000000000UL >> (shift - 1)); + } + else + { + if (shift == 64) + { + return 0UL; + } + + return add >> shift; + } + } + else /* if (eSize != 64) */ + { + if (shift == 64) + { + return 0UL; + } + + return add >> shift; + } + } + else /* if (!round) */ + { + if (shift >= eSize) + { + return 0UL; + } + + return value >> shift; + } + } + + private static long SignedSignSatQ(long op, int eSize, ExecutionContext context) // eSize := {8, 16, 32, 64}. + { + long tMaxValue = (1L << (eSize - 1)) - 1L; + long tMinValue = -(1L << (eSize - 1)); + + if (op > 0L) + { + context.Fpsr |= FPSR.Qc; + + return tMaxValue; + } + else if (op < 0L) + { + context.Fpsr |= FPSR.Qc; + + return tMinValue; + } + else + { + return 0L; + } + } + + private static ulong UnsignedSignSatQ(ulong op, int eSize, ExecutionContext context) // eSize := {8, 16, 32, 64}. + { + ulong tMaxValue = ulong.MaxValue >> (64 - eSize); + + if (op > 0UL) + { + context.Fpsr |= FPSR.Qc; + + return tMaxValue; + } + else + { + return 0UL; + } + } +#endregion + +#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 "Rounding" + public static double Round(double value) + { + ExecutionContext context = NativeInterface.GetContext(); + + FPRoundingMode roundMode = context.Fpcr.GetRoundingMode(); + + if (roundMode == FPRoundingMode.ToNearest) + { + return Math.Round(value); // even + } + else if (roundMode == FPRoundingMode.TowardsPlusInfinity) + { + return Math.Ceiling(value); + } + else if (roundMode == FPRoundingMode.TowardsMinusInfinity) + { + return Math.Floor(value); + } + else /* if (roundMode == FPRoundingMode.TowardsZero) */ + { + return Math.Truncate(value); + } + } + + public static float RoundF(float value) + { + ExecutionContext context = NativeInterface.GetContext(); + + FPRoundingMode roundMode = context.Fpcr.GetRoundingMode(); + + if (roundMode == FPRoundingMode.ToNearest) + { + return MathF.Round(value); // even + } + else if (roundMode == FPRoundingMode.TowardsPlusInfinity) + { + return MathF.Ceiling(value); + } + else if (roundMode == FPRoundingMode.TowardsMinusInfinity) + { + return MathF.Floor(value); + } + else /* if (roundMode == FPRoundingMode.TowardsZero) */ + { + return MathF.Truncate(value); + } + } +#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 "Saturating" + public static long SignedSrcSignedDstSatQ(long op, int size) + { + ExecutionContext context = NativeInterface.GetContext(); + + int eSize = 8 << size; + + long tMaxValue = (1L << (eSize - 1)) - 1L; + long tMinValue = -(1L << (eSize - 1)); + + if (op > tMaxValue) + { + context.Fpsr |= FPSR.Qc; + + return tMaxValue; + } + else if (op < tMinValue) + { + context.Fpsr |= FPSR.Qc; + + return tMinValue; + } + else + { + return op; + } + } + + public static ulong SignedSrcUnsignedDstSatQ(long op, int size) + { + ExecutionContext context = NativeInterface.GetContext(); + + int eSize = 8 << size; + + ulong tMaxValue = (1UL << eSize) - 1UL; + ulong tMinValue = 0UL; + + if (op > (long)tMaxValue) + { + context.Fpsr |= FPSR.Qc; + + return tMaxValue; + } + else if (op < (long)tMinValue) + { + context.Fpsr |= FPSR.Qc; + + return tMinValue; + } + else + { + return (ulong)op; + } + } + + public static long UnsignedSrcSignedDstSatQ(ulong op, int size) + { + ExecutionContext context = NativeInterface.GetContext(); + + int eSize = 8 << size; + + long tMaxValue = (1L << (eSize - 1)) - 1L; + + if (op > (ulong)tMaxValue) + { + context.Fpsr |= FPSR.Qc; + + return tMaxValue; + } + else + { + return (long)op; + } + } + + public static ulong UnsignedSrcUnsignedDstSatQ(ulong op, int size) + { + ExecutionContext context = NativeInterface.GetContext(); + + int eSize = 8 << size; + + ulong tMaxValue = (1UL << eSize) - 1UL; + + if (op > tMaxValue) + { + context.Fpsr |= FPSR.Qc; + + return tMaxValue; + } + else + { + return op; + } + } + + public static long UnarySignedSatQAbsOrNeg(long op) + { + ExecutionContext context = NativeInterface.GetContext(); + + if (op == long.MinValue) + { + context.Fpsr |= FPSR.Qc; + + return long.MaxValue; + } + else + { + return op; + } + } + + public static long BinarySignedSatQAdd(long op1, long op2) + { + ExecutionContext context = NativeInterface.GetContext(); + + long add = op1 + op2; + + if ((~(op1 ^ op2) & (op1 ^ add)) < 0L) + { + context.Fpsr |= FPSR.Qc; + + if (op1 < 0L) + { + return long.MinValue; + } + else + { + return long.MaxValue; + } + } + else + { + return add; + } + } + + public static ulong BinaryUnsignedSatQAdd(ulong op1, ulong op2) + { + ExecutionContext context = NativeInterface.GetContext(); + + ulong add = op1 + op2; + + if ((add < op1) && (add < op2)) + { + context.Fpsr |= FPSR.Qc; + + return ulong.MaxValue; + } + else + { + return add; + } + } + + public static long BinarySignedSatQSub(long op1, long op2) + { + ExecutionContext context = NativeInterface.GetContext(); + + long sub = op1 - op2; + + if (((op1 ^ op2) & (op1 ^ sub)) < 0L) + { + context.Fpsr |= FPSR.Qc; + + if (op1 < 0L) + { + return long.MinValue; + } + else + { + return long.MaxValue; + } + } + else + { + return sub; + } + } + + public static ulong BinaryUnsignedSatQSub(ulong op1, ulong op2) + { + ExecutionContext context = NativeInterface.GetContext(); + + ulong sub = op1 - op2; + + if (op1 < op2) + { + context.Fpsr |= FPSR.Qc; + + return ulong.MinValue; + } + else + { + return sub; + } + } + + public static long BinarySignedSatQAcc(ulong op1, long op2) + { + ExecutionContext context = NativeInterface.GetContext(); + + if (op1 <= (ulong)long.MaxValue) + { + // op1 from ulong.MinValue to (ulong)long.MaxValue + // op2 from long.MinValue to long.MaxValue + + long add = (long)op1 + op2; + + if ((~op2 & add) < 0L) + { + context.Fpsr |= FPSR.Qc; + + return long.MaxValue; + } + else + { + return add; + } + } + else if (op2 >= 0L) + { + // op1 from (ulong)long.MaxValue + 1UL to ulong.MaxValue + // op2 from (long)ulong.MinValue to long.MaxValue + + context.Fpsr |= FPSR.Qc; + + return long.MaxValue; + } + else + { + // op1 from (ulong)long.MaxValue + 1UL to ulong.MaxValue + // op2 from long.MinValue to (long)ulong.MinValue - 1L + + ulong add = op1 + (ulong)op2; + + if (add > (ulong)long.MaxValue) + { + context.Fpsr |= FPSR.Qc; + + return long.MaxValue; + } + else + { + return (long)add; + } + } + } + + public static ulong BinaryUnsignedSatQAcc(long op1, ulong op2) + { + ExecutionContext context = NativeInterface.GetContext(); + + if (op1 >= 0L) + { + // op1 from (long)ulong.MinValue to long.MaxValue + // op2 from ulong.MinValue to ulong.MaxValue + + ulong add = (ulong)op1 + op2; + + if ((add < (ulong)op1) && (add < op2)) + { + context.Fpsr |= FPSR.Qc; + + return ulong.MaxValue; + } + else + { + return add; + } + } + else if (op2 > (ulong)long.MaxValue) + { + // op1 from long.MinValue to (long)ulong.MinValue - 1L + // op2 from (ulong)long.MaxValue + 1UL to ulong.MaxValue + + return (ulong)op1 + op2; + } + else + { + // op1 from long.MinValue to (long)ulong.MinValue - 1L + // op2 from ulong.MinValue to (ulong)long.MaxValue + + long add = op1 + (long)op2; + + if (add < (long)ulong.MinValue) + { + context.Fpsr |= FPSR.Qc; + + return ulong.MinValue; + } + else + { + return (ulong)add; + } + } + } +#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 readonly byte[] ClzNibbleTbl = { 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; + } + + public static ulong CountSetBits8(ulong value) // "size" is 8 (SIMD&FP Inst.). + { + value = ((value >> 1) & 0x55ul) + (value & 0x55ul); + value = ((value >> 2) & 0x33ul) + (value & 0x33ul); + + return (value >> 4) + (value & 0x0ful); + } +#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.GetUInt32(1), + hash_abcd.GetUInt32(2), + hash_abcd.GetUInt32(3)); + + hash_e += Rol(hash_abcd.GetUInt32(0), 5) + t + wk.GetUInt32(e); + + t = Rol(hash_abcd.GetUInt32(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.GetUInt32(1), + hash_abcd.GetUInt32(2), + hash_abcd.GetUInt32(3)); + + hash_e += Rol(hash_abcd.GetUInt32(0), 5) + t + wk.GetUInt32(e); + + t = Rol(hash_abcd.GetUInt32(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.GetUInt32(1), + hash_abcd.GetUInt32(2), + hash_abcd.GetUInt32(3)); + + hash_e += Rol(hash_abcd.GetUInt32(0), 5) + t + wk.GetUInt32(e); + + t = Rol(hash_abcd.GetUInt32(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.GetUInt64(0); + ulong t1 = w0_3.GetUInt64(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.GetUInt32(0); + uint tE1 = t.GetUInt32(1); + uint tE2 = t.GetUInt32(2); + uint tE3 = t.GetUInt32(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.GetUInt32(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_efgh, V128 hash_abcd, 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).GetUInt32(e <= 2 ? e + 1 : 0); + + elt = elt.Ror(7) ^ elt.Ror(18) ^ elt.Lsr(3); + + elt += w0_3.GetUInt32(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.GetUInt64(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.GetUInt32(e) + w8_11.GetUInt32(e + 1); + + result.Insert(e, elt); + } + + t1 = result.GetUInt64(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.GetUInt32(e) + (e == 2 ? w8_11 : w12_15).GetUInt32(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.GetUInt32(0), + y.GetUInt32(1), + y.GetUInt32(2)); + + uint maj = ShaMajority(x.GetUInt32(0), + x.GetUInt32(1), + x.GetUInt32(2)); + + uint t1 = y.GetUInt32(3) + ShaHashSigma1(y.GetUInt32(0)) + chs + w.GetUInt32(e); + + uint t2 = t1 + x.GetUInt32(3); + + x.Insert(3, t2); + + t2 = t1 + ShaHashSigma0(x.GetUInt32(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.GetUInt32(3); + uint xE3 = x.GetUInt32(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 + } +} diff --git a/ARMeilleure/Instructions/SoftFloat.cs b/ARMeilleure/Instructions/SoftFloat.cs new file mode 100644 index 0000000000..256bc5b975 --- /dev/null +++ b/ARMeilleure/Instructions/SoftFloat.cs @@ -0,0 +1,2767 @@ +using ARMeilleure.State; +using System; +using System.Diagnostics; + +namespace ARMeilleure.Instructions +{ + static class SoftFloat + { + static SoftFloat() + { + RecipEstimateTable = BuildRecipEstimateTable(); + RecipSqrtEstimateTable = BuildRecipSqrtEstimateTable(); + } + + internal static readonly byte[] RecipEstimateTable; + internal 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; + } + } + + 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 = FPDefaultNaN(); + } + else + { + result = FPConvertNaN(valueBits); + } + + if (type == FPType.SNaN) + { + FPProcessException(FPException.InvalidOp, context); + } + } + else if (type == FPType.Infinity) + { + result = FPInfinity(sign); + } + else if (type == FPType.Zero) + { + result = FPZero(sign); + } + else + { + result = FPRoundCv(real, context); + } + + return result; + } + + private static float FPDefaultNaN() + { + return -float.NaN; + } + + private static float FPInfinity(bool sign) + { + return sign ? float.NegativeInfinity : float.PositiveInfinity; + } + + private static float FPZero(bool sign) + { + return sign ? -0f : +0f; + } + + private static float FPMaxNormal(bool sign) + { + return sign ? float.MinValue : float.MaxValue; + } + + private 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; + } + + 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 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)) + { + 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 ? FPInfinity(sign) : FPMaxNormal(sign); + + FPProcessException(FPException.Overflow, context); + + error = 1d; + } + else + { + result = BitConverter.Int32BitsToSingle( + (int)((sign ? 1u : 0u) << 31 | (biasedExp & 0xFFu) << 23 | (intMant & 0x007FFFFFu))); + } + + if (error != 0d) + { + 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)); + } + + private static void FPProcessException(FPException exc, ExecutionContext context) + { + int enable = (int)exc + 8; + + if ((context.Fpcr & (FPCR)(1 << enable)) != 0) + { + throw new NotImplementedException("Floating-point trap handling."); + } + else + { + context.Fpsr |= (FPSR)(1 << (int)exc); + } + } + } + + 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 = FPZero(sign); + } + else if ((context.Fpcr & FPCR.Dn) != 0) + { + resultBits = FPDefaultNaN(); + } + else + { + resultBits = FPConvertNaN(valueBits); + } + + if (type == FPType.SNaN || altHp) + { + FPProcessException(FPException.InvalidOp, context); + } + } + else if (type == FPType.Infinity) + { + if (altHp) + { + resultBits = (ushort)((sign ? 1u : 0u) << 15 | 0x7FFFu); + + FPProcessException(FPException.InvalidOp, context); + } + else + { + resultBits = FPInfinity(sign); + } + } + else if (type == FPType.Zero) + { + resultBits = FPZero(sign); + } + else + { + resultBits = FPRoundCv(real, context); + } + + return resultBits; + } + + private static ushort FPDefaultNaN() + { + return (ushort)0x7E00u; + } + + private static ushort FPInfinity(bool sign) + { + return sign ? (ushort)0xFC00u : (ushort)0x7C00u; + } + + private static ushort FPZero(bool sign) + { + return sign ? (ushort)0x8000u : (ushort)0x0000u; + } + + private static ushort FPMaxNormal(bool sign) + { + return sign ? (ushort)0xFBFFu : (ushort)0x7BFFu; + } + + 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) + { + 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 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)) + { + 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); + + 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); + + FPProcessException(FPException.InvalidOp, context); + + error = 0d; + } + else + { + resultBits = (ushort)((sign ? 1u : 0u) << 15 | (biasedExp & 0x1Fu) << 10 | (intMant & 0x03FFu)); + } + } + + if (error != 0d) + { + FPProcessException(FPException.Inexact, context); + } + + return resultBits; + } + + private static ushort FPConvertNaN(uint valueBits) + { + return (ushort)((valueBits & 0x80000000u) >> 16 | 0x7E00u | (valueBits & 0x003FE000u) >> 13); + } + + private static void FPProcessException(FPException exc, ExecutionContext context) + { + int enable = (int)exc + 8; + + if ((context.Fpcr & (FPCR)(1 << enable)) != 0) + { + throw new NotImplementedException("Floating-point trap handling."); + } + else + { + context.Fpsr |= (FPSR)(1 << (int)exc); + } + } + } + + static class SoftFloat32 + { + public static float FPAdd(float value1, float value2) + { + ExecutionContext context = NativeInterface.GetContext(); + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context); + + float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context); + + 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(); + + FPProcessException(FPException.InvalidOp, context); + } + 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 ((context.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(); + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out _, context); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out _, context); + + 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) + { + FPProcessException(FPException.InvalidOp, context); + } + } + 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) + { + ExecutionContext context = NativeInterface.GetContext(); + + value1 = value1.FPUnpack(out FPType type1, out _, out _, context); + value2 = value2.FPUnpack(out FPType type2, out _, out _, context); + + 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) + { + FPProcessException(FPException.InvalidOp, context); + } + } + else + { + result = ZerosOrOnes(value1 == value2); + } + + return result; + } + + public static float FPCompareGE(float value1, float value2) + { + ExecutionContext context = NativeInterface.GetContext(); + + value1 = value1.FPUnpack(out FPType type1, out _, out _, context); + value2 = value2.FPUnpack(out FPType type2, out _, out _, context); + + float result; + + if (type1 == FPType.SNaN || type1 == FPType.QNaN || type2 == FPType.SNaN || type2 == FPType.QNaN) + { + result = ZerosOrOnes(false); + + FPProcessException(FPException.InvalidOp, context); + } + else + { + result = ZerosOrOnes(value1 >= value2); + } + + return result; + } + + public static float FPCompareGT(float value1, float value2) + { + ExecutionContext context = NativeInterface.GetContext(); + + value1 = value1.FPUnpack(out FPType type1, out _, out _, context); + value2 = value2.FPUnpack(out FPType type2, out _, out _, context); + + float result; + + if (type1 == FPType.SNaN || type1 == FPType.QNaN || type2 == FPType.SNaN || type2 == FPType.QNaN) + { + result = ZerosOrOnes(false); + + FPProcessException(FPException.InvalidOp, context); + } + 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 FPDiv(float value1, float value2) + { + ExecutionContext context = NativeInterface.GetContext(); + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context); + + float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context); + + 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(); + + FPProcessException(FPException.InvalidOp, context); + } + else if (inf1 || zero2) + { + result = FPInfinity(sign1 ^ sign2); + + if (!inf1) + { + FPProcessException(FPException.DivideByZero, context); + } + } + else if (zero1 || inf2) + { + result = FPZero(sign1 ^ sign2); + } + else + { + result = value1 / value2; + + if ((context.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) + { + ExecutionContext context = NativeInterface.GetContext(); + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context); + + float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context); + + if (!done) + { + if (value1 > value2) + { + if (type1 == FPType.Infinity) + { + result = FPInfinity(sign1); + } + else if (type1 == FPType.Zero) + { + result = FPZero(sign1 && sign2); + } + else + { + result = value1; + } + } + else + { + if (type2 == FPType.Infinity) + { + result = FPInfinity(sign2); + } + else if (type2 == FPType.Zero) + { + result = FPZero(sign1 && sign2); + } + else + { + result = value2; + + if ((context.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) + { + ExecutionContext context = NativeInterface.GetContext(); + + value1.FPUnpack(out FPType type1, out _, out _, context); + value2.FPUnpack(out FPType type2, out _, out _, context); + + if (type1 == FPType.QNaN && type2 != FPType.QNaN) + { + value1 = FPInfinity(true); + } + else if (type1 != FPType.QNaN && type2 == FPType.QNaN) + { + value2 = FPInfinity(true); + } + + return FPMax(value1, value2); + } + + public static float FPMin(float value1, float value2) + { + ExecutionContext context = NativeInterface.GetContext(); + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context); + + float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context); + + if (!done) + { + if (value1 < value2) + { + if (type1 == FPType.Infinity) + { + result = FPInfinity(sign1); + } + else if (type1 == FPType.Zero) + { + result = FPZero(sign1 || sign2); + } + else + { + result = value1; + } + } + else + { + if (type2 == FPType.Infinity) + { + result = FPInfinity(sign2); + } + else if (type2 == FPType.Zero) + { + result = FPZero(sign1 || sign2); + } + else + { + result = value2; + + if ((context.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) + { + ExecutionContext context = NativeInterface.GetContext(); + + value1.FPUnpack(out FPType type1, out _, out _, context); + value2.FPUnpack(out FPType type2, out _, out _, context); + + if (type1 == FPType.QNaN && type2 != FPType.QNaN) + { + value1 = FPInfinity(false); + } + else if (type1 != FPType.QNaN && type2 == FPType.QNaN) + { + value2 = FPInfinity(false); + } + + return FPMin(value1, value2); + } + + public static float FPMul(float value1, float value2) + { + ExecutionContext context = NativeInterface.GetContext(); + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context); + + float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context); + + 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(); + + FPProcessException(FPException.InvalidOp, context); + } + else if (inf1 || inf2) + { + result = FPInfinity(sign1 ^ sign2); + } + else if (zero1 || zero2) + { + result = FPZero(sign1 ^ sign2); + } + else + { + result = value1 * value2; + + if ((context.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) + { + ExecutionContext context = NativeInterface.GetContext(); + + valueA = valueA.FPUnpack(out FPType typeA, out bool signA, out uint addend, context); + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context); + + 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); + + if (typeA == FPType.QNaN && ((inf1 && zero2) || (zero1 && inf2))) + { + result = FPDefaultNaN(); + + FPProcessException(FPException.InvalidOp, context); + } + + 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(); + + FPProcessException(FPException.InvalidOp, context); + } + 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 ((context.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 FPMulX(float value1, float value2) + { + ExecutionContext context = NativeInterface.GetContext(); + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context); + + float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context); + + 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 ((context.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) + { + ExecutionContext context = NativeInterface.GetContext(); + + value.FPUnpack(out FPType type, out bool sign, out uint op, context); + + float result; + + if (type == FPType.SNaN || type == FPType.QNaN) + { + result = FPProcessNaN(type, op, context); + } + else if (type == FPType.Infinity) + { + result = FPZero(sign); + } + else if (type == FPType.Zero) + { + result = FPInfinity(sign); + + FPProcessException(FPException.DivideByZero, context); + } + else if (MathF.Abs(value) < MathF.Pow(2f, -128)) + { + bool overflowToInf; + + switch (context.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); + + FPProcessException(FPException.Overflow, context); + FPProcessException(FPException.Inexact, context); + } + else if ((context.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 FPRecipStepFused(float value1, float value2) + { + ExecutionContext context = NativeInterface.GetContext(); + + value1 = value1.FPNeg(); + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context); + + float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context); + + 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 ((context.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(); + + value.FPUnpack(out FPType type, out bool sign, out uint op, context); + + float result; + + if (type == FPType.SNaN || type == FPType.QNaN) + { + result = FPProcessNaN(type, op, context); + } + 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) + { + ExecutionContext context = NativeInterface.GetContext(); + + value.FPUnpack(out FPType type, out bool sign, out uint op, context); + + float result; + + if (type == FPType.SNaN || type == FPType.QNaN) + { + result = FPProcessNaN(type, op, context); + } + else if (type == FPType.Zero) + { + result = FPInfinity(sign); + + FPProcessException(FPException.DivideByZero, context); + } + else if (sign) + { + result = FPDefaultNaN(); + + FPProcessException(FPException.InvalidOp, context); + } + 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 FPRSqrtStepFused(float value1, float value2) + { + ExecutionContext context = NativeInterface.GetContext(); + + value1 = value1.FPNeg(); + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context); + + float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context); + + 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 ((context.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(); + + value = value.FPUnpack(out FPType type, out bool sign, out uint op, context); + + float result; + + if (type == FPType.SNaN || type == FPType.QNaN) + { + result = FPProcessNaN(type, op, context); + } + else if (type == FPType.Zero) + { + result = FPZero(sign); + } + else if (type == FPType.Infinity && !sign) + { + result = FPInfinity(sign); + } + else if (sign) + { + result = FPDefaultNaN(); + + FPProcessException(FPException.InvalidOp, context); + } + else + { + result = MathF.Sqrt(value); + + if ((context.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) + { + ExecutionContext context = NativeInterface.GetContext(); + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context); + + float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context); + + 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(); + + FPProcessException(FPException.InvalidOp, context); + } + 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 ((context.Fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0f); + } + } + } + + return result; + } + + private static float FPDefaultNaN() + { + return -float.NaN; + } + + private static float FPInfinity(bool sign) + { + return sign ? float.NegativeInfinity : float.PositiveInfinity; + } + + private static float FPZero(bool sign) + { + return sign ? -0f : +0f; + } + + private static float FPMaxNormal(bool sign) + { + return sign ? float.MinValue : float.MaxValue; + } + + private static float FPTwo(bool sign) + { + return sign ? -2f : +2f; + } + + 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) + { + valueBits = (uint)BitConverter.SingleToInt32Bits(value); + + sign = (~valueBits & 0x80000000u) == 0u; + + if ((valueBits & 0x7F800000u) == 0u) + { + if ((valueBits & 0x007FFFFFu) == 0u || (context.Fpcr & FPCR.Fz) != 0) + { + type = FPType.Zero; + value = FPZero(sign); + + if ((valueBits & 0x007FFFFFu) != 0u) + { + FPProcessException(FPException.InputDenorm, context); + } + } + 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) + { + done = true; + + if (type1 == FPType.SNaN) + { + return FPProcessNaN(type1, op1, context); + } + else if (type2 == FPType.SNaN) + { + return FPProcessNaN(type2, op2, context); + } + else if (type1 == FPType.QNaN) + { + return FPProcessNaN(type1, op1, context); + } + else if (type2 == FPType.QNaN) + { + return FPProcessNaN(type2, op2, context); + } + + 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) + { + done = true; + + if (type1 == FPType.SNaN) + { + return FPProcessNaN(type1, op1, context); + } + else if (type2 == FPType.SNaN) + { + return FPProcessNaN(type2, op2, context); + } + else if (type3 == FPType.SNaN) + { + return FPProcessNaN(type3, op3, context); + } + else if (type1 == FPType.QNaN) + { + return FPProcessNaN(type1, op1, context); + } + else if (type2 == FPType.QNaN) + { + return FPProcessNaN(type2, op2, context); + } + else if (type3 == FPType.QNaN) + { + return FPProcessNaN(type3, op3, context); + } + + done = false; + + return FPZero(false); + } + + private static float FPProcessNaN(FPType type, uint op, ExecutionContext context) + { + if (type == FPType.SNaN) + { + op |= 1u << 22; + + FPProcessException(FPException.InvalidOp, context); + } + + if ((context.Fpcr & FPCR.Dn) != 0) + { + return FPDefaultNaN(); + } + + return BitConverter.Int32BitsToSingle((int)op); + } + + private static void FPProcessException(FPException exc, ExecutionContext context) + { + int enable = (int)exc + 8; + + if ((context.Fpcr & (FPCR)(1 << enable)) != 0) + { + throw new NotImplementedException("Floating-point trap handling."); + } + else + { + context.Fpsr |= (FPSR)(1 << (int)exc); + } + } + } + + static class SoftFloat64 + { + public static double FPAdd(double value1, double value2) + { + ExecutionContext context = NativeInterface.GetContext(); + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context); + + double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context); + + 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(); + + FPProcessException(FPException.InvalidOp, context); + } + 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 ((context.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(); + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out _, context); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out _, context); + + 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) + { + FPProcessException(FPException.InvalidOp, context); + } + } + 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) + { + ExecutionContext context = NativeInterface.GetContext(); + + value1 = value1.FPUnpack(out FPType type1, out _, out _, context); + value2 = value2.FPUnpack(out FPType type2, out _, out _, context); + + 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) + { + FPProcessException(FPException.InvalidOp, context); + } + } + else + { + result = ZerosOrOnes(value1 == value2); + } + + return result; + } + + public static double FPCompareGE(double value1, double value2) + { + ExecutionContext context = NativeInterface.GetContext(); + + value1 = value1.FPUnpack(out FPType type1, out _, out _, context); + value2 = value2.FPUnpack(out FPType type2, out _, out _, context); + + double result; + + if (type1 == FPType.SNaN || type1 == FPType.QNaN || type2 == FPType.SNaN || type2 == FPType.QNaN) + { + result = ZerosOrOnes(false); + + FPProcessException(FPException.InvalidOp, context); + } + else + { + result = ZerosOrOnes(value1 >= value2); + } + + return result; + } + + public static double FPCompareGT(double value1, double value2) + { + ExecutionContext context = NativeInterface.GetContext(); + + value1 = value1.FPUnpack(out FPType type1, out _, out _, context); + value2 = value2.FPUnpack(out FPType type2, out _, out _, context); + + double result; + + if (type1 == FPType.SNaN || type1 == FPType.QNaN || type2 == FPType.SNaN || type2 == FPType.QNaN) + { + result = ZerosOrOnes(false); + + FPProcessException(FPException.InvalidOp, context); + } + 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 FPDiv(double value1, double value2) + { + ExecutionContext context = NativeInterface.GetContext(); + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context); + + double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context); + + 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(); + + FPProcessException(FPException.InvalidOp, context); + } + else if (inf1 || zero2) + { + result = FPInfinity(sign1 ^ sign2); + + if (!inf1) + { + FPProcessException(FPException.DivideByZero, context); + } + } + else if (zero1 || inf2) + { + result = FPZero(sign1 ^ sign2); + } + else + { + result = value1 / value2; + + if ((context.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) + { + ExecutionContext context = NativeInterface.GetContext(); + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context); + + double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context); + + if (!done) + { + if (value1 > value2) + { + if (type1 == FPType.Infinity) + { + result = FPInfinity(sign1); + } + else if (type1 == FPType.Zero) + { + result = FPZero(sign1 && sign2); + } + else + { + result = value1; + } + } + else + { + if (type2 == FPType.Infinity) + { + result = FPInfinity(sign2); + } + else if (type2 == FPType.Zero) + { + result = FPZero(sign1 && sign2); + } + else + { + result = value2; + + if ((context.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) + { + ExecutionContext context = NativeInterface.GetContext(); + + value1.FPUnpack(out FPType type1, out _, out _, context); + value2.FPUnpack(out FPType type2, out _, out _, context); + + if (type1 == FPType.QNaN && type2 != FPType.QNaN) + { + value1 = FPInfinity(true); + } + else if (type1 != FPType.QNaN && type2 == FPType.QNaN) + { + value2 = FPInfinity(true); + } + + return FPMax(value1, value2); + } + + public static double FPMin(double value1, double value2) + { + ExecutionContext context = NativeInterface.GetContext(); + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context); + + double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context); + + if (!done) + { + if (value1 < value2) + { + if (type1 == FPType.Infinity) + { + result = FPInfinity(sign1); + } + else if (type1 == FPType.Zero) + { + result = FPZero(sign1 || sign2); + } + else + { + result = value1; + } + } + else + { + if (type2 == FPType.Infinity) + { + result = FPInfinity(sign2); + } + else if (type2 == FPType.Zero) + { + result = FPZero(sign1 || sign2); + } + else + { + result = value2; + + if ((context.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) + { + ExecutionContext context = NativeInterface.GetContext(); + + value1.FPUnpack(out FPType type1, out _, out _, context); + value2.FPUnpack(out FPType type2, out _, out _, context); + + if (type1 == FPType.QNaN && type2 != FPType.QNaN) + { + value1 = FPInfinity(false); + } + else if (type1 != FPType.QNaN && type2 == FPType.QNaN) + { + value2 = FPInfinity(false); + } + + return FPMin(value1, value2); + } + + public static double FPMul(double value1, double value2) + { + ExecutionContext context = NativeInterface.GetContext(); + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context); + + double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context); + + 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(); + + FPProcessException(FPException.InvalidOp, context); + } + else if (inf1 || inf2) + { + result = FPInfinity(sign1 ^ sign2); + } + else if (zero1 || zero2) + { + result = FPZero(sign1 ^ sign2); + } + else + { + result = value1 * value2; + + if ((context.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) + { + ExecutionContext context = NativeInterface.GetContext(); + + valueA = valueA.FPUnpack(out FPType typeA, out bool signA, out ulong addend, context); + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context); + + 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); + + if (typeA == FPType.QNaN && ((inf1 && zero2) || (zero1 && inf2))) + { + result = FPDefaultNaN(); + + FPProcessException(FPException.InvalidOp, context); + } + + 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(); + + FPProcessException(FPException.InvalidOp, context); + } + 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 ((context.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 FPMulX(double value1, double value2) + { + ExecutionContext context = NativeInterface.GetContext(); + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context); + + double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context); + + 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 ((context.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) + { + ExecutionContext context = NativeInterface.GetContext(); + + value.FPUnpack(out FPType type, out bool sign, out ulong op, context); + + double result; + + if (type == FPType.SNaN || type == FPType.QNaN) + { + result = FPProcessNaN(type, op, context); + } + else if (type == FPType.Infinity) + { + result = FPZero(sign); + } + else if (type == FPType.Zero) + { + result = FPInfinity(sign); + + FPProcessException(FPException.DivideByZero, context); + } + else if (Math.Abs(value) < Math.Pow(2d, -1024)) + { + bool overflowToInf; + + switch (context.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); + + FPProcessException(FPException.Overflow, context); + FPProcessException(FPException.Inexact, context); + } + else if ((context.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 FPRecipStepFused(double value1, double value2) + { + ExecutionContext context = NativeInterface.GetContext(); + + value1 = value1.FPNeg(); + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context); + + double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context); + + 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 ((context.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(); + + value.FPUnpack(out FPType type, out bool sign, out ulong op, context); + + double result; + + if (type == FPType.SNaN || type == FPType.QNaN) + { + result = FPProcessNaN(type, op, context); + } + 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) + { + ExecutionContext context = NativeInterface.GetContext(); + + value.FPUnpack(out FPType type, out bool sign, out ulong op, context); + + double result; + + if (type == FPType.SNaN || type == FPType.QNaN) + { + result = FPProcessNaN(type, op, context); + } + else if (type == FPType.Zero) + { + result = FPInfinity(sign); + + FPProcessException(FPException.DivideByZero, context); + } + else if (sign) + { + result = FPDefaultNaN(); + + FPProcessException(FPException.InvalidOp, context); + } + 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 FPRSqrtStepFused(double value1, double value2) + { + ExecutionContext context = NativeInterface.GetContext(); + + value1 = value1.FPNeg(); + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context); + + double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context); + + 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 ((context.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(); + + value = value.FPUnpack(out FPType type, out bool sign, out ulong op, context); + + double result; + + if (type == FPType.SNaN || type == FPType.QNaN) + { + result = FPProcessNaN(type, op, context); + } + else if (type == FPType.Zero) + { + result = FPZero(sign); + } + else if (type == FPType.Infinity && !sign) + { + result = FPInfinity(sign); + } + else if (sign) + { + result = FPDefaultNaN(); + + FPProcessException(FPException.InvalidOp, context); + } + else + { + result = Math.Sqrt(value); + + if ((context.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) + { + ExecutionContext context = NativeInterface.GetContext(); + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context); + + double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context); + + 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(); + + FPProcessException(FPException.InvalidOp, context); + } + 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 ((context.Fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0d); + } + } + } + + return result; + } + + private static double FPDefaultNaN() + { + return -double.NaN; + } + + private static double FPInfinity(bool sign) + { + return sign ? double.NegativeInfinity : double.PositiveInfinity; + } + + private static double FPZero(bool sign) + { + return sign ? -0d : +0d; + } + + private static double FPMaxNormal(bool sign) + { + return sign ? double.MinValue : double.MaxValue; + } + + private static double FPTwo(bool sign) + { + return sign ? -2d : +2d; + } + + 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) + { + valueBits = (ulong)BitConverter.DoubleToInt64Bits(value); + + sign = (~valueBits & 0x8000000000000000ul) == 0ul; + + if ((valueBits & 0x7FF0000000000000ul) == 0ul) + { + if ((valueBits & 0x000FFFFFFFFFFFFFul) == 0ul || (context.Fpcr & FPCR.Fz) != 0) + { + type = FPType.Zero; + value = FPZero(sign); + + if ((valueBits & 0x000FFFFFFFFFFFFFul) != 0ul) + { + FPProcessException(FPException.InputDenorm, context); + } + } + 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) + { + done = true; + + if (type1 == FPType.SNaN) + { + return FPProcessNaN(type1, op1, context); + } + else if (type2 == FPType.SNaN) + { + return FPProcessNaN(type2, op2, context); + } + else if (type1 == FPType.QNaN) + { + return FPProcessNaN(type1, op1, context); + } + else if (type2 == FPType.QNaN) + { + return FPProcessNaN(type2, op2, context); + } + + 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) + { + done = true; + + if (type1 == FPType.SNaN) + { + return FPProcessNaN(type1, op1, context); + } + else if (type2 == FPType.SNaN) + { + return FPProcessNaN(type2, op2, context); + } + else if (type3 == FPType.SNaN) + { + return FPProcessNaN(type3, op3, context); + } + else if (type1 == FPType.QNaN) + { + return FPProcessNaN(type1, op1, context); + } + else if (type2 == FPType.QNaN) + { + return FPProcessNaN(type2, op2, context); + } + else if (type3 == FPType.QNaN) + { + return FPProcessNaN(type3, op3, context); + } + + done = false; + + return FPZero(false); + } + + private static double FPProcessNaN(FPType type, ulong op, ExecutionContext context) + { + if (type == FPType.SNaN) + { + op |= 1ul << 51; + + FPProcessException(FPException.InvalidOp, context); + } + + if ((context.Fpcr & FPCR.Dn) != 0) + { + return FPDefaultNaN(); + } + + return BitConverter.Int64BitsToDouble((long)op); + } + + private static void FPProcessException(FPException exc, ExecutionContext context) + { + int enable = (int)exc + 8; + + if ((context.Fpcr & (FPCR)(1 << enable)) != 0) + { + throw new NotImplementedException("Floating-point trap handling."); + } + else + { + context.Fpsr |= (FPSR)(1 << (int)exc); + } + } + } +} diff --git a/ARMeilleure/IntermediateRepresentation/BasicBlock.cs b/ARMeilleure/IntermediateRepresentation/BasicBlock.cs new file mode 100644 index 0000000000..06839f309f --- /dev/null +++ b/ARMeilleure/IntermediateRepresentation/BasicBlock.cs @@ -0,0 +1,83 @@ +using System.Collections.Generic; + +namespace ARMeilleure.IntermediateRepresentation +{ + class BasicBlock + { + public int Index { get; set; } + + public LinkedListNode Node { get; set; } + + public LinkedList Operations { get; } + + private BasicBlock _next; + private BasicBlock _branch; + + public BasicBlock Next + { + get => _next; + set => _next = AddSuccessor(_next, value); + } + + public BasicBlock Branch + { + get => _branch; + set => _branch = AddSuccessor(_branch, value); + } + + public List Predecessors { get; } + + public HashSet DominanceFrontiers { get; } + + public BasicBlock ImmediateDominator { get; set; } + + public BasicBlock() + { + Operations = new LinkedList(); + + Predecessors = new List(); + + DominanceFrontiers = new HashSet(); + + Index = -1; + } + + public BasicBlock(int index) : this() + { + Index = index; + } + + private BasicBlock AddSuccessor(BasicBlock oldBlock, BasicBlock newBlock) + { + oldBlock?.Predecessors.Remove(this); + newBlock?.Predecessors.Add(this); + + return newBlock; + } + + public void Append(Node node) + { + // If the branch block is not null, then the list of operations + // should end with a branch instruction. We insert the new operation + // before this branch. + if (_branch != null || (Operations.Last != null && IsLeafBlock())) + { + Operations.AddBefore(Operations.Last, node); + } + else + { + Operations.AddLast(node); + } + } + + private bool IsLeafBlock() + { + return _branch == null && _next == null; + } + + public Node GetLastOp() + { + return Operations.Last?.Value; + } + } +} \ No newline at end of file diff --git a/ARMeilleure/IntermediateRepresentation/Instruction.cs b/ARMeilleure/IntermediateRepresentation/Instruction.cs new file mode 100644 index 0000000000..4c4ecb8f2d --- /dev/null +++ b/ARMeilleure/IntermediateRepresentation/Instruction.cs @@ -0,0 +1,79 @@ +namespace ARMeilleure.IntermediateRepresentation +{ + enum Instruction + { + Add, + BitwiseAnd, + BitwiseExclusiveOr, + BitwiseNot, + BitwiseOr, + Branch, + BranchIfFalse, + BranchIfTrue, + ByteSwap, + Call, + CompareAndSwap128, + CompareEqual, + CompareGreater, + CompareGreaterOrEqual, + CompareGreaterOrEqualUI, + CompareGreaterUI, + CompareLess, + CompareLessOrEqual, + CompareLessOrEqualUI, + CompareLessUI, + CompareNotEqual, + ConditionalSelect, + ConvertI64ToI32, + ConvertToFP, + ConvertToFPUI, + Copy, + CountLeadingZeros, + Divide, + DivideUI, + Load, + Load16, + Load8, + LoadArgument, + Multiply, + Multiply64HighSI, + Multiply64HighUI, + Negate, + Return, + RotateRight, + ShiftLeft, + ShiftRightSI, + ShiftRightUI, + SignExtend16, + SignExtend32, + SignExtend8, + StackAlloc, + Store, + Store16, + Store8, + Subtract, + VectorCreateScalar, + VectorExtract, + VectorExtract16, + VectorExtract8, + VectorInsert, + VectorInsert16, + VectorInsert8, + VectorOne, + VectorZero, + VectorZeroUpper64, + VectorZeroUpper96, + ZeroExtend16, + ZeroExtend32, + ZeroExtend8, + + Clobber, + CpuId, + Extended, + Fill, + LoadFromContext, + Spill, + SpillArg, + StoreToContext + } +} \ No newline at end of file diff --git a/ARMeilleure/IntermediateRepresentation/Intrinsic.cs b/ARMeilleure/IntermediateRepresentation/Intrinsic.cs new file mode 100644 index 0000000000..c3f375c4c2 --- /dev/null +++ b/ARMeilleure/IntermediateRepresentation/Intrinsic.cs @@ -0,0 +1,146 @@ +namespace ARMeilleure.IntermediateRepresentation +{ + enum Intrinsic + { + X86Addpd, + X86Addps, + X86Addsd, + X86Addss, + X86Andnpd, + X86Andnps, + X86Andpd, + X86Andps, + X86Blendvpd, + X86Blendvps, + X86Cmppd, + X86Cmpps, + X86Cmpsd, + X86Cmpss, + X86Comisdeq, + X86Comisdge, + X86Comisdlt, + X86Comisseq, + X86Comissge, + X86Comisslt, + X86Cvtdq2pd, + X86Cvtdq2ps, + X86Cvtpd2dq, + X86Cvtpd2ps, + X86Cvtps2dq, + X86Cvtps2pd, + X86Cvtsd2si, + X86Cvtsd2ss, + X86Cvtsi2sd, + X86Cvtsi2si, + X86Cvtsi2ss, + X86Cvtss2sd, + X86Cvtss2si, + X86Divpd, + X86Divps, + X86Divsd, + X86Divss, + X86Haddpd, + X86Haddps, + X86Maxpd, + X86Maxps, + X86Maxsd, + X86Maxss, + X86Minpd, + X86Minps, + X86Minsd, + X86Minss, + X86Movhlps, + X86Movlhps, + X86Mulpd, + X86Mulps, + X86Mulsd, + X86Mulss, + X86Paddb, + X86Paddd, + X86Paddq, + X86Paddw, + X86Pand, + X86Pandn, + X86Pavgb, + X86Pavgw, + X86Pblendvb, + 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, + 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, + X86Shufpd, + X86Shufps, + X86Sqrtpd, + X86Sqrtps, + X86Sqrtsd, + X86Sqrtss, + X86Subpd, + X86Subps, + X86Subsd, + X86Subss, + X86Unpckhpd, + X86Unpckhps, + X86Unpcklpd, + X86Unpcklps, + X86Xorpd, + X86Xorps + } +} \ No newline at end of file diff --git a/ARMeilleure/IntermediateRepresentation/IntrinsicOperation.cs b/ARMeilleure/IntermediateRepresentation/IntrinsicOperation.cs new file mode 100644 index 0000000000..34781b7005 --- /dev/null +++ b/ARMeilleure/IntermediateRepresentation/IntrinsicOperation.cs @@ -0,0 +1,12 @@ +namespace ARMeilleure.IntermediateRepresentation +{ + class IntrinsicOperation : Operation + { + public Intrinsic Intrinsic { get; } + + public IntrinsicOperation(Intrinsic intrin, Operand dest, params Operand[] sources) : base(Instruction.Extended, dest, sources) + { + Intrinsic = intrin; + } + } +} \ No newline at end of file diff --git a/ARMeilleure/IntermediateRepresentation/MemoryOperand.cs b/ARMeilleure/IntermediateRepresentation/MemoryOperand.cs new file mode 100644 index 0000000000..742842fa72 --- /dev/null +++ b/ARMeilleure/IntermediateRepresentation/MemoryOperand.cs @@ -0,0 +1,25 @@ +namespace ARMeilleure.IntermediateRepresentation +{ + class MemoryOperand : Operand + { + public Operand BaseAddress { get; set; } + public Operand Index { get; set; } + + public Multiplier Scale { get; } + + public int Displacement { get; } + + public MemoryOperand( + OperandType type, + Operand baseAddress, + Operand index = null, + Multiplier scale = Multiplier.x1, + int displacement = 0) : base(OperandKind.Memory, type) + { + BaseAddress = baseAddress; + Index = index; + Scale = scale; + Displacement = displacement; + } + } +} \ No newline at end of file diff --git a/ARMeilleure/IntermediateRepresentation/Multiplier.cs b/ARMeilleure/IntermediateRepresentation/Multiplier.cs new file mode 100644 index 0000000000..23582072b1 --- /dev/null +++ b/ARMeilleure/IntermediateRepresentation/Multiplier.cs @@ -0,0 +1,10 @@ +namespace ARMeilleure.IntermediateRepresentation +{ + enum Multiplier + { + x1 = 0, + x2 = 1, + x4 = 2, + x8 = 3 + } +} \ No newline at end of file diff --git a/ARMeilleure/IntermediateRepresentation/Node.cs b/ARMeilleure/IntermediateRepresentation/Node.cs new file mode 100644 index 0000000000..167acd0721 --- /dev/null +++ b/ARMeilleure/IntermediateRepresentation/Node.cs @@ -0,0 +1,163 @@ +using System; +using System.Collections.Generic; + +namespace ARMeilleure.IntermediateRepresentation +{ + class Node + { + public Operand Destination + { + get + { + return _destinations.Length != 0 ? GetDestination(0) : null; + } + set + { + if (value != null) + { + SetDestinations(new Operand[] { value }); + } + else + { + SetDestinations(new Operand[0]); + } + } + } + + private Operand[] _destinations; + private Operand[] _sources; + + private LinkedListNode[] _asgUseNodes; + private LinkedListNode[] _srcUseNodes; + + public int DestinationsCount => _destinations.Length; + public int SourcesCount => _sources.Length; + + public Node(Operand destination, int sourcesCount) + { + Destination = destination; + + _sources = new Operand[sourcesCount]; + + _srcUseNodes = new LinkedListNode[sourcesCount]; + } + + public Node(Operand[] destinations, int sourcesCount) + { + SetDestinations(destinations ?? throw new ArgumentNullException(nameof(destinations))); + + _sources = new Operand[sourcesCount]; + + _srcUseNodes = new LinkedListNode[sourcesCount]; + } + + public Operand GetDestination(int index) + { + return _destinations[index]; + } + + public Operand GetSource(int index) + { + return _sources[index]; + } + + public void SetDestination(int index, Operand destination) + { + Operand oldOp = _destinations[index]; + + if (oldOp != null && oldOp.Kind == OperandKind.LocalVariable) + { + oldOp.Assignments.Remove(_asgUseNodes[index]); + } + + if (destination != null && destination.Kind == OperandKind.LocalVariable) + { + _asgUseNodes[index] = destination.Assignments.AddLast(this); + } + + _destinations[index] = destination; + } + + public void SetSource(int index, Operand source) + { + Operand oldOp = _sources[index]; + + if (oldOp != null && oldOp.Kind == OperandKind.LocalVariable) + { + oldOp.Uses.Remove(_srcUseNodes[index]); + } + + if (source != null && source.Kind == OperandKind.LocalVariable) + { + _srcUseNodes[index] = source.Uses.AddLast(this); + } + + _sources[index] = source; + } + + public void SetDestinations(Operand[] destinations) + { + if (_destinations != null) + { + for (int index = 0; index < _destinations.Length; index++) + { + Operand oldOp = _destinations[index]; + + if (oldOp != null && oldOp.Kind == OperandKind.LocalVariable) + { + oldOp.Assignments.Remove(_asgUseNodes[index]); + } + } + + _destinations = destinations; + } + else + { + _destinations = new Operand[destinations.Length]; + } + + _asgUseNodes = new LinkedListNode[destinations.Length]; + + for (int index = 0; index < destinations.Length; index++) + { + Operand newOp = destinations[index]; + + _destinations[index] = newOp; + + if (newOp.Kind == OperandKind.LocalVariable) + { + _asgUseNodes[index] = newOp.Assignments.AddLast(this); + } + } + } + + public void SetSources(Operand[] sources) + { + for (int index = 0; index < _sources.Length; index++) + { + Operand oldOp = _sources[index]; + + if (oldOp != null && oldOp.Kind == OperandKind.LocalVariable) + { + oldOp.Uses.Remove(_srcUseNodes[index]); + } + } + + _sources = new Operand[sources.Length]; + + _srcUseNodes = new LinkedListNode[sources.Length]; + + for (int index = 0; index < sources.Length; index++) + { + Operand newOp = sources[index]; + + _sources[index] = newOp; + + if (newOp.Kind == OperandKind.LocalVariable) + { + _srcUseNodes[index] = newOp.Uses.AddLast(this); + } + } + } + } +} \ No newline at end of file diff --git a/ARMeilleure/IntermediateRepresentation/Operand.cs b/ARMeilleure/IntermediateRepresentation/Operand.cs new file mode 100644 index 0000000000..2df6256fc6 --- /dev/null +++ b/ARMeilleure/IntermediateRepresentation/Operand.cs @@ -0,0 +1,124 @@ +using System; +using System.Collections.Generic; + +namespace ARMeilleure.IntermediateRepresentation +{ + class Operand + { + public OperandKind Kind { get; } + + public OperandType Type { get; } + + public ulong Value { get; private set; } + + public LinkedList Assignments { get; } + public LinkedList Uses { get; } + + private Operand() + { + Assignments = new LinkedList(); + Uses = new LinkedList(); + } + + public Operand(OperandKind kind, OperandType type = OperandType.None) : this() + { + Kind = kind; + Type = type; + } + + public Operand(int value) : this(OperandKind.Constant, OperandType.I32) + { + Value = (uint)value; + } + + public Operand(uint value) : this(OperandKind.Constant, OperandType.I32) + { + Value = (uint)value; + } + + public Operand(long value) : this(OperandKind.Constant, OperandType.I64) + { + Value = (ulong)value; + } + + public Operand(ulong value) : this(OperandKind.Constant, OperandType.I64) + { + Value = value; + } + + public Operand(float value) : this(OperandKind.Constant, OperandType.FP32) + { + Value = (ulong)BitConverter.SingleToInt32Bits(value); + } + + public Operand(double value) : this(OperandKind.Constant, OperandType.FP64) + { + Value = (ulong)BitConverter.DoubleToInt64Bits(value); + } + + public Operand(int index, RegisterType regType, OperandType type) : this() + { + Kind = OperandKind.Register; + Type = type; + + Value = (ulong)((int)regType << 24 | index); + } + + public Register GetRegister() + { + return new Register((int)Value & 0xffffff, (RegisterType)(Value >> 24)); + } + + 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); + } + + internal void NumberLocal(int number) + { + if (Kind != OperandKind.LocalVariable) + { + throw new InvalidOperationException("The operand is not a local variable."); + } + + Value = (ulong)number; + } + + public override int GetHashCode() + { + if (Kind == OperandKind.LocalVariable) + { + return base.GetHashCode(); + } + else + { + return (int)Value ^ ((int)Kind << 16) ^ ((int)Type << 20); + } + } + } +} \ No newline at end of file diff --git a/ARMeilleure/IntermediateRepresentation/OperandHelper.cs b/ARMeilleure/IntermediateRepresentation/OperandHelper.cs new file mode 100644 index 0000000000..4a930e03f4 --- /dev/null +++ b/ARMeilleure/IntermediateRepresentation/OperandHelper.cs @@ -0,0 +1,68 @@ +using ARMeilleure.State; +using System; + +namespace ARMeilleure.IntermediateRepresentation +{ + static class OperandHelper + { + public static Operand Const(OperandType type, long value) + { + return type == OperandType.I32 ? new Operand((int)value) : new Operand(value); + } + + public static Operand Const(bool value) + { + return new Operand(value ? 1 : 0); + } + + public static Operand Const(int value) + { + return new Operand(value); + } + + public static Operand Const(uint value) + { + return new Operand(value); + } + + public static Operand Const(long value) + { + return new Operand(value); + } + + public static Operand Const(ulong value) + { + return new Operand(value); + } + + public static Operand ConstF(float value) + { + return new Operand(value); + } + + public static Operand ConstF(double value) + { + return new Operand(value); + } + + public static Operand Label() + { + return new Operand(OperandKind.Label); + } + + public static Operand Local(OperandType type) + { + return new Operand(OperandKind.LocalVariable, type); + } + + public static Operand Register(int index, RegisterType regType, OperandType type) + { + return new Operand(index, regType, type); + } + + public static Operand Undef() + { + return new Operand(OperandKind.Undefined); + } + } +} \ No newline at end of file diff --git a/ARMeilleure/IntermediateRepresentation/OperandKind.cs b/ARMeilleure/IntermediateRepresentation/OperandKind.cs new file mode 100644 index 0000000000..5761835344 --- /dev/null +++ b/ARMeilleure/IntermediateRepresentation/OperandKind.cs @@ -0,0 +1,12 @@ +namespace ARMeilleure.IntermediateRepresentation +{ + enum OperandKind + { + Constant, + Label, + LocalVariable, + Memory, + Register, + Undefined + } +} \ No newline at end of file diff --git a/ARMeilleure/IntermediateRepresentation/OperandType.cs b/ARMeilleure/IntermediateRepresentation/OperandType.cs new file mode 100644 index 0000000000..bfdf5130cf --- /dev/null +++ b/ARMeilleure/IntermediateRepresentation/OperandType.cs @@ -0,0 +1,51 @@ +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}\"."); + } + } +} \ No newline at end of file diff --git a/ARMeilleure/IntermediateRepresentation/Operation.cs b/ARMeilleure/IntermediateRepresentation/Operation.cs new file mode 100644 index 0000000000..620bf3f6e2 --- /dev/null +++ b/ARMeilleure/IntermediateRepresentation/Operation.cs @@ -0,0 +1,40 @@ +namespace ARMeilleure.IntermediateRepresentation +{ + class Operation : Node + { + public Instruction Instruction { get; private set; } + + public Operation( + Instruction instruction, + Operand destination, + params Operand[] sources) : base(destination, sources.Length) + { + Instruction = instruction; + + for (int index = 0; index < sources.Length; index++) + { + SetSource(index, sources[index]); + } + } + + public Operation( + Instruction instruction, + Operand[] destinations, + Operand[] sources) : base(destinations, sources.Length) + { + Instruction = instruction; + + for (int index = 0; index < sources.Length; index++) + { + SetSource(index, sources[index]); + } + } + + public void TurnIntoCopy(Operand source) + { + Instruction = Instruction.Copy; + + SetSources(new Operand[] { source }); + } + } +} \ No newline at end of file diff --git a/ARMeilleure/IntermediateRepresentation/PhiNode.cs b/ARMeilleure/IntermediateRepresentation/PhiNode.cs new file mode 100644 index 0000000000..30fc4d384c --- /dev/null +++ b/ARMeilleure/IntermediateRepresentation/PhiNode.cs @@ -0,0 +1,22 @@ +namespace ARMeilleure.IntermediateRepresentation +{ + class PhiNode : Node + { + private BasicBlock[] _blocks; + + public PhiNode(Operand destination, int predecessorsCount) : base(destination, predecessorsCount) + { + _blocks = new BasicBlock[predecessorsCount]; + } + + public BasicBlock GetBlock(int index) + { + return _blocks[index]; + } + + public void SetBlock(int index, BasicBlock block) + { + _blocks[index] = block; + } + } +} \ No newline at end of file diff --git a/ARMeilleure/IntermediateRepresentation/Register.cs b/ARMeilleure/IntermediateRepresentation/Register.cs new file mode 100644 index 0000000000..745b315382 --- /dev/null +++ b/ARMeilleure/IntermediateRepresentation/Register.cs @@ -0,0 +1,43 @@ +using System; + +namespace ARMeilleure.IntermediateRepresentation +{ + 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/ARMeilleure/IntermediateRepresentation/RegisterType.cs b/ARMeilleure/IntermediateRepresentation/RegisterType.cs new file mode 100644 index 0000000000..e71795cb94 --- /dev/null +++ b/ARMeilleure/IntermediateRepresentation/RegisterType.cs @@ -0,0 +1,9 @@ +namespace ARMeilleure.IntermediateRepresentation +{ + enum RegisterType + { + Integer, + Vector, + Flag + } +} \ No newline at end of file diff --git a/ARMeilleure/Memory/MemoryHelper.cs b/ARMeilleure/Memory/MemoryHelper.cs new file mode 100644 index 0000000000..8e310d4d7f --- /dev/null +++ b/ARMeilleure/Memory/MemoryHelper.cs @@ -0,0 +1,71 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; + +namespace ARMeilleure.Memory +{ + public static class MemoryHelper + { + public static void FillWithZeros(MemoryManager memory, long position, int size) + { + int size8 = size & ~(8 - 1); + + for (int offs = 0; offs < size8; offs += 8) + { + memory.WriteInt64(position + offs, 0); + } + + for (int offs = size8; offs < (size - size8); offs++) + { + memory.WriteByte(position + offs, 0); + } + } + + public unsafe static T Read(MemoryManager memory, long position) where T : struct + { + long size = Marshal.SizeOf(); + + byte[] data = memory.ReadBytes(position, size); + + fixed (byte* ptr = data) + { + return Marshal.PtrToStructure((IntPtr)ptr); + } + } + + public unsafe static void Write(MemoryManager memory, long position, T value) where T : struct + { + long size = Marshal.SizeOf(); + + byte[] data = new byte[size]; + + fixed (byte* ptr = data) + { + Marshal.StructureToPtr(value, (IntPtr)ptr, false); + } + + memory.WriteBytes(position, data); + } + + public static string ReadAsciiString(MemoryManager memory, long position, long maxSize = -1) + { + using (MemoryStream ms = new MemoryStream()) + { + for (long offs = 0; offs < maxSize || maxSize == -1; offs++) + { + byte value = (byte)memory.ReadByte(position + offs); + + if (value == 0) + { + break; + } + + ms.WriteByte(value); + } + + return Encoding.ASCII.GetString(ms.ToArray()); + } + } + } +} \ No newline at end of file diff --git a/ARMeilleure/Memory/MemoryManagement.cs b/ARMeilleure/Memory/MemoryManagement.cs new file mode 100644 index 0000000000..e299ae49da --- /dev/null +++ b/ARMeilleure/Memory/MemoryManagement.cs @@ -0,0 +1,90 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace ARMeilleure.Memory +{ + public static class MemoryManagement + { + public static IntPtr Allocate(ulong size) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + IntPtr sizeNint = new IntPtr((long)size); + + return MemoryManagementWindows.Allocate(sizeNint); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || + RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + return MemoryManagementUnix.Allocate(size); + } + else + { + throw new PlatformNotSupportedException(); + } + } + + public static IntPtr AllocateWriteTracked(ulong size) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + IntPtr sizeNint = new IntPtr((long)size); + + return MemoryManagementWindows.AllocateWriteTracked(sizeNint); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || + RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + return MemoryManagementUnix.Allocate(size); + } + else + { + throw new PlatformNotSupportedException(); + } + } + + public static void Reprotect(IntPtr address, ulong size, MemoryProtection permission) + { + bool result; + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + IntPtr sizeNint = new IntPtr((long)size); + + result = MemoryManagementWindows.Reprotect(address, sizeNint, permission); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || + RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + result = MemoryManagementUnix.Reprotect(address, size, permission); + } + else + { + throw new PlatformNotSupportedException(); + } + + if (!result) + { + throw new MemoryProtectionException(permission); + } + } + + public static bool Free(IntPtr address) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return MemoryManagementWindows.Free(address); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || + RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + return MemoryManagementUnix.Free(address); + } + else + { + throw new PlatformNotSupportedException(); + } + } + } +} \ No newline at end of file diff --git a/ARMeilleure/Memory/MemoryManagementUnix.cs b/ARMeilleure/Memory/MemoryManagementUnix.cs new file mode 100644 index 0000000000..3331fb428f --- /dev/null +++ b/ARMeilleure/Memory/MemoryManagementUnix.cs @@ -0,0 +1,71 @@ +using Mono.Unix.Native; +using System; + +namespace ARMeilleure.Memory +{ + static class MemoryManagementUnix + { + public static IntPtr Allocate(ulong size) + { + ulong pageSize = (ulong)Syscall.sysconf(SysconfName._SC_PAGESIZE); + + const MmapProts prot = MmapProts.PROT_READ | MmapProts.PROT_WRITE; + + const MmapFlags flags = MmapFlags.MAP_PRIVATE | MmapFlags.MAP_ANONYMOUS; + + IntPtr ptr = Syscall.mmap(IntPtr.Zero, size + pageSize, prot, flags, -1, 0); + + if (ptr == IntPtr.Zero) + { + throw new OutOfMemoryException(); + } + + unsafe + { + ptr = new IntPtr(ptr.ToInt64() + (long)pageSize); + + *((ulong*)ptr - 1) = size; + } + + return ptr; + } + + public static bool Reprotect(IntPtr address, ulong size, Memory.MemoryProtection protection) + { + MmapProts prot = GetProtection(protection); + + return Syscall.mprotect(address, size, prot) == 0; + } + + private static MmapProts GetProtection(Memory.MemoryProtection protection) + { + switch (protection) + { + case Memory.MemoryProtection.None: return MmapProts.PROT_NONE; + case Memory.MemoryProtection.Read: return MmapProts.PROT_READ; + case Memory.MemoryProtection.ReadAndWrite: return MmapProts.PROT_READ | MmapProts.PROT_WRITE; + case Memory.MemoryProtection.ReadAndExecute: return MmapProts.PROT_READ | MmapProts.PROT_EXEC; + case Memory.MemoryProtection.ReadWriteExecute: return MmapProts.PROT_READ | MmapProts.PROT_WRITE | MmapProts.PROT_EXEC; + case Memory.MemoryProtection.Execute: return MmapProts.PROT_EXEC; + + default: throw new ArgumentException($"Invalid permission \"{protection}\"."); + } + } + + public static bool Free(IntPtr address) + { + ulong pageSize = (ulong)Syscall.sysconf(SysconfName._SC_PAGESIZE); + + ulong size; + + unsafe + { + size = *((ulong*)address - 1); + + address = new IntPtr(address.ToInt64() - (long)pageSize); + } + + return Syscall.munmap(address, size + pageSize) == 0; + } + } +} \ No newline at end of file diff --git a/ARMeilleure/Memory/MemoryManagementWindows.cs b/ARMeilleure/Memory/MemoryManagementWindows.cs new file mode 100644 index 0000000000..ae64b5c62b --- /dev/null +++ b/ARMeilleure/Memory/MemoryManagementWindows.cs @@ -0,0 +1,119 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace ARMeilleure.Memory +{ + static class MemoryManagementWindows + { + [Flags] + private enum AllocationType : uint + { + Commit = 0x1000, + Reserve = 0x2000, + Decommit = 0x4000, + Release = 0x8000, + Reset = 0x80000, + Physical = 0x400000, + TopDown = 0x100000, + WriteWatch = 0x200000, + LargePages = 0x20000000 + } + + [Flags] + private enum MemoryProtection : uint + { + NoAccess = 0x01, + ReadOnly = 0x02, + ReadWrite = 0x04, + WriteCopy = 0x08, + Execute = 0x10, + ExecuteRead = 0x20, + ExecuteReadWrite = 0x40, + ExecuteWriteCopy = 0x80, + GuardModifierflag = 0x100, + NoCacheModifierflag = 0x200, + WriteCombineModifierflag = 0x400 + } + + [DllImport("kernel32.dll")] + private static extern IntPtr VirtualAlloc( + IntPtr lpAddress, + IntPtr dwSize, + AllocationType flAllocationType, + MemoryProtection flProtect); + + [DllImport("kernel32.dll")] + private static extern bool VirtualProtect( + IntPtr lpAddress, + IntPtr dwSize, + MemoryProtection flNewProtect, + out MemoryProtection lpflOldProtect); + + [DllImport("kernel32.dll")] + private static extern bool VirtualFree( + IntPtr lpAddress, + IntPtr dwSize, + AllocationType dwFreeType); + + public static IntPtr Allocate(IntPtr size) + { + const AllocationType flags = + AllocationType.Reserve | + AllocationType.Commit; + + IntPtr ptr = VirtualAlloc(IntPtr.Zero, size, flags, MemoryProtection.ReadWrite); + + if (ptr == IntPtr.Zero) + { + throw new OutOfMemoryException(); + } + + return ptr; + } + + public static IntPtr AllocateWriteTracked(IntPtr size) + { + const AllocationType flags = + AllocationType.Reserve | + AllocationType.Commit | + AllocationType.WriteWatch; + + IntPtr ptr = VirtualAlloc(IntPtr.Zero, size, flags, MemoryProtection.ReadWrite); + + if (ptr == IntPtr.Zero) + { + throw new OutOfMemoryException(); + } + + return ptr; + } + + public static bool Reprotect(IntPtr address, IntPtr size, Memory.MemoryProtection protection) + { + MemoryProtection prot = GetProtection(protection); + + return VirtualProtect(address, size, prot, out _); + } + + private static MemoryProtection GetProtection(Memory.MemoryProtection protection) + { + switch (protection) + { + case Memory.MemoryProtection.None: return MemoryProtection.NoAccess; + case Memory.MemoryProtection.Read: return MemoryProtection.ReadOnly; + case Memory.MemoryProtection.ReadAndWrite: return MemoryProtection.ReadWrite; + case Memory.MemoryProtection.ReadAndExecute: return MemoryProtection.ExecuteRead; + case Memory.MemoryProtection.ReadWriteExecute: return MemoryProtection.ExecuteReadWrite; + case Memory.MemoryProtection.Execute: return MemoryProtection.Execute; + + default: throw new ArgumentException($"Invalid permission \"{protection}\"."); + } + } + + public static bool Free(IntPtr address) + { + return VirtualFree(address, IntPtr.Zero, AllocationType.Release); + } + } +} \ No newline at end of file diff --git a/ARMeilleure/Memory/MemoryManager.cs b/ARMeilleure/Memory/MemoryManager.cs new file mode 100644 index 0000000000..e4e8b2d26d --- /dev/null +++ b/ARMeilleure/Memory/MemoryManager.cs @@ -0,0 +1,738 @@ +using ARMeilleure.State; +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; + +using static ARMeilleure.Memory.MemoryManagement; + +namespace ARMeilleure.Memory +{ + public unsafe class MemoryManager + { + public const int PageBits = 12; + public const int PageSize = 1 << PageBits; + public const int PageMask = PageSize - 1; + + internal const long PteFlagsMask = 7; + + public IntPtr Ram { get; private set; } + + private byte* _ramPtr; + + private IntPtr _pageTable; + + internal IntPtr PageTable => _pageTable; + + internal int PtLevelBits { get; } + internal int PtLevelSize { get; } + internal int PtLevelMask { get; } + + public int AddressSpaceBits { get; } + public long AddressSpaceSize { get; } + + public MemoryManager( + IntPtr ram, + int addressSpaceBits = 48, + bool useFlatPageTable = false) + { + Ram = ram; + + _ramPtr = (byte*)ram; + + AddressSpaceBits = addressSpaceBits; + AddressSpaceSize = 1L << addressSpaceBits; + + // When flat page table is requested, we use a single + // array for the mappings of the entire address space. + // This has better performance, but also high memory usage. + // The multi level page table uses 9 bits per level, so + // the memory usage is lower, but the performance is also + // lower, since each address translation requires multiple reads. + if (useFlatPageTable) + { + PtLevelBits = addressSpaceBits - PageBits; + } + else + { + PtLevelBits = 9; + } + + PtLevelSize = 1 << PtLevelBits; + PtLevelMask = PtLevelSize - 1; + + _pageTable = Allocate((ulong)(PtLevelSize * IntPtr.Size)); + } + + public void Map(long va, long pa, long size) + { + SetPtEntries(va, _ramPtr + pa, size); + } + + public void Unmap(long position, long size) + { + SetPtEntries(position, null, size); + } + + public bool IsMapped(long position) + { + return Translate(position) != IntPtr.Zero; + } + + public long GetPhysicalAddress(long virtualAddress) + { + byte* ptr = (byte*)Translate(virtualAddress); + + return (long)(ptr - _ramPtr); + } + + private IntPtr Translate(long position) + { + if (!IsValidPosition(position)) + { + return IntPtr.Zero; + } + + byte* ptr = GetPtEntry(position); + + ulong ptrUlong = (ulong)ptr; + + if ((ptrUlong & PteFlagsMask) != 0) + { + ptrUlong &= ~(ulong)PteFlagsMask; + + ptr = (byte*)ptrUlong; + } + + if (ptr == null) + { + return IntPtr.Zero; + } + + return new IntPtr(ptr + (position & PageMask)); + } + + private IntPtr TranslateWrite(long position) + { + if (!IsValidPosition(position)) + { + return IntPtr.Zero; + } + + byte* ptr = GetPtEntry(position); + + ulong ptrUlong = (ulong)ptr; + + if ((ptrUlong & PteFlagsMask) != 0) + { + ClearPtEntryFlag(position, PteFlagsMask); + + ptrUlong &= ~(ulong)PteFlagsMask; + + ptr = (byte*)ptrUlong; + } + + return new IntPtr(ptr + (position & PageMask)); + } + + private byte* GetPtEntry(long position) + { + return *(byte**)GetPtPtr(position); + } + + private void SetPtEntries(long va, byte* ptr, long size) + { + long endPosition = (va + size + PageMask) & ~PageMask; + + while ((ulong)va < (ulong)endPosition) + { + SetPtEntry(va, ptr); + + va += PageSize; + + if (ptr != null) + { + ptr += PageSize; + } + } + } + + private void SetPtEntry(long position, byte* ptr) + { + *(byte**)GetPtPtr(position) = ptr; + } + + private void SetPtEntryFlag(long position, long flag) + { + ModifyPtEntryFlag(position, flag, setFlag: true); + } + + private void ClearPtEntryFlag(long position, long flag) + { + ModifyPtEntryFlag(position, flag, setFlag: false); + } + + private void ModifyPtEntryFlag(long position, long flag, bool setFlag) + { + IntPtr* pt = (IntPtr*)_pageTable; + + while (true) + { + IntPtr* ptPtr = GetPtPtr(position); + + IntPtr old = *ptPtr; + + long modified = old.ToInt64(); + + if (setFlag) + { + modified |= flag; + } + else + { + modified &= ~flag; + } + + IntPtr origValue = Interlocked.CompareExchange(ref *ptPtr, new IntPtr(modified), old); + + if (origValue == old) + { + break; + } + } + } + + private IntPtr* GetPtPtr(long position) + { + if (!IsValidPosition(position)) + { + throw new ArgumentOutOfRangeException(nameof(position)); + } + + IntPtr nextPtr = _pageTable; + + IntPtr* ptePtr = null; + + int bit = PageBits; + + while (true) + { + long index = (position >> bit) & PtLevelMask; + + ptePtr = &((IntPtr*)nextPtr)[index]; + + bit += PtLevelBits; + + if (bit >= AddressSpaceBits) + { + break; + } + + nextPtr = *ptePtr; + + if (nextPtr == IntPtr.Zero) + { + // Entry does not yet exist, allocate a new one. + IntPtr newPtr = Allocate((ulong)(PtLevelSize * IntPtr.Size)); + + // Try to swap the current pointer (should be zero), with the allocated one. + nextPtr = Interlocked.CompareExchange(ref *ptePtr, newPtr, IntPtr.Zero); + + // If the old pointer is not null, then another thread already has set it. + if (nextPtr != IntPtr.Zero) + { + Free(newPtr); + } + else + { + nextPtr = newPtr; + } + } + } + + return ptePtr; + } + + public unsafe (ulong, ulong)[] GetModifiedRanges(ulong address, ulong size, int id) + { + ulong idMask = 1UL << id; + + List<(ulong, ulong)> ranges = new List<(ulong, ulong)>(); + + ulong endAddress = (address + size + PageMask) & ~(ulong)PageMask; + + address &= ~(ulong)PageMask; + + ulong currAddr = address; + ulong currSize = 0; + + while (address < endAddress) + { + // If the address is invalid, we stop and consider all the remaining memory + // as not modified (since the address is invalid, we can't check, and technically + // the memory doesn't exist). + if (!IsValidPosition((long)address)) + { + break; + } + + byte* ptr = ((byte**)_pageTable)[address >> PageBits]; + + ulong ptrUlong = (ulong)ptr; + + if ((ptrUlong & idMask) == 0) + { + // Modified. + currSize += PageSize; + + SetPtEntryFlag((long)address, (long)idMask); + } + else + { + if (currSize != 0) + { + ranges.Add((currAddr, currSize)); + } + + currAddr = address + PageSize; + currSize = 0; + } + + address += PageSize; + } + + if (currSize != 0) + { + ranges.Add((currAddr, currSize)); + } + + return ranges.ToArray(); + } + + private bool IsContiguous(long position, long size) + { + long endPos = position + size; + + position &= ~PageMask; + + long expectedPa = GetPhysicalAddress(position); + + while ((ulong)position < (ulong)endPos) + { + long pa = GetPhysicalAddress(position); + + if (pa != expectedPa) + { + return false; + } + + position += PageSize; + expectedPa += PageSize; + } + + return true; + } + + public bool IsValidPosition(long position) + { + return (ulong)position < (ulong)AddressSpaceSize; + } + + internal V128 AtomicLoadInt128(long position) + { + if ((position & 0xf) != 0) + { + AbortWithAlignmentFault(position); + } + + IntPtr ptr = TranslateWrite(position); + + return MemoryManagerPal.AtomicLoad128(ptr); + } + + internal bool AtomicCompareExchangeByte(long position, byte expected, byte desired) + { + int* ptr = (int*)Translate(position); + + int currentValue = *ptr; + + int expected32 = (currentValue & ~byte.MaxValue) | expected; + int desired32 = (currentValue & ~byte.MaxValue) | desired; + + return Interlocked.CompareExchange(ref *ptr, desired32, expected32) == expected32; + } + + internal bool AtomicCompareExchangeInt16(long position, short expected, short desired) + { + if ((position & 1) != 0) + { + AbortWithAlignmentFault(position); + } + + int* ptr = (int*)Translate(position); + + int currentValue = *ptr; + + int expected32 = (currentValue & ~ushort.MaxValue) | (ushort)expected; + int desired32 = (currentValue & ~ushort.MaxValue) | (ushort)desired; + + return Interlocked.CompareExchange(ref *ptr, desired32, expected32) == expected32; + } + + public bool AtomicCompareExchangeInt32(long position, int expected, int desired) + { + if ((position & 3) != 0) + { + AbortWithAlignmentFault(position); + } + + int* ptr = (int*)TranslateWrite(position); + + return Interlocked.CompareExchange(ref *ptr, desired, expected) == expected; + } + + internal bool AtomicCompareExchangeInt64(long position, long expected, long desired) + { + if ((position & 7) != 0) + { + AbortWithAlignmentFault(position); + } + + long* ptr = (long*)TranslateWrite(position); + + return Interlocked.CompareExchange(ref *ptr, desired, expected) == expected; + } + + internal bool AtomicCompareExchangeInt128(long position, V128 expected, V128 desired) + { + if ((position & 0xf) != 0) + { + AbortWithAlignmentFault(position); + } + + IntPtr ptr = TranslateWrite(position); + + return MemoryManagerPal.CompareAndSwap128(ptr, expected, desired) == expected; + } + + public int AtomicIncrementInt32(long position) + { + if ((position & 3) != 0) + { + AbortWithAlignmentFault(position); + } + + int* ptr = (int*)TranslateWrite(position); + + return Interlocked.Increment(ref *ptr); + } + + public int AtomicDecrementInt32(long position) + { + if ((position & 3) != 0) + { + AbortWithAlignmentFault(position); + } + + int* ptr = (int*)TranslateWrite(position); + + return Interlocked.Decrement(ref *ptr); + } + + private void AbortWithAlignmentFault(long position) + { + // TODO: Abort mode and exception support on the CPU. + throw new InvalidOperationException($"Tried to compare exchange a misaligned address 0x{position:X16}."); + } + + public sbyte ReadSByte(long position) + { + return (sbyte)ReadByte(position); + } + + public short ReadInt16(long position) + { + return (short)ReadUInt16(position); + } + + public int ReadInt32(long position) + { + return (int)ReadUInt32(position); + } + + public long ReadInt64(long position) + { + return (long)ReadUInt64(position); + } + + public byte ReadByte(long position) + { + return *((byte*)Translate(position)); + } + + public ushort ReadUInt16(long position) + { + if ((position & 1) == 0) + { + return *((ushort*)Translate(position)); + } + else + { + return (ushort)(ReadByte(position + 0) << 0 | + ReadByte(position + 1) << 8); + } + } + + public uint ReadUInt32(long position) + { + if ((position & 3) == 0) + { + return *((uint*)Translate(position)); + } + else + { + return (uint)(ReadUInt16(position + 0) << 0 | + ReadUInt16(position + 2) << 16); + } + } + + public ulong ReadUInt64(long position) + { + if ((position & 7) == 0) + { + return *((ulong*)Translate(position)); + } + else + { + return (ulong)ReadUInt32(position + 0) << 0 | + (ulong)ReadUInt32(position + 4) << 32; + } + } + + public V128 ReadVector128(long position) + { + return new V128(ReadUInt64(position), ReadUInt64(position + 8)); + } + + public byte[] ReadBytes(long position, long size) + { + long endAddr = position + size; + + if ((ulong)size > int.MaxValue) + { + throw new ArgumentOutOfRangeException(nameof(size)); + } + + if ((ulong)endAddr < (ulong)position) + { + throw new ArgumentOutOfRangeException(nameof(position)); + } + + byte[] data = new byte[size]; + + int offset = 0; + + while ((ulong)position < (ulong)endAddr) + { + long pageLimit = (position + PageSize) & ~(long)PageMask; + + if ((ulong)pageLimit > (ulong)endAddr) + { + pageLimit = endAddr; + } + + int copySize = (int)(pageLimit - position); + + Marshal.Copy(Translate(position), data, offset, copySize); + + position += copySize; + offset += copySize; + } + + return data; + } + + public ReadOnlySpan GetSpan(ulong address, ulong size) + { + if (IsContiguous(address, size)) + { + return new ReadOnlySpan((void*)Translate((long)address), (int)size); + } + else + { + return ReadBytes((long)address, (long)size); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool IsContiguous(ulong address, ulong size) + { + if (!IsValidPosition((long)address)) + { + return false; + } + + ulong endVa = (address + size + PageMask) & ~(ulong)PageMask; + + address &= ~(ulong)PageMask; + + int pages = (int)((endVa - address) / PageSize); + + for (int page = 0; page < pages - 1; page++) + { + if (!IsValidPosition((long)address + PageSize)) + { + return false; + } + + if (GetPtEntry((long)address) + PageSize != GetPtEntry((long)address + PageSize)) + { + return false; + } + + address += PageSize; + } + + return true; + } + + public void WriteSByte(long position, sbyte value) + { + WriteByte(position, (byte)value); + } + + public void WriteInt16(long position, short value) + { + WriteUInt16(position, (ushort)value); + } + + public void WriteInt32(long position, int value) + { + WriteUInt32(position, (uint)value); + } + + public void WriteInt64(long position, long value) + { + WriteUInt64(position, (ulong)value); + } + + public void WriteByte(long position, byte value) + { + *((byte*)TranslateWrite(position)) = value; + } + + public void WriteUInt16(long position, ushort value) + { + if ((position & 1) == 0) + { + *((ushort*)TranslateWrite(position)) = value; + } + else + { + WriteByte(position + 0, (byte)(value >> 0)); + WriteByte(position + 1, (byte)(value >> 8)); + } + } + + public void WriteUInt32(long position, uint value) + { + if ((position & 3) == 0) + { + *((uint*)TranslateWrite(position)) = value; + } + else + { + WriteUInt16(position + 0, (ushort)(value >> 0)); + WriteUInt16(position + 2, (ushort)(value >> 16)); + } + } + + public void WriteUInt64(long position, ulong value) + { + if ((position & 7) == 0) + { + *((ulong*)TranslateWrite(position)) = value; + } + else + { + WriteUInt32(position + 0, (uint)(value >> 0)); + WriteUInt32(position + 4, (uint)(value >> 32)); + } + } + + public void WriteVector128(long position, V128 value) + { + WriteUInt64(position + 0, value.GetUInt64(0)); + WriteUInt64(position + 8, value.GetUInt64(1)); + } + + public void WriteBytes(long position, byte[] data) + { + long endAddr = position + data.Length; + + if ((ulong)endAddr < (ulong)position) + { + throw new ArgumentOutOfRangeException(nameof(position)); + } + + int offset = 0; + + while ((ulong)position < (ulong)endAddr) + { + long pageLimit = (position + PageSize) & ~(long)PageMask; + + if ((ulong)pageLimit > (ulong)endAddr) + { + pageLimit = endAddr; + } + + int copySize = (int)(pageLimit - position); + + Marshal.Copy(data, offset, TranslateWrite(position), copySize); + + position += copySize; + offset += copySize; + } + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + IntPtr ptr = Interlocked.Exchange(ref _pageTable, IntPtr.Zero); + + if (ptr != IntPtr.Zero) + { + FreePageTableEntry(ptr, PageBits); + } + } + + private void FreePageTableEntry(IntPtr ptr, int levelBitEnd) + { + levelBitEnd += PtLevelBits; + + if (levelBitEnd >= AddressSpaceBits) + { + Free(ptr); + + return; + } + + for (int index = 0; index < PtLevelSize; index++) + { + IntPtr ptePtr = ((IntPtr*)ptr)[index]; + + if (ptePtr != IntPtr.Zero) + { + FreePageTableEntry(ptePtr, levelBitEnd); + } + } + + Free(ptr); + } + } +} \ No newline at end of file diff --git a/ARMeilleure/Memory/MemoryManagerPal.cs b/ARMeilleure/Memory/MemoryManagerPal.cs new file mode 100644 index 0000000000..64191a0acb --- /dev/null +++ b/ARMeilleure/Memory/MemoryManagerPal.cs @@ -0,0 +1,77 @@ +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; +using System; + +namespace ARMeilleure.Memory +{ + static class MemoryManagerPal + { + private delegate V128 CompareExchange128(IntPtr address, V128 expected, V128 desired); + + private static CompareExchange128 _compareExchange128; + + private static object _lock; + + static MemoryManagerPal() + { + _lock = new object(); + } + + public static V128 AtomicLoad128(IntPtr address) + { + return GetCompareAndSwap128()(address, V128.Zero, V128.Zero); + } + + public static V128 CompareAndSwap128(IntPtr address, V128 expected, V128 desired) + { + return GetCompareAndSwap128()(address, expected, desired); + } + + private static CompareExchange128 GetCompareAndSwap128() + { + if (_compareExchange128 == null) + { + GenerateCompareAndSwap128(); + } + + return _compareExchange128; + } + + private static void GenerateCompareAndSwap128() + { + lock (_lock) + { + if (_compareExchange128 != null) + { + return; + } + + EmitterContext context = new EmitterContext(); + + Operand address = context.LoadArgument(OperandType.I64, 0); + Operand expected = context.LoadArgument(OperandType.V128, 1); + Operand desired = context.LoadArgument(OperandType.V128, 2); + + Operand result = context.CompareAndSwap128(address, expected, desired); + + context.Return(result); + + ControlFlowGraph cfg = context.GetControlFlowGraph(); + + OperandType[] argTypes = new OperandType[] + { + OperandType.I64, + OperandType.V128, + OperandType.V128 + }; + + _compareExchange128 = Compiler.Compile( + cfg, + argTypes, + OperandType.V128, + CompilerOptions.HighCq); + } + } + } +} \ No newline at end of file diff --git a/ARMeilleure/Memory/MemoryProtection.cs b/ARMeilleure/Memory/MemoryProtection.cs new file mode 100644 index 0000000000..6bc16f8ea1 --- /dev/null +++ b/ARMeilleure/Memory/MemoryProtection.cs @@ -0,0 +1,17 @@ +using System; + +namespace ARMeilleure.Memory +{ + [Flags] + public enum MemoryProtection + { + None = 0, + Read = 1 << 0, + Write = 1 << 1, + Execute = 1 << 2, + + ReadAndWrite = Read | Write, + ReadAndExecute = Read | Execute, + ReadWriteExecute = Read | Write | Execute + } +} \ No newline at end of file diff --git a/ARMeilleure/Memory/MemoryProtectionException.cs b/ARMeilleure/Memory/MemoryProtectionException.cs new file mode 100644 index 0000000000..6313ce6a1e --- /dev/null +++ b/ARMeilleure/Memory/MemoryProtectionException.cs @@ -0,0 +1,9 @@ +using System; + +namespace ARMeilleure.Memory +{ + class MemoryProtectionException : Exception + { + public MemoryProtectionException(MemoryProtection protection) : base($"Failed to set memory protection to \"{protection}\".") { } + } +} \ No newline at end of file diff --git a/ARMeilleure/Optimizations.cs b/ARMeilleure/Optimizations.cs new file mode 100644 index 0000000000..28af0936c8 --- /dev/null +++ b/ARMeilleure/Optimizations.cs @@ -0,0 +1,35 @@ +using ARMeilleure.CodeGen.X86; + +namespace ARMeilleure +{ + public static class Optimizations + { + public static bool AssumeStrictAbiCompliance { get; set; } = true; + + public static bool FastFP { 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 ForceLegacySse + { + get => HardwareCapabilities.ForceLegacySse; + set => HardwareCapabilities.ForceLegacySse = value; + } + + internal static bool UseSse => UseSseIfAvailable && HardwareCapabilities.SupportsSse; + internal static bool UseSse2 => UseSse2IfAvailable && HardwareCapabilities.SupportsSse2; + internal static bool UseSse3 => UseSse3IfAvailable && HardwareCapabilities.SupportsSse3; + internal static bool UseSsse3 => UseSsse3IfAvailable && HardwareCapabilities.SupportsSsse3; + internal static bool UseSse41 => UseSse41IfAvailable && HardwareCapabilities.SupportsSse41; + internal static bool UseSse42 => UseSse42IfAvailable && HardwareCapabilities.SupportsSse42; + internal static bool UsePopCnt => UsePopCntIfAvailable && HardwareCapabilities.SupportsPopcnt; + internal static bool UseAvx => UseAvxIfAvailable && HardwareCapabilities.SupportsAvx && !ForceLegacySse; + } +} \ No newline at end of file diff --git a/ARMeilleure/State/Aarch32Mode.cs b/ARMeilleure/State/Aarch32Mode.cs new file mode 100644 index 0000000000..395e288aab --- /dev/null +++ b/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/ARMeilleure/State/ExecutionContext.cs b/ARMeilleure/State/ExecutionContext.cs new file mode 100644 index 0000000000..406766803e --- /dev/null +++ b/ARMeilleure/State/ExecutionContext.cs @@ -0,0 +1,130 @@ +using System; +using System.Diagnostics; + +namespace ARMeilleure.State +{ + public class ExecutionContext + { + private const int MinCountForCheck = 40000; + + private NativeContext _nativeContext; + + internal IntPtr NativeContextPtr => _nativeContext.BasePtr; + + private bool _interrupted; + + private static Stopwatch _tickCounter; + + private static double _hostTickFreq; + + public uint CtrEl0 => 0x8444c004; + public uint DczidEl0 => 0x00000004; + + public ulong CntfrqEl0 { get; set; } + public ulong CntpctEl0 + { + get + { + double ticks = _tickCounter.ElapsedTicks * _hostTickFreq; + + return (ulong)(ticks * CntfrqEl0); + } + } + + public long TpidrEl0 { get; set; } + public long Tpidr { get; set; } + + public FPCR Fpcr { get; set; } + public FPSR Fpsr { get; set; } + + 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; set; } + + public event EventHandler Interrupt; + public event EventHandler Break; + public event EventHandler SupervisorCall; + public event EventHandler Undefined; + + static ExecutionContext() + { + _hostTickFreq = 1.0 / Stopwatch.Frequency; + + _tickCounter = new Stopwatch(); + + _tickCounter.Start(); + } + + public ExecutionContext() + { + _nativeContext = new NativeContext(); + + 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); + + internal void CheckInterrupt() + { + if (_interrupted) + { + _interrupted = false; + + Interrupt?.Invoke(this, EventArgs.Empty); + } + + _nativeContext.SetCounter(MinCountForCheck); + } + + public void RequestInterrupt() + { + _interrupted = true; + } + + internal void OnBreak(ulong address, int imm) + { + Break?.Invoke(this, new InstExceptionEventArgs(address, imm)); + } + + internal void OnSupervisorCall(ulong address, int imm) + { + SupervisorCall?.Invoke(this, new InstExceptionEventArgs(address, imm)); + } + + internal void OnUndefined(ulong address, int opCode) + { + Undefined?.Invoke(this, new InstUndefinedEventArgs(address, opCode)); + } + + public void Dispose() + { + _nativeContext.Dispose(); + } + } +} \ No newline at end of file diff --git a/ARMeilleure/State/ExecutionMode.cs b/ARMeilleure/State/ExecutionMode.cs new file mode 100644 index 0000000000..eaed9d27f1 --- /dev/null +++ b/ARMeilleure/State/ExecutionMode.cs @@ -0,0 +1,9 @@ +namespace ARMeilleure.State +{ + enum ExecutionMode + { + Aarch32Arm, + Aarch32Thumb, + Aarch64 + } +} \ No newline at end of file diff --git a/ARMeilleure/State/FPCR.cs b/ARMeilleure/State/FPCR.cs new file mode 100644 index 0000000000..511681fa94 --- /dev/null +++ b/ARMeilleure/State/FPCR.cs @@ -0,0 +1,23 @@ +using System; + +namespace ARMeilleure.State +{ + [Flags] + public enum FPCR + { + Ufe = 1 << 11, + Fz = 1 << 24, + Dn = 1 << 25, + Ahp = 1 << 26 + } + + public static class FPCRExtensions + { + private const int RModeShift = 22; + + public static FPRoundingMode GetRoundingMode(this FPCR fpcr) + { + return (FPRoundingMode)(((int)fpcr >> RModeShift) & 3); + } + } +} diff --git a/ARMeilleure/State/FPException.cs b/ARMeilleure/State/FPException.cs new file mode 100644 index 0000000000..e24e07af18 --- /dev/null +++ b/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/ChocolArm64/State/ARoundMode.cs b/ARMeilleure/State/FPRoundingMode.cs similarity index 70% rename from ChocolArm64/State/ARoundMode.cs rename to ARMeilleure/State/FPRoundingMode.cs index 9896f3075e..ee4f876686 100644 --- a/ChocolArm64/State/ARoundMode.cs +++ b/ARMeilleure/State/FPRoundingMode.cs @@ -1,10 +1,10 @@ -namespace ChocolArm64.State +namespace ARMeilleure.State { - public enum ARoundMode + public enum FPRoundingMode { ToNearest = 0, TowardsPlusInfinity = 1, TowardsMinusInfinity = 2, TowardsZero = 3 } -} \ No newline at end of file +} diff --git a/ARMeilleure/State/FPSR.cs b/ARMeilleure/State/FPSR.cs new file mode 100644 index 0000000000..c20dc43930 --- /dev/null +++ b/ARMeilleure/State/FPSR.cs @@ -0,0 +1,11 @@ +using System; + +namespace ARMeilleure.State +{ + [Flags] + public enum FPSR + { + Ufc = 1 << 3, + Qc = 1 << 27 + } +} diff --git a/ARMeilleure/State/FPType.cs b/ARMeilleure/State/FPType.cs new file mode 100644 index 0000000000..84e0db8da2 --- /dev/null +++ b/ARMeilleure/State/FPType.cs @@ -0,0 +1,11 @@ +namespace ARMeilleure.State +{ + enum FPType + { + Nonzero, + Zero, + Infinity, + QNaN, + SNaN + } +} diff --git a/ARMeilleure/State/InstExceptionEventArgs.cs b/ARMeilleure/State/InstExceptionEventArgs.cs new file mode 100644 index 0000000000..c2460e4b4f --- /dev/null +++ b/ARMeilleure/State/InstExceptionEventArgs.cs @@ -0,0 +1,16 @@ +using System; + +namespace ARMeilleure.State +{ + public class InstExceptionEventArgs : EventArgs + { + public ulong Address { get; } + public int Id { get; } + + public InstExceptionEventArgs(ulong address, int id) + { + Address = address; + Id = id; + } + } +} \ No newline at end of file diff --git a/ARMeilleure/State/InstUndefinedEventArgs.cs b/ARMeilleure/State/InstUndefinedEventArgs.cs new file mode 100644 index 0000000000..c02b648e14 --- /dev/null +++ b/ARMeilleure/State/InstUndefinedEventArgs.cs @@ -0,0 +1,16 @@ +using System; + +namespace ARMeilleure.State +{ + public class InstUndefinedEventArgs : EventArgs + { + public ulong Address { get; } + public int OpCode { get; } + + public InstUndefinedEventArgs(ulong address, int opCode) + { + Address = address; + OpCode = opCode; + } + } +} \ No newline at end of file diff --git a/ARMeilleure/State/NativeContext.cs b/ARMeilleure/State/NativeContext.cs new file mode 100644 index 0000000000..4e6a5302f5 --- /dev/null +++ b/ARMeilleure/State/NativeContext.cs @@ -0,0 +1,157 @@ +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Memory; +using System; +using System.Runtime.InteropServices; + +namespace ARMeilleure.State +{ + class NativeContext : IDisposable + { + private const int IntSize = 8; + private const int VecSize = 16; + private const int FlagSize = 4; + private const int ExtraSize = 4; + + private const int TotalSize = RegisterConsts.IntRegsCount * IntSize + + RegisterConsts.VecRegsCount * VecSize + + RegisterConsts.FlagsCount * FlagSize + ExtraSize; + + public IntPtr BasePtr { get; } + + public NativeContext() + { + BasePtr = MemoryManagement.Allocate(TotalSize); + } + + public ulong GetX(int index) + { + if ((uint)index >= RegisterConsts.IntRegsCount) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + return (ulong)Marshal.ReadInt64(BasePtr, index * IntSize); + } + + public void SetX(int index, ulong value) + { + if ((uint)index >= RegisterConsts.IntRegsCount) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + Marshal.WriteInt64(BasePtr, index * IntSize, (long)value); + } + + public V128 GetV(int index) + { + if ((uint)index >= RegisterConsts.IntRegsCount) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + int offset = RegisterConsts.IntRegsCount * IntSize + index * VecSize; + + return new V128( + Marshal.ReadInt64(BasePtr, offset + 0), + Marshal.ReadInt64(BasePtr, offset + 8)); + } + + public void SetV(int index, V128 value) + { + if ((uint)index >= RegisterConsts.IntRegsCount) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + int offset = RegisterConsts.IntRegsCount * IntSize + index * VecSize; + + Marshal.WriteInt64(BasePtr, offset + 0, value.GetInt64(0)); + Marshal.WriteInt64(BasePtr, offset + 8, value.GetInt64(1)); + } + + public bool GetPstateFlag(PState flag) + { + if ((uint)flag >= RegisterConsts.FlagsCount) + { + throw new ArgumentException($"Invalid flag \"{flag}\" specified."); + } + + int offset = + RegisterConsts.IntRegsCount * IntSize + + RegisterConsts.VecRegsCount * VecSize + (int)flag * FlagSize; + + int value = Marshal.ReadInt32(BasePtr, offset); + + return value != 0; + } + + public void SetPstateFlag(PState flag, bool value) + { + if ((uint)flag >= RegisterConsts.FlagsCount) + { + throw new ArgumentException($"Invalid flag \"{flag}\" specified."); + } + + int offset = + RegisterConsts.IntRegsCount * IntSize + + RegisterConsts.VecRegsCount * VecSize + (int)flag * FlagSize; + + Marshal.WriteInt32(BasePtr, offset, value ? 1 : 0); + } + + public int GetCounter() + { + return Marshal.ReadInt32(BasePtr, GetCounterOffset()); + } + + public void SetCounter(int value) + { + Marshal.WriteInt32(BasePtr, GetCounterOffset(), value); + } + + public static int GetRegisterOffset(Register reg) + { + int offset, size; + + if (reg.Type == RegisterType.Integer) + { + offset = reg.Index * IntSize; + + size = IntSize; + } + else if (reg.Type == RegisterType.Vector) + { + offset = RegisterConsts.IntRegsCount * IntSize + reg.Index * VecSize; + + size = VecSize; + } + else /* if (reg.Type == RegisterType.Flag) */ + { + offset = RegisterConsts.IntRegsCount * IntSize + + RegisterConsts.VecRegsCount * VecSize + reg.Index * FlagSize; + + size = FlagSize; + } + + if ((uint)(offset + size) > (uint)TotalSize) + { + throw new ArgumentException("Invalid register."); + } + + return offset; + } + + public static int GetCounterOffset() + { + return RegisterConsts.IntRegsCount * IntSize + + RegisterConsts.VecRegsCount * VecSize + + RegisterConsts.FlagsCount * FlagSize; + } + + public void Dispose() + { + MemoryManagement.Free(BasePtr); + } + } +} \ No newline at end of file diff --git a/ARMeilleure/State/PState.cs b/ARMeilleure/State/PState.cs new file mode 100644 index 0000000000..ce755e952b --- /dev/null +++ b/ARMeilleure/State/PState.cs @@ -0,0 +1,16 @@ +using System; + +namespace ARMeilleure.State +{ + [Flags] + public enum PState + { + TFlag = 5, + EFlag = 9, + + VFlag = 28, + CFlag = 29, + ZFlag = 30, + NFlag = 31 + } +} diff --git a/ARMeilleure/State/RegisterAlias.cs b/ARMeilleure/State/RegisterAlias.cs new file mode 100644 index 0000000000..ae0d456283 --- /dev/null +++ b/ARMeilleure/State/RegisterAlias.cs @@ -0,0 +1,41 @@ +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 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/ARMeilleure/State/RegisterConsts.cs b/ARMeilleure/State/RegisterConsts.cs new file mode 100644 index 0000000000..a85117bb2b --- /dev/null +++ b/ARMeilleure/State/RegisterConsts.cs @@ -0,0 +1,13 @@ +namespace ARMeilleure.State +{ + static class RegisterConsts + { + public const int IntRegsCount = 32; + public const int VecRegsCount = 32; + public const int FlagsCount = 32; + public const int IntAndVecRegsCount = IntRegsCount + VecRegsCount; + public const int TotalCount = IntRegsCount + VecRegsCount + FlagsCount; + + public const int ZeroIndex = 31; + } +} \ No newline at end of file diff --git a/ARMeilleure/State/V128.cs b/ARMeilleure/State/V128.cs new file mode 100644 index 0000000000..eeb9ff1ca3 --- /dev/null +++ b/ARMeilleure/State/V128.cs @@ -0,0 +1,214 @@ +using System; + +namespace ARMeilleure.State +{ + public struct V128 : IEquatable + { + private ulong _e0; + private ulong _e1; + + private static V128 _zero = new V128(0, 0); + + public static V128 Zero => _zero; + + public V128(float value) : this(value, 0, 0, 0) { } + + public V128(double value) : this(value, 0) { } + + 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; + } + + public V128(double e0, double e1) + { + _e0 = (ulong)BitConverter.DoubleToInt64Bits(e0); + _e1 = (ulong)BitConverter.DoubleToInt64Bits(e1); + } + + public V128(int e0, int e1, int e2, int e3) + { + _e0 = (ulong)(uint)e0 << 0; + _e0 |= (ulong)(uint)e1 << 32; + _e1 = (ulong)(uint)e2 << 0; + _e1 |= (ulong)(uint)e3 << 32; + } + + 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; + } + + public V128(long e0, long e1) + { + _e0 = (ulong)e0; + _e1 = (ulong)e1; + } + + public V128(ulong e0, ulong e1) + { + _e0 = e0; + _e1 = e1; + } + + public V128(byte[] data) + { + _e0 = (ulong)BitConverter.ToInt64(data, 0); + _e1 = (ulong)BitConverter.ToInt64(data, 8); + } + + public void Insert(int index, uint value) + { + switch (index) + { + case 0: _e0 = (_e0 & 0xffffffff00000000) | ((ulong)value << 0); break; + case 1: _e0 = (_e0 & 0x00000000ffffffff) | ((ulong)value << 32); break; + case 2: _e1 = (_e1 & 0xffffffff00000000) | ((ulong)value << 0); break; + case 3: _e1 = (_e1 & 0x00000000ffffffff) | ((ulong)value << 32); break; + + default: throw new ArgumentOutOfRangeException(nameof(index)); + } + } + + public void Insert(int index, ulong value) + { + switch (index) + { + case 0: _e0 = value; break; + case 1: _e1 = value; break; + + default: throw new ArgumentOutOfRangeException(nameof(index)); + } + } + + public float AsFloat() + { + return GetFloat(0); + } + + public double AsDouble() + { + return GetDouble(0); + } + + public float GetFloat(int index) + { + return BitConverter.Int32BitsToSingle(GetInt32(index)); + } + + public double GetDouble(int index) + { + return BitConverter.Int64BitsToDouble(GetInt64(index)); + } + + public int GetInt32(int index) => (int)GetUInt32(index); + public long GetInt64(int index) => (long)GetUInt64(index); + + public uint GetUInt32(int index) + { + switch (index) + { + case 0: return (uint)(_e0 >> 0); + case 1: return (uint)(_e0 >> 32); + case 2: return (uint)(_e1 >> 0); + case 3: return (uint)(_e1 >> 32); + } + + throw new ArgumentOutOfRangeException(nameof(index)); + } + + public ulong GetUInt64(int index) + { + switch (index) + { + case 0: return _e0; + case 1: return _e1; + } + + throw new ArgumentOutOfRangeException(nameof(index)); + } + + public byte[] ToArray() + { + byte[] e0Data = BitConverter.GetBytes(_e0); + byte[] e1Data = BitConverter.GetBytes(_e1); + + byte[] data = new byte[16]; + + Buffer.BlockCopy(e0Data, 0, data, 0, 8); + Buffer.BlockCopy(e1Data, 0, data, 8, 8); + + return data; + } + + public override int GetHashCode() + { + return HashCode.Combine(_e0, _e1); + } + + public static V128 operator ~(V128 x) + { + return new V128(~x._e0, ~x._e1); + } + + public static V128 operator &(V128 x, V128 y) + { + return new V128(x._e0 & y._e0, x._e1 & y._e1); + } + + public static V128 operator |(V128 x, V128 y) + { + return new V128(x._e0 | y._e0, x._e1 | y._e1); + } + + public static V128 operator ^(V128 x, V128 y) + { + return new V128(x._e0 ^ y._e0, x._e1 ^ y._e1); + } + + public static V128 operator <<(V128 x, int shift) + { + ulong shiftOut = x._e0 >> (64 - shift); + + return new V128(x._e0 << shift, (x._e1 << shift) | shiftOut); + } + + public static V128 operator >>(V128 x, int shift) + { + ulong shiftOut = x._e1 & ((1UL << shift) - 1); + + return new V128((x._e0 >> shift) | (shiftOut << (64 - shift)), x._e1 >> shift); + } + + public static bool operator ==(V128 x, V128 y) + { + return x.Equals(y); + } + + public static bool operator !=(V128 x, V128 y) + { + return !x.Equals(y); + } + + public override bool Equals(object obj) + { + return obj is V128 vector && Equals(vector); + } + + public bool Equals(V128 other) + { + return other._e0 == _e0 && other._e1 == _e1; + } + + public override string ToString() + { + return $"0x{_e1:X16}{_e0:X16}"; + } + } +} \ No newline at end of file diff --git a/ARMeilleure/Statistics.cs b/ARMeilleure/Statistics.cs new file mode 100644 index 0000000000..e80ee59d60 --- /dev/null +++ b/ARMeilleure/Statistics.cs @@ -0,0 +1,92 @@ +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; + + [ThreadStatic] + private static Stopwatch _executionTimer; + + 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/ARMeilleure/Translation/ArmEmitterContext.cs b/ARMeilleure/Translation/ArmEmitterContext.cs new file mode 100644 index 0000000000..d35e985e6c --- /dev/null +++ b/ARMeilleure/Translation/ArmEmitterContext.cs @@ -0,0 +1,153 @@ +using ARMeilleure.Decoders; +using ARMeilleure.Instructions; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Memory; +using ARMeilleure.State; +using System.Collections.Generic; + +using static ARMeilleure.IntermediateRepresentation.OperandHelper; + +namespace ARMeilleure.Translation +{ + class ArmEmitterContext : EmitterContext + { + private 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(); + } + } + + public OpCode CurrOp { get; set; } + + public MemoryManager Memory { get; } + + public Aarch32Mode Mode { get; } + + public ArmEmitterContext(MemoryManager memory, Aarch32Mode mode) + { + Memory = memory; + Mode = mode; + + _labels = new Dictionary(); + } + + 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 Operand TryGetComparisonResult(Condition condition) + { + if (_optOpLastCompare == null || _optOpLastCompare != _optOpLastFlagSet) + { + return null; + } + + 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 null; + } + } +} \ No newline at end of file diff --git a/ARMeilleure/Translation/Compiler.cs b/ARMeilleure/Translation/Compiler.cs new file mode 100644 index 0000000000..4075a7f069 --- /dev/null +++ b/ARMeilleure/Translation/Compiler.cs @@ -0,0 +1,47 @@ +using ARMeilleure.CodeGen; +using ARMeilleure.CodeGen.X86; +using ARMeilleure.Diagnostics; +using ARMeilleure.IntermediateRepresentation; +using System; +using System.Runtime.InteropServices; + +namespace ARMeilleure.Translation +{ + static class Compiler + { + public static T Compile( + ControlFlowGraph cfg, + OperandType[] funcArgTypes, + OperandType funcReturnType, + CompilerOptions options) + { + Logger.StartPass(PassName.Dominance); + + Dominance.FindDominators(cfg); + Dominance.FindDominanceFrontiers(cfg); + + Logger.EndPass(PassName.Dominance); + + Logger.StartPass(PassName.SsaConstruction); + + if ((options & CompilerOptions.SsaForm) != 0) + { + Ssa.Construct(cfg); + } + else + { + RegisterToLocal.Rename(cfg); + } + + Logger.EndPass(PassName.SsaConstruction, cfg); + + CompilerContext cctx = new CompilerContext(cfg, funcArgTypes, funcReturnType, options); + + CompiledFunction func = CodeGenerator.Generate(cctx); + + IntPtr codePtr = JitCache.Map(func); + + return Marshal.GetDelegateForFunctionPointer(codePtr); + } + } +} \ No newline at end of file diff --git a/ARMeilleure/Translation/CompilerContext.cs b/ARMeilleure/Translation/CompilerContext.cs new file mode 100644 index 0000000000..cfe5ad1e50 --- /dev/null +++ b/ARMeilleure/Translation/CompilerContext.cs @@ -0,0 +1,26 @@ +using ARMeilleure.IntermediateRepresentation; + +namespace ARMeilleure.Translation +{ + 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/ARMeilleure/Translation/CompilerOptions.cs b/ARMeilleure/Translation/CompilerOptions.cs new file mode 100644 index 0000000000..53998ec6f3 --- /dev/null +++ b/ARMeilleure/Translation/CompilerOptions.cs @@ -0,0 +1,16 @@ +using System; + +namespace ARMeilleure.Translation +{ + [Flags] + enum CompilerOptions + { + None = 0, + SsaForm = 1 << 0, + Optimize = 1 << 1, + Lsra = 1 << 2, + + MediumCq = SsaForm | Optimize, + HighCq = SsaForm | Optimize | Lsra + } +} \ No newline at end of file diff --git a/ARMeilleure/Translation/ControlFlowGraph.cs b/ARMeilleure/Translation/ControlFlowGraph.cs new file mode 100644 index 0000000000..758f1f968a --- /dev/null +++ b/ARMeilleure/Translation/ControlFlowGraph.cs @@ -0,0 +1,158 @@ +using ARMeilleure.IntermediateRepresentation; +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace ARMeilleure.Translation +{ + class ControlFlowGraph + { + public BasicBlock Entry { get; } + + public LinkedList Blocks { get; } + + public BasicBlock[] PostOrderBlocks { get; } + + public int[] PostOrderMap { get; } + + public ControlFlowGraph(BasicBlock entry, LinkedList blocks) + { + Entry = entry; + Blocks = blocks; + + RemoveUnreachableBlocks(blocks); + + HashSet visited = new HashSet(); + + Stack blockStack = new Stack(); + + PostOrderBlocks = new BasicBlock[blocks.Count]; + + PostOrderMap = new int[blocks.Count]; + + visited.Add(entry); + + blockStack.Push(entry); + + int index = 0; + + while (blockStack.TryPop(out BasicBlock block)) + { + if (block.Next != null && visited.Add(block.Next)) + { + blockStack.Push(block); + blockStack.Push(block.Next); + } + else if (block.Branch != null && visited.Add(block.Branch)) + { + blockStack.Push(block); + blockStack.Push(block.Branch); + } + else + { + PostOrderMap[block.Index] = index; + + PostOrderBlocks[index++] = block; + } + } + } + + private void RemoveUnreachableBlocks(LinkedList blocks) + { + HashSet visited = new HashSet(); + + Queue workQueue = new Queue(); + + visited.Add(Entry); + + workQueue.Enqueue(Entry); + + while (workQueue.TryDequeue(out BasicBlock block)) + { + Debug.Assert(block.Index != -1, "Invalid block index."); + + if (block.Next != null && visited.Add(block.Next)) + { + workQueue.Enqueue(block.Next); + } + + if (block.Branch != null && visited.Add(block.Branch)) + { + workQueue.Enqueue(block.Branch); + } + } + + if (visited.Count < blocks.Count) + { + // Remove unreachable blocks and renumber. + int index = 0; + + for (LinkedListNode node = blocks.First; node != null;) + { + LinkedListNode nextNode = node.Next; + + BasicBlock block = node.Value; + + if (!visited.Contains(block)) + { + block.Next = null; + block.Branch = null; + + blocks.Remove(node); + } + else + { + block.Index = index++; + } + + node = nextNode; + } + } + } + + public BasicBlock SplitEdge(BasicBlock predecessor, BasicBlock successor) + { + BasicBlock splitBlock = new BasicBlock(Blocks.Count); + + if (predecessor.Next == successor) + { + predecessor.Next = splitBlock; + } + + if (predecessor.Branch == successor) + { + predecessor.Branch = splitBlock; + } + + if (splitBlock.Predecessors.Count == 0) + { + throw new ArgumentException("Predecessor and successor are not connected."); + } + + // Insert the new block on the list of blocks. + BasicBlock succPrev = successor.Node.Previous?.Value; + + if (succPrev != null && succPrev != predecessor && succPrev.Next == successor) + { + // Can't insert after the predecessor or before the successor. + // Here, we insert it before the successor by also spliting another + // edge (the one between the block before "successor" and "successor"). + BasicBlock splitBlock2 = new BasicBlock(splitBlock.Index + 1); + + succPrev.Next = splitBlock2; + + splitBlock2.Branch = successor; + + splitBlock2.Operations.AddLast(new Operation(Instruction.Branch, null)); + + Blocks.AddBefore(successor.Node, splitBlock2); + } + + splitBlock.Next = successor; + + Blocks.AddBefore(successor.Node, splitBlock); + + return splitBlock; + } + } +} \ No newline at end of file diff --git a/ARMeilleure/Translation/DelegateCache.cs b/ARMeilleure/Translation/DelegateCache.cs new file mode 100644 index 0000000000..7328c61a67 --- /dev/null +++ b/ARMeilleure/Translation/DelegateCache.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Concurrent; +using System.Reflection; + +namespace ARMeilleure.Translation +{ + static class DelegateCache + { + private static ConcurrentDictionary _delegates; + + static DelegateCache() + { + _delegates = new ConcurrentDictionary(); + } + + public static Delegate GetOrAdd(Delegate dlg) + { + return _delegates.GetOrAdd(GetKey(dlg.Method), (key) => dlg); + } + + private static string GetKey(MethodInfo info) + { + return $"{info.DeclaringType.FullName}.{info.Name}"; + } + } +} \ No newline at end of file diff --git a/ARMeilleure/Translation/Dominance.cs b/ARMeilleure/Translation/Dominance.cs new file mode 100644 index 0000000000..bb55169ed0 --- /dev/null +++ b/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) + { + foreach (BasicBlock block in cfg.Blocks) + { + 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/ARMeilleure/Translation/EmitterContext.cs b/ARMeilleure/Translation/EmitterContext.cs new file mode 100644 index 0000000000..13cf677c77 --- /dev/null +++ b/ARMeilleure/Translation/EmitterContext.cs @@ -0,0 +1,562 @@ +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; + +using static ARMeilleure.IntermediateRepresentation.OperandHelper; + +namespace ARMeilleure.Translation +{ + class EmitterContext + { + private Dictionary _irLabels; + + private LinkedList _irBlocks; + + private BasicBlock _irBlock; + + private bool _needsNewBlock; + + public EmitterContext() + { + _irLabels = new Dictionary(); + + _irBlocks = new LinkedList(); + + _needsNewBlock = true; + } + + 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) + { + Add(Instruction.Branch, null); + + BranchToLabel(label); + } + + public void BranchIfFalse(Operand label, Operand op1) + { + Add(Instruction.BranchIfFalse, null, op1); + + BranchToLabel(label); + } + + public void BranchIfTrue(Operand label, Operand op1) + { + Add(Instruction.BranchIfTrue, null, op1); + + BranchToLabel(label); + } + + public Operand ByteSwap(Operand op1) + { + return Add(Instruction.ByteSwap, Local(op1.Type), op1); + } + + public Operand Call(Delegate func, params Operand[] callArgs) + { + // Add the delegate to the cache to ensure it will not be garbage collected. + func = DelegateCache.GetOrAdd(func); + + IntPtr ptr = Marshal.GetFunctionPointerForDelegate(func); + + OperandType returnType = GetOperandType(func.Method.ReturnType); + + return Call(Const(ptr.ToInt64()), returnType, callArgs); + } + + private static Dictionary _typeCodeToOperandTypeMap = + new Dictionary() + { + { TypeCode.Boolean, OperandType.I32 }, + { TypeCode.Byte, OperandType.I32 }, + { TypeCode.Char, OperandType.I32 }, + { TypeCode.Double, OperandType.FP64 }, + { TypeCode.Int16, OperandType.I32 }, + { TypeCode.Int32, OperandType.I32 }, + { TypeCode.Int64, OperandType.I64 }, + { TypeCode.SByte, OperandType.I32 }, + { TypeCode.Single, OperandType.FP32 }, + { TypeCode.UInt16, OperandType.I32 }, + { TypeCode.UInt32, OperandType.I32 }, + { TypeCode.UInt64, OperandType.I64 } + }; + + private static OperandType GetOperandType(Type type) + { + if (_typeCodeToOperandTypeMap.TryGetValue(Type.GetTypeCode(type), out OperandType ot)) + { + return ot; + } + else if (type == typeof(V128)) + { + return OperandType.V128; + } + else if (type == typeof(void)) + { + return OperandType.None; + } + + 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, null, args); + } + } + + public Operand CompareAndSwap128(Operand address, Operand expected, Operand desired) + { + return Add(Instruction.CompareAndSwap128, Local(OperandType.V128), 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) + { + throw new ArgumentException($"Invalid dest operand kind \"{dest.Kind}\"."); + } + + return Add(Instruction.Copy, dest, op1); + } + + public Operand CountLeadingZeros(Operand op1) + { + return Add(Instruction.CountLeadingZeros, Local(op1.Type), op1); + } + + internal Operand CpuId() + { + return Add(Instruction.CpuId, Local(OperandType.I64)); + } + + 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 ICompareEqual(Operand op1, Operand op2) + { + return Add(Instruction.CompareEqual, Local(OperandType.I32), op1, op2); + } + + public Operand ICompareGreater(Operand op1, Operand op2) + { + return Add(Instruction.CompareGreater, Local(OperandType.I32), op1, op2); + } + + public Operand ICompareGreaterOrEqual(Operand op1, Operand op2) + { + return Add(Instruction.CompareGreaterOrEqual, Local(OperandType.I32), op1, op2); + } + + public Operand ICompareGreaterOrEqualUI(Operand op1, Operand op2) + { + return Add(Instruction.CompareGreaterOrEqualUI, Local(OperandType.I32), op1, op2); + } + + public Operand ICompareGreaterUI(Operand op1, Operand op2) + { + return Add(Instruction.CompareGreaterUI, Local(OperandType.I32), op1, op2); + } + + public Operand ICompareLess(Operand op1, Operand op2) + { + return Add(Instruction.CompareLess, Local(OperandType.I32), op1, op2); + } + + public Operand ICompareLessOrEqual(Operand op1, Operand op2) + { + return Add(Instruction.CompareLessOrEqual, Local(OperandType.I32), op1, op2); + } + + public Operand ICompareLessOrEqualUI(Operand op1, Operand op2) + { + return Add(Instruction.CompareLessOrEqualUI, Local(OperandType.I32), op1, op2); + } + + public Operand ICompareLessUI(Operand op1, Operand op2) + { + return Add(Instruction.CompareLessUI, Local(OperandType.I32), op1, op2); + } + + public Operand ICompareNotEqual(Operand op1, Operand op2) + { + return Add(Instruction.CompareNotEqual, Local(OperandType.I32), op1, op2); + } + + 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 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, null, 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, null, address, value); + } + + public void Store16(Operand address, Operand value) + { + Add(Instruction.Store16, null, address, value); + } + + public void Store8(Operand address, Operand value) + { + Add(Instruction.Store8, null, 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 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 Operand Add(Instruction inst, Operand dest = null, params Operand[] sources) + { + if (_needsNewBlock) + { + NewNextBlock(); + } + + Operation operation = new Operation(inst, dest, sources); + + _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); + } + + private Operand Add(Intrinsic intrin, Operand dest, params Operand[] sources) + { + if (_needsNewBlock) + { + NewNextBlock(); + } + + IntrinsicOperation operation = new IntrinsicOperation(intrin, dest, sources); + + _irBlock.Operations.AddLast(operation); + + return dest; + } + + private void BranchToLabel(Operand label) + { + if (!_irLabels.TryGetValue(label, out BasicBlock branchBlock)) + { + branchBlock = new BasicBlock(); + + _irLabels.Add(label, branchBlock); + } + + _irBlock.Branch = branchBlock; + + _needsNewBlock = true; + } + + public void MarkLabel(Operand label) + { + if (_irLabels.TryGetValue(label, out BasicBlock nextBlock)) + { + nextBlock.Index = _irBlocks.Count; + nextBlock.Node = _irBlocks.AddLast(nextBlock); + + NextBlock(nextBlock); + } + else + { + NewNextBlock(); + + _irLabels.Add(label, _irBlock); + } + } + + private void NewNextBlock() + { + BasicBlock block = new BasicBlock(_irBlocks.Count); + + block.Node = _irBlocks.AddLast(block); + + NextBlock(block); + } + + private void NextBlock(BasicBlock nextBlock) + { + if (_irBlock != null && !EndsWithUnconditional(_irBlock)) + { + _irBlock.Next = nextBlock; + } + + _irBlock = nextBlock; + + _needsNewBlock = false; + } + + private static bool EndsWithUnconditional(BasicBlock block) + { + Operation lastOp = block.GetLastOp() as Operation; + + if (lastOp == null) + { + return false; + } + + return lastOp.Instruction == Instruction.Branch || + lastOp.Instruction == Instruction.Return; + } + + public ControlFlowGraph GetControlFlowGraph() + { + return new ControlFlowGraph(_irBlocks.First.Value, _irBlocks); + } + } +} \ No newline at end of file diff --git a/ARMeilleure/Translation/GuestFunction.cs b/ARMeilleure/Translation/GuestFunction.cs new file mode 100644 index 0000000000..ac131a0d15 --- /dev/null +++ b/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/ARMeilleure/Translation/JitCache.cs b/ARMeilleure/Translation/JitCache.cs new file mode 100644 index 0000000000..73f04a966d --- /dev/null +++ b/ARMeilleure/Translation/JitCache.cs @@ -0,0 +1,135 @@ +using ARMeilleure.CodeGen; +using ARMeilleure.Memory; +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; + +namespace ARMeilleure.Translation +{ + 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 = 512 * 1024 * 1024; + + private static IntPtr _basePointer; + + private static int _offset; + + private static List _cacheEntries; + + private static object _lock; + + static JitCache() + { + _basePointer = MemoryManagement.Allocate(CacheSize); + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + JitUnwindWindows.InstallFunctionTableHandler(_basePointer, CacheSize); + + // The first page is used for the table based SEH structs. + _offset = PageSize; + } + + _cacheEntries = new List(); + + _lock = new object(); + } + + public static IntPtr Map(CompiledFunction func) + { + byte[] code = func.Code; + + lock (_lock) + { + int funcOffset = Allocate(code.Length); + + IntPtr funcPtr = _basePointer + funcOffset; + + Marshal.Copy(code, 0, funcPtr, code.Length); + + ReprotectRange(funcOffset, code.Length); + + Add(new JitCacheEntry(funcOffset, code.Length, func.UnwindInfo)); + + return funcPtr; + } + } + + private static void ReprotectRange(int offset, int size) + { + // Map pages that are already full as RX. + // Map pages that are not full yet as RWX. + // On unix, the address must be page aligned. + int endOffs = offset + size; + + int pageStart = offset & ~PageMask; + int pageEnd = endOffs & ~PageMask; + + int fullPagesSize = pageEnd - pageStart; + + if (fullPagesSize != 0) + { + IntPtr funcPtr = _basePointer + pageStart; + + MemoryManagement.Reprotect(funcPtr, (ulong)fullPagesSize, MemoryProtection.ReadAndExecute); + } + + int remaining = endOffs - pageEnd; + + if (remaining != 0) + { + IntPtr funcPtr = _basePointer + pageEnd; + + MemoryManagement.Reprotect(funcPtr, (ulong)remaining, MemoryProtection.ReadWriteExecute); + } + } + + private static int Allocate(int codeSize) + { + codeSize = checked(codeSize + (CodeAlignment - 1)) & ~(CodeAlignment - 1); + + int allocOffset = _offset; + + _offset += codeSize; + + if ((ulong)(uint)_offset > CacheSize) + { + throw new OutOfMemoryException(); + } + + return allocOffset; + } + + private static void Add(JitCacheEntry entry) + { + _cacheEntries.Add(entry); + } + + public static bool TryFind(int offset, out JitCacheEntry entry) + { + lock (_lock) + { + foreach (JitCacheEntry cacheEntry in _cacheEntries) + { + int endOffset = cacheEntry.Offset + cacheEntry.Size; + + if (offset >= cacheEntry.Offset && offset < endOffset) + { + entry = cacheEntry; + + return true; + } + } + } + + entry = default(JitCacheEntry); + + return false; + } + } +} \ No newline at end of file diff --git a/ARMeilleure/Translation/JitCacheEntry.cs b/ARMeilleure/Translation/JitCacheEntry.cs new file mode 100644 index 0000000000..87d020e683 --- /dev/null +++ b/ARMeilleure/Translation/JitCacheEntry.cs @@ -0,0 +1,19 @@ +using ARMeilleure.CodeGen.Unwinding; + +namespace ARMeilleure.Translation +{ + struct JitCacheEntry + { + public int Offset { get; } + public int Size { get; } + + public UnwindInfo UnwindInfo { get; } + + public JitCacheEntry(int offset, int size, UnwindInfo unwindInfo) + { + Offset = offset; + Size = size; + UnwindInfo = unwindInfo; + } + } +} \ No newline at end of file diff --git a/ARMeilleure/Translation/JitUnwindWindows.cs b/ARMeilleure/Translation/JitUnwindWindows.cs new file mode 100644 index 0000000000..108dc2c560 --- /dev/null +++ b/ARMeilleure/Translation/JitUnwindWindows.cs @@ -0,0 +1,164 @@ +using ARMeilleure.IntermediateRepresentation; +using System; +using System.Runtime.InteropServices; + +namespace ARMeilleure.Translation +{ + static class JitUnwindWindows + { + private const int MaxUnwindCodesArraySize = 9 + 10 * 2 + 3; + + 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 UnwindOperation + { + 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); + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] + private static unsafe extern bool RtlInstallFunctionTableCallback( + ulong tableIdentifier, + ulong baseAddress, + uint length, + GetRuntimeFunctionCallback callback, + IntPtr context, + 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) + { + ulong codeCachePtr = (ulong)codeCachePointer.ToInt64(); + + _sizeOfRuntimeFunction = Marshal.SizeOf(); + + bool result; + + unsafe + { + _runtimeFunction = (RuntimeFunction*)codeCachePointer; + + _unwindInfo = (UnwindInfo*)(codeCachePointer + _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 JitCacheEntry funcEntry)) + { + // Not found. + return null; + } + + var unwindInfo = funcEntry.UnwindInfo; + + int codeIndex = 0; + + int spOffset = unwindInfo.FixedAllocSize; + + foreach (var entry in unwindInfo.PushEntries) + { + if (entry.Type == RegisterType.Vector) + { + spOffset -= 16; + } + } + + for (int index = unwindInfo.PushEntries.Length - 1; index >= 0; index--) + { + var entry = unwindInfo.PushEntries[index]; + + if (entry.Type == RegisterType.Vector) + { + ushort uwop = PackUwop(UnwindOperation.SaveXmm128, entry.StreamEndOffset, entry.Index); + + _unwindInfo->UnwindCodes[codeIndex++] = uwop; + _unwindInfo->UnwindCodes[codeIndex++] = (ushort)spOffset; + + spOffset += 16; + } + } + + _unwindInfo->UnwindCodes[0] = PackUwop(UnwindOperation.AllocLarge, unwindInfo.PrologueSize, 1); + _unwindInfo->UnwindCodes[1] = (ushort)(unwindInfo.FixedAllocSize >> 0); + _unwindInfo->UnwindCodes[2] = (ushort)(unwindInfo.FixedAllocSize >> 16); + + codeIndex += 3; + + for (int index = unwindInfo.PushEntries.Length - 1; index >= 0; index--) + { + var entry = unwindInfo.PushEntries[index]; + + if (entry.Type == RegisterType.Integer) + { + ushort uwop = PackUwop(UnwindOperation.PushNonvol, entry.StreamEndOffset, entry.Index); + + _unwindInfo->UnwindCodes[codeIndex++] = uwop; + } + } + + _unwindInfo->VersionAndFlags = 1; + _unwindInfo->SizeOfProlog = (byte)unwindInfo.PrologueSize; + _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 PackUwop(UnwindOperation uwop, int prologOffset, int opInfo) + { + return (ushort)(prologOffset | ((int)uwop << 8) | (opInfo << 12)); + } + } +} \ No newline at end of file diff --git a/ARMeilleure/Translation/PriorityQueue.cs b/ARMeilleure/Translation/PriorityQueue.cs new file mode 100644 index 0000000000..ab593dc07f --- /dev/null +++ b/ARMeilleure/Translation/PriorityQueue.cs @@ -0,0 +1,39 @@ +using System.Collections.Concurrent; + +namespace ARMeilleure.Translation +{ + class PriorityQueue + { + private ConcurrentQueue[] _queues; + + public PriorityQueue(int priorities) + { + _queues = new ConcurrentQueue[priorities]; + + for (int index = 0; index < priorities; index++) + { + _queues[index] = new ConcurrentQueue(); + } + } + + public void Enqueue(int priority, T value) + { + _queues[priority].Enqueue(value); + } + + public bool TryDequeue(out T value) + { + for (int index = 0; index < _queues.Length; index++) + { + if (_queues[index].TryDequeue(out value)) + { + return true; + } + } + + value = default(T); + + return false; + } + } +} \ No newline at end of file diff --git a/ARMeilleure/Translation/RegisterToLocal.cs b/ARMeilleure/Translation/RegisterToLocal.cs new file mode 100644 index 0000000000..aa91801824 --- /dev/null +++ b/ARMeilleure/Translation/RegisterToLocal.cs @@ -0,0 +1,52 @@ +using ARMeilleure.IntermediateRepresentation; +using System.Collections.Generic; + +using static ARMeilleure.IntermediateRepresentation.OperandHelper; + +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; + } + + foreach (BasicBlock block in cfg.Blocks) + { + foreach (Node node in block.Operations) + { + Operand dest = node.Destination; + + if (dest != null && 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/ARMeilleure/Translation/RegisterUsage.cs b/ARMeilleure/Translation/RegisterUsage.cs new file mode 100644 index 0000000000..4164786b90 --- /dev/null +++ b/ARMeilleure/Translation/RegisterUsage.cs @@ -0,0 +1,413 @@ +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using System; + +using static ARMeilleure.IntermediateRepresentation.OperandHelper; + +namespace ARMeilleure.Translation +{ + static class RegisterUsage + { + private const long CallerSavedIntRegistersMask = 0x7fL << 9; + private const long PStateNzcvFlagsMask = 0xfL << 60; + + private const long CallerSavedVecRegistersMask = 0xffffL << 16; + + private const int RegsCount = 32; + private const int RegsMask = RegsCount - 1; + + private struct RegisterMask : IEquatable + { + public long IntMask { get; set; } + public long VecMask { get; set; } + + public RegisterMask(long intMask, long vecMask) + { + IntMask = intMask; + VecMask = vecMask; + } + + public static RegisterMask operator &(RegisterMask x, RegisterMask y) + { + return new RegisterMask(x.IntMask & y.IntMask, x.VecMask & y.VecMask); + } + + public static RegisterMask operator |(RegisterMask x, RegisterMask y) + { + return new RegisterMask(x.IntMask | y.IntMask, x.VecMask | y.VecMask); + } + + public static RegisterMask operator ~(RegisterMask x) + { + 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 IntMask == other.IntMask && VecMask == other.VecMask; + } + + public override int GetHashCode() + { + return HashCode.Combine(IntMask, VecMask); + } + } + + public static void RunPass(ControlFlowGraph cfg, bool isCompleteFunction) + { + // Compute local register inputs and outputs used inside blocks. + RegisterMask[] localInputs = new RegisterMask[cfg.Blocks.Count]; + RegisterMask[] localOutputs = new RegisterMask[cfg.Blocks.Count]; + + foreach (BasicBlock block in cfg.Blocks) + { + foreach (Node node in block.Operations) + { + Operation operation = node as Operation; + + for (int srcIndex = 0; srcIndex < operation.SourcesCount; srcIndex++) + { + Operand source = operation.GetSource(srcIndex); + + if (source.Kind != OperandKind.Register) + { + continue; + } + + Register register = source.GetRegister(); + + localInputs[block.Index] |= GetMask(register) & ~localOutputs[block.Index]; + } + + if (operation.Destination != null && operation.Destination.Kind == OperandKind.Register) + { + localOutputs[block.Index] |= GetMask(operation.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]; + } + + if (Exchange(globalCmnOutputs, block.Index, cmnOutputs)) + { + modified = true; + } + + outputs |= localOutputs[block.Index]; + + if (Exchange(globalOutputs, block.Index, globalOutputs[block.Index] | outputs)) + { + modified = true; + } + } + else if (Exchange(globalOutputs, block.Index, localOutputs[block.Index])) + { + modified = true; + } + } + + // Compute register inputs. + for (int index = 0; index < cfg.PostOrderBlocks.Length; index++) + { + BasicBlock block = cfg.PostOrderBlocks[index]; + + RegisterMask inputs = localInputs[block.Index]; + + if (block.Next != null) + { + inputs |= globalInputs[block.Next.Index]; + } + + if (block.Branch != null) + { + inputs |= globalInputs[block.Branch.Index]; + } + + inputs &= ~globalCmnOutputs[block.Index]; + + if (Exchange(globalInputs, block.Index, globalInputs[block.Index] | inputs)) + { + modified = true; + } + } + + firstPass = false; + } + while (modified); + + // Insert load and store context instructions where needed. + foreach (BasicBlock block in cfg.Blocks) + { + bool hasContextLoad = HasContextLoad(block); + + if (hasContextLoad) + { + block.Operations.RemoveFirst(); + } + + // 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) + { + LoadLocals(block, globalInputs[block.Index].VecMask, RegisterType.Vector); + LoadLocals(block, globalInputs[block.Index].IntMask, RegisterType.Integer); + } + + bool hasContextStore = HasContextStore(block); + + if (hasContextStore) + { + block.Operations.RemoveLast(); + } + + if (EndsWithReturn(block) || hasContextStore) + { + StoreLocals(block, globalOutputs[block.Index].IntMask, RegisterType.Integer, isCompleteFunction); + StoreLocals(block, globalOutputs[block.Index].VecMask, RegisterType.Vector, isCompleteFunction); + } + } + } + + private static bool HasContextLoad(BasicBlock block) + { + return StartsWith(block, Instruction.LoadFromContext) && block.Operations.First.Value.SourcesCount == 0; + } + + private static bool HasContextStore(BasicBlock block) + { + return EndsWith(block, Instruction.StoreToContext) && block.GetLastOp().SourcesCount == 0; + } + + private static bool StartsWith(BasicBlock block, Instruction inst) + { + if (block.Operations.Count == 0) + { + return false; + } + + return block.Operations.First.Value is Operation operation && operation.Instruction == inst; + } + + private static bool EndsWith(BasicBlock block, Instruction inst) + { + if (block.Operations.Count == 0) + { + return false; + } + + return block.Operations.Last.Value is Operation operation && operation.Instruction == inst; + } + + 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.Vector: vecMask = 1L << register.Index; break; + } + + return new RegisterMask(intMask, vecMask); + } + + private static bool Exchange(RegisterMask[] masks, int blkIndex, RegisterMask value) + { + RegisterMask oldValue = masks[blkIndex]; + + masks[blkIndex] = value; + + return oldValue != value; + } + + private static void LoadLocals(BasicBlock block, long inputs, RegisterType baseType) + { + Operand arg0 = Local(OperandType.I64); + + for (int bit = 63; bit >= 0; bit--) + { + long mask = 1L << bit; + + if ((inputs & mask) == 0) + { + continue; + } + + Operand dest = GetRegFromBit(bit, baseType); + + long offset = NativeContext.GetRegisterOffset(dest.GetRegister()); + + Operand addr = Local(OperandType.I64); + + Operation loadOp = new Operation(Instruction.Load, dest, addr); + + block.Operations.AddFirst(loadOp); + + Operation calcOffsOp = new Operation(Instruction.Add, addr, arg0, Const(offset)); + + block.Operations.AddFirst(calcOffsOp); + } + + Operation loadArg0 = new Operation(Instruction.LoadArgument, arg0, Const(0)); + + block.Operations.AddFirst(loadArg0); + } + + private static void StoreLocals(BasicBlock block, long outputs, RegisterType baseType, bool isCompleteFunction) + { + if (Optimizations.AssumeStrictAbiCompliance && isCompleteFunction) + { + if (baseType == RegisterType.Integer || baseType == RegisterType.Flag) + { + outputs = ClearCallerSavedIntRegs(outputs); + } + else /* if (baseType == RegisterType.Vector) */ + { + outputs = ClearCallerSavedVecRegs(outputs); + } + } + + Operand arg0 = Local(OperandType.I64); + + Operation loadArg0 = new Operation(Instruction.LoadArgument, arg0, Const(0)); + + block.Append(loadArg0); + + for (int bit = 0; bit < 64; bit++) + { + long mask = 1L << bit; + + if ((outputs & mask) == 0) + { + continue; + } + + Operand source = GetRegFromBit(bit, baseType); + + long offset = NativeContext.GetRegisterOffset(source.GetRegister()); + + Operand addr = Local(OperandType.I64); + + Operation calcOffsOp = new Operation(Instruction.Add, addr, arg0, Const(offset)); + + block.Append(calcOffsOp); + + Operation storeOp = new Operation(Instruction.Store, null, addr, source); + + block.Append(storeOp); + } + } + + private static Operand GetRegFromBit(int bit, RegisterType baseType) + { + if (bit < RegsCount) + { + return new Operand(bit, baseType, GetOperandType(baseType)); + } + else if (baseType == RegisterType.Integer) + { + return new Operand(bit & RegsMask, RegisterType.Flag, OperandType.I32); + } + else + { + throw new ArgumentOutOfRangeException(nameof(bit)); + } + } + + private static OperandType GetOperandType(RegisterType type) + { + switch (type) + { + case RegisterType.Flag: return OperandType.I32; + case RegisterType.Integer: return OperandType.I64; + case RegisterType.Vector: return OperandType.V128; + } + + throw new ArgumentException($"Invalid register type \"{type}\"."); + } + + private static bool EndsWithReturn(BasicBlock block) + { + if (!(block.GetLastOp() is Operation operation)) + { + return false; + } + + return operation.Instruction == Instruction.Return; + } + + private static long ClearCallerSavedIntRegs(long mask) + { + // TODO: ARM32 support. + mask &= ~(CallerSavedIntRegistersMask | PStateNzcvFlagsMask); + + return mask; + } + + private static long ClearCallerSavedVecRegs(long mask) + { + // TODO: ARM32 support. + mask &= ~CallerSavedVecRegistersMask; + + return mask; + } + } +} \ No newline at end of file diff --git a/ARMeilleure/Translation/SsaConstruction.cs b/ARMeilleure/Translation/SsaConstruction.cs new file mode 100644 index 0000000000..ccf5259154 --- /dev/null +++ b/ARMeilleure/Translation/SsaConstruction.cs @@ -0,0 +1,293 @@ +using ARMeilleure.Common; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using System.Collections.Generic; + +using static ARMeilleure.IntermediateRepresentation.OperandHelper; + +namespace ARMeilleure.Translation +{ + static partial class Ssa + { + private class DefMap + { + private Dictionary _map; + + private BitMap _phiMasks; + + public DefMap() + { + _map = new Dictionary(); + + _phiMasks = new BitMap(RegisterConsts.TotalCount); + } + + public bool TryAddOperand(Register reg, Operand operand) + { + return _map.TryAdd(reg, operand); + } + + public bool TryGetOperand(Register reg, out Operand operand) + { + return _map.TryGetValue(reg, out operand); + } + + public bool AddPhi(Register reg) + { + return _phiMasks.Set(GetIdFromRegister(reg)); + } + + public bool HasPhi(Register reg) + { + return _phiMasks.IsSet(GetIdFromRegister(reg)); + } + } + + public static void Construct(ControlFlowGraph cfg) + { + DefMap[] globalDefs = new DefMap[cfg.Blocks.Count]; + + foreach (BasicBlock block in cfg.Blocks) + { + globalDefs[block.Index] = new DefMap(); + } + + Queue dfPhiBlocks = new Queue(); + + // First pass, get all defs and locals uses. + foreach (BasicBlock block in cfg.Blocks) + { + Operand[] localDefs = new Operand[RegisterConsts.TotalCount]; + + LinkedListNode node = block.Operations.First; + + Operand RenameLocal(Operand operand) + { + if (operand != null && operand.Kind == OperandKind.Register) + { + Operand local = localDefs[GetIdFromRegister(operand.GetRegister())]; + + operand = local ?? operand; + } + + return operand; + } + + while (node != null) + { + if (node.Value is Operation operation) + { + for (int index = 0; index < operation.SourcesCount; index++) + { + operation.SetSource(index, RenameLocal(operation.GetSource(index))); + } + + Operand dest = operation.Destination; + + if (dest != null && dest.Kind == OperandKind.Register) + { + Operand local = Local(dest.Type); + + localDefs[GetIdFromRegister(dest.GetRegister())] = local; + + operation.Destination = local; + } + } + + node = node.Next; + } + + for (int index = 0; index < RegisterConsts.TotalCount; index++) + { + Operand local = localDefs[index]; + + if (local == null) + { + continue; + } + + Register reg = GetRegisterFromId(index); + + globalDefs[block.Index].TryAddOperand(reg, local); + + dfPhiBlocks.Enqueue(block); + + while (dfPhiBlocks.TryDequeue(out BasicBlock dfPhiBlock)) + { + foreach (BasicBlock domFrontier in dfPhiBlock.DominanceFrontiers) + { + if (globalDefs[domFrontier.Index].AddPhi(reg)) + { + dfPhiBlocks.Enqueue(domFrontier); + } + } + } + } + } + + // Second pass, rename variables with definitions on different blocks. + foreach (BasicBlock block in cfg.Blocks) + { + Operand[] localDefs = new Operand[RegisterConsts.TotalCount]; + + LinkedListNode node = block.Operations.First; + + Operand RenameGlobal(Operand operand) + { + if (operand != null && operand.Kind == OperandKind.Register) + { + int key = GetIdFromRegister(operand.GetRegister()); + + Operand local = localDefs[key]; + + if (local == null) + { + local = FindDef(globalDefs, block, operand); + + localDefs[key] = local; + } + + operand = local; + } + + return operand; + } + + while (node != null) + { + if (node.Value is Operation operation) + { + for (int index = 0; index < operation.SourcesCount; index++) + { + operation.SetSource(index, RenameGlobal(operation.GetSource(index))); + } + } + + node = node.Next; + } + } + } + + private static Operand FindDef(DefMap[] globalDefs, BasicBlock current, Operand operand) + { + if (globalDefs[current.Index].HasPhi(operand.GetRegister())) + { + 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]; + + Register reg = operand.GetRegister(); + + if (defMap.TryGetOperand(reg, out Operand lastDef)) + { + return lastDef; + } + + if (defMap.HasPhi(reg)) + { + 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); + + PhiNode phi = new PhiNode(local, block.Predecessors.Count); + + AddPhi(block, phi); + + globalDefs[block.Index].TryAddOperand(operand.GetRegister(), local); + + 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, PhiNode phi) + { + LinkedListNode node = block.Operations.First; + + if (node != null) + { + while (node.Next?.Value is PhiNode) + { + node = node.Next; + } + } + + if (node?.Value is PhiNode) + { + block.Operations.AddAfter(node, phi); + } + else + { + block.Operations.AddFirst(phi); + } + } + + private static int GetIdFromRegister(Register reg) + { + if (reg.Type == RegisterType.Integer) + { + return reg.Index; + } + else if (reg.Type == RegisterType.Vector) + { + return RegisterConsts.IntRegsCount + reg.Index; + } + else /* if (reg.Type == RegisterType.Flag) */ + { + return RegisterConsts.IntAndVecRegsCount + reg.Index; + } + } + + private static Register GetRegisterFromId(int id) + { + if (id < RegisterConsts.IntRegsCount) + { + return new Register(id, RegisterType.Integer); + } + else if (id < RegisterConsts.IntAndVecRegsCount) + { + return new Register(id - RegisterConsts.IntRegsCount, RegisterType.Vector); + } + else /* if (id < RegisterConsts.TotalCount) */ + { + return new Register(id - RegisterConsts.IntAndVecRegsCount, RegisterType.Flag); + } + } + } +} \ No newline at end of file diff --git a/ARMeilleure/Translation/SsaDeconstruction.cs b/ARMeilleure/Translation/SsaDeconstruction.cs new file mode 100644 index 0000000000..2ba78bdf43 --- /dev/null +++ b/ARMeilleure/Translation/SsaDeconstruction.cs @@ -0,0 +1,46 @@ +using ARMeilleure.IntermediateRepresentation; +using System.Collections.Generic; + +using static ARMeilleure.IntermediateRepresentation.OperandHelper; + +namespace ARMeilleure.Translation +{ + static partial class Ssa + { + public static void Deconstruct(ControlFlowGraph cfg) + { + foreach (BasicBlock block in cfg.Blocks) + { + LinkedListNode node = block.Operations.First; + + while (node?.Value is PhiNode phi) + { + LinkedListNode nextNode = node.Next; + + Operand local = Local(phi.Destination.Type); + + for (int index = 0; index < phi.SourcesCount; index++) + { + BasicBlock predecessor = phi.GetBlock(index); + + Operand source = phi.GetSource(index); + + predecessor.Append(new Operation(Instruction.Copy, local, source)); + + phi.SetSource(index, null); + } + + Operation copyOp = new Operation(Instruction.Copy, phi.Destination, local); + + block.Operations.AddBefore(node, copyOp); + + phi.Destination = null; + + block.Operations.Remove(node); + + node = nextNode; + } + } + } + } +} \ No newline at end of file diff --git a/ARMeilleure/Translation/TranslatedFunction.cs b/ARMeilleure/Translation/TranslatedFunction.cs new file mode 100644 index 0000000000..06069cf8fe --- /dev/null +++ b/ARMeilleure/Translation/TranslatedFunction.cs @@ -0,0 +1,30 @@ +using System.Threading; + +namespace ARMeilleure.Translation +{ + class TranslatedFunction + { + private const int MinCallsForRejit = 100; + + private GuestFunction _func; + + private bool _rejit; + private int _callCount; + + public TranslatedFunction(GuestFunction func, bool rejit) + { + _func = func; + _rejit = rejit; + } + + public ulong Execute(State.ExecutionContext context) + { + return _func(context.NativeContextPtr); + } + + public bool ShouldRejit() + { + return _rejit && Interlocked.Increment(ref _callCount) == MinCallsForRejit; + } + } +} \ No newline at end of file diff --git a/ARMeilleure/Translation/Translator.cs b/ARMeilleure/Translation/Translator.cs new file mode 100644 index 0000000000..4725ca59d8 --- /dev/null +++ b/ARMeilleure/Translation/Translator.cs @@ -0,0 +1,254 @@ +using ARMeilleure.Decoders; +using ARMeilleure.Diagnostics; +using ARMeilleure.Instructions; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Memory; +using ARMeilleure.State; +using System; +using System.Collections.Concurrent; +using System.Threading; + +using static ARMeilleure.IntermediateRepresentation.OperandHelper; + +namespace ARMeilleure.Translation +{ + public class Translator + { + private const ulong CallFlag = InstEmitFlowHelper.CallFlag; + + private MemoryManager _memory; + + private ConcurrentDictionary _funcs; + + private PriorityQueue _backgroundQueue; + + private AutoResetEvent _backgroundTranslatorEvent; + + private volatile int _threadCount; + + public Translator(MemoryManager memory) + { + _memory = memory; + + _funcs = new ConcurrentDictionary(); + + _backgroundQueue = new PriorityQueue(2); + + _backgroundTranslatorEvent = new AutoResetEvent(false); + } + + private void TranslateQueuedSubs() + { + while (_threadCount != 0) + { + if (_backgroundQueue.TryDequeue(out ulong address)) + { + TranslatedFunction func = Translate(address, ExecutionMode.Aarch64, highCq: true); + + _funcs.AddOrUpdate(address, func, (key, oldFunc) => func); + } + else + { + _backgroundTranslatorEvent.WaitOne(); + } + } + } + + public void Execute(State.ExecutionContext context, ulong address) + { + if (Interlocked.Increment(ref _threadCount) == 1) + { + Thread backgroundTranslatorThread = new Thread(TranslateQueuedSubs) + { + Name = "CPU.BackgroundTranslatorThread", + Priority = ThreadPriority.Lowest + }; + + backgroundTranslatorThread.Start(); + } + + Statistics.InitializeTimer(); + + NativeInterface.RegisterThread(context, _memory); + + do + { + address = ExecuteSingle(context, address); + } + while (context.Running && (address & ~1UL) != 0); + + NativeInterface.UnregisterThread(); + + if (Interlocked.Decrement(ref _threadCount) == 0) + { + _backgroundTranslatorEvent.Set(); + } + } + + public ulong ExecuteSingle(State.ExecutionContext context, ulong address) + { + TranslatedFunction func = GetOrTranslate(address, context.ExecutionMode); + + Statistics.StartTimer(); + + ulong nextAddr = func.Execute(context); + + Statistics.StopTimer(address); + + return nextAddr; + } + + private TranslatedFunction GetOrTranslate(ulong address, ExecutionMode mode) + { + // TODO: Investigate how we should handle code at unaligned addresses. + // Currently, those low bits are used to store special flags. + bool isCallTarget = (address & CallFlag) != 0; + + address &= ~CallFlag; + + if (!_funcs.TryGetValue(address, out TranslatedFunction func)) + { + func = Translate(address, mode, highCq: false); + + _funcs.TryAdd(address, func); + } + else if (isCallTarget && func.ShouldRejit()) + { + _backgroundQueue.Enqueue(0, address); + + _backgroundTranslatorEvent.Set(); + } + + return func; + } + + private TranslatedFunction Translate(ulong address, ExecutionMode mode, bool highCq) + { + ArmEmitterContext context = new ArmEmitterContext(_memory, Aarch32Mode.User); + + Logger.StartPass(PassName.Decoding); + + Block[] blocks = highCq + ? Decoder.DecodeFunction (_memory, address, mode) + : Decoder.DecodeBasicBlock(_memory, address, mode); + + 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); + + Logger.EndPass(PassName.Translation); + + Logger.StartPass(PassName.RegisterUsage); + + RegisterUsage.RunPass(cfg, isCompleteFunction: false); + + Logger.EndPass(PassName.RegisterUsage); + + OperandType[] argTypes = new OperandType[] { OperandType.I64 }; + + CompilerOptions options = highCq + ? CompilerOptions.HighCq + : CompilerOptions.None; + + GuestFunction func = Compiler.Compile(cfg, argTypes, OperandType.I64, options); + + return new TranslatedFunction(func, rejit: !highCq); + } + + private static ControlFlowGraph EmitAndGetCFG(ArmEmitterContext context, Block[] blocks) + { + for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++) + { + Block block = blocks[blkIndex]; + + context.CurrBlock = block; + + context.MarkLabel(context.GetLabel(block.Address)); + + 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 && block.Branch != null && block.Branch.Address <= block.Address) + { + EmitSynchronization(context); + } + + Operand lblPredicateSkip = null; + + 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 != null) + { + context.MarkLabel(lblPredicateSkip); + + // If this is the last op on the block, and there's no "next" block + // after this one, then we have to return right now, with the address + // of the next instruction to be executed (in the case that the condition + // is false, and the branch was not taken, as all basic blocks should end + // with some kind of branch). + if (isLastOp && block.Next == null) + { + context.Return(Const(opCode.Address + (ulong)opCode.OpCodeSizeInBytes)); + } + } + } + } + + return context.GetControlFlowGraph(); + } + + private static void EmitSynchronization(EmitterContext context) + { + long countOffs = NativeContext.GetCounterOffset(); + + Operand countAddr = context.Add(context.LoadArgument(OperandType.I64, 0), Const(countOffs)); + + Operand count = context.Load(OperandType.I32, countAddr); + + Operand lblNonZero = Label(); + Operand lblExit = Label(); + + context.BranchIfTrue(lblNonZero, count); + + context.Call(new _Void(NativeInterface.CheckSynchronization)); + + context.Branch(lblExit); + + context.MarkLabel(lblNonZero); + + count = context.Subtract(count, Const(1)); + + context.Store(countAddr, count); + + context.MarkLabel(lblExit); + } + } +} \ No newline at end of file diff --git a/CONFIG.md b/CONFIG.md index b5de9fa6dc..9b60c6162b 100644 --- a/CONFIG.md +++ b/CONFIG.md @@ -1,140 +1,177 @@ ## Config File -`Ryujinx.conf` should be present in executable folder (It's an *.ini file) following this format: +`Config.jsonc` should be present in executable folder. The available settings follow: -- `Logging_Enable_Info` *(bool)* +- `graphics_shaders_dump_path` *(string)* + + Dump shaders in local directory (e.g. `C:\ShaderDumps`) + +- `logging_enable_debug` *(bool)* + + Enable the Debug Logging. + +- `logging_enable_stub` *(bool)* + + Enable the Trace Logging. + +- `logging_enable_info` *(bool)* Enable the Informations Logging. -- `Logging_Enable_Trace` *(bool)* +- `logging_enable_warn` *(bool)* - Enable the Trace Logging (Enabled in Debug recommended). - -- `Logging_Enable_Debug` *(bool)* + Enable the Warning Logging. - Enable the Debug Logging (Enabled in Debug recommended). +- `logging_enable_error` *(bool)* -- `Logging_Enable_Warn` *(bool)* + Enable the Error Logging. - Enable the Warning Logging (Enabled in Debug recommended). - -- `Logging_Enable_Error` *(bool)* - - Enable the Error Logging (Enabled in Debug recommended). - -- `Logging_Enable_Fatal` *(bool)* - - Enable the Fatal Logging (Enabled in Debug recommended). - -- `Logging_Enable_Ipc` *(bool)* - - Enable the Ipc Message Logging. - -- `Logging_Enable_LogFile` *(bool)* +- `enable_file_log` *(bool)* Enable writing the logging inside a Ryujinx.log file. - -- `GamePad_Index` *(int)* - The index of the Controller Device. - -- `GamePad_Deadzone` *(float)* +- `system_language` *(string)* - The deadzone of both analog sticks on the Controller. + Change System Language, [System Language list](https://gist.github.com/HorrorTroll/b6e4a88d774c3c9b3bdf54d79a7ca43b) -- `GamePad_Enable` *(bool)* - - Whether or not to enable Controller Support. - -- `Controls_Left_JoyConKeyboard_XX` *(int)* - ``` - Controls_Left_JoyConKeyboard_Stick_Up (int) - Controls_Left_JoyConKeyboard_Stick_Down (int) - Controls_Left_JoyConKeyboard_Stick_Left (int) - Controls_Left_JoyConKeyboard_Stick_Right (int) - Controls_Left_JoyConKeyboard_Stick_Button (int) - Controls_Left_JoyConKeyboard_DPad_Up (int) - Controls_Left_JoyConKeyboard_DPad_Down (int) - Controls_Left_JoyConKeyboard_DPad_Left (int) - Controls_Left_JoyConKeyboard_DPad_Right (int) - Controls_Left_JoyConKeyboard_Button_Minus (int) - Controls_Left_JoyConKeyboard_Button_L (int) - Controls_Left_JoyConKeyboard_Button_ZL (int) - ``` - - Keys of the Left Emulated Joycon, the values depend of the [OpenTK Enum Keys](https://github.com/opentk/opentk/blob/develop/src/OpenTK/Input/Key.cs). - - OpenTK use a QWERTY layout, so pay attention if you use another Keyboard Layout. - - Ex: `Controls_Left_JoyConKeyboard_Button_Minus = 52` > Tab key (All Layout). +- `docked_mode` *(bool)* -- `Controls_Right_JoyConKeyboard_XX` *(int)* - ``` - Controls_Right_JoyConKeyboard_Stick_Up (int) - Controls_Right_JoyConKeyboard_Stick_Down (int) - Controls_Right_JoyConKeyboard_Stick_Left (int) - Controls_Right_JoyConKeyboard_Stick_Right (int) - Controls_Right_JoyConKeyboard_Stick_Button (int) - Controls_Right_JoyConKeyboard_Button_A (int) - Controls_Right_JoyConKeyboard_Button_B (int) - Controls_Right_JoyConKeyboard_Button_X (int) - Controls_Right_JoyConKeyboard_Button_Y (int) - Controls_Right_JoyConKeyboard_Button_Plus (int) - Controls_Right_JoyConKeyboard_Button_R (int) - Controls_Right_JoyConKeyboard_Button_ZR (int) - ``` + Enable or Disable Docked Mode - Keys of the right Emulated Joycon, the values depend of the [OpenTK Enum Keys](https://github.com/opentk/opentk/blob/develop/src/OpenTK/Input/Key.cs). - - OpenTK use a QWERTY layout, so pay attention if you use another Keyboard Layout. - - Ex: `Controls_Right_JoyConKeyboard_Button_A = 83` > A key (QWERTY Layout) / Q key (AZERTY Layout). - -- `Controls_Left_JoyConController_XX` *(String)* - ``` - Controls_Left_JoyConController_Stick (String) - Controls_Left_JoyConController_Stick_Button (String) - Controls_Left_JoyConController_DPad_Up (String) - Controls_Left_JoyConController_DPad_Down (String) - Controls_Left_JoyConController_DPad_Left (String) - Controls_Left_JoyConController_DPad_Right (String) - Controls_Left_JoyConController_Button_Minus (String) - Controls_Left_JoyConController_Button_L (String) - Controls_Left_JoyConController_Button_ZL (String) - ``` - -- `Controls_Right_JoyConController_XX` *(String)* - ``` - Controls_Right_JoyConController_Stick (String) - Controls_Right_JoyConController_Stick_Button (String) - Controls_Right_JoyConController_Button_A (String) - Controls_Right_JoyConController_Button_B (String) - Controls_Right_JoyConController_Button_X (String) - Controls_Right_JoyConController_Button_Y (String) - Controls_Right_JoyConController_Button_Plus (String) - Controls_Right_JoyConController_Button_R (String) - Controls_Right_JoyConController_Button_ZR (String) - ``` +- `enable_vsync` *(bool)* -- Valid Button Mappings - - A = The A / Cross Button - - B = The B / Circle Button - - X = The X / Square Button - - Y = The Y / Triangle Button - - LStick = The Left Analog Stick when Pressed Down - - RStick = The Right Analog Stick when Pressed Down - - Start = The Start / Options Button - - Back = The Select / Back / Share Button - - RShoulder = The Right Shoulder Button - - LShoulder = The Left Shoulder Button - - RTrigger = The Right Trigger - - LTrigger = The Left Trigger - - DPadUp = Up on the DPad - - DPadDown = Down on the DPad - - DPadLeft = Left on the DPad - - DpadRight = Right on the DPad -- Valid Joystick Mappings - - LJoystick = The Left Analog Stick - - RJoystick = The Right Analog Stick + Enable or Disable Game Vsync - On more obscure / weird controllers this can vary, so if this list doesn't work, trial and error will. \ No newline at end of file +- `enable_multicore_scheduling` *(bool)* + + Enable or Disable Multi-core scheduling of threads + +- `enable_fs_integrity_checks` *(bool)* + + Enable integrity checks on Switch content files + +- `controller_type` *(string)* + + The primary controller's type. + Supported Values: `Handheld`, `ProController`, `NpadPair`, `NpadLeft`, `NpadRight` + +- `keyboard_controls` *(object)* : + - `left_joycon` *(object)* : + Left JoyCon Keyboard Bindings + - `stick_up` *(string)* + - `stick_down` *(string)* + - `stick_left` *(string)* + - `stick_right` *(string)* + - `stick_button` *(string)* + - `dpad_up` *(string)* + - `dpad_down` *(string)* + - `dpad_left` *(string)* + - `dpad_right` *(string)* + - `button_minus` *(string)* + - `button_l` *(string)* + - `button_zl` *(string)* + - `right_joycon` *(object)* : + Right JoyCon Keyboard Bindings + - `stick_up` *(string)* + - `stick_down` *(string)* + - `stick_left` *(string)* + - `stick_right` *(string)* + - `stick_button` *(string)* + - `button_a` *(string)* + - `button_b` *(string)* + - `button_x` *(string)* + - `button_y` *(string)* + - `button_plus` *(string)* + - `button_r` *(string)* + - `button_zr` *(string)* + +- `joystick_controls` *(object)* : + - `enabled` *(bool)* + Whether or not to enable Controller Support. + - `index` *(int)* + The index of the Controller Device. + - `deadzone` *(number)* + The deadzone of both analog sticks on the Controller. + - `trigger_threshold` *(number)* + The value of how pressed down each trigger has to be in order to register a button press + - `left_joycon` *(object)* : + Left JoyCon Controller Bindings + - `stick` *(string)* + - `stick_button` *(string)* + - `dpad_up` *(string)* + - `dpad_down` *(string)* + - `dpad_left` *(string)* + - `dpad_right` *(string)* + - `button_minus` *(string)* + - `button_l` *(string)* + - `button_zl` *(string)* + - `right_joycon` *(object)* : + Right JoyCon Controller Bindings + - `stick` *(string)* + - `stick_button` *(string)* + - `button_a` *(string)* + - `button_b` *(string)* + - `button_x` *(string)* + - `button_y` *(string)* + - `button_plus` *(string)* + - `button_r` *(string)* + - `button_zr` *(string)* + +### Default Mapping. + #### Controller + - Left Joycon: + - Analog Stick = Axis 0 + - DPad Up = DPad Up #Hat0 Up + - DPad Down = DPad Down #Hat0 Down + - DPad Left = DPad Left #Hat0 Left + - DPad Right = DPad Right #Hat0 Right + - Minus = Button 10 + - L = Button 6 + - ZL = Button 8 + + - Right Joycon: + - Analog Stick = Axis 2 + - A = Button 0 + - B = Button 1 + - X = Button 3 + - Y = Button 4 + - Plus = Button 11 + - R = Button 7 + - ZR = Button 9 + + #### Keyboard + - Left Joycon: + - Stick Up = W + - Stick Down = S + - Stick Left = A + - Stick Right = D + - Stick Button = F + - DPad Up = Up + - DPad Down = Down + - DPad Left = Left + - DPad Right = Right + - Minus = - + - L = E + - ZL = Q + + - Right Joycon: + - Stick Up = I + - Stick Down = K + - Stick Left = J + - Stick Right = L + - Stick Button = H + - A = Z + - B = X + - X = C + - Y = V + - Plus = + + - R = U + - ZR = O + +### Valid Button Mappings. + - Button# = A button on the controller. # should not exceed the max # of buttons detected on your controller. + - Axis# = An analog axis on the controller. It can be a stick control, or a motion control axis. + - Hat# = A Point of View (POV), Hat or Directional Pad control on the controller. + + Button configuration and controller capabilities differ from one controller to another. Please use a + configuration tool to find out the actual button configuration of your controller. \ No newline at end of file diff --git a/ChocolArm64/ABitUtils.cs b/ChocolArm64/ABitUtils.cs deleted file mode 100644 index 357dd45d15..0000000000 --- a/ChocolArm64/ABitUtils.cs +++ /dev/null @@ -1,57 +0,0 @@ -namespace ChocolArm64 -{ - static class ABitUtils - { - public static int CountBitsSet(long Value) - { - int Count = 0; - - for (int Bit = 0; Bit < 64; Bit++) - { - Count += (int)(Value >> Bit) & 1; - } - - return Count; - } - - public static int HighestBitSet32(int Value) - { - for (int Bit = 31; Bit >= 0; Bit--) - { - if (((Value >> Bit) & 1) != 0) - { - return Bit; - } - } - - return -1; - } - - 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 long FillWithOnes(int Bits) - { - return Bits == 64 ? -1L : (1L << Bits) - 1; - } - - public static long RotateRight(long Bits, int Shift, int Size) - { - return (Bits >> Shift) | (Bits << (Size - Shift)); - } - - public static bool IsPow2(int Value) - { - return Value != 0 && (Value & (Value - 1)) == 0; - } - } -} \ No newline at end of file diff --git a/ChocolArm64/AOpCodeTable.cs b/ChocolArm64/AOpCodeTable.cs deleted file mode 100644 index fb4763ef8c..0000000000 --- a/ChocolArm64/AOpCodeTable.cs +++ /dev/null @@ -1,581 +0,0 @@ -using ChocolArm64.Decoder; -using ChocolArm64.Decoder32; -using ChocolArm64.Instruction; -using ChocolArm64.Instruction32; -using ChocolArm64.State; -using System; - -namespace ChocolArm64 -{ - static class AOpCodeTable - { - static AOpCodeTable() - { -#region "OpCode Table (AArch32)" - //Integer - SetA32("<<<<1010xxxxxxxxxxxxxxxxxxxxxxxx", A32InstInterpret.B, typeof(A32OpCodeBImmAl)); - SetA32("<<<<1011xxxxxxxxxxxxxxxxxxxxxxxx", A32InstInterpret.Bl, typeof(A32OpCodeBImmAl)); - SetA32("1111101xxxxxxxxxxxxxxxxxxxxxxxxx", A32InstInterpret.Blx, typeof(A32OpCodeBImmAl)); -#endregion - -#region "OpCode Table (AArch64)" - //Integer - SetA64("x0011010000xxxxx000000xxxxxxxxxx", AInstEmit.Adc, typeof(AOpCodeAluRs)); - SetA64("x0111010000xxxxx000000xxxxxxxxxx", AInstEmit.Adcs, typeof(AOpCodeAluRs)); - SetA64("x00100010xxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Add, typeof(AOpCodeAluImm)); - SetA64("00001011<<0xxxxx0xxxxxxxxxxxxxxx", AInstEmit.Add, typeof(AOpCodeAluRs)); - SetA64("10001011<<0xxxxxxxxxxxxxxxxxxxxx", AInstEmit.Add, typeof(AOpCodeAluRs)); - SetA64("x0001011001xxxxxxxx0xxxxxxxxxxxx", AInstEmit.Add, typeof(AOpCodeAluRx)); - SetA64("x0001011001xxxxxxxx100xxxxxxxxxx", AInstEmit.Add, typeof(AOpCodeAluRx)); - SetA64("x01100010xxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Adds, typeof(AOpCodeAluImm)); - SetA64("00101011<<0xxxxx0xxxxxxxxxxxxxxx", AInstEmit.Adds, typeof(AOpCodeAluRs)); - SetA64("10101011<<0xxxxxxxxxxxxxxxxxxxxx", AInstEmit.Adds, typeof(AOpCodeAluRs)); - SetA64("x0101011001xxxxxxxx0xxxxxxxxxxxx", AInstEmit.Adds, typeof(AOpCodeAluRx)); - SetA64("x0101011001xxxxxxxx100xxxxxxxxxx", AInstEmit.Adds, typeof(AOpCodeAluRx)); - SetA64("0xx10000xxxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Adr, typeof(AOpCodeAdr)); - SetA64("1xx10000xxxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Adrp, typeof(AOpCodeAdr)); - SetA64("0001001000xxxxxxxxxxxxxxxxxxxxxx", AInstEmit.And, typeof(AOpCodeAluImm)); - SetA64("100100100xxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.And, typeof(AOpCodeAluImm)); - SetA64("00001010xx0xxxxx0xxxxxxxxxxxxxxx", AInstEmit.And, typeof(AOpCodeAluRs)); - SetA64("10001010xx0xxxxxxxxxxxxxxxxxxxxx", AInstEmit.And, typeof(AOpCodeAluRs)); - SetA64("0111001000xxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Ands, typeof(AOpCodeAluImm)); - SetA64("111100100xxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Ands, typeof(AOpCodeAluImm)); - SetA64("01101010xx0xxxxx0xxxxxxxxxxxxxxx", AInstEmit.Ands, typeof(AOpCodeAluRs)); - SetA64("11101010xx0xxxxxxxxxxxxxxxxxxxxx", AInstEmit.Ands, typeof(AOpCodeAluRs)); - SetA64("x0011010110xxxxx001010xxxxxxxxxx", AInstEmit.Asrv, typeof(AOpCodeAluRs)); - SetA64("000101xxxxxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.B, typeof(AOpCodeBImmAl)); - SetA64("01010100xxxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.B_Cond, typeof(AOpCodeBImmCond)); - SetA64("00110011000xxxxx0xxxxxxxxxxxxxxx", AInstEmit.Bfm, typeof(AOpCodeBfm)); - SetA64("1011001101xxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Bfm, typeof(AOpCodeBfm)); - SetA64("00001010xx1xxxxx0xxxxxxxxxxxxxxx", AInstEmit.Bic, typeof(AOpCodeAluRs)); - SetA64("10001010xx1xxxxxxxxxxxxxxxxxxxxx", AInstEmit.Bic, typeof(AOpCodeAluRs)); - SetA64("01101010xx1xxxxx0xxxxxxxxxxxxxxx", AInstEmit.Bics, typeof(AOpCodeAluRs)); - SetA64("11101010xx1xxxxxxxxxxxxxxxxxxxxx", AInstEmit.Bics, typeof(AOpCodeAluRs)); - SetA64("100101xxxxxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Bl, typeof(AOpCodeBImmAl)); - SetA64("11010110001xxxxx000000xxxxxxxxxx", AInstEmit.Blr, typeof(AOpCodeBReg)); - SetA64("11010110000xxxxx000000xxxxxxxxxx", AInstEmit.Br, typeof(AOpCodeBReg)); - SetA64("11010100001xxxxxxxxxxxxxxxx00000", AInstEmit.Brk, typeof(AOpCodeException)); - SetA64("x0110101xxxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Cbnz, typeof(AOpCodeBImmCmp)); - SetA64("x0110100xxxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Cbz, typeof(AOpCodeBImmCmp)); - SetA64("x0111010010xxxxxxxxx10xxxxx0xxxx", AInstEmit.Ccmn, typeof(AOpCodeCcmpImm)); - SetA64("x0111010010xxxxxxxxx00xxxxx0xxxx", AInstEmit.Ccmn, typeof(AOpCodeCcmpReg)); - SetA64("x1111010010xxxxxxxxx10xxxxx0xxxx", AInstEmit.Ccmp, typeof(AOpCodeCcmpImm)); - SetA64("x1111010010xxxxxxxxx00xxxxx0xxxx", AInstEmit.Ccmp, typeof(AOpCodeCcmpReg)); - SetA64("11010101000000110011xxxx01011111", AInstEmit.Clrex, typeof(AOpCodeSystem)); - SetA64("x101101011000000000101xxxxxxxxxx", AInstEmit.Cls, typeof(AOpCodeAlu)); - SetA64("x101101011000000000100xxxxxxxxxx", AInstEmit.Clz, typeof(AOpCodeAlu)); - SetA64("00011010110xxxxx010000xxxxxxxxxx", AInstEmit.Crc32b, typeof(AOpCodeAluRs)); - SetA64("00011010110xxxxx010001xxxxxxxxxx", AInstEmit.Crc32h, typeof(AOpCodeAluRs)); - SetA64("00011010110xxxxx010010xxxxxxxxxx", AInstEmit.Crc32w, typeof(AOpCodeAluRs)); - SetA64("10011010110xxxxx010011xxxxxxxxxx", AInstEmit.Crc32x, typeof(AOpCodeAluRs)); - SetA64("00011010110xxxxx010100xxxxxxxxxx", AInstEmit.Crc32cb, typeof(AOpCodeAluRs)); - SetA64("00011010110xxxxx010101xxxxxxxxxx", AInstEmit.Crc32ch, typeof(AOpCodeAluRs)); - SetA64("00011010110xxxxx010110xxxxxxxxxx", AInstEmit.Crc32cw, typeof(AOpCodeAluRs)); - SetA64("10011010110xxxxx010111xxxxxxxxxx", AInstEmit.Crc32cx, typeof(AOpCodeAluRs)); - SetA64("x0011010100xxxxxxxxx00xxxxxxxxxx", AInstEmit.Csel, typeof(AOpCodeCsel)); - SetA64("x0011010100xxxxxxxxx01xxxxxxxxxx", AInstEmit.Csinc, typeof(AOpCodeCsel)); - SetA64("x1011010100xxxxxxxxx00xxxxxxxxxx", AInstEmit.Csinv, typeof(AOpCodeCsel)); - SetA64("x1011010100xxxxxxxxx01xxxxxxxxxx", AInstEmit.Csneg, typeof(AOpCodeCsel)); - SetA64("11010101000000110011xxxx10111111", AInstEmit.Dmb, typeof(AOpCodeSystem)); - SetA64("11010101000000110011xxxx10011111", AInstEmit.Dsb, typeof(AOpCodeSystem)); - SetA64("01001010xx1xxxxx0xxxxxxxxxxxxxxx", AInstEmit.Eon, typeof(AOpCodeAluRs)); - SetA64("11001010xx1xxxxxxxxxxxxxxxxxxxxx", AInstEmit.Eon, typeof(AOpCodeAluRs)); - SetA64("0101001000xxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Eor, typeof(AOpCodeAluImm)); - SetA64("110100100xxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Eor, typeof(AOpCodeAluImm)); - SetA64("01001010xx0xxxxx0xxxxxxxxxxxxxxx", AInstEmit.Eor, typeof(AOpCodeAluRs)); - SetA64("11001010xx0xxxxxxxxxxxxxxxxxxxxx", AInstEmit.Eor, typeof(AOpCodeAluRs)); - SetA64("00010011100xxxxx0xxxxxxxxxxxxxxx", AInstEmit.Extr, typeof(AOpCodeAluRs)); - SetA64("10010011110xxxxxxxxxxxxxxxxxxxxx", AInstEmit.Extr, typeof(AOpCodeAluRs)); - SetA64("11010101000000110010xxxxxxx11111", AInstEmit.Hint, typeof(AOpCodeSystem)); - SetA64("xx001000110xxxxx1xxxxxxxxxxxxxxx", AInstEmit.Ldar, typeof(AOpCodeMemEx)); - SetA64("1x001000011xxxxx1xxxxxxxxxxxxxxx", AInstEmit.Ldaxp, typeof(AOpCodeMemEx)); - SetA64("xx001000010xxxxx1xxxxxxxxxxxxxxx", AInstEmit.Ldaxr, typeof(AOpCodeMemEx)); - SetA64("<<10100xx1xxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Ldp, typeof(AOpCodeMemPair)); - SetA64("xx111000010xxxxxxxxxxxxxxxxxxxxx", AInstEmit.Ldr, typeof(AOpCodeMemImm)); - SetA64("xx11100101xxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Ldr, typeof(AOpCodeMemImm)); - SetA64("xx111000011xxxxxxxxx10xxxxxxxxxx", AInstEmit.Ldr, typeof(AOpCodeMemReg)); - SetA64("xx011000xxxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.LdrLit, typeof(AOpCodeMemLit)); - SetA64("0x1110001x0xxxxxxxxxxxxxxxxxxxxx", AInstEmit.Ldrs, typeof(AOpCodeMemImm)); - SetA64("0x1110011xxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Ldrs, typeof(AOpCodeMemImm)); - SetA64("10111000100xxxxxxxxxxxxxxxxxxxxx", AInstEmit.Ldrs, typeof(AOpCodeMemImm)); - SetA64("1011100110xxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Ldrs, typeof(AOpCodeMemImm)); - SetA64("0x1110001x1xxxxxxxxx10xxxxxxxxxx", AInstEmit.Ldrs, typeof(AOpCodeMemReg)); - SetA64("10111000101xxxxxxxxx10xxxxxxxxxx", AInstEmit.Ldrs, typeof(AOpCodeMemReg)); - SetA64("xx001000010xxxxx0xxxxxxxxxxxxxxx", AInstEmit.Ldxr, typeof(AOpCodeMemEx)); - SetA64("1x001000011xxxxx0xxxxxxxxxxxxxxx", AInstEmit.Ldxp, typeof(AOpCodeMemEx)); - SetA64("x0011010110xxxxx001000xxxxxxxxxx", AInstEmit.Lslv, typeof(AOpCodeAluRs)); - SetA64("x0011010110xxxxx001001xxxxxxxxxx", AInstEmit.Lsrv, typeof(AOpCodeAluRs)); - SetA64("x0011011000xxxxx0xxxxxxxxxxxxxxx", AInstEmit.Madd, typeof(AOpCodeMul)); - SetA64("0111001010xxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Movk, typeof(AOpCodeMov)); - SetA64("111100101xxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Movk, typeof(AOpCodeMov)); - SetA64("0001001010xxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Movn, typeof(AOpCodeMov)); - SetA64("100100101xxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Movn, typeof(AOpCodeMov)); - SetA64("0101001010xxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Movz, typeof(AOpCodeMov)); - SetA64("110100101xxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Movz, typeof(AOpCodeMov)); - SetA64("110101010011xxxxxxxxxxxxxxxxxxxx", AInstEmit.Mrs, typeof(AOpCodeSystem)); - SetA64("110101010001xxxxxxxxxxxxxxxxxxxx", AInstEmit.Msr, typeof(AOpCodeSystem)); - SetA64("x0011011000xxxxx1xxxxxxxxxxxxxxx", AInstEmit.Msub, typeof(AOpCodeMul)); - SetA64("11010101000000110010000000011111", AInstEmit.Nop, typeof(AOpCodeSystem)); - SetA64("00101010xx1xxxxx0xxxxxxxxxxxxxxx", AInstEmit.Orn, typeof(AOpCodeAluRs)); - SetA64("10101010xx1xxxxxxxxxxxxxxxxxxxxx", AInstEmit.Orn, typeof(AOpCodeAluRs)); - SetA64("0011001000xxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Orr, typeof(AOpCodeAluImm)); - SetA64("101100100xxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Orr, typeof(AOpCodeAluImm)); - SetA64("00101010xx0xxxxx0xxxxxxxxxxxxxxx", AInstEmit.Orr, typeof(AOpCodeAluRs)); - SetA64("10101010xx0xxxxxxxxxxxxxxxxxxxxx", AInstEmit.Orr, typeof(AOpCodeAluRs)); - SetA64("1111100110xxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Pfrm, typeof(AOpCodeMemImm)); - SetA64("11111000100xxxxxxxxx00xxxxxxxxxx", AInstEmit.Pfrm, typeof(AOpCodeMemImm)); - SetA64("11011000xxxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Pfrm, typeof(AOpCodeMemLit)); - SetA64("x101101011000000000000xxxxxxxxxx", AInstEmit.Rbit, typeof(AOpCodeAlu)); - SetA64("11010110010xxxxx000000xxxxxxxxxx", AInstEmit.Ret, typeof(AOpCodeBReg)); - SetA64("x101101011000000000001xxxxxxxxxx", AInstEmit.Rev16, typeof(AOpCodeAlu)); - SetA64("x101101011000000000010xxxxxxxxxx", AInstEmit.Rev32, typeof(AOpCodeAlu)); - SetA64("1101101011000000000011xxxxxxxxxx", AInstEmit.Rev64, typeof(AOpCodeAlu)); - SetA64("x0011010110xxxxx001011xxxxxxxxxx", AInstEmit.Rorv, typeof(AOpCodeAluRs)); - SetA64("x1011010000xxxxx000000xxxxxxxxxx", AInstEmit.Sbc, typeof(AOpCodeAluRs)); - SetA64("x1111010000xxxxx000000xxxxxxxxxx", AInstEmit.Sbcs, typeof(AOpCodeAluRs)); - SetA64("00010011000xxxxx0xxxxxxxxxxxxxxx", AInstEmit.Sbfm, typeof(AOpCodeBfm)); - SetA64("1001001101xxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Sbfm, typeof(AOpCodeBfm)); - SetA64("x0011010110xxxxx000011xxxxxxxxxx", AInstEmit.Sdiv, typeof(AOpCodeAluRs)); - SetA64("10011011001xxxxx0xxxxxxxxxxxxxxx", AInstEmit.Smaddl, typeof(AOpCodeMul)); - SetA64("10011011001xxxxx1xxxxxxxxxxxxxxx", AInstEmit.Smsubl, typeof(AOpCodeMul)); - SetA64("10011011010xxxxx0xxxxxxxxxxxxxxx", AInstEmit.Smulh, typeof(AOpCodeMul)); - SetA64("xx001000100xxxxx1xxxxxxxxxxxxxxx", AInstEmit.Stlr, typeof(AOpCodeMemEx)); - SetA64("1x001000001xxxxx1xxxxxxxxxxxxxxx", AInstEmit.Stlxp, typeof(AOpCodeMemEx)); - SetA64("xx001000000xxxxx1xxxxxxxxxxxxxxx", AInstEmit.Stlxr, typeof(AOpCodeMemEx)); - SetA64("x010100xx0xxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Stp, typeof(AOpCodeMemPair)); - SetA64("xx111000000xxxxxxxxxxxxxxxxxxxxx", AInstEmit.Str, typeof(AOpCodeMemImm)); - SetA64("xx11100100xxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Str, typeof(AOpCodeMemImm)); - SetA64("xx111000001xxxxxxxxx10xxxxxxxxxx", AInstEmit.Str, typeof(AOpCodeMemReg)); - SetA64("1x001000001xxxxx0xxxxxxxxxxxxxxx", AInstEmit.Stxp, typeof(AOpCodeMemEx)); - SetA64("xx001000000xxxxx0xxxxxxxxxxxxxxx", AInstEmit.Stxr, typeof(AOpCodeMemEx)); - SetA64("x10100010xxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Sub, typeof(AOpCodeAluImm)); - SetA64("01001011<<0xxxxx0xxxxxxxxxxxxxxx", AInstEmit.Sub, typeof(AOpCodeAluRs)); - SetA64("11001011<<0xxxxxxxxxxxxxxxxxxxxx", AInstEmit.Sub, typeof(AOpCodeAluRs)); - SetA64("x1001011001xxxxxxxx0xxxxxxxxxxxx", AInstEmit.Sub, typeof(AOpCodeAluRx)); - SetA64("x1001011001xxxxxxxx100xxxxxxxxxx", AInstEmit.Sub, typeof(AOpCodeAluRx)); - SetA64("x11100010xxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Subs, typeof(AOpCodeAluImm)); - SetA64("01101011<<0xxxxx0xxxxxxxxxxxxxxx", AInstEmit.Subs, typeof(AOpCodeAluRs)); - SetA64("11101011<<0xxxxxxxxxxxxxxxxxxxxx", AInstEmit.Subs, typeof(AOpCodeAluRs)); - SetA64("x1101011001xxxxxxxx0xxxxxxxxxxxx", AInstEmit.Subs, typeof(AOpCodeAluRx)); - SetA64("x1101011001xxxxxxxx100xxxxxxxxxx", AInstEmit.Subs, typeof(AOpCodeAluRx)); - SetA64("11010100000xxxxxxxxxxxxxxxx00001", AInstEmit.Svc, typeof(AOpCodeException)); - SetA64("1101010100001xxxxxxxxxxxxxxxxxxx", AInstEmit.Sys, typeof(AOpCodeSystem)); - SetA64("x0110111xxxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Tbnz, typeof(AOpCodeBImmTest)); - SetA64("x0110110xxxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Tbz, typeof(AOpCodeBImmTest)); - SetA64("01010011000xxxxx0xxxxxxxxxxxxxxx", AInstEmit.Ubfm, typeof(AOpCodeBfm)); - SetA64("1101001101xxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Ubfm, typeof(AOpCodeBfm)); - SetA64("x0011010110xxxxx000010xxxxxxxxxx", AInstEmit.Udiv, typeof(AOpCodeAluRs)); - SetA64("10011011101xxxxx0xxxxxxxxxxxxxxx", AInstEmit.Umaddl, typeof(AOpCodeMul)); - SetA64("10011011101xxxxx1xxxxxxxxxxxxxxx", AInstEmit.Umsubl, typeof(AOpCodeMul)); - SetA64("10011011110xxxxx0xxxxxxxxxxxxxxx", AInstEmit.Umulh, typeof(AOpCodeMul)); - - //Vector - SetA64("0101111011100000101110xxxxxxxxxx", AInstEmit.Abs_S, typeof(AOpCodeSimd)); - SetA64("0>001110<<100000101110xxxxxxxxxx", AInstEmit.Abs_V, typeof(AOpCodeSimd)); - SetA64("01011110111xxxxx100001xxxxxxxxxx", AInstEmit.Add_S, typeof(AOpCodeSimdReg)); - SetA64("0>001110<<1xxxxx100001xxxxxxxxxx", AInstEmit.Add_V, typeof(AOpCodeSimdReg)); - SetA64("0x001110<<1xxxxx010000xxxxxxxxxx", AInstEmit.Addhn_V, typeof(AOpCodeSimdReg)); - SetA64("0101111011110001101110xxxxxxxxxx", AInstEmit.Addp_S, typeof(AOpCodeSimd)); - SetA64("0>001110<<1xxxxx101111xxxxxxxxxx", AInstEmit.Addp_V, typeof(AOpCodeSimdReg)); - SetA64("000011100x110001101110xxxxxxxxxx", AInstEmit.Addv_V, typeof(AOpCodeSimd)); - SetA64("01001110<<110001101110xxxxxxxxxx", AInstEmit.Addv_V, typeof(AOpCodeSimd)); - SetA64("0x001110001xxxxx000111xxxxxxxxxx", AInstEmit.And_V, typeof(AOpCodeSimdReg)); - SetA64("0x001110011xxxxx000111xxxxxxxxxx", AInstEmit.Bic_V, typeof(AOpCodeSimdReg)); - SetA64("0x10111100000xxx<101110<<1xxxxx100011xxxxxxxxxx", AInstEmit.Cmeq_V, typeof(AOpCodeSimdReg)); - SetA64("0>001110<<100000100110xxxxxxxxxx", AInstEmit.Cmeq_V, typeof(AOpCodeSimd)); - SetA64("01011110111xxxxx001111xxxxxxxxxx", AInstEmit.Cmge_S, typeof(AOpCodeSimdReg)); - SetA64("0111111011100000100010xxxxxxxxxx", AInstEmit.Cmge_S, typeof(AOpCodeSimd)); - SetA64("0>001110<<1xxxxx001111xxxxxxxxxx", AInstEmit.Cmge_V, typeof(AOpCodeSimdReg)); - SetA64("0>101110<<100000100010xxxxxxxxxx", AInstEmit.Cmge_V, typeof(AOpCodeSimd)); - SetA64("01011110111xxxxx001101xxxxxxxxxx", AInstEmit.Cmgt_S, typeof(AOpCodeSimdReg)); - SetA64("0101111011100000100010xxxxxxxxxx", AInstEmit.Cmgt_S, typeof(AOpCodeSimd)); - SetA64("0>001110<<1xxxxx001101xxxxxxxxxx", AInstEmit.Cmgt_V, typeof(AOpCodeSimdReg)); - SetA64("0>001110<<100000100010xxxxxxxxxx", AInstEmit.Cmgt_V, typeof(AOpCodeSimd)); - SetA64("01111110111xxxxx001101xxxxxxxxxx", AInstEmit.Cmhi_S, typeof(AOpCodeSimdReg)); - SetA64("0>101110<<1xxxxx001101xxxxxxxxxx", AInstEmit.Cmhi_V, typeof(AOpCodeSimdReg)); - SetA64("01111110111xxxxx001111xxxxxxxxxx", AInstEmit.Cmhs_S, typeof(AOpCodeSimdReg)); - SetA64("0>101110<<1xxxxx001111xxxxxxxxxx", AInstEmit.Cmhs_V, typeof(AOpCodeSimdReg)); - SetA64("0111111011100000100110xxxxxxxxxx", AInstEmit.Cmle_S, typeof(AOpCodeSimd)); - SetA64("0>101110<<100000100110xxxxxxxxxx", AInstEmit.Cmle_V, typeof(AOpCodeSimd)); - SetA64("0101111011100000101010xxxxxxxxxx", AInstEmit.Cmlt_S, typeof(AOpCodeSimd)); - SetA64("0>001110<<100000101010xxxxxxxxxx", AInstEmit.Cmlt_V, typeof(AOpCodeSimd)); - SetA64("01011110111xxxxx100011xxxxxxxxxx", AInstEmit.Cmtst_S, typeof(AOpCodeSimdReg)); - SetA64("0>001110<<1xxxxx100011xxxxxxxxxx", AInstEmit.Cmtst_V, typeof(AOpCodeSimdReg)); - SetA64("0x00111000100000010110xxxxxxxxxx", AInstEmit.Cnt_V, typeof(AOpCodeSimd)); - SetA64("0x001110000xxxxx000011xxxxxxxxxx", AInstEmit.Dup_Gp, typeof(AOpCodeSimdIns)); - SetA64("01011110000xxxxx000001xxxxxxxxxx", AInstEmit.Dup_S, typeof(AOpCodeSimdIns)); - SetA64("0x001110000xxxxx000001xxxxxxxxxx", AInstEmit.Dup_V, typeof(AOpCodeSimdIns)); - SetA64("0x101110001xxxxx000111xxxxxxxxxx", AInstEmit.Eor_V, typeof(AOpCodeSimdReg)); - SetA64("0>101110000xxxxx00011101<100000111110xxxxxxxxxx", AInstEmit.Fabs_V, typeof(AOpCodeSimd)); - SetA64("000111100x1xxxxx001010xxxxxxxxxx", AInstEmit.Fadd_S, typeof(AOpCodeSimdReg)); - SetA64("0>0011100<1xxxxx110101xxxxxxxxxx", AInstEmit.Fadd_V, typeof(AOpCodeSimdReg)); - SetA64("011111100x110000110110xxxxxxxxxx", AInstEmit.Faddp_S, typeof(AOpCodeSimd)); - SetA64("0>1011100<1xxxxx110101xxxxxxxxxx", AInstEmit.Faddp_V, typeof(AOpCodeSimdReg)); - SetA64("000111100x1xxxxxxxxx01xxxxx0xxxx", AInstEmit.Fccmp_S, typeof(AOpCodeSimdFcond)); - SetA64("000111100x1xxxxxxxxx01xxxxx1xxxx", AInstEmit.Fccmpe_S, typeof(AOpCodeSimdFcond)); - SetA64("010111100x1xxxxx111001xxxxxxxxxx", AInstEmit.Fcmeq_S, typeof(AOpCodeSimdReg)); - SetA64("010111101x100000110110xxxxxxxxxx", AInstEmit.Fcmeq_S, typeof(AOpCodeSimd)); - SetA64("0>0011100<1xxxxx111001xxxxxxxxxx", AInstEmit.Fcmeq_V, typeof(AOpCodeSimdReg)); - SetA64("0>0011101<100000110110xxxxxxxxxx", AInstEmit.Fcmeq_V, typeof(AOpCodeSimd)); - SetA64("011111100x1xxxxx111001xxxxxxxxxx", AInstEmit.Fcmge_S, typeof(AOpCodeSimdReg)); - SetA64("011111101x100000110010xxxxxxxxxx", AInstEmit.Fcmge_S, typeof(AOpCodeSimd)); - SetA64("0>1011100<1xxxxx111001xxxxxxxxxx", AInstEmit.Fcmge_V, typeof(AOpCodeSimdReg)); - SetA64("0>1011101<100000110010xxxxxxxxxx", AInstEmit.Fcmge_V, typeof(AOpCodeSimd)); - SetA64("011111101x1xxxxx111001xxxxxxxxxx", AInstEmit.Fcmgt_S, typeof(AOpCodeSimdReg)); - SetA64("010111101x100000110010xxxxxxxxxx", AInstEmit.Fcmgt_S, typeof(AOpCodeSimd)); - SetA64("0>1011101<1xxxxx111001xxxxxxxxxx", AInstEmit.Fcmgt_V, typeof(AOpCodeSimdReg)); - SetA64("0>0011101<100000110010xxxxxxxxxx", AInstEmit.Fcmgt_V, typeof(AOpCodeSimd)); - SetA64("011111101x100000110110xxxxxxxxxx", AInstEmit.Fcmle_S, typeof(AOpCodeSimd)); - SetA64("0>1011101<100000110110xxxxxxxxxx", AInstEmit.Fcmle_V, typeof(AOpCodeSimd)); - SetA64("010111101x100000111010xxxxxxxxxx", AInstEmit.Fcmlt_S, typeof(AOpCodeSimd)); - SetA64("0>0011101<100000111010xxxxxxxxxx", AInstEmit.Fcmlt_V, typeof(AOpCodeSimd)); - SetA64("000111100x1xxxxx001000xxxxx0x000", AInstEmit.Fcmp_S, typeof(AOpCodeSimdReg)); - SetA64("000111100x1xxxxx001000xxxxx1x000", AInstEmit.Fcmpe_S, typeof(AOpCodeSimdReg)); - SetA64("000111100x1xxxxxxxxx11xxxxxxxxxx", AInstEmit.Fcsel_S, typeof(AOpCodeSimdFcond)); - SetA64("000111100x10001xx10000xxxxxxxxxx", AInstEmit.Fcvt_S, typeof(AOpCodeSimd)); - SetA64("x00111100x100100000000xxxxxxxxxx", AInstEmit.Fcvtas_Gp, typeof(AOpCodeSimdCvt)); - SetA64("x00111100x100101000000xxxxxxxxxx", AInstEmit.Fcvtau_Gp, typeof(AOpCodeSimdCvt)); - SetA64("0x0011100x100001011110xxxxxxxxxx", AInstEmit.Fcvtl_V, typeof(AOpCodeSimd)); - SetA64("x00111100x110000000000xxxxxxxxxx", AInstEmit.Fcvtms_Gp, typeof(AOpCodeSimdCvt)); - SetA64("x00111100x110001000000xxxxxxxxxx", AInstEmit.Fcvtmu_Gp, typeof(AOpCodeSimdCvt)); - SetA64("0x0011100x100001011010xxxxxxxxxx", AInstEmit.Fcvtn_V, typeof(AOpCodeSimd)); - SetA64("x00111100x101000000000xxxxxxxxxx", AInstEmit.Fcvtps_Gp, typeof(AOpCodeSimdCvt)); - SetA64("x00111100x101001000000xxxxxxxxxx", AInstEmit.Fcvtpu_Gp, typeof(AOpCodeSimdCvt)); - SetA64("x00111100x111000000000xxxxxxxxxx", AInstEmit.Fcvtzs_Gp, typeof(AOpCodeSimdCvt)); - SetA64("x00111100x011000xxxxxxxxxxxxxxxx", AInstEmit.Fcvtzs_Gp_Fix, typeof(AOpCodeSimdCvt)); - SetA64("010111101x100001101110xxxxxxxxxx", AInstEmit.Fcvtzs_S, typeof(AOpCodeSimd)); - SetA64("0>0011101<100001101110xxxxxxxxxx", AInstEmit.Fcvtzs_V, typeof(AOpCodeSimd)); - SetA64("0x0011110>>xxxxx111111xxxxxxxxxx", AInstEmit.Fcvtzs_V, typeof(AOpCodeSimdShImm)); - SetA64("x00111100x111001000000xxxxxxxxxx", AInstEmit.Fcvtzu_Gp, typeof(AOpCodeSimdCvt)); - SetA64("x00111100x011001xxxxxxxxxxxxxxxx", AInstEmit.Fcvtzu_Gp_Fix, typeof(AOpCodeSimdCvt)); - SetA64("011111101x100001101110xxxxxxxxxx", AInstEmit.Fcvtzu_S, typeof(AOpCodeSimd)); - SetA64("0>1011101<100001101110xxxxxxxxxx", AInstEmit.Fcvtzu_V, typeof(AOpCodeSimd)); - SetA64("0x1011110>>xxxxx111111xxxxxxxxxx", AInstEmit.Fcvtzu_V, typeof(AOpCodeSimdShImm)); - SetA64("000111100x1xxxxx000110xxxxxxxxxx", AInstEmit.Fdiv_S, typeof(AOpCodeSimdReg)); - SetA64("0>1011100<1xxxxx111111xxxxxxxxxx", AInstEmit.Fdiv_V, typeof(AOpCodeSimdReg)); - SetA64("000111110x0xxxxx0xxxxxxxxxxxxxxx", AInstEmit.Fmadd_S, typeof(AOpCodeSimdReg)); - SetA64("000111100x1xxxxx010010xxxxxxxxxx", AInstEmit.Fmax_S, typeof(AOpCodeSimdReg)); - SetA64("0x0011100x1xxxxx111101xxxxxxxxxx", AInstEmit.Fmax_V, typeof(AOpCodeSimdReg)); - SetA64("000111100x1xxxxx011010xxxxxxxxxx", AInstEmit.Fmaxnm_S, typeof(AOpCodeSimdReg)); - SetA64("000111100x1xxxxx010110xxxxxxxxxx", AInstEmit.Fmin_S, typeof(AOpCodeSimdReg)); - SetA64("0x0011101x1xxxxx111101xxxxxxxxxx", AInstEmit.Fmin_V, typeof(AOpCodeSimdReg)); - SetA64("000111100x1xxxxx011110xxxxxxxxxx", AInstEmit.Fminnm_S, typeof(AOpCodeSimdReg)); - SetA64("010111111<0011100<1xxxxx110011xxxxxxxxxx", AInstEmit.Fmla_V, typeof(AOpCodeSimdReg)); - SetA64("0x0011111<0011101<1xxxxx110011xxxxxxxxxx", AInstEmit.Fmls_V, typeof(AOpCodeSimdReg)); - SetA64("0x0011111<1011100<1xxxxx110111xxxxxxxxxx", AInstEmit.Fmul_V, typeof(AOpCodeSimdReg)); - SetA64("0x0011111<1011101<100000111110xxxxxxxxxx", AInstEmit.Fneg_V, typeof(AOpCodeSimd)); - SetA64("000111110x1xxxxx0xxxxxxxxxxxxxxx", AInstEmit.Fnmadd_S, typeof(AOpCodeSimdReg)); - SetA64("000111110x1xxxxx1xxxxxxxxxxxxxxx", AInstEmit.Fnmsub_S, typeof(AOpCodeSimdReg)); - SetA64("000111100x1xxxxx100010xxxxxxxxxx", AInstEmit.Fnmul_S, typeof(AOpCodeSimdReg)); - SetA64("010111101x100001110110xxxxxxxxxx", AInstEmit.Frecpe_S, typeof(AOpCodeSimd)); - SetA64("0>0011101<100001110110xxxxxxxxxx", AInstEmit.Frecpe_V, typeof(AOpCodeSimd)); - SetA64("010111100x1xxxxx111111xxxxxxxxxx", AInstEmit.Frecps_S, typeof(AOpCodeSimdReg)); - SetA64("0>0011100<1xxxxx111111xxxxxxxxxx", AInstEmit.Frecps_V, typeof(AOpCodeSimdReg)); - SetA64("000111100x100110010000xxxxxxxxxx", AInstEmit.Frinta_S, typeof(AOpCodeSimd)); - SetA64("0>1011100<100001100010xxxxxxxxxx", AInstEmit.Frinta_V, typeof(AOpCodeSimd)); - SetA64("000111100x100111110000xxxxxxxxxx", AInstEmit.Frinti_S, typeof(AOpCodeSimd)); - SetA64("0>1011101<100001100110xxxxxxxxxx", AInstEmit.Frinti_V, typeof(AOpCodeSimd)); - SetA64("000111100x100101010000xxxxxxxxxx", AInstEmit.Frintm_S, typeof(AOpCodeSimd)); - SetA64("0>0011100<100001100110xxxxxxxxxx", AInstEmit.Frintm_V, typeof(AOpCodeSimd)); - SetA64("000111100x100100010000xxxxxxxxxx", AInstEmit.Frintn_S, typeof(AOpCodeSimd)); - SetA64("0>0011100<100001100010xxxxxxxxxx", AInstEmit.Frintn_V, typeof(AOpCodeSimd)); - SetA64("000111100x100100110000xxxxxxxxxx", AInstEmit.Frintp_S, typeof(AOpCodeSimd)); - SetA64("0>0011101<100001100010xxxxxxxxxx", AInstEmit.Frintp_V, typeof(AOpCodeSimd)); - SetA64("000111100x100111010000xxxxxxxxxx", AInstEmit.Frintx_S, typeof(AOpCodeSimd)); - SetA64("0>1011100<100001100110xxxxxxxxxx", AInstEmit.Frintx_V, typeof(AOpCodeSimd)); - SetA64("011111101x100001110110xxxxxxxxxx", AInstEmit.Frsqrte_S, typeof(AOpCodeSimd)); - SetA64("0>1011101<100001110110xxxxxxxxxx", AInstEmit.Frsqrte_V, typeof(AOpCodeSimd)); - SetA64("010111101x1xxxxx111111xxxxxxxxxx", AInstEmit.Frsqrts_S, typeof(AOpCodeSimdReg)); - SetA64("0>0011101<1xxxxx111111xxxxxxxxxx", AInstEmit.Frsqrts_V, typeof(AOpCodeSimdReg)); - SetA64("000111100x100001110000xxxxxxxxxx", AInstEmit.Fsqrt_S, typeof(AOpCodeSimd)); - SetA64("000111100x1xxxxx001110xxxxxxxxxx", AInstEmit.Fsub_S, typeof(AOpCodeSimdReg)); - SetA64("0>0011101<1xxxxx110101xxxxxxxxxx", AInstEmit.Fsub_V, typeof(AOpCodeSimdReg)); - SetA64("01001110000xxxxx000111xxxxxxxxxx", AInstEmit.Ins_Gp, typeof(AOpCodeSimdIns)); - SetA64("01101110000xxxxx0xxxx1xxxxxxxxxx", AInstEmit.Ins_V, typeof(AOpCodeSimdIns)); - SetA64("0x00110001000000xxxxxxxxxxxxxxxx", AInstEmit.Ld__Vms, typeof(AOpCodeSimdMemMs)); - SetA64("0x001100110xxxxxxxxxxxxxxxxxxxxx", AInstEmit.Ld__Vms, typeof(AOpCodeSimdMemMs)); - SetA64("0x00110101x00000xxxxxxxxxxxxxxxx", AInstEmit.Ld__Vss, typeof(AOpCodeSimdMemSs)); - SetA64("0x00110111xxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Ld__Vss, typeof(AOpCodeSimdMemSs)); - SetA64("xx10110xx1xxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Ldp, typeof(AOpCodeSimdMemPair)); - SetA64("xx111100x10xxxxxxxxx00xxxxxxxxxx", AInstEmit.Ldr, typeof(AOpCodeSimdMemImm)); - SetA64("xx111100x10xxxxxxxxx01xxxxxxxxxx", AInstEmit.Ldr, typeof(AOpCodeSimdMemImm)); - SetA64("xx111100x10xxxxxxxxx11xxxxxxxxxx", AInstEmit.Ldr, typeof(AOpCodeSimdMemImm)); - SetA64("xx111101x1xxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Ldr, typeof(AOpCodeSimdMemImm)); - SetA64("xx111100x11xxxxxxxxx10xxxxxxxxxx", AInstEmit.Ldr, typeof(AOpCodeSimdMemReg)); - SetA64("xx011100xxxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.LdrLit, typeof(AOpCodeSimdMemLit)); - SetA64("0x001110<<1xxxxx100101xxxxxxxxxx", AInstEmit.Mla_V, typeof(AOpCodeSimdReg)); - SetA64("0x101111xxxxxxxx0000x0xxxxxxxxxx", AInstEmit.Mla_Ve, typeof(AOpCodeSimdRegElem)); - SetA64("0x101110<<1xxxxx100101xxxxxxxxxx", AInstEmit.Mls_V, typeof(AOpCodeSimdReg)); - SetA64("0x00111100000xxx0xx001xxxxxxxxxx", AInstEmit.Movi_V, typeof(AOpCodeSimdImm)); - SetA64("0x00111100000xxx10x001xxxxxxxxxx", AInstEmit.Movi_V, typeof(AOpCodeSimdImm)); - SetA64("0x00111100000xxx110x01xxxxxxxxxx", AInstEmit.Movi_V, typeof(AOpCodeSimdImm)); - SetA64("0xx0111100000xxx111001xxxxxxxxxx", AInstEmit.Movi_V, typeof(AOpCodeSimdImm)); - SetA64("0x001110<<1xxxxx100111xxxxxxxxxx", AInstEmit.Mul_V, typeof(AOpCodeSimdReg)); - SetA64("0x001111xxxxxxxx1000x0xxxxxxxxxx", AInstEmit.Mul_Ve, typeof(AOpCodeSimdRegElem)); - SetA64("0x10111100000xxx0xx001xxxxxxxxxx", AInstEmit.Mvni_V, typeof(AOpCodeSimdImm)); - SetA64("0x10111100000xxx10x001xxxxxxxxxx", AInstEmit.Mvni_V, typeof(AOpCodeSimdImm)); - SetA64("0x10111100000xxx110x01xxxxxxxxxx", AInstEmit.Mvni_V, typeof(AOpCodeSimdImm)); - SetA64("0111111011100000101110xxxxxxxxxx", AInstEmit.Neg_S, typeof(AOpCodeSimd)); - SetA64("0>101110<<100000101110xxxxxxxxxx", AInstEmit.Neg_V, typeof(AOpCodeSimd)); - SetA64("0x10111000100000010110xxxxxxxxxx", AInstEmit.Not_V, typeof(AOpCodeSimd)); - SetA64("0x001110111xxxxx000111xxxxxxxxxx", AInstEmit.Orn_V, typeof(AOpCodeSimdReg)); - SetA64("0x001110101xxxxx000111xxxxxxxxxx", AInstEmit.Orr_V, typeof(AOpCodeSimdReg)); - SetA64("0x00111100000xxx<>>>xxx010101xxxxxxxxxx", AInstEmit.Shl_S, typeof(AOpCodeSimdShImm)); - SetA64("0x0011110>>>>xxx010101xxxxxxxxxx", AInstEmit.Shl_V, typeof(AOpCodeSimdShImm)); - SetA64("0x101110<<100001001110xxxxxxxxxx", AInstEmit.Shll_V, typeof(AOpCodeSimd)); - SetA64("0x00111100>>>xxx100001xxxxxxxxxx", AInstEmit.Shrn_V, typeof(AOpCodeSimdShImm)); - SetA64("0x1011110>>>>xxx010101xxxxxxxxxx", AInstEmit.Sli_V, typeof(AOpCodeSimdShImm)); - SetA64("0x001110<<1xxxxx011001xxxxxxxxxx", AInstEmit.Smax_V, typeof(AOpCodeSimdReg)); - SetA64("0x001110<<1xxxxx101001xxxxxxxxxx", AInstEmit.Smaxp_V, typeof(AOpCodeSimdReg)); - SetA64("0x001110<<1xxxxx011011xxxxxxxxxx", AInstEmit.Smin_V, typeof(AOpCodeSimdReg)); - SetA64("0x001110<<1xxxxx101011xxxxxxxxxx", AInstEmit.Sminp_V, typeof(AOpCodeSimdReg)); - SetA64("0x001110<<1xxxxx100000xxxxxxxxxx", AInstEmit.Smlal_V, typeof(AOpCodeSimdReg)); - SetA64("0x001110<<1xxxxx110000xxxxxxxxxx", AInstEmit.Smull_V, typeof(AOpCodeSimdReg)); - SetA64("01011110<<100001010010xxxxxxxxxx", AInstEmit.Sqxtn_S, typeof(AOpCodeSimd)); - SetA64("0x001110<<100001010010xxxxxxxxxx", AInstEmit.Sqxtn_V, typeof(AOpCodeSimd)); - SetA64("01111110<<100001001010xxxxxxxxxx", AInstEmit.Sqxtun_S, typeof(AOpCodeSimd)); - SetA64("0x101110<<100001001010xxxxxxxxxx", AInstEmit.Sqxtun_V, typeof(AOpCodeSimd)); - SetA64("0>001110<<1xxxxx010001xxxxxxxxxx", AInstEmit.Sshl_V, typeof(AOpCodeSimdReg)); - SetA64("0x00111100>>>xxx101001xxxxxxxxxx", AInstEmit.Sshll_V, typeof(AOpCodeSimdShImm)); - SetA64("010111110>>>>xxx000001xxxxxxxxxx", AInstEmit.Sshr_S, typeof(AOpCodeSimdShImm)); - SetA64("0x0011110>>>>xxx000001xxxxxxxxxx", AInstEmit.Sshr_V, typeof(AOpCodeSimdShImm)); - SetA64("0x0011110>>>>xxx000101xxxxxxxxxx", AInstEmit.Ssra_V, typeof(AOpCodeSimdShImm)); - SetA64("0x00110000000000xxxxxxxxxxxxxxxx", AInstEmit.St__Vms, typeof(AOpCodeSimdMemMs)); - SetA64("0x001100100xxxxxxxxxxxxxxxxxxxxx", AInstEmit.St__Vms, typeof(AOpCodeSimdMemMs)); - SetA64("0x00110100x00000xxxxxxxxxxxxxxxx", AInstEmit.St__Vss, typeof(AOpCodeSimdMemSs)); - SetA64("0x00110110xxxxxxxxxxxxxxxxxxxxxx", AInstEmit.St__Vss, typeof(AOpCodeSimdMemSs)); - SetA64("xx10110xx0xxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Stp, typeof(AOpCodeSimdMemPair)); - SetA64("xx111100x00xxxxxxxxx00xxxxxxxxxx", AInstEmit.Str, typeof(AOpCodeSimdMemImm)); - SetA64("xx111100x00xxxxxxxxx01xxxxxxxxxx", AInstEmit.Str, typeof(AOpCodeSimdMemImm)); - SetA64("xx111100x00xxxxxxxxx11xxxxxxxxxx", AInstEmit.Str, typeof(AOpCodeSimdMemImm)); - SetA64("xx111101x0xxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Str, typeof(AOpCodeSimdMemImm)); - SetA64("xx111100x01xxxxxxxxx10xxxxxxxxxx", AInstEmit.Str, typeof(AOpCodeSimdMemReg)); - SetA64("01111110111xxxxx100001xxxxxxxxxx", AInstEmit.Sub_S, typeof(AOpCodeSimdReg)); - SetA64("0>101110<<1xxxxx100001xxxxxxxxxx", AInstEmit.Sub_V, typeof(AOpCodeSimdReg)); - SetA64("0x001110<<1xxxxx011000xxxxxxxxxx", AInstEmit.Subhn_V, typeof(AOpCodeSimdReg)); - SetA64("0x001110000xxxxx0xx000xxxxxxxxxx", AInstEmit.Tbl_V, typeof(AOpCodeSimdTbl)); - SetA64("0>001110<<0xxxxx001010xxxxxxxxxx", AInstEmit.Trn1_V, typeof(AOpCodeSimdReg)); - SetA64("0>001110<<0xxxxx011010xxxxxxxxxx", AInstEmit.Trn2_V, typeof(AOpCodeSimdReg)); - SetA64("0x101110<<1xxxxx011111xxxxxxxxxx", AInstEmit.Uaba_V, typeof(AOpCodeSimdReg)); - SetA64("0x101110<<1xxxxx010100xxxxxxxxxx", AInstEmit.Uabal_V, typeof(AOpCodeSimdReg)); - SetA64("0x101110<<1xxxxx011101xxxxxxxxxx", AInstEmit.Uabd_V, typeof(AOpCodeSimdReg)); - SetA64("0x101110<<1xxxxx011100xxxxxxxxxx", AInstEmit.Uabdl_V, typeof(AOpCodeSimdReg)); - SetA64("0x101110<<1xxxxx000000xxxxxxxxxx", AInstEmit.Uaddl_V, typeof(AOpCodeSimdReg)); - SetA64("001011100x110000001110xxxxxxxxxx", AInstEmit.Uaddlv_V, typeof(AOpCodeSimd)); - SetA64("01101110<<110000001110xxxxxxxxxx", AInstEmit.Uaddlv_V, typeof(AOpCodeSimd)); - SetA64("0x101110<<1xxxxx000100xxxxxxxxxx", AInstEmit.Uaddw_V, typeof(AOpCodeSimdReg)); - SetA64("x0011110xx100011000000xxxxxxxxxx", AInstEmit.Ucvtf_Gp, typeof(AOpCodeSimdCvt)); - SetA64("011111100x100001110110xxxxxxxxxx", AInstEmit.Ucvtf_S, typeof(AOpCodeSimd)); - SetA64("0x1011100x100001110110xxxxxxxxxx", AInstEmit.Ucvtf_V, typeof(AOpCodeSimd)); - SetA64("0x101110<<1xxxxx000001xxxxxxxxxx", AInstEmit.Uhadd_V, typeof(AOpCodeSimdReg)); - SetA64("0x101110<<1xxxxx011001xxxxxxxxxx", AInstEmit.Umax_V, typeof(AOpCodeSimdReg)); - SetA64("0x101110<<1xxxxx101001xxxxxxxxxx", AInstEmit.Umaxp_V, typeof(AOpCodeSimdReg)); - SetA64("0x101110<<1xxxxx011011xxxxxxxxxx", AInstEmit.Umin_V, typeof(AOpCodeSimdReg)); - SetA64("0x101110<<1xxxxx101011xxxxxxxxxx", AInstEmit.Uminp_V, typeof(AOpCodeSimdReg)); - SetA64("0x001110000xxxxx001111xxxxxxxxxx", AInstEmit.Umov_S, typeof(AOpCodeSimdIns)); - SetA64("0x101110<<1xxxxx110000xxxxxxxxxx", AInstEmit.Umull_V, typeof(AOpCodeSimdReg)); - SetA64("01111110<<100001010010xxxxxxxxxx", AInstEmit.Uqxtn_S, typeof(AOpCodeSimd)); - SetA64("0x101110<<100001010010xxxxxxxxxx", AInstEmit.Uqxtn_V, typeof(AOpCodeSimd)); - SetA64("0>101110<<1xxxxx010001xxxxxxxxxx", AInstEmit.Ushl_V, typeof(AOpCodeSimdReg)); - SetA64("0x10111100>>>xxx101001xxxxxxxxxx", AInstEmit.Ushll_V, typeof(AOpCodeSimdShImm)); - SetA64("011111110>>>>xxx000001xxxxxxxxxx", AInstEmit.Ushr_S, typeof(AOpCodeSimdShImm)); - SetA64("0x1011110>>>>xxx000001xxxxxxxxxx", AInstEmit.Ushr_V, typeof(AOpCodeSimdShImm)); - SetA64("0x1011110>>>>xxx000101xxxxxxxxxx", AInstEmit.Usra_V, typeof(AOpCodeSimdShImm)); - SetA64("0>001110<<0xxxxx000110xxxxxxxxxx", AInstEmit.Uzp1_V, typeof(AOpCodeSimdReg)); - SetA64("0>001110<<0xxxxx010110xxxxxxxxxx", AInstEmit.Uzp2_V, typeof(AOpCodeSimdReg)); - SetA64("0x001110<<100001001010xxxxxxxxxx", AInstEmit.Xtn_V, typeof(AOpCodeSimd)); - SetA64("0>001110<<0xxxxx001110xxxxxxxxxx", AInstEmit.Zip1_V, typeof(AOpCodeSimdReg)); - SetA64("0>001110<<0xxxxx011110xxxxxxxxxx", AInstEmit.Zip2_V, typeof(AOpCodeSimdReg)); -#endregion - } - - private class TreeNode - { - public int Mask; - public int Value; - - public TreeNode Next; - - public AInst Inst; - - public TreeNode(int Mask, int Value, AInst Inst) - { - this.Mask = Mask; - this.Value = Value; - this.Inst = Inst; - } - } - - private static TreeNode InstHeadA32; - private static TreeNode InstHeadA64; - - private static void SetA32(string Encoding, AInstInterpreter Interpreter, Type Type) - { - Set(Encoding, new AInst(Interpreter, null, Type), AExecutionMode.AArch32); - } - - private static void SetA64(string Encoding, AInstEmitter Emitter, Type Type) - { - Set(Encoding, new AInst(null, Emitter, Type), AExecutionMode.AArch64); - } - - private static void Set(string Encoding, AInst Inst, AExecutionMode Mode) - { - 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) - { - InsertTop(XMask, Value, Inst, Mode); - - 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) - { - InsertTop(XMask, Value | Mask, Inst, Mode); - } - } - } - - private static void InsertTop( - int XMask, - int Value, - AInst Inst, - AExecutionMode Mode) - { - TreeNode Node = new TreeNode(XMask, Value, Inst); - - if (Mode == AExecutionMode.AArch64) - { - Node.Next = InstHeadA64; - - InstHeadA64 = Node; - } - else - { - Node.Next = InstHeadA32; - - InstHeadA32 = Node; - } - } - - public static AInst GetInstA32(int OpCode) - { - return GetInst(InstHeadA32, OpCode); - } - - public static AInst GetInstA64(int OpCode) - { - return GetInst(InstHeadA64, OpCode); - } - - private static AInst GetInst(TreeNode Head, int OpCode) - { - TreeNode Node = Head; - - do - { - if ((OpCode & Node.Mask) == Node.Value) - { - return Node.Inst; - } - } - while ((Node = Node.Next) != null); - - return AInst.Undefined; - } - } -} diff --git a/ChocolArm64/AOptimizations.cs b/ChocolArm64/AOptimizations.cs deleted file mode 100644 index 800cf363d7..0000000000 --- a/ChocolArm64/AOptimizations.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Runtime.Intrinsics.X86; - -public static class AOptimizations -{ - public static bool DisableMemoryChecks = false; - - public static bool GenerateCallStack = true; - - private static bool UseAllSseIfAvailable = true; - - private static bool UseSseIfAvailable = true; - private static bool UseSse2IfAvailable = true; - private static bool UseSse41IfAvailable = true; - private static bool UseSse42IfAvailable = true; - - internal static bool UseSse = (UseAllSseIfAvailable && UseSseIfAvailable) && Sse.IsSupported; - internal static bool UseSse2 = (UseAllSseIfAvailable && UseSse2IfAvailable) && Sse2.IsSupported; - internal static bool UseSse41 = (UseAllSseIfAvailable && UseSse41IfAvailable) && Sse41.IsSupported; - internal static bool UseSse42 = (UseAllSseIfAvailable && UseSse42IfAvailable) && Sse42.IsSupported; -} \ No newline at end of file diff --git a/ChocolArm64/AThread.cs b/ChocolArm64/AThread.cs deleted file mode 100644 index 4fc79d5ef2..0000000000 --- a/ChocolArm64/AThread.cs +++ /dev/null @@ -1,69 +0,0 @@ -using ChocolArm64.Memory; -using ChocolArm64.State; -using System; -using System.Threading; - -namespace ChocolArm64 -{ - public class AThread - { - public AThreadState ThreadState { get; private set; } - public AMemory Memory { get; private set; } - - private long EntryPoint; - - private ATranslator Translator; - - private Thread Work; - - public event EventHandler WorkFinished; - - public int ThreadId => ThreadState.ThreadId; - - private int IsExecuting; - - public AThread(ATranslator Translator, AMemory Memory, long EntryPoint) - { - this.Translator = Translator; - this.Memory = Memory; - this.EntryPoint = EntryPoint; - - ThreadState = new AThreadState(); - - ThreadState.ExecutionMode = AExecutionMode.AArch64; - - ThreadState.Running = true; - } - - public bool Execute() - { - if (Interlocked.Exchange(ref IsExecuting, 1) == 1) - { - return false; - } - - Work = new Thread(delegate() - { - Translator.ExecuteSubroutine(this, EntryPoint); - - Memory.RemoveMonitor(ThreadState); - - WorkFinished?.Invoke(this, EventArgs.Empty); - }); - - Work.Start(); - - return true; - } - - public void StopExecution() - { - ThreadState.Running = false; - } - - public bool IsCurrentThread() - { - return Thread.CurrentThread == Work; - } - } -} \ No newline at end of file diff --git a/ChocolArm64/ATranslatedSub.cs b/ChocolArm64/ATranslatedSub.cs deleted file mode 100644 index 9dbc378ec0..0000000000 --- a/ChocolArm64/ATranslatedSub.cs +++ /dev/null @@ -1,150 +0,0 @@ -using ChocolArm64.Memory; -using ChocolArm64.State; -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using System.Reflection; -using System.Reflection.Emit; - -namespace ChocolArm64 -{ - class ATranslatedSub - { - private delegate long AA64Subroutine(AThreadState Register, AMemory Memory); - - private AA64Subroutine ExecDelegate; - - public static int StateArgIdx { get; private set; } - public static int MemoryArgIdx { get; private set; } - - public static Type[] FixedArgTypes { get; private set; } - - public DynamicMethod Method { get; private set; } - - public ReadOnlyCollection Params { get; private set; } - - private HashSet Callers; - - private ATranslatedSubType Type; - - private int CallCount; - - private bool NeedsReJit; - - private int MinCallCountForReJit = 250; - - public ATranslatedSub(DynamicMethod Method, List Params) - { - if (Method == null) - { - throw new ArgumentNullException(nameof(Method)); - } - - if (Params == null) - { - throw new ArgumentNullException(nameof(Params)); - } - - this.Method = Method; - this.Params = Params.AsReadOnly(); - - Callers = new HashSet(); - - PrepareDelegate(); - } - - static ATranslatedSub() - { - MethodInfo MthdInfo = typeof(AA64Subroutine).GetMethod("Invoke"); - - ParameterInfo[] Params = MthdInfo.GetParameters(); - - FixedArgTypes = new Type[Params.Length]; - - for (int Index = 0; Index < Params.Length; Index++) - { - Type ParamType = Params[Index].ParameterType; - - FixedArgTypes[Index] = ParamType; - - if (ParamType == typeof(AThreadState)) - { - StateArgIdx = Index; - } - else if (ParamType == typeof(AMemory)) - { - MemoryArgIdx = Index; - } - } - } - - private void PrepareDelegate() - { - string Name = $"{Method.Name}_Dispatch"; - - DynamicMethod Mthd = new DynamicMethod(Name, typeof(long), FixedArgTypes); - - ILGenerator Generator = Mthd.GetILGenerator(); - - Generator.EmitLdargSeq(FixedArgTypes.Length); - - foreach (ARegister Reg in Params) - { - Generator.EmitLdarg(StateArgIdx); - - Generator.Emit(OpCodes.Ldfld, Reg.GetField()); - } - - Generator.Emit(OpCodes.Call, Method); - Generator.Emit(OpCodes.Ret); - - ExecDelegate = (AA64Subroutine)Mthd.CreateDelegate(typeof(AA64Subroutine)); - } - - public bool ShouldReJit() - { - if (NeedsReJit && CallCount < MinCallCountForReJit) - { - CallCount++; - - return false; - } - - return NeedsReJit; - } - - public long Execute(AThreadState ThreadState, AMemory Memory) - { - return ExecDelegate(ThreadState, Memory); - } - - public void AddCaller(long Position) - { - lock (Callers) - { - Callers.Add(Position); - } - } - - public long[] GetCallerPositions() - { - lock (Callers) - { - return Callers.ToArray(); - } - } - - public void SetType(ATranslatedSubType Type) - { - this.Type = Type; - - if (Type == ATranslatedSubType.SubTier0) - { - NeedsReJit = true; - } - } - - public void MarkForReJit() => NeedsReJit = true; - } -} \ No newline at end of file diff --git a/ChocolArm64/ATranslatedSubType.cs b/ChocolArm64/ATranslatedSubType.cs deleted file mode 100644 index 14893abdfa..0000000000 --- a/ChocolArm64/ATranslatedSubType.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace ChocolArm64 -{ - enum ATranslatedSubType - { - SubTier0, - SubTier1 - } -} \ No newline at end of file diff --git a/ChocolArm64/ATranslator.cs b/ChocolArm64/ATranslator.cs deleted file mode 100644 index 2d9fcb1415..0000000000 --- a/ChocolArm64/ATranslator.cs +++ /dev/null @@ -1,206 +0,0 @@ -using ChocolArm64.Decoder; -using ChocolArm64.Events; -using ChocolArm64.Instruction; -using ChocolArm64.Memory; -using ChocolArm64.State; -using ChocolArm64.Translation; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Reflection.Emit; - -namespace ChocolArm64 -{ - public class ATranslator - { - private ConcurrentDictionary CachedSubs; - - private ConcurrentDictionary SymbolTable; - - public event EventHandler CpuTrace; - - public bool EnableCpuTrace { get; set; } - - public ATranslator(IReadOnlyDictionary SymbolTable = null) - { - CachedSubs = new ConcurrentDictionary(); - - if (SymbolTable != null) - { - this.SymbolTable = new ConcurrentDictionary(SymbolTable); - } - else - { - this.SymbolTable = new ConcurrentDictionary(); - } - } - - internal void ExecuteSubroutine(AThread Thread, long Position) - { - //TODO: Both the execute A32/A64 methods should be merged on the future, - //when both ISAs are implemented with the interpreter and JIT. - //As of now, A32 only has a interpreter and A64 a JIT. - AThreadState State = Thread.ThreadState; - AMemory Memory = Thread.Memory; - - if (State.ExecutionMode == AExecutionMode.AArch32) - { - ExecuteSubroutineA32(State, Memory); - } - else - { - ExecuteSubroutineA64(State, Memory, Position); - } - } - - private void ExecuteSubroutineA32(AThreadState State, AMemory Memory) - { - do - { - AOpCode OpCode = ADecoder.DecodeOpCode(State, Memory, State.R15); - - OpCode.Interpreter(State, Memory, OpCode); - } - while (State.R15 != 0 && State.Running); - } - - private void ExecuteSubroutineA64(AThreadState State, AMemory Memory, long Position) - { - do - { - if (EnableCpuTrace) - { - if (!SymbolTable.TryGetValue(Position, out string SubName)) - { - SubName = string.Empty; - } - - CpuTrace?.Invoke(this, new ACpuTraceEventArgs(Position, SubName)); - } - - if (!CachedSubs.TryGetValue(Position, out ATranslatedSub Sub)) - { - Sub = TranslateTier0(State, Memory, Position); - } - - if (Sub.ShouldReJit()) - { - TranslateTier1(State, Memory, Position); - } - - Position = Sub.Execute(State, Memory); - } - while (Position != 0 && State.Running); - } - - internal bool TryGetCachedSub(AOpCode OpCode, out ATranslatedSub Sub) - { - if (OpCode.Emitter != AInstEmit.Bl) - { - Sub = null; - - return false; - } - - return TryGetCachedSub(((AOpCodeBImmAl)OpCode).Imm, out Sub); - } - - internal bool TryGetCachedSub(long Position, out ATranslatedSub Sub) - { - return CachedSubs.TryGetValue(Position, out Sub); - } - - internal bool HasCachedSub(long Position) - { - return CachedSubs.ContainsKey(Position); - } - - private ATranslatedSub TranslateTier0(AThreadState State, AMemory Memory, long Position) - { - ABlock Block = ADecoder.DecodeBasicBlock(State, this, Memory, Position); - - ABlock[] Graph = new ABlock[] { Block }; - - string SubName = GetSubName(Position); - - AILEmitterCtx Context = new AILEmitterCtx(this, Graph, Block, SubName); - - do - { - Context.EmitOpCode(); - } - while (Context.AdvanceOpCode()); - - ATranslatedSub Subroutine = Context.GetSubroutine(); - - Subroutine.SetType(ATranslatedSubType.SubTier0); - - CachedSubs.AddOrUpdate(Position, Subroutine, (Key, OldVal) => Subroutine); - - AOpCode LastOp = Block.GetLastOp(); - - return Subroutine; - } - - private void TranslateTier1(AThreadState State, AMemory Memory, long Position) - { - (ABlock[] Graph, ABlock Root) Cfg = ADecoder.DecodeSubroutine(State, this, Memory, Position); - - string SubName = GetSubName(Position); - - PropagateName(Cfg.Graph, SubName); - - AILEmitterCtx Context = new AILEmitterCtx(this, Cfg.Graph, Cfg.Root, SubName); - - if (Context.CurrBlock.Position != Position) - { - Context.Emit(OpCodes.Br, Context.GetLabel(Position)); - } - - do - { - Context.EmitOpCode(); - } - while (Context.AdvanceOpCode()); - - //Mark all methods that calls this method for ReJiting, - //since we can now call it directly which is faster. - if (CachedSubs.TryGetValue(Position, out ATranslatedSub OldSub)) - { - foreach (long CallerPos in OldSub.GetCallerPositions()) - { - if (CachedSubs.TryGetValue(Position, out ATranslatedSub CallerSub)) - { - CallerSub.MarkForReJit(); - } - } - } - - ATranslatedSub Subroutine = Context.GetSubroutine(); - - Subroutine.SetType(ATranslatedSubType.SubTier1); - - CachedSubs.AddOrUpdate(Position, Subroutine, (Key, OldVal) => Subroutine); - } - - private string GetSubName(long Position) - { - return SymbolTable.GetOrAdd(Position, $"Sub{Position:x16}"); - } - - private void PropagateName(ABlock[] Graph, string Name) - { - foreach (ABlock Block in Graph) - { - AOpCode LastOp = Block.GetLastOp(); - - if (LastOp != null && - (LastOp.Emitter == AInstEmit.Bl || - LastOp.Emitter == AInstEmit.Blr)) - { - SymbolTable.TryAdd(LastOp.Position + 4, Name); - } - } - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Decoder/ABlock.cs b/ChocolArm64/Decoder/ABlock.cs deleted file mode 100644 index 7a0fc60773..0000000000 --- a/ChocolArm64/Decoder/ABlock.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Collections.Generic; - -namespace ChocolArm64.Decoder -{ - class ABlock - { - public long Position { get; set; } - public long EndPosition { get; set; } - - public ABlock Next { get; set; } - public ABlock Branch { get; set; } - - public List OpCodes { get; private set; } - - public ABlock() - { - OpCodes = new List(); - } - - public ABlock(long Position) : this() - { - this.Position = Position; - } - - public AOpCode GetLastOp() - { - if (OpCodes.Count > 0) - { - return OpCodes[OpCodes.Count - 1]; - } - - return null; - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Decoder/ACond.cs b/ChocolArm64/Decoder/ACond.cs deleted file mode 100644 index f2da8bd29f..0000000000 --- a/ChocolArm64/Decoder/ACond.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace ChocolArm64.Decoder -{ - enum ACond - { - Eq = 0, - Ne = 1, - Ge_Un = 2, - Lt_Un = 3, - Mi = 4, - Pl = 5, - Vs = 6, - Vc = 7, - Gt_Un = 8, - Le_Un = 9, - Ge = 10, - Lt = 11, - Gt = 12, - Le = 13, - Al = 14, - Nv = 15 - } -} \ No newline at end of file diff --git a/ChocolArm64/Decoder/ADecoder.cs b/ChocolArm64/Decoder/ADecoder.cs deleted file mode 100644 index b154a54cd2..0000000000 --- a/ChocolArm64/Decoder/ADecoder.cs +++ /dev/null @@ -1,243 +0,0 @@ -using ChocolArm64.Instruction; -using ChocolArm64.Memory; -using ChocolArm64.State; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Reflection.Emit; - -namespace ChocolArm64.Decoder -{ - static class ADecoder - { - private delegate object OpActivator(AInst Inst, long Position, int OpCode); - - private static ConcurrentDictionary OpActivators; - - static ADecoder() - { - OpActivators = new ConcurrentDictionary(); - } - - public static ABlock DecodeBasicBlock( - AThreadState State, - ATranslator Translator, - AMemory Memory, - long Start) - { - ABlock Block = new ABlock(Start); - - FillBlock(State, Memory, Block); - - return Block; - } - - public static (ABlock[] Graph, ABlock Root) DecodeSubroutine( - AThreadState State, - ATranslator Translator, - AMemory Memory, - long Start) - { - Dictionary Visited = new Dictionary(); - Dictionary VisitedEnd = new Dictionary(); - - Queue Blocks = new Queue(); - - ABlock Enqueue(long Position) - { - if (!Visited.TryGetValue(Position, out ABlock Output)) - { - Output = new ABlock(Position); - - Blocks.Enqueue(Output); - - Visited.Add(Position, Output); - } - - return Output; - } - - ABlock Root = Enqueue(Start); - - while (Blocks.Count > 0) - { - ABlock Current = Blocks.Dequeue(); - - FillBlock(State, Memory, Current); - - //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. - if (Current.OpCodes.Count > 0) - { - bool HasCachedSub = false; - - AOpCode LastOp = Current.GetLastOp(); - - if (LastOp is AOpCodeBImm Op) - { - if (Op.Emitter == AInstEmit.Bl) - { - HasCachedSub = Translator.HasCachedSub(Op.Imm); - } - else - { - Current.Branch = Enqueue(Op.Imm); - } - } - - if (!((LastOp is AOpCodeBImmAl) || - (LastOp is AOpCodeBReg)) || HasCachedSub) - { - Current.Next = Enqueue(Current.EndPosition); - } - } - - //If we have on the graph two blocks with the same end position, - //then we need to split the bigger block and have two small blocks, - //the end position of the bigger "Current" block should then be == to - //the position of the "Smaller" block. - while (VisitedEnd.TryGetValue(Current.EndPosition, out ABlock Smaller)) - { - if (Current.Position > Smaller.Position) - { - ABlock Temp = Smaller; - - Smaller = Current; - Current = Temp; - } - - Current.EndPosition = Smaller.Position; - Current.Next = Smaller; - Current.Branch = null; - - Current.OpCodes.RemoveRange( - Current.OpCodes.Count - Smaller.OpCodes.Count, - Smaller.OpCodes.Count); - - VisitedEnd[Smaller.EndPosition] = Smaller; - } - - VisitedEnd.Add(Current.EndPosition, Current); - } - - //Make and sort Graph blocks array by position. - ABlock[] Graph = new ABlock[Visited.Count]; - - while (Visited.Count > 0) - { - ulong FirstPos = ulong.MaxValue; - - foreach (ABlock Block in Visited.Values) - { - if (FirstPos > (ulong)Block.Position) - FirstPos = (ulong)Block.Position; - } - - ABlock Current = Visited[(long)FirstPos]; - - do - { - Graph[Graph.Length - Visited.Count] = Current; - - Visited.Remove(Current.Position); - - Current = Current.Next; - } - while (Current != null); - } - - return (Graph, Root); - } - - private static void FillBlock(AThreadState State, AMemory Memory, ABlock Block) - { - long Position = Block.Position; - - AOpCode OpCode; - - do - { - //TODO: This needs to be changed to support both AArch32 and AArch64, - //once JIT support is introduced on AArch32 aswell. - OpCode = DecodeOpCode(State, Memory, Position); - - Block.OpCodes.Add(OpCode); - - Position += 4; - } - while (!(IsBranch(OpCode) || IsException(OpCode))); - - Block.EndPosition = Position; - } - - private static bool IsBranch(AOpCode OpCode) - { - return OpCode is AOpCodeBImm || - OpCode is AOpCodeBReg; - } - - private static bool IsException(AOpCode OpCode) - { - return OpCode.Emitter == AInstEmit.Brk || - OpCode.Emitter == AInstEmit.Svc || - OpCode.Emitter == AInstEmit.Und; - } - - public static AOpCode DecodeOpCode(AThreadState State, AMemory Memory, long Position) - { - int OpCode = Memory.ReadInt32(Position); - - AInst Inst; - - if (State.ExecutionMode == AExecutionMode.AArch64) - { - Inst = AOpCodeTable.GetInstA64(OpCode); - } - else - { - //TODO: Thumb support. - Inst = AOpCodeTable.GetInstA32(OpCode); - } - - AOpCode DecodedOpCode = new AOpCode(AInst.Undefined, Position, OpCode); - - if (Inst.Type != null) - { - DecodedOpCode = MakeOpCode(Inst.Type, Inst, Position, OpCode); - } - - return DecodedOpCode; - } - - private static AOpCode MakeOpCode(Type Type, AInst Inst, long Position, int OpCode) - { - if (Type == null) - { - throw new ArgumentNullException(nameof(Type)); - } - - OpActivator CreateInstance = OpActivators.GetOrAdd(Type, CacheOpActivator); - - return (AOpCode)CreateInstance(Inst, Position, OpCode); - } - - private static OpActivator CacheOpActivator(Type Type) - { - Type[] ArgTypes = new Type[] { typeof(AInst), typeof(long), typeof(int) }; - - DynamicMethod Mthd = new DynamicMethod($"Make{Type.Name}", Type, ArgTypes); - - ILGenerator Generator = Mthd.GetILGenerator(); - - Generator.Emit(OpCodes.Ldarg_0); - Generator.Emit(OpCodes.Ldarg_1); - Generator.Emit(OpCodes.Ldarg_2); - Generator.Emit(OpCodes.Newobj, Type.GetConstructor(ArgTypes)); - Generator.Emit(OpCodes.Ret); - - return (OpActivator)Mthd.CreateDelegate(typeof(OpActivator)); - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Decoder/ADecoderHelper.cs b/ChocolArm64/Decoder/ADecoderHelper.cs deleted file mode 100644 index a2179f49e3..0000000000 --- a/ChocolArm64/Decoder/ADecoderHelper.cs +++ /dev/null @@ -1,107 +0,0 @@ -using System; - -namespace ChocolArm64.Decoder -{ - static class ADecoderHelper - { - 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 = ABitUtils.HighestBitSet32((~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 = ABitUtils.FillWithOnes(S + 1); - long TMask = ABitUtils.FillWithOnes(((S - R) & Levels) + 1); - - if (R > 0) - { - WMask = ABitUtils.RotateRight(WMask, R, Size); - WMask &= ABitUtils.FillWithOnes(Size); - } - - return new BitMask() - { - WMask = ABitUtils.Replicate(WMask, Size), - TMask = ABitUtils.Replicate(TMask, Size), - - Pos = ImmS, - Shift = ImmR - }; - } - - public static long DecodeImm8Float(long Imm, int Size) - { - int E = 0, F = 0; - - switch (Size) - { - case 0: E = 8; F = 23; break; - case 1: E = 11; F = 52; break; - - default: throw new ArgumentOutOfRangeException(nameof(Size)); - } - - long Value = (Imm & 0x3f) << F - 4; - - long EBit = (Imm >> 6) & 1; - long SBit = (Imm >> 7) & 1; - - if (EBit != 0) - { - Value |= (1L << E - 3) - 1 << F + 2; - } - - Value |= (EBit ^ 1) << F + E - 1; - Value |= SBit << F + E; - - return Value; - } - - 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; - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Decoder/AOpCode.cs b/ChocolArm64/Decoder/AOpCode.cs deleted file mode 100644 index bdc8f13aa0..0000000000 --- a/ChocolArm64/Decoder/AOpCode.cs +++ /dev/null @@ -1,40 +0,0 @@ -using ChocolArm64.Instruction; -using ChocolArm64.State; -using System; - -namespace ChocolArm64.Decoder -{ - class AOpCode : IAOpCode - { - public long Position { get; private set; } - public int RawOpCode { get; private set; } - - public AInstEmitter Emitter { get; protected set; } - public AInstInterpreter Interpreter { get; protected set; } - public ARegisterSize RegisterSize { get; protected set; } - - public AOpCode(AInst Inst, long Position, int OpCode) - { - this.Position = Position; - this.RawOpCode = OpCode; - - RegisterSize = ARegisterSize.Int64; - - Emitter = Inst.Emitter; - Interpreter = Inst.Interpreter; - } - - public int GetBitsCount() - { - switch (RegisterSize) - { - case ARegisterSize.Int32: return 32; - case ARegisterSize.Int64: return 64; - case ARegisterSize.SIMD64: return 64; - case ARegisterSize.SIMD128: return 128; - } - - throw new InvalidOperationException(); - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Decoder/AOpCodeAdr.cs b/ChocolArm64/Decoder/AOpCodeAdr.cs deleted file mode 100644 index 3396281f45..0000000000 --- a/ChocolArm64/Decoder/AOpCodeAdr.cs +++ /dev/null @@ -1,18 +0,0 @@ -using ChocolArm64.Instruction; - -namespace ChocolArm64.Decoder -{ - class AOpCodeAdr : AOpCode - { - public int Rd { get; private set; } - public long Imm { get; private set; } - - public AOpCodeAdr(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) - { - Rd = OpCode & 0x1f; - - Imm = ADecoderHelper.DecodeImmS19_2(OpCode); - Imm |= ((long)OpCode >> 29) & 3; - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Decoder/AOpCodeAlu.cs b/ChocolArm64/Decoder/AOpCodeAlu.cs deleted file mode 100644 index e1a44f04b4..0000000000 --- a/ChocolArm64/Decoder/AOpCodeAlu.cs +++ /dev/null @@ -1,24 +0,0 @@ -using ChocolArm64.Instruction; -using ChocolArm64.State; - -namespace ChocolArm64.Decoder -{ - class AOpCodeAlu : AOpCode, IAOpCodeAlu - { - public int Rd { get; protected set; } - public int Rn { get; private set; } - - public ADataOp DataOp { get; private set; } - - public AOpCodeAlu(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) - { - Rd = (OpCode >> 0) & 0x1f; - Rn = (OpCode >> 5) & 0x1f; - DataOp = (ADataOp)((OpCode >> 24) & 0x3); - - RegisterSize = (OpCode >> 31) != 0 - ? ARegisterSize.Int64 - : ARegisterSize.Int32; - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Decoder/AOpCodeAluImm.cs b/ChocolArm64/Decoder/AOpCodeAluImm.cs deleted file mode 100644 index e913475ad2..0000000000 --- a/ChocolArm64/Decoder/AOpCodeAluImm.cs +++ /dev/null @@ -1,39 +0,0 @@ -using ChocolArm64.Instruction; -using System; - -namespace ChocolArm64.Decoder -{ - class AOpCodeAluImm : AOpCodeAlu, IAOpCodeAluImm - { - public long Imm { get; private set; } - - public AOpCodeAluImm(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) - { - if (DataOp == ADataOp.Arithmetic) - { - Imm = (OpCode >> 10) & 0xfff; - - int Shift = (OpCode >> 22) & 3; - - Imm <<= Shift * 12; - } - else if (DataOp == ADataOp.Logical) - { - var BM = ADecoderHelper.DecodeBitMask(OpCode, true); - - if (BM.IsUndefined) - { - Emitter = AInstEmit.Und; - - return; - } - - Imm = BM.WMask; - } - else - { - throw new ArgumentException(nameof(OpCode)); - } - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Decoder/AOpCodeAluRs.cs b/ChocolArm64/Decoder/AOpCodeAluRs.cs deleted file mode 100644 index 9c215be383..0000000000 --- a/ChocolArm64/Decoder/AOpCodeAluRs.cs +++ /dev/null @@ -1,29 +0,0 @@ -using ChocolArm64.Instruction; - -namespace ChocolArm64.Decoder -{ - class AOpCodeAluRs : AOpCodeAlu, IAOpCodeAluRs - { - public int Shift { get; private set; } - public int Rm { get; private set; } - - public AShiftType ShiftType { get; private set; } - - public AOpCodeAluRs(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) - { - int Shift = (OpCode >> 10) & 0x3f; - - if (Shift >= GetBitsCount()) - { - Emitter = AInstEmit.Und; - - return; - } - - this.Shift = Shift; - - Rm = (OpCode >> 16) & 0x1f; - ShiftType = (AShiftType)((OpCode >> 22) & 0x3); - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Decoder/AOpCodeAluRx.cs b/ChocolArm64/Decoder/AOpCodeAluRx.cs deleted file mode 100644 index 7dd72a6842..0000000000 --- a/ChocolArm64/Decoder/AOpCodeAluRx.cs +++ /dev/null @@ -1,19 +0,0 @@ -using ChocolArm64.Instruction; - -namespace ChocolArm64.Decoder -{ - class AOpCodeAluRx : AOpCodeAlu, IAOpCodeAluRx - { - public int Shift { get; private set; } - public int Rm { get; private set; } - - public AIntType IntType { get; private set; } - - public AOpCodeAluRx(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) - { - Shift = (OpCode >> 10) & 0x7; - IntType = (AIntType)((OpCode >> 13) & 0x7); - Rm = (OpCode >> 16) & 0x1f; - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Decoder/AOpCodeBImm.cs b/ChocolArm64/Decoder/AOpCodeBImm.cs deleted file mode 100644 index 6519d281dc..0000000000 --- a/ChocolArm64/Decoder/AOpCodeBImm.cs +++ /dev/null @@ -1,11 +0,0 @@ -using ChocolArm64.Instruction; - -namespace ChocolArm64.Decoder -{ - class AOpCodeBImm : AOpCode - { - public long Imm { get; protected set; } - - public AOpCodeBImm(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) { } - } -} \ No newline at end of file diff --git a/ChocolArm64/Decoder/AOpCodeBImmAl.cs b/ChocolArm64/Decoder/AOpCodeBImmAl.cs deleted file mode 100644 index a4ff686d6d..0000000000 --- a/ChocolArm64/Decoder/AOpCodeBImmAl.cs +++ /dev/null @@ -1,12 +0,0 @@ -using ChocolArm64.Instruction; - -namespace ChocolArm64.Decoder -{ - class AOpCodeBImmAl : AOpCodeBImm - { - public AOpCodeBImmAl(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) - { - Imm = Position + ADecoderHelper.DecodeImm26_2(OpCode); - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Decoder/AOpCodeBImmCmp.cs b/ChocolArm64/Decoder/AOpCodeBImmCmp.cs deleted file mode 100644 index 0f16b73e0e..0000000000 --- a/ChocolArm64/Decoder/AOpCodeBImmCmp.cs +++ /dev/null @@ -1,21 +0,0 @@ -using ChocolArm64.Instruction; -using ChocolArm64.State; - -namespace ChocolArm64.Decoder -{ - class AOpCodeBImmCmp : AOpCodeBImm - { - public int Rt { get; private set; } - - public AOpCodeBImmCmp(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) - { - Rt = OpCode & 0x1f; - - Imm = Position + ADecoderHelper.DecodeImmS19_2(OpCode); - - RegisterSize = (OpCode >> 31) != 0 - ? ARegisterSize.Int64 - : ARegisterSize.Int32; - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Decoder/AOpCodeBImmCond.cs b/ChocolArm64/Decoder/AOpCodeBImmCond.cs deleted file mode 100644 index 1310feb8d3..0000000000 --- a/ChocolArm64/Decoder/AOpCodeBImmCond.cs +++ /dev/null @@ -1,25 +0,0 @@ -using ChocolArm64.Instruction; - -namespace ChocolArm64.Decoder -{ - class AOpCodeBImmCond : AOpCodeBImm, IAOpCodeCond - { - public ACond Cond { get; private set; } - - public AOpCodeBImmCond(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) - { - int O0 = (OpCode >> 4) & 1; - - if (O0 != 0) - { - Emitter = AInstEmit.Und; - - return; - } - - Cond = (ACond)(OpCode & 0xf); - - Imm = Position + ADecoderHelper.DecodeImmS19_2(OpCode); - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Decoder/AOpCodeBImmTest.cs b/ChocolArm64/Decoder/AOpCodeBImmTest.cs deleted file mode 100644 index 73e57b7ab9..0000000000 --- a/ChocolArm64/Decoder/AOpCodeBImmTest.cs +++ /dev/null @@ -1,20 +0,0 @@ -using ChocolArm64.Instruction; - -namespace ChocolArm64.Decoder -{ - class AOpCodeBImmTest : AOpCodeBImm - { - public int Rt { get; private set; } - public int Pos { get; private set; } - - public AOpCodeBImmTest(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) - { - Rt = OpCode & 0x1f; - - Imm = Position + ADecoderHelper.DecodeImmS14_2(OpCode); - - Pos = (OpCode >> 19) & 0x1f; - Pos |= (OpCode >> 26) & 0x20; - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Decoder/AOpCodeBReg.cs b/ChocolArm64/Decoder/AOpCodeBReg.cs deleted file mode 100644 index c9c600af5b..0000000000 --- a/ChocolArm64/Decoder/AOpCodeBReg.cs +++ /dev/null @@ -1,24 +0,0 @@ -using ChocolArm64.Instruction; - -namespace ChocolArm64.Decoder -{ - class AOpCodeBReg : AOpCode - { - public int Rn { get; private set; } - - public AOpCodeBReg(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) - { - int Op4 = (OpCode >> 0) & 0x1f; - int Op2 = (OpCode >> 16) & 0x1f; - - if (Op2 != 0b11111 || Op4 != 0b00000) - { - Emitter = AInstEmit.Und; - - return; - } - - Rn = (OpCode >> 5) & 0x1f; - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Decoder/AOpCodeBfm.cs b/ChocolArm64/Decoder/AOpCodeBfm.cs deleted file mode 100644 index 6498d8ec69..0000000000 --- a/ChocolArm64/Decoder/AOpCodeBfm.cs +++ /dev/null @@ -1,29 +0,0 @@ -using ChocolArm64.Instruction; - -namespace ChocolArm64.Decoder -{ - class AOpCodeBfm : AOpCodeAlu - { - public long WMask { get; private set; } - public long TMask { get; private set; } - public int Pos { get; private set; } - public int Shift { get; private set; } - - public AOpCodeBfm(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) - { - var BM = ADecoderHelper.DecodeBitMask(OpCode, false); - - if (BM.IsUndefined) - { - Emitter = AInstEmit.Und; - - return; - } - - WMask = BM.WMask; - TMask = BM.TMask; - Pos = BM.Pos; - Shift = BM.Shift; - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Decoder/AOpCodeCcmp.cs b/ChocolArm64/Decoder/AOpCodeCcmp.cs deleted file mode 100644 index d0c7f779c8..0000000000 --- a/ChocolArm64/Decoder/AOpCodeCcmp.cs +++ /dev/null @@ -1,31 +0,0 @@ -using ChocolArm64.Instruction; -using ChocolArm64.State; - -namespace ChocolArm64.Decoder -{ - class AOpCodeCcmp : AOpCodeAlu, IAOpCodeCond - { - public int NZCV { get; private set; } - protected int RmImm; - - public ACond Cond { get; private set; } - - public AOpCodeCcmp(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) - { - int O3 = (OpCode >> 4) & 1; - - if (O3 != 0) - { - Emitter = AInstEmit.Und; - - return; - } - - NZCV = (OpCode >> 0) & 0xf; - Cond = (ACond)((OpCode >> 12) & 0xf); - RmImm = (OpCode >> 16) & 0x1f; - - Rd = AThreadState.ZRIndex; - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Decoder/AOpCodeCcmpImm.cs b/ChocolArm64/Decoder/AOpCodeCcmpImm.cs deleted file mode 100644 index 803eefc249..0000000000 --- a/ChocolArm64/Decoder/AOpCodeCcmpImm.cs +++ /dev/null @@ -1,11 +0,0 @@ -using ChocolArm64.Instruction; - -namespace ChocolArm64.Decoder -{ - class AOpCodeCcmpImm : AOpCodeCcmp, IAOpCodeAluImm - { - public long Imm => RmImm; - - public AOpCodeCcmpImm(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) { } - } -} \ No newline at end of file diff --git a/ChocolArm64/Decoder/AOpCodeCcmpReg.cs b/ChocolArm64/Decoder/AOpCodeCcmpReg.cs deleted file mode 100644 index c364ae68b4..0000000000 --- a/ChocolArm64/Decoder/AOpCodeCcmpReg.cs +++ /dev/null @@ -1,15 +0,0 @@ -using ChocolArm64.Instruction; - -namespace ChocolArm64.Decoder -{ - class AOpCodeCcmpReg : AOpCodeCcmp, IAOpCodeAluRs - { - public int Rm => RmImm; - - public int Shift => 0; - - public AShiftType ShiftType => AShiftType.Lsl; - - public AOpCodeCcmpReg(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) { } - } -} \ No newline at end of file diff --git a/ChocolArm64/Decoder/AOpCodeCsel.cs b/ChocolArm64/Decoder/AOpCodeCsel.cs deleted file mode 100644 index cdef3e745c..0000000000 --- a/ChocolArm64/Decoder/AOpCodeCsel.cs +++ /dev/null @@ -1,17 +0,0 @@ -using ChocolArm64.Instruction; - -namespace ChocolArm64.Decoder -{ - class AOpCodeCsel : AOpCodeAlu, IAOpCodeCond - { - public int Rm { get; private set; } - - public ACond Cond { get; private set; } - - public AOpCodeCsel(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) - { - Rm = (OpCode >> 16) & 0x1f; - Cond = (ACond)((OpCode >> 12) & 0xf); - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Decoder/AOpCodeException.cs b/ChocolArm64/Decoder/AOpCodeException.cs deleted file mode 100644 index 4579c1a7b9..0000000000 --- a/ChocolArm64/Decoder/AOpCodeException.cs +++ /dev/null @@ -1,14 +0,0 @@ -using ChocolArm64.Instruction; - -namespace ChocolArm64.Decoder -{ - class AOpCodeException : AOpCode - { - public int Id { get; private set; } - - public AOpCodeException(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) - { - Id = (OpCode >> 5) & 0xffff; - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Decoder/AOpCodeMem.cs b/ChocolArm64/Decoder/AOpCodeMem.cs deleted file mode 100644 index be5367cf61..0000000000 --- a/ChocolArm64/Decoder/AOpCodeMem.cs +++ /dev/null @@ -1,19 +0,0 @@ -using ChocolArm64.Instruction; - -namespace ChocolArm64.Decoder -{ - class AOpCodeMem : AOpCode - { - 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 AOpCodeMem(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) - { - Rt = (OpCode >> 0) & 0x1f; - Rn = (OpCode >> 5) & 0x1f; - Size = (OpCode >> 30) & 0x3; - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Decoder/AOpCodeMemEx.cs b/ChocolArm64/Decoder/AOpCodeMemEx.cs deleted file mode 100644 index 3a28cfd73a..0000000000 --- a/ChocolArm64/Decoder/AOpCodeMemEx.cs +++ /dev/null @@ -1,16 +0,0 @@ -using ChocolArm64.Instruction; - -namespace ChocolArm64.Decoder -{ - class AOpCodeMemEx : AOpCodeMem - { - public int Rt2 { get; private set; } - public int Rs { get; private set; } - - public AOpCodeMemEx(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) - { - Rt2 = (OpCode >> 10) & 0x1f; - Rs = (OpCode >> 16) & 0x1f; - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Decoder/AOpCodeMemImm.cs b/ChocolArm64/Decoder/AOpCodeMemImm.cs deleted file mode 100644 index 14edc51487..0000000000 --- a/ChocolArm64/Decoder/AOpCodeMemImm.cs +++ /dev/null @@ -1,53 +0,0 @@ -using ChocolArm64.Instruction; - -namespace ChocolArm64.Decoder -{ - class AOpCodeMemImm : AOpCodeMem - { - public long Imm { get; protected set; } - public bool WBack { get; protected set; } - public bool PostIdx { get; protected set; } - protected bool Unscaled { get; private set; } - - private enum MemOp - { - Unscaled = 0, - PostIndexed = 1, - Unprivileged = 2, - PreIndexed = 3, - Unsigned - } - - public AOpCodeMemImm(AInst Inst, long Position, int OpCode) : base(Inst, Position, 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. - Imm = (OpCode << 43) >> 55; - } - else - { - //12-bits Unsigned Immediate. - Imm = ((OpCode >> 10) & 0xfff) << Size; - } - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Decoder/AOpCodeMemLit.cs b/ChocolArm64/Decoder/AOpCodeMemLit.cs deleted file mode 100644 index ad719a1942..0000000000 --- a/ChocolArm64/Decoder/AOpCodeMemLit.cs +++ /dev/null @@ -1,28 +0,0 @@ -using ChocolArm64.Instruction; - -namespace ChocolArm64.Decoder -{ - class AOpCodeMemLit : AOpCode, IAOpCodeLit - { - public int Rt { get; private set; } - public long Imm { get; private set; } - public int Size { get; private set; } - public bool Signed { get; private set; } - public bool Prefetch { get; private set; } - - public AOpCodeMemLit(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) - { - Rt = OpCode & 0x1f; - - Imm = Position + ADecoderHelper.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/ChocolArm64/Decoder/AOpCodeMemPair.cs b/ChocolArm64/Decoder/AOpCodeMemPair.cs deleted file mode 100644 index ec866c84e5..0000000000 --- a/ChocolArm64/Decoder/AOpCodeMemPair.cs +++ /dev/null @@ -1,25 +0,0 @@ -using ChocolArm64.Instruction; - -namespace ChocolArm64.Decoder -{ - class AOpCodeMemPair : AOpCodeMemImm - { - public int Rt2 { get; private set; } - - public AOpCodeMemPair(AInst Inst, long Position, int OpCode) : base(Inst, Position, 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) - { - Imm = ((long)(OpCode >> 15) << 57) >> (57 - Size); - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Decoder/AOpCodeMemReg.cs b/ChocolArm64/Decoder/AOpCodeMemReg.cs deleted file mode 100644 index 989271282f..0000000000 --- a/ChocolArm64/Decoder/AOpCodeMemReg.cs +++ /dev/null @@ -1,20 +0,0 @@ -using ChocolArm64.Instruction; - -namespace ChocolArm64.Decoder -{ - class AOpCodeMemReg : AOpCodeMem - { - public bool Shift { get; private set; } - public int Rm { get; private set; } - - public AIntType IntType { get; private set; } - - public AOpCodeMemReg(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) - { - Shift = ((OpCode >> 12) & 0x1) != 0; - IntType = (AIntType)((OpCode >> 13) & 0x7); - Rm = (OpCode >> 16) & 0x1f; - Extend64 = ((OpCode >> 22) & 0x3) == 2; - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Decoder/AOpCodeMov.cs b/ChocolArm64/Decoder/AOpCodeMov.cs deleted file mode 100644 index d5398646d1..0000000000 --- a/ChocolArm64/Decoder/AOpCodeMov.cs +++ /dev/null @@ -1,36 +0,0 @@ -using ChocolArm64.Instruction; -using ChocolArm64.State; - -namespace ChocolArm64.Decoder -{ - class AOpCodeMov : AOpCode - { - public int Rd { get; private set; } - public long Imm { get; private set; } - public int Pos { get; private set; } - - public AOpCodeMov(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) - { - int P1 = (OpCode >> 22) & 1; - int SF = (OpCode >> 31) & 1; - - if (SF == 0 && P1 != 0) - { - Emitter = AInstEmit.Und; - - return; - } - - Rd = (OpCode >> 0) & 0x1f; - Imm = (OpCode >> 5) & 0xffff; - Pos = (OpCode >> 21) & 0x3; - - Pos <<= 4; - Imm <<= Pos; - - RegisterSize = (OpCode >> 31) != 0 - ? ARegisterSize.Int64 - : ARegisterSize.Int32; - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Decoder/AOpCodeMul.cs b/ChocolArm64/Decoder/AOpCodeMul.cs deleted file mode 100644 index ca2b0cdb38..0000000000 --- a/ChocolArm64/Decoder/AOpCodeMul.cs +++ /dev/null @@ -1,16 +0,0 @@ -using ChocolArm64.Instruction; - -namespace ChocolArm64.Decoder -{ - class AOpCodeMul : AOpCodeAlu - { - public int Rm { get; private set; } - public int Ra { get; private set; } - - public AOpCodeMul(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) - { - Ra = (OpCode >> 10) & 0x1f; - Rm = (OpCode >> 16) & 0x1f; - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Decoder/AOpCodeSimd.cs b/ChocolArm64/Decoder/AOpCodeSimd.cs deleted file mode 100644 index 4170851721..0000000000 --- a/ChocolArm64/Decoder/AOpCodeSimd.cs +++ /dev/null @@ -1,25 +0,0 @@ -using ChocolArm64.Instruction; -using ChocolArm64.State; - -namespace ChocolArm64.Decoder -{ - class AOpCodeSimd : AOpCode, IAOpCodeSimd - { - public int Rd { get; private set; } - public int Rn { get; private set; } - public int Opc { get; private set; } - public int Size { get; protected set; } - - public AOpCodeSimd(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) - { - Rd = (OpCode >> 0) & 0x1f; - Rn = (OpCode >> 5) & 0x1f; - Opc = (OpCode >> 15) & 0x3; - Size = (OpCode >> 22) & 0x3; - - RegisterSize = ((OpCode >> 30) & 1) != 0 - ? ARegisterSize.SIMD128 - : ARegisterSize.SIMD64; - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Decoder/AOpCodeSimdCvt.cs b/ChocolArm64/Decoder/AOpCodeSimdCvt.cs deleted file mode 100644 index 41f4d3b143..0000000000 --- a/ChocolArm64/Decoder/AOpCodeSimdCvt.cs +++ /dev/null @@ -1,31 +0,0 @@ -using ChocolArm64.Instruction; -using ChocolArm64.State; - -namespace ChocolArm64.Decoder -{ - class AOpCodeSimdCvt : AOpCodeSimd - { - public int FBits { get; private set; } - - public AOpCodeSimdCvt(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) - { - //TODO: - //Und of Fixed Point variants. - int Scale = (OpCode >> 10) & 0x3f; - int SF = (OpCode >> 31) & 0x1; - - /*if (Type != SF && !(Type == 2 && SF == 1)) - { - Emitter = AInstEmit.Und; - - return; - }*/ - - FBits = 64 - Scale; - - RegisterSize = SF != 0 - ? ARegisterSize.Int64 - : ARegisterSize.Int32; - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Decoder/AOpCodeSimdExt.cs b/ChocolArm64/Decoder/AOpCodeSimdExt.cs deleted file mode 100644 index 888e447030..0000000000 --- a/ChocolArm64/Decoder/AOpCodeSimdExt.cs +++ /dev/null @@ -1,14 +0,0 @@ -using ChocolArm64.Instruction; - -namespace ChocolArm64.Decoder -{ - class AOpCodeSimdExt : AOpCodeSimdReg - { - public int Imm4 { get; private set; } - - public AOpCodeSimdExt(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) - { - Imm4 = (OpCode >> 11) & 0xf; - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Decoder/AOpCodeSimdFcond.cs b/ChocolArm64/Decoder/AOpCodeSimdFcond.cs deleted file mode 100644 index e38e742473..0000000000 --- a/ChocolArm64/Decoder/AOpCodeSimdFcond.cs +++ /dev/null @@ -1,17 +0,0 @@ -using ChocolArm64.Instruction; - -namespace ChocolArm64.Decoder -{ - class AOpCodeSimdFcond : AOpCodeSimdReg, IAOpCodeCond - { - public int NZCV { get; private set; } - - public ACond Cond { get; private set; } - - public AOpCodeSimdFcond(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) - { - NZCV = (OpCode >> 0) & 0xf; - Cond = (ACond)((OpCode >> 12) & 0xf); - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Decoder/AOpCodeSimdFmov.cs b/ChocolArm64/Decoder/AOpCodeSimdFmov.cs deleted file mode 100644 index 3f88895985..0000000000 --- a/ChocolArm64/Decoder/AOpCodeSimdFmov.cs +++ /dev/null @@ -1,33 +0,0 @@ -using ChocolArm64.Instruction; - -namespace ChocolArm64.Decoder -{ - class AOpCodeSimdFmov : AOpCode, IAOpCodeSimd - { - public int Rd { get; private set; } - public long Imm { get; private set; } - public int Size { get; private set; } - - public AOpCodeSimdFmov(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) - { - int Imm5 = (OpCode >> 5) & 0x1f; - int Type = (OpCode >> 22) & 0x3; - - if (Imm5 != 0b00000 || Type > 1) - { - Emitter = AInstEmit.Und; - - return; - } - - Size = Type; - - long Imm; - - Rd = (OpCode >> 0) & 0x1f; - Imm = (OpCode >> 13) & 0xff; - - this.Imm = ADecoderHelper.DecodeImm8Float(Imm, Type); - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Decoder/AOpCodeSimdImm.cs b/ChocolArm64/Decoder/AOpCodeSimdImm.cs deleted file mode 100644 index e7dfe62114..0000000000 --- a/ChocolArm64/Decoder/AOpCodeSimdImm.cs +++ /dev/null @@ -1,101 +0,0 @@ -using ChocolArm64.Instruction; -using ChocolArm64.State; - -namespace ChocolArm64.Decoder -{ - class AOpCodeSimdImm : AOpCode, IAOpCodeSimd - { - public int Rd { get; private set; } - public long Imm { get; private set; } - public int Size { get; private set; } - - public AOpCodeSimdImm(AInst Inst, long Position, int OpCode) : base(Inst, Position, 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) - { - Size = ModeLow != 0 ? Op : 3; - - switch (Op | (ModeLow << 1)) - { - case 0: - //64-bits Immediate. - //Transform abcd efgh into abcd efgh abcd efgh ... - Imm = (long)((ulong)Imm * 0x0101010101010101); - break; - - case 1: - //64-bits Immediate. - //Transform abcd efgh into aaaa aaaa bbbb bbbb ... - 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: - case 3: - //Floating point Immediate. - Imm = ADecoderHelper.DecodeImm8Float(Imm, Size); - 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; - } - - this.Imm = Imm; - - RegisterSize = ((OpCode >> 30) & 1) != 0 - ? ARegisterSize.SIMD128 - : ARegisterSize.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/ChocolArm64/Decoder/AOpCodeSimdIns.cs b/ChocolArm64/Decoder/AOpCodeSimdIns.cs deleted file mode 100644 index 0b60bbe837..0000000000 --- a/ChocolArm64/Decoder/AOpCodeSimdIns.cs +++ /dev/null @@ -1,36 +0,0 @@ -using ChocolArm64.Instruction; - -namespace ChocolArm64.Decoder -{ - class AOpCodeSimdIns : AOpCodeSimd - { - public int SrcIndex { get; private set; } - public int DstIndex { get; private set; } - - public AOpCodeSimdIns(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) - { - int Imm4 = (OpCode >> 11) & 0xf; - int Imm5 = (OpCode >> 16) & 0x1f; - - if (Imm5 == 0b10000) - { - Emitter = AInstEmit.Und; - - 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/ChocolArm64/Decoder/AOpCodeSimdMemImm.cs b/ChocolArm64/Decoder/AOpCodeSimdMemImm.cs deleted file mode 100644 index 1ef19a5d67..0000000000 --- a/ChocolArm64/Decoder/AOpCodeSimdMemImm.cs +++ /dev/null @@ -1,19 +0,0 @@ -using ChocolArm64.Instruction; - -namespace ChocolArm64.Decoder -{ - class AOpCodeSimdMemImm : AOpCodeMemImm, IAOpCodeSimd - { - public AOpCodeSimdMemImm(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) - { - Size |= (OpCode >> 21) & 4; - - if (!WBack && !Unscaled && Size >= 4) - { - Imm <<= 4; - } - - Extend64 = false; - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Decoder/AOpCodeSimdMemLit.cs b/ChocolArm64/Decoder/AOpCodeSimdMemLit.cs deleted file mode 100644 index ea6fe00be9..0000000000 --- a/ChocolArm64/Decoder/AOpCodeSimdMemLit.cs +++ /dev/null @@ -1,31 +0,0 @@ -using ChocolArm64.Instruction; - -namespace ChocolArm64.Decoder -{ - class AOpCodeSimdMemLit : AOpCode, IAOpCodeSimd, IAOpCodeLit - { - public int Rt { get; private set; } - public long Imm { get; private set; } - public int Size { get; private set; } - public bool Signed => false; - public bool Prefetch => false; - - public AOpCodeSimdMemLit(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) - { - int Opc = (OpCode >> 30) & 3; - - if (Opc == 3) - { - Emitter = AInstEmit.Und; - - return; - } - - Rt = OpCode & 0x1f; - - Imm = Position + ADecoderHelper.DecodeImmS19_2(OpCode); - - Size = Opc + 2; - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Decoder/AOpCodeSimdMemPair.cs b/ChocolArm64/Decoder/AOpCodeSimdMemPair.cs deleted file mode 100644 index db99e3d44d..0000000000 --- a/ChocolArm64/Decoder/AOpCodeSimdMemPair.cs +++ /dev/null @@ -1,16 +0,0 @@ -using ChocolArm64.Instruction; - -namespace ChocolArm64.Decoder -{ - class AOpCodeSimdMemPair : AOpCodeMemPair, IAOpCodeSimd - { - public AOpCodeSimdMemPair(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) - { - Size = ((OpCode >> 30) & 3) + 2; - - Extend64 = false; - - DecodeImm(OpCode); - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Decoder/AOpCodeSimdMemReg.cs b/ChocolArm64/Decoder/AOpCodeSimdMemReg.cs deleted file mode 100644 index aabf484611..0000000000 --- a/ChocolArm64/Decoder/AOpCodeSimdMemReg.cs +++ /dev/null @@ -1,14 +0,0 @@ -using ChocolArm64.Instruction; - -namespace ChocolArm64.Decoder -{ - class AOpCodeSimdMemReg : AOpCodeMemReg, IAOpCodeSimd - { - public AOpCodeSimdMemReg(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) - { - Size |= (OpCode >> 21) & 4; - - Extend64 = false; - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Decoder/AOpCodeSimdMemSs.cs b/ChocolArm64/Decoder/AOpCodeSimdMemSs.cs deleted file mode 100644 index c8794ff5a5..0000000000 --- a/ChocolArm64/Decoder/AOpCodeSimdMemSs.cs +++ /dev/null @@ -1,98 +0,0 @@ -using ChocolArm64.Instruction; -using ChocolArm64.State; - -namespace ChocolArm64.Decoder -{ - class AOpCodeSimdMemSs : AOpCodeMemReg, IAOpCodeSimd - { - public int SElems { get; private set; } - public int Index { get; private set; } - public bool Replicate { get; private set; } - public bool WBack { get; private set; } - - public AOpCodeSimdMemSs(AInst Inst, long Position, int OpCode) : base(Inst, Position, 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) - { - Inst = AInst.Undefined; - - return; - } - - Index >>= 1; - - break; - } - - case 2: - { - if ((Size & 2) != 0 || - ((Size & 1) != 0 && S != 0)) - { - Inst = AInst.Undefined; - - return; - } - - if ((Size & 1) != 0) - { - Index >>= 3; - - Scale = 3; - } - else - { - Index >>= 2; - } - - break; - } - - case 3: - { - if (L == 0 || S != 0) - { - Inst = AInst.Undefined; - - return; - } - - Scale = Size; - - Replicate = true; - - break; - } - } - - this.Index = Index; - this.SElems = SElems; - this.Size = Scale; - - Extend64 = false; - - WBack = ((OpCode >> 23) & 1) != 0; - - RegisterSize = Q != 0 - ? ARegisterSize.SIMD128 - : ARegisterSize.SIMD64; - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Decoder/AOpCodeSimdReg.cs b/ChocolArm64/Decoder/AOpCodeSimdReg.cs deleted file mode 100644 index 702ffed1e2..0000000000 --- a/ChocolArm64/Decoder/AOpCodeSimdReg.cs +++ /dev/null @@ -1,18 +0,0 @@ -using ChocolArm64.Instruction; - -namespace ChocolArm64.Decoder -{ - class AOpCodeSimdReg : AOpCodeSimd - { - public bool Bit3 { get; private set; } - public int Ra { get; private set; } - public int Rm { get; protected set; } - - public AOpCodeSimdReg(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) - { - Bit3 = ((OpCode >> 3) & 0x1) != 0; - Ra = (OpCode >> 10) & 0x1f; - Rm = (OpCode >> 16) & 0x1f; - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Decoder/AOpCodeSimdRegElem.cs b/ChocolArm64/Decoder/AOpCodeSimdRegElem.cs deleted file mode 100644 index d6dc4bd231..0000000000 --- a/ChocolArm64/Decoder/AOpCodeSimdRegElem.cs +++ /dev/null @@ -1,31 +0,0 @@ -using ChocolArm64.Instruction; - -namespace ChocolArm64.Decoder -{ - class AOpCodeSimdRegElem : AOpCodeSimdReg - { - public int Index { get; private set; } - - public AOpCodeSimdRegElem(AInst Inst, long Position, int OpCode) : base(Inst, Position, 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: Emitter = AInstEmit.Und; return; - } - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Decoder/AOpCodeSimdRegElemF.cs b/ChocolArm64/Decoder/AOpCodeSimdRegElemF.cs deleted file mode 100644 index e61d7093a7..0000000000 --- a/ChocolArm64/Decoder/AOpCodeSimdRegElemF.cs +++ /dev/null @@ -1,22 +0,0 @@ -using ChocolArm64.Instruction; - -namespace ChocolArm64.Decoder -{ - class AOpCodeSimdRegElemF : AOpCodeSimdReg - { - public int Index { get; private set; } - - public AOpCodeSimdRegElemF(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) - { - if ((Size & 1) != 0) - { - Index = (OpCode >> 11) & 1; - } - else - { - Index = (OpCode >> 21) & 1 | - (OpCode >> 10) & 2; - } - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Decoder/AOpCodeSimdShImm.cs b/ChocolArm64/Decoder/AOpCodeSimdShImm.cs deleted file mode 100644 index 6c8398817c..0000000000 --- a/ChocolArm64/Decoder/AOpCodeSimdShImm.cs +++ /dev/null @@ -1,16 +0,0 @@ -using ChocolArm64.Instruction; - -namespace ChocolArm64.Decoder -{ - class AOpCodeSimdShImm : AOpCodeSimd - { - public int Imm { get; private set; } - - public AOpCodeSimdShImm(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) - { - Imm = (OpCode >> 16) & 0x7f; - - Size = ABitUtils.HighestBitSet32(Imm >> 3); - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Decoder/AOpCodeSimdTbl.cs b/ChocolArm64/Decoder/AOpCodeSimdTbl.cs deleted file mode 100644 index c8ae5bac74..0000000000 --- a/ChocolArm64/Decoder/AOpCodeSimdTbl.cs +++ /dev/null @@ -1,12 +0,0 @@ -using ChocolArm64.Instruction; - -namespace ChocolArm64.Decoder -{ - class AOpCodeSimdTbl : AOpCodeSimdReg - { - public AOpCodeSimdTbl(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) - { - Size = ((OpCode >> 13) & 3) + 1; - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Decoder/AOpCodeSystem.cs b/ChocolArm64/Decoder/AOpCodeSystem.cs deleted file mode 100644 index 3d81a5d451..0000000000 --- a/ChocolArm64/Decoder/AOpCodeSystem.cs +++ /dev/null @@ -1,24 +0,0 @@ -using ChocolArm64.Instruction; - -namespace ChocolArm64.Decoder -{ - class AOpCodeSystem : AOpCode - { - public int Rt { get; private set; } - public int Op2 { get; private set; } - public int CRm { get; private set; } - public int CRn { get; private set; } - public int Op1 { get; private set; } - public int Op0 { get; private set; } - - public AOpCodeSystem(AInst Inst, long Position, int OpCode) : base(Inst, Position, 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/ChocolArm64/Decoder/AShiftType.cs b/ChocolArm64/Decoder/AShiftType.cs deleted file mode 100644 index 34ceea2087..0000000000 --- a/ChocolArm64/Decoder/AShiftType.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace ChocolArm64.Decoder -{ - enum AShiftType - { - Lsl, - Lsr, - Asr, - Ror - } -} \ No newline at end of file diff --git a/ChocolArm64/Decoder/IAOpCode.cs b/ChocolArm64/Decoder/IAOpCode.cs deleted file mode 100644 index 44bf9cb2f1..0000000000 --- a/ChocolArm64/Decoder/IAOpCode.cs +++ /dev/null @@ -1,13 +0,0 @@ -using ChocolArm64.Instruction; -using ChocolArm64.State; - -namespace ChocolArm64.Decoder -{ - interface IAOpCode - { - long Position { get; } - - AInstEmitter Emitter { get; } - ARegisterSize RegisterSize { get; } - } -} \ No newline at end of file diff --git a/ChocolArm64/Decoder/IAOpCodeAlu.cs b/ChocolArm64/Decoder/IAOpCodeAlu.cs deleted file mode 100644 index 22af4c82d6..0000000000 --- a/ChocolArm64/Decoder/IAOpCodeAlu.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace ChocolArm64.Decoder -{ - interface IAOpCodeAlu : IAOpCode - { - int Rd { get; } - int Rn { get; } - - ADataOp DataOp { get; } - } -} \ No newline at end of file diff --git a/ChocolArm64/Decoder/IAOpCodeAluImm.cs b/ChocolArm64/Decoder/IAOpCodeAluImm.cs deleted file mode 100644 index 04b5c5f7d1..0000000000 --- a/ChocolArm64/Decoder/IAOpCodeAluImm.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace ChocolArm64.Decoder -{ - interface IAOpCodeAluImm : IAOpCodeAlu - { - long Imm { get; } - } -} \ No newline at end of file diff --git a/ChocolArm64/Decoder/IAOpCodeAluRs.cs b/ChocolArm64/Decoder/IAOpCodeAluRs.cs deleted file mode 100644 index 5ca9de4032..0000000000 --- a/ChocolArm64/Decoder/IAOpCodeAluRs.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace ChocolArm64.Decoder -{ - interface IAOpCodeAluRs : IAOpCodeAlu - { - int Shift { get; } - int Rm { get; } - - AShiftType ShiftType { get; } - } -} \ No newline at end of file diff --git a/ChocolArm64/Decoder/IAOpCodeAluRx.cs b/ChocolArm64/Decoder/IAOpCodeAluRx.cs deleted file mode 100644 index b49d5325a9..0000000000 --- a/ChocolArm64/Decoder/IAOpCodeAluRx.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace ChocolArm64.Decoder -{ - interface IAOpCodeAluRx : IAOpCodeAlu - { - int Shift { get; } - int Rm { get; } - - AIntType IntType { get; } - } -} \ No newline at end of file diff --git a/ChocolArm64/Decoder/IAOpCodeCond.cs b/ChocolArm64/Decoder/IAOpCodeCond.cs deleted file mode 100644 index 1655abaac0..0000000000 --- a/ChocolArm64/Decoder/IAOpCodeCond.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace ChocolArm64.Decoder -{ - interface IAOpCodeCond : IAOpCode - { - ACond Cond { get; } - } -} \ No newline at end of file diff --git a/ChocolArm64/Decoder/IAOpCodeLit.cs b/ChocolArm64/Decoder/IAOpCodeLit.cs deleted file mode 100644 index 0f5092d076..0000000000 --- a/ChocolArm64/Decoder/IAOpCodeLit.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace ChocolArm64.Decoder -{ - interface IAOpCodeLit : IAOpCode - { - int Rt { get; } - long Imm { get; } - int Size { get; } - bool Signed { get; } - bool Prefetch { get; } - } -} \ No newline at end of file diff --git a/ChocolArm64/Decoder/IAOpCodeSimd.cs b/ChocolArm64/Decoder/IAOpCodeSimd.cs deleted file mode 100644 index 19032ad940..0000000000 --- a/ChocolArm64/Decoder/IAOpCodeSimd.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace ChocolArm64.Decoder -{ - interface IAOpCodeSimd : IAOpCode - { - int Size { get; } - } -} \ No newline at end of file diff --git a/ChocolArm64/Decoder32/A32OpCode.cs b/ChocolArm64/Decoder32/A32OpCode.cs deleted file mode 100644 index 56f870df6c..0000000000 --- a/ChocolArm64/Decoder32/A32OpCode.cs +++ /dev/null @@ -1,15 +0,0 @@ -using ChocolArm64.Decoder; -using ChocolArm64.Instruction; - -namespace ChocolArm64.Decoder32 -{ - class A32OpCode : AOpCode - { - public ACond Cond { get; private set; } - - public A32OpCode(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) - { - Cond = (ACond)((uint)OpCode >> 28); - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Decoder32/A32OpCodeBImmAl.cs b/ChocolArm64/Decoder32/A32OpCodeBImmAl.cs deleted file mode 100644 index 71bca7f95d..0000000000 --- a/ChocolArm64/Decoder32/A32OpCodeBImmAl.cs +++ /dev/null @@ -1,16 +0,0 @@ -using ChocolArm64.Instruction; - -namespace ChocolArm64.Decoder32 -{ - class A32OpCodeBImmAl : A32OpCode - { - public int Imm; - public int H; - - public A32OpCodeBImmAl(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) - { - Imm = (OpCode << 8) >> 6; - H = (OpCode >> 23) & 2; - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Events/ACpuTraceEventArgs.cs b/ChocolArm64/Events/ACpuTraceEventArgs.cs deleted file mode 100644 index fedf3865b1..0000000000 --- a/ChocolArm64/Events/ACpuTraceEventArgs.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; - -namespace ChocolArm64.Events -{ - public class ACpuTraceEventArgs : EventArgs - { - public long Position { get; private set; } - - public string SubName { get; private set; } - - public ACpuTraceEventArgs(long Position, string SubName) - { - this.Position = Position; - this.SubName = SubName; - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Events/AInstExceptionEventArgs.cs b/ChocolArm64/Events/AInstExceptionEventArgs.cs deleted file mode 100644 index a6853ea10e..0000000000 --- a/ChocolArm64/Events/AInstExceptionEventArgs.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; - -namespace ChocolArm64.Events -{ - public class AInstExceptionEventArgs : EventArgs - { - public long Position { get; private set; } - public int Id { get; private set; } - - public AInstExceptionEventArgs(long Position, int Id) - { - this.Position = Position; - this.Id = Id; - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Events/AInstUndefinedEventArgs.cs b/ChocolArm64/Events/AInstUndefinedEventArgs.cs deleted file mode 100644 index cdc1728bd8..0000000000 --- a/ChocolArm64/Events/AInstUndefinedEventArgs.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; - -namespace ChocolArm64.Events -{ - public class AInstUndefinedEventArgs : EventArgs - { - public long Position { get; private set; } - public int RawOpCode { get; private set; } - - public AInstUndefinedEventArgs(long Position, int RawOpCode) - { - this.Position = Position; - this.RawOpCode = RawOpCode; - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Exceptions/VmmAccessViolationException.cs b/ChocolArm64/Exceptions/VmmAccessViolationException.cs deleted file mode 100644 index a557502e50..0000000000 --- a/ChocolArm64/Exceptions/VmmAccessViolationException.cs +++ /dev/null @@ -1,14 +0,0 @@ -using ChocolArm64.Memory; -using System; - -namespace ChocolArm64.Exceptions -{ - public class VmmAccessViolationException : Exception - { - private const string ExMsg = "Address 0x{0:x16} does not have \"{1}\" permission!"; - - public VmmAccessViolationException() { } - - public VmmAccessViolationException(long Position, AMemoryPerm Perm) : base(string.Format(ExMsg, Position, Perm)) { } - } -} \ No newline at end of file diff --git a/ChocolArm64/Exceptions/VmmOutOfMemoryException.cs b/ChocolArm64/Exceptions/VmmOutOfMemoryException.cs deleted file mode 100644 index c11384dae8..0000000000 --- a/ChocolArm64/Exceptions/VmmOutOfMemoryException.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; - -namespace ChocolArm64.Exceptions -{ - public class VmmOutOfMemoryException : Exception - { - private const string ExMsg = "Failed to allocate {0} bytes of memory!"; - - public VmmOutOfMemoryException() { } - - public VmmOutOfMemoryException(long Size) : base(string.Format(ExMsg, Size)) { } - } -} \ No newline at end of file diff --git a/ChocolArm64/Exceptions/VmmPageFaultException.cs b/ChocolArm64/Exceptions/VmmPageFaultException.cs deleted file mode 100644 index d55c2c1ca9..0000000000 --- a/ChocolArm64/Exceptions/VmmPageFaultException.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; - -namespace ChocolArm64.Exceptions -{ - public class VmmPageFaultException : Exception - { - private const string ExMsg = "Tried to access unmapped address 0x{0:x16}!"; - - public VmmPageFaultException() { } - - public VmmPageFaultException(long Position) : base(string.Format(ExMsg, Position)) { } - } -} \ No newline at end of file diff --git a/ChocolArm64/Instruction/AInst.cs b/ChocolArm64/Instruction/AInst.cs deleted file mode 100644 index 7409353614..0000000000 --- a/ChocolArm64/Instruction/AInst.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; - -namespace ChocolArm64.Instruction -{ - struct AInst - { - public AInstInterpreter Interpreter { get; private set; } - public AInstEmitter Emitter { get; private set; } - public Type Type { get; private set; } - - public static AInst Undefined => new AInst(null, AInstEmit.Und, null); - - public AInst(AInstInterpreter Interpreter, AInstEmitter Emitter, Type Type) - { - this.Interpreter = Interpreter; - this.Emitter = Emitter; - this.Type = Type; - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Instruction/AInstEmitAlu.cs b/ChocolArm64/Instruction/AInstEmitAlu.cs deleted file mode 100644 index 490387e129..0000000000 --- a/ChocolArm64/Instruction/AInstEmitAlu.cs +++ /dev/null @@ -1,392 +0,0 @@ -using ChocolArm64.Decoder; -using ChocolArm64.State; -using ChocolArm64.Translation; -using System; -using System.Reflection; -using System.Reflection.Emit; - -using static ChocolArm64.Instruction.AInstEmitAluHelper; - -namespace ChocolArm64.Instruction -{ - static partial class AInstEmit - { - public static void Adc(AILEmitterCtx Context) => EmitAdc(Context, false); - public static void Adcs(AILEmitterCtx Context) => EmitAdc(Context, true); - - private static void EmitAdc(AILEmitterCtx Context, bool SetFlags) - { - EmitDataLoadOpers(Context); - - Context.Emit(OpCodes.Add); - - Context.EmitLdflg((int)APState.CBit); - - Type[] MthdTypes = new Type[] { typeof(bool) }; - - MethodInfo MthdInfo = typeof(Convert).GetMethod(nameof(Convert.ToInt32), MthdTypes); - - Context.EmitCall(MthdInfo); - - if (Context.CurrOp.RegisterSize != ARegisterSize.Int32) - { - Context.Emit(OpCodes.Conv_U8); - } - - Context.Emit(OpCodes.Add); - - if (SetFlags) - { - Context.EmitZNFlagCheck(); - - EmitAdcsCCheck(Context); - EmitAddsVCheck(Context); - } - - EmitDataStore(Context); - } - - public static void Add(AILEmitterCtx Context) => EmitDataOp(Context, OpCodes.Add); - - public static void Adds(AILEmitterCtx Context) - { - EmitDataLoadOpers(Context); - - Context.Emit(OpCodes.Add); - - Context.EmitZNFlagCheck(); - - EmitAddsCCheck(Context); - EmitAddsVCheck(Context); - EmitDataStoreS(Context); - } - - public static void And(AILEmitterCtx Context) => EmitDataOp(Context, OpCodes.And); - - public static void Ands(AILEmitterCtx Context) - { - EmitDataLoadOpers(Context); - - Context.Emit(OpCodes.And); - - EmitZeroCVFlags(Context); - - Context.EmitZNFlagCheck(); - - EmitDataStoreS(Context); - } - - public static void Asrv(AILEmitterCtx Context) => EmitDataOpShift(Context, OpCodes.Shr); - - public static void Bic(AILEmitterCtx Context) => EmitBic(Context, false); - public static void Bics(AILEmitterCtx Context) => EmitBic(Context, true); - - private static void EmitBic(AILEmitterCtx Context, bool SetFlags) - { - EmitDataLoadOpers(Context); - - Context.Emit(OpCodes.Not); - Context.Emit(OpCodes.And); - - if (SetFlags) - { - EmitZeroCVFlags(Context); - - Context.EmitZNFlagCheck(); - } - - EmitDataStore(Context, SetFlags); - } - - public static void Cls(AILEmitterCtx Context) - { - AOpCodeAlu Op = (AOpCodeAlu)Context.CurrOp; - - Context.EmitLdintzr(Op.Rn); - - Context.EmitLdc_I4(Op.RegisterSize == ARegisterSize.Int32 ? 32 : 64); - - ASoftFallback.EmitCall(Context, nameof(ASoftFallback.CountLeadingSigns)); - - Context.EmitStintzr(Op.Rd); - } - - public static void Clz(AILEmitterCtx Context) - { - AOpCodeAlu Op = (AOpCodeAlu)Context.CurrOp; - - Context.EmitLdintzr(Op.Rn); - - Context.EmitLdc_I4(Op.RegisterSize == ARegisterSize.Int32 ? 32 : 64); - - ASoftFallback.EmitCall(Context, nameof(ASoftFallback.CountLeadingZeros)); - - Context.EmitStintzr(Op.Rd); - } - - public static void Eon(AILEmitterCtx Context) - { - EmitDataLoadOpers(Context); - - Context.Emit(OpCodes.Not); - Context.Emit(OpCodes.Xor); - - EmitDataStore(Context); - } - - public static void Eor(AILEmitterCtx Context) => EmitDataOp(Context, OpCodes.Xor); - - public static void Extr(AILEmitterCtx Context) - { - //TODO: Ensure that the Shift is valid for the Is64Bits. - AOpCodeAluRs Op = (AOpCodeAluRs)Context.CurrOp; - - Context.EmitLdintzr(Op.Rm); - - if (Op.Shift > 0) - { - Context.EmitLdc_I4(Op.Shift); - - Context.Emit(OpCodes.Shr_Un); - - Context.EmitLdintzr(Op.Rn); - Context.EmitLdc_I4(Op.GetBitsCount() - Op.Shift); - - Context.Emit(OpCodes.Shl); - Context.Emit(OpCodes.Or); - } - - EmitDataStore(Context); - } - - public static void Lslv(AILEmitterCtx Context) => EmitDataOpShift(Context, OpCodes.Shl); - public static void Lsrv(AILEmitterCtx Context) => EmitDataOpShift(Context, OpCodes.Shr_Un); - - public static void Sbc(AILEmitterCtx Context) => EmitSbc(Context, false); - public static void Sbcs(AILEmitterCtx Context) => EmitSbc(Context, true); - - private static void EmitSbc(AILEmitterCtx Context, bool SetFlags) - { - EmitDataLoadOpers(Context); - - Context.Emit(OpCodes.Sub); - - Context.EmitLdflg((int)APState.CBit); - - Type[] MthdTypes = new Type[] { typeof(bool) }; - - MethodInfo MthdInfo = typeof(Convert).GetMethod(nameof(Convert.ToInt32), MthdTypes); - - Context.EmitCall(MthdInfo); - - Context.EmitLdc_I4(1); - - Context.Emit(OpCodes.Xor); - - if (Context.CurrOp.RegisterSize != ARegisterSize.Int32) - { - Context.Emit(OpCodes.Conv_U8); - } - - Context.Emit(OpCodes.Sub); - - if (SetFlags) - { - Context.EmitZNFlagCheck(); - - EmitSbcsCCheck(Context); - EmitSubsVCheck(Context); - } - - EmitDataStore(Context); - } - - public static void Sub(AILEmitterCtx Context) => EmitDataOp(Context, OpCodes.Sub); - - public static void Subs(AILEmitterCtx Context) - { - Context.TryOptMarkCondWithoutCmp(); - - EmitDataLoadOpers(Context); - - Context.Emit(OpCodes.Sub); - - Context.EmitZNFlagCheck(); - - EmitSubsCCheck(Context); - EmitSubsVCheck(Context); - EmitDataStoreS(Context); - } - - public static void Orn(AILEmitterCtx Context) - { - EmitDataLoadOpers(Context); - - Context.Emit(OpCodes.Not); - Context.Emit(OpCodes.Or); - - EmitDataStore(Context); - } - - public static void Orr(AILEmitterCtx Context) => EmitDataOp(Context, OpCodes.Or); - - public static void Rbit(AILEmitterCtx Context) => EmitFallback32_64(Context, - nameof(ASoftFallback.ReverseBits32), - nameof(ASoftFallback.ReverseBits64)); - - public static void Rev16(AILEmitterCtx Context) => EmitFallback32_64(Context, - nameof(ASoftFallback.ReverseBytes16_32), - nameof(ASoftFallback.ReverseBytes16_64)); - - public static void Rev32(AILEmitterCtx Context) => EmitFallback32_64(Context, - nameof(ASoftFallback.ReverseBytes32_32), - nameof(ASoftFallback.ReverseBytes32_64)); - - private static void EmitFallback32_64(AILEmitterCtx Context, string Name32, string Name64) - { - AOpCodeAlu Op = (AOpCodeAlu)Context.CurrOp; - - Context.EmitLdintzr(Op.Rn); - - if (Op.RegisterSize == ARegisterSize.Int32) - { - ASoftFallback.EmitCall(Context, Name32); - } - else - { - ASoftFallback.EmitCall(Context, Name64); - } - - Context.EmitStintzr(Op.Rd); - } - - public static void Rev64(AILEmitterCtx Context) - { - AOpCodeAlu Op = (AOpCodeAlu)Context.CurrOp; - - Context.EmitLdintzr(Op.Rn); - - ASoftFallback.EmitCall(Context, nameof(ASoftFallback.ReverseBytes64)); - - Context.EmitStintzr(Op.Rd); - } - - public static void Rorv(AILEmitterCtx Context) - { - EmitDataLoadRn(Context); - EmitDataLoadShift(Context); - - Context.Emit(OpCodes.Shr_Un); - - EmitDataLoadRn(Context); - - Context.EmitLdc_I4(Context.CurrOp.GetBitsCount()); - - EmitDataLoadShift(Context); - - Context.Emit(OpCodes.Sub); - Context.Emit(OpCodes.Shl); - Context.Emit(OpCodes.Or); - - EmitDataStore(Context); - } - - public static void Sdiv(AILEmitterCtx Context) => EmitDiv(Context, OpCodes.Div); - public static void Udiv(AILEmitterCtx Context) => EmitDiv(Context, OpCodes.Div_Un); - - private static void EmitDiv(AILEmitterCtx Context, OpCode ILOp) - { - //If Rm == 0, Rd = 0 (division by zero). - Context.EmitLdc_I(0); - - EmitDataLoadRm(Context); - - Context.EmitLdc_I(0); - - AILLabel BadDiv = new AILLabel(); - - Context.Emit(OpCodes.Beq_S, BadDiv); - Context.Emit(OpCodes.Pop); - - if (ILOp == OpCodes.Div) - { - //If Rn == INT_MIN && Rm == -1, Rd = INT_MIN (overflow). - long IntMin = 1L << (Context.CurrOp.GetBitsCount() - 1); - - Context.EmitLdc_I(IntMin); - - EmitDataLoadRn(Context); - - Context.EmitLdc_I(IntMin); - - Context.Emit(OpCodes.Ceq); - - EmitDataLoadRm(Context); - - Context.EmitLdc_I(-1); - - Context.Emit(OpCodes.Ceq); - Context.Emit(OpCodes.And); - Context.Emit(OpCodes.Brtrue_S, BadDiv); - Context.Emit(OpCodes.Pop); - } - - EmitDataLoadRn(Context); - EmitDataLoadRm(Context); - - Context.Emit(ILOp); - - Context.MarkLabel(BadDiv); - - EmitDataStore(Context); - } - - private static void EmitDataOp(AILEmitterCtx Context, OpCode ILOp) - { - EmitDataLoadOpers(Context); - - Context.Emit(ILOp); - - EmitDataStore(Context); - } - - private static void EmitDataOpShift(AILEmitterCtx Context, OpCode ILOp) - { - EmitDataLoadRn(Context); - EmitDataLoadShift(Context); - - Context.Emit(ILOp); - - EmitDataStore(Context); - } - - private static void EmitDataLoadShift(AILEmitterCtx Context) - { - EmitDataLoadRm(Context); - - Context.EmitLdc_I(Context.CurrOp.GetBitsCount() - 1); - - Context.Emit(OpCodes.And); - - //Note: Only 32-bits shift values are valid, so when the value is 64-bits - //we need to cast it to a 32-bits integer. This is fine because we - //AND the value and only keep the lower 5 or 6 bits anyway -- it - //could very well fit on a byte. - if (Context.CurrOp.RegisterSize != ARegisterSize.Int32) - { - Context.Emit(OpCodes.Conv_I4); - } - } - - private static void EmitZeroCVFlags(AILEmitterCtx Context) - { - Context.EmitLdc_I4(0); - - Context.EmitStflg((int)APState.VBit); - - Context.EmitLdc_I4(0); - - Context.EmitStflg((int)APState.CBit); - } - } -} diff --git a/ChocolArm64/Instruction/AInstEmitAluHelper.cs b/ChocolArm64/Instruction/AInstEmitAluHelper.cs deleted file mode 100644 index ef9dd7a7a5..0000000000 --- a/ChocolArm64/Instruction/AInstEmitAluHelper.cs +++ /dev/null @@ -1,212 +0,0 @@ -using ChocolArm64.Decoder; -using ChocolArm64.State; -using ChocolArm64.Translation; -using System.Reflection.Emit; - -namespace ChocolArm64.Instruction -{ - static class AInstEmitAluHelper - { - public static void EmitAdcsCCheck(AILEmitterCtx Context) - { - //C = (Rd == Rn && CIn) || Rd < Rn - Context.EmitSttmp(); - Context.EmitLdtmp(); - Context.EmitLdtmp(); - - EmitDataLoadRn(Context); - - Context.Emit(OpCodes.Ceq); - - Context.EmitLdflg((int)APState.CBit); - - Context.Emit(OpCodes.And); - - Context.EmitLdtmp(); - - EmitDataLoadRn(Context); - - Context.Emit(OpCodes.Clt_Un); - Context.Emit(OpCodes.Or); - - Context.EmitStflg((int)APState.CBit); - } - - public static void EmitAddsCCheck(AILEmitterCtx Context) - { - //C = Rd < Rn - Context.Emit(OpCodes.Dup); - - EmitDataLoadRn(Context); - - Context.Emit(OpCodes.Clt_Un); - - Context.EmitStflg((int)APState.CBit); - } - - public static void EmitAddsVCheck(AILEmitterCtx Context) - { - //V = (Rd ^ Rn) & ~(Rn ^ Rm) < 0 - Context.Emit(OpCodes.Dup); - - EmitDataLoadRn(Context); - - Context.Emit(OpCodes.Xor); - - EmitDataLoadOpers(Context); - - Context.Emit(OpCodes.Xor); - Context.Emit(OpCodes.Not); - Context.Emit(OpCodes.And); - - Context.EmitLdc_I(0); - - Context.Emit(OpCodes.Clt); - - Context.EmitStflg((int)APState.VBit); - } - - public static void EmitSbcsCCheck(AILEmitterCtx Context) - { - //C = (Rn == Rm && CIn) || Rn > Rm - EmitDataLoadOpers(Context); - - Context.Emit(OpCodes.Ceq); - - Context.EmitLdflg((int)APState.CBit); - - Context.Emit(OpCodes.And); - - EmitDataLoadOpers(Context); - - Context.Emit(OpCodes.Cgt_Un); - Context.Emit(OpCodes.Or); - - Context.EmitStflg((int)APState.CBit); - } - - public static void EmitSubsCCheck(AILEmitterCtx Context) - { - //C = Rn == Rm || Rn > Rm = !(Rn < Rm) - EmitDataLoadOpers(Context); - - Context.Emit(OpCodes.Clt_Un); - - Context.EmitLdc_I4(1); - - Context.Emit(OpCodes.Xor); - - Context.EmitStflg((int)APState.CBit); - } - - public static void EmitSubsVCheck(AILEmitterCtx Context) - { - //V = (Rd ^ Rn) & (Rn ^ Rm) < 0 - Context.Emit(OpCodes.Dup); - - EmitDataLoadRn(Context); - - Context.Emit(OpCodes.Xor); - - EmitDataLoadOpers(Context); - - Context.Emit(OpCodes.Xor); - Context.Emit(OpCodes.And); - - Context.EmitLdc_I(0); - - Context.Emit(OpCodes.Clt); - - Context.EmitStflg((int)APState.VBit); - } - - public static void EmitDataLoadRm(AILEmitterCtx Context) - { - Context.EmitLdintzr(((IAOpCodeAluRs)Context.CurrOp).Rm); - } - - public static void EmitDataLoadOpers(AILEmitterCtx Context) - { - EmitDataLoadRn(Context); - EmitDataLoadOper2(Context); - } - - public static void EmitDataLoadRn(AILEmitterCtx Context) - { - IAOpCodeAlu Op = (IAOpCodeAlu)Context.CurrOp; - - if (Op.DataOp == ADataOp.Logical || Op is IAOpCodeAluRs) - { - Context.EmitLdintzr(Op.Rn); - } - else - { - Context.EmitLdint(Op.Rn); - } - } - - public static void EmitDataLoadOper2(AILEmitterCtx Context) - { - switch (Context.CurrOp) - { - case IAOpCodeAluImm Op: - Context.EmitLdc_I(Op.Imm); - break; - - case IAOpCodeAluRs Op: - Context.EmitLdintzr(Op.Rm); - - switch (Op.ShiftType) - { - case AShiftType.Lsl: Context.EmitLsl(Op.Shift); break; - case AShiftType.Lsr: Context.EmitLsr(Op.Shift); break; - case AShiftType.Asr: Context.EmitAsr(Op.Shift); break; - case AShiftType.Ror: Context.EmitRor(Op.Shift); break; - } - break; - - case IAOpCodeAluRx Op: - Context.EmitLdintzr(Op.Rm); - Context.EmitCast(Op.IntType); - Context.EmitLsl(Op.Shift); - break; - } - } - - public static void EmitDataStore(AILEmitterCtx Context) => EmitDataStore(Context, false); - public static void EmitDataStoreS(AILEmitterCtx Context) => EmitDataStore(Context, true); - - public static void EmitDataStore(AILEmitterCtx Context, bool SetFlags) - { - IAOpCodeAlu Op = (IAOpCodeAlu)Context.CurrOp; - - if (SetFlags || Op is IAOpCodeAluRs) - { - Context.EmitStintzr(Op.Rd); - } - else - { - Context.EmitStint(Op.Rd); - } - } - - public static void EmitSetNZCV(AILEmitterCtx Context, int NZCV) - { - Context.EmitLdc_I4((NZCV >> 0) & 1); - - Context.EmitStflg((int)APState.VBit); - - Context.EmitLdc_I4((NZCV >> 1) & 1); - - Context.EmitStflg((int)APState.CBit); - - Context.EmitLdc_I4((NZCV >> 2) & 1); - - Context.EmitStflg((int)APState.ZBit); - - Context.EmitLdc_I4((NZCV >> 3) & 1); - - Context.EmitStflg((int)APState.NBit); - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Instruction/AInstEmitBfm.cs b/ChocolArm64/Instruction/AInstEmitBfm.cs deleted file mode 100644 index 2e8f250858..0000000000 --- a/ChocolArm64/Instruction/AInstEmitBfm.cs +++ /dev/null @@ -1,208 +0,0 @@ -using ChocolArm64.Decoder; -using ChocolArm64.State; -using ChocolArm64.Translation; -using System.Reflection.Emit; - -namespace ChocolArm64.Instruction -{ - static partial class AInstEmit - { - public static void Bfm(AILEmitterCtx Context) - { - AOpCodeBfm Op = (AOpCodeBfm)Context.CurrOp; - - EmitBfmLoadRn(Context); - - Context.EmitLdintzr(Op.Rd); - Context.EmitLdc_I(~Op.WMask & Op.TMask); - - Context.Emit(OpCodes.And); - Context.Emit(OpCodes.Or); - - Context.EmitLdintzr(Op.Rd); - Context.EmitLdc_I(~Op.TMask); - - Context.Emit(OpCodes.And); - Context.Emit(OpCodes.Or); - - Context.EmitStintzr(Op.Rd); - } - - public static void Sbfm(AILEmitterCtx Context) - { - AOpCodeBfm Op = (AOpCodeBfm)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) - { - EmitSbfmCast(Context, OpCodes.Conv_I1); - } - else if (Op.Pos == 15 && Op.Shift == 0) - { - EmitSbfmCast(Context, OpCodes.Conv_I2); - } - else if (Op.Pos == 31 && Op.Shift == 0) - { - EmitSbfmCast(Context, OpCodes.Conv_I4); - } - else - { - EmitBfmLoadRn(Context); - - Context.EmitLdintzr(Op.Rn); - - Context.EmitLsl(BitsCount - 1 - Op.Pos); - Context.EmitAsr(BitsCount - 1); - - Context.EmitLdc_I(~Op.TMask); - - Context.Emit(OpCodes.And); - Context.Emit(OpCodes.Or); - - Context.EmitStintzr(Op.Rd); - } - } - - public static void Ubfm(AILEmitterCtx Context) - { - AOpCodeBfm Op = (AOpCodeBfm)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) - { - EmitUbfmCast(Context, OpCodes.Conv_U1); - } - else if (Op.Pos == 15 && Op.Shift == 0) - { - EmitUbfmCast(Context, OpCodes.Conv_U2); - } - else - { - EmitBfmLoadRn(Context); - - Context.EmitStintzr(Op.Rd); - } - } - - private static void EmitSbfiz(AILEmitterCtx Context) => EmitBfiz(Context, true); - private static void EmitUbfiz(AILEmitterCtx Context) => EmitBfiz(Context, false); - - private static void EmitBfiz(AILEmitterCtx Context, bool Signed) - { - AOpCodeBfm Op = (AOpCodeBfm)Context.CurrOp; - - int Width = Op.Pos + 1; - - Context.EmitLdintzr(Op.Rn); - - Context.EmitLsl(Op.GetBitsCount() - Width); - - if (Signed) - { - Context.EmitAsr(Op.Shift - Width); - } - else - { - Context.EmitLsr(Op.Shift - Width); - } - - Context.EmitStintzr(Op.Rd); - } - - private static void EmitSbfmCast(AILEmitterCtx Context, OpCode ILOp) - { - EmitBfmCast(Context, ILOp, true); - } - - private static void EmitUbfmCast(AILEmitterCtx Context, OpCode ILOp) - { - EmitBfmCast(Context, ILOp, false); - } - - private static void EmitBfmCast(AILEmitterCtx Context, OpCode ILOp, bool Signed) - { - AOpCodeBfm Op = (AOpCodeBfm)Context.CurrOp; - - Context.EmitLdintzr(Op.Rn); - - Context.Emit(ILOp); - - if (Op.RegisterSize != ARegisterSize.Int32) - { - Context.Emit(Signed - ? OpCodes.Conv_I8 - : OpCodes.Conv_U8); - } - - Context.EmitStintzr(Op.Rd); - } - - private static void EmitSbfmShift(AILEmitterCtx Context) - { - EmitBfmShift(Context, true); - } - - private static void EmitUbfmShift(AILEmitterCtx Context) - { - EmitBfmShift(Context, false); - } - - private static void EmitBfmShift(AILEmitterCtx Context, bool Signed) - { - AOpCodeBfm Op = (AOpCodeBfm)Context.CurrOp; - - Context.EmitLdintzr(Op.Rn); - Context.EmitLdc_I4(Op.Shift); - - Context.Emit(Signed - ? OpCodes.Shr - : OpCodes.Shr_Un); - - Context.EmitStintzr(Op.Rd); - } - - private static void EmitBfmLsl(AILEmitterCtx Context) - { - AOpCodeBfm Op = (AOpCodeBfm)Context.CurrOp; - - Context.EmitLdintzr(Op.Rn); - - Context.EmitLsl(Op.GetBitsCount() - Op.Shift); - - Context.EmitStintzr(Op.Rd); - } - - private static void EmitBfmLoadRn(AILEmitterCtx Context) - { - AOpCodeBfm Op = (AOpCodeBfm)Context.CurrOp; - - Context.EmitLdintzr(Op.Rn); - - Context.EmitRor(Op.Shift); - - Context.EmitLdc_I(Op.WMask & Op.TMask); - - Context.Emit(OpCodes.And); - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Instruction/AInstEmitCcmp.cs b/ChocolArm64/Instruction/AInstEmitCcmp.cs deleted file mode 100644 index 7153a6a0d1..0000000000 --- a/ChocolArm64/Instruction/AInstEmitCcmp.cs +++ /dev/null @@ -1,81 +0,0 @@ -using ChocolArm64.Decoder; -using ChocolArm64.State; -using ChocolArm64.Translation; -using System; -using System.Reflection.Emit; - -using static ChocolArm64.Instruction.AInstEmitAluHelper; - -namespace ChocolArm64.Instruction -{ - static partial class AInstEmit - { - private enum CcmpOp - { - Cmp, - Cmn - } - - public static void Ccmn(AILEmitterCtx Context) => EmitCcmp(Context, CcmpOp.Cmn); - public static void Ccmp(AILEmitterCtx Context) => EmitCcmp(Context, CcmpOp.Cmp); - - private static void EmitCcmp(AILEmitterCtx Context, CcmpOp CmpOp) - { - AOpCodeCcmp Op = (AOpCodeCcmp)Context.CurrOp; - - AILLabel LblTrue = new AILLabel(); - AILLabel LblEnd = new AILLabel(); - - Context.EmitCondBranch(LblTrue, Op.Cond); - - Context.EmitLdc_I4((Op.NZCV >> 0) & 1); - - Context.EmitStflg((int)APState.VBit); - - Context.EmitLdc_I4((Op.NZCV >> 1) & 1); - - Context.EmitStflg((int)APState.CBit); - - Context.EmitLdc_I4((Op.NZCV >> 2) & 1); - - Context.EmitStflg((int)APState.ZBit); - - Context.EmitLdc_I4((Op.NZCV >> 3) & 1); - - Context.EmitStflg((int)APState.NBit); - - Context.Emit(OpCodes.Br_S, LblEnd); - - Context.MarkLabel(LblTrue); - - EmitDataLoadOpers(Context); - - if (CmpOp == CcmpOp.Cmp) - { - Context.Emit(OpCodes.Sub); - - Context.EmitZNFlagCheck(); - - EmitSubsCCheck(Context); - EmitSubsVCheck(Context); - } - else if (CmpOp == CcmpOp.Cmn) - { - Context.Emit(OpCodes.Add); - - Context.EmitZNFlagCheck(); - - EmitAddsCCheck(Context); - EmitAddsVCheck(Context); - } - else - { - throw new ArgumentException(nameof(CmpOp)); - } - - Context.Emit(OpCodes.Pop); - - Context.MarkLabel(LblEnd); - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Instruction/AInstEmitCsel.cs b/ChocolArm64/Instruction/AInstEmitCsel.cs deleted file mode 100644 index 218767524e..0000000000 --- a/ChocolArm64/Instruction/AInstEmitCsel.cs +++ /dev/null @@ -1,58 +0,0 @@ -using ChocolArm64.Decoder; -using ChocolArm64.Translation; -using System.Reflection.Emit; - -namespace ChocolArm64.Instruction -{ - static partial class AInstEmit - { - private enum CselOperation - { - None, - Increment, - Invert, - Negate - } - - public static void Csel(AILEmitterCtx Context) => EmitCsel(Context, CselOperation.None); - public static void Csinc(AILEmitterCtx Context) => EmitCsel(Context, CselOperation.Increment); - public static void Csinv(AILEmitterCtx Context) => EmitCsel(Context, CselOperation.Invert); - public static void Csneg(AILEmitterCtx Context) => EmitCsel(Context, CselOperation.Negate); - - private static void EmitCsel(AILEmitterCtx Context, CselOperation CselOp) - { - AOpCodeCsel Op = (AOpCodeCsel)Context.CurrOp; - - AILLabel LblTrue = new AILLabel(); - AILLabel LblEnd = new AILLabel(); - - Context.EmitCondBranch(LblTrue, Op.Cond); - Context.EmitLdintzr(Op.Rm); - - if (CselOp == CselOperation.Increment) - { - Context.EmitLdc_I(1); - - Context.Emit(OpCodes.Add); - } - else if (CselOp == CselOperation.Invert) - { - Context.Emit(OpCodes.Not); - } - else if (CselOp == CselOperation.Negate) - { - Context.Emit(OpCodes.Neg); - } - - Context.Emit(OpCodes.Br_S, LblEnd); - - Context.MarkLabel(LblTrue); - - Context.EmitLdintzr(Op.Rn); - - Context.MarkLabel(LblEnd); - - Context.EmitStintzr(Op.Rd); - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Instruction/AInstEmitException.cs b/ChocolArm64/Instruction/AInstEmitException.cs deleted file mode 100644 index 73d2096732..0000000000 --- a/ChocolArm64/Instruction/AInstEmitException.cs +++ /dev/null @@ -1,86 +0,0 @@ -using ChocolArm64.Decoder; -using ChocolArm64.State; -using ChocolArm64.Translation; -using System.Reflection.Emit; - -namespace ChocolArm64.Instruction -{ - static partial class AInstEmit - { - public static void Brk(AILEmitterCtx Context) - { - EmitExceptionCall(Context, nameof(AThreadState.OnBreak)); - } - - public static void Svc(AILEmitterCtx Context) - { - EmitExceptionCall(Context, nameof(AThreadState.OnSvcCall)); - } - - private static void EmitExceptionCall(AILEmitterCtx Context, string MthdName) - { - AOpCodeException Op = (AOpCodeException)Context.CurrOp; - - Context.EmitStoreState(); - - Context.EmitLdarg(ATranslatedSub.StateArgIdx); - - Context.EmitLdc_I8(Op.Position); - Context.EmitLdc_I4(Op.Id); - - Context.EmitPrivateCall(typeof(AThreadState), MthdName); - - //Check if the thread should still be running, if it isn't then we return 0 - //to force a return to the dispatcher and then exit the thread. - Context.EmitLdarg(ATranslatedSub.StateArgIdx); - - Context.EmitCallPropGet(typeof(AThreadState), nameof(AThreadState.Running)); - - AILLabel LblEnd = new AILLabel(); - - Context.Emit(OpCodes.Brtrue_S, LblEnd); - - Context.EmitLdc_I8(0); - - Context.Emit(OpCodes.Ret); - - Context.MarkLabel(LblEnd); - - if (Context.CurrBlock.Next != null) - { - Context.EmitLoadState(Context.CurrBlock.Next); - } - else - { - Context.EmitLdc_I8(Op.Position + 4); - - Context.Emit(OpCodes.Ret); - } - } - - public static void Und(AILEmitterCtx Context) - { - AOpCode Op = Context.CurrOp; - - Context.EmitStoreState(); - - Context.EmitLdarg(ATranslatedSub.StateArgIdx); - - Context.EmitLdc_I8(Op.Position); - Context.EmitLdc_I4(Op.RawOpCode); - - Context.EmitPrivateCall(typeof(AThreadState), nameof(AThreadState.OnUndefined)); - - if (Context.CurrBlock.Next != null) - { - Context.EmitLoadState(Context.CurrBlock.Next); - } - else - { - Context.EmitLdc_I8(Op.Position + 4); - - Context.Emit(OpCodes.Ret); - } - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Instruction/AInstEmitFlow.cs b/ChocolArm64/Instruction/AInstEmitFlow.cs deleted file mode 100644 index 89979d0509..0000000000 --- a/ChocolArm64/Instruction/AInstEmitFlow.cs +++ /dev/null @@ -1,220 +0,0 @@ -using ChocolArm64.Decoder; -using ChocolArm64.State; -using ChocolArm64.Translation; -using System.Reflection.Emit; - -namespace ChocolArm64.Instruction -{ - static partial class AInstEmit - { - public static void B(AILEmitterCtx Context) - { - AOpCodeBImmAl Op = (AOpCodeBImmAl)Context.CurrOp; - - if (Context.CurrBlock.Branch != null) - { - Context.Emit(OpCodes.Br, Context.GetLabel(Op.Imm)); - } - else - { - Context.EmitStoreState(); - Context.EmitLdc_I8(Op.Imm); - - Context.Emit(OpCodes.Ret); - } - } - - public static void B_Cond(AILEmitterCtx Context) - { - AOpCodeBImmCond Op = (AOpCodeBImmCond)Context.CurrOp; - - EmitBranch(Context, Op.Cond); - } - - public static void Bl(AILEmitterCtx Context) - { - AOpCodeBImmAl Op = (AOpCodeBImmAl)Context.CurrOp; - - if (AOptimizations.GenerateCallStack) - { - Context.EmitLdarg(ATranslatedSub.StateArgIdx); - Context.EmitLdc_I8(Op.Imm); - - Context.EmitPrivateCall(typeof(AThreadState), nameof(AThreadState.EnterMethod)); - } - - Context.EmitLdc_I(Op.Position + 4); - Context.EmitStint(AThreadState.LRIndex); - Context.EmitStoreState(); - - if (Context.TryOptEmitSubroutineCall()) - { - //Note: the return value of the called method will be placed - //at the Stack, the return value is always a Int64 with the - //return address of the function. We check if the address is - //correct, if it isn't we keep returning until we reach the dispatcher. - Context.Emit(OpCodes.Dup); - - Context.EmitLdc_I8(Op.Position + 4); - - AILLabel LblContinue = new AILLabel(); - - Context.Emit(OpCodes.Beq_S, LblContinue); - Context.Emit(OpCodes.Ret); - - Context.MarkLabel(LblContinue); - - Context.Emit(OpCodes.Pop); - - Context.EmitLoadState(Context.CurrBlock.Next); - } - else - { - Context.EmitLdc_I8(Op.Imm); - - Context.Emit(OpCodes.Ret); - } - } - - public static void Blr(AILEmitterCtx Context) - { - AOpCodeBReg Op = (AOpCodeBReg)Context.CurrOp; - - if (AOptimizations.GenerateCallStack) - { - Context.EmitLdarg(ATranslatedSub.StateArgIdx); - Context.EmitLdintzr(Op.Rn); - - Context.EmitPrivateCall(typeof(AThreadState), nameof(AThreadState.EnterMethod)); - } - - Context.EmitLdc_I(Op.Position + 4); - Context.EmitStint(AThreadState.LRIndex); - Context.EmitStoreState(); - Context.EmitLdintzr(Op.Rn); - - Context.Emit(OpCodes.Ret); - } - - public static void Br(AILEmitterCtx Context) - { - AOpCodeBReg Op = (AOpCodeBReg)Context.CurrOp; - - if (AOptimizations.GenerateCallStack) - { - Context.EmitLdarg(ATranslatedSub.StateArgIdx); - Context.EmitLdintzr(Op.Rn); - - Context.EmitPrivateCall(typeof(AThreadState), nameof(AThreadState.JumpMethod)); - } - - Context.EmitStoreState(); - Context.EmitLdintzr(Op.Rn); - - Context.Emit(OpCodes.Ret); - } - - public static void Cbnz(AILEmitterCtx Context) => EmitCb(Context, OpCodes.Bne_Un); - public static void Cbz(AILEmitterCtx Context) => EmitCb(Context, OpCodes.Beq); - - private static void EmitCb(AILEmitterCtx Context, OpCode ILOp) - { - AOpCodeBImmCmp Op = (AOpCodeBImmCmp)Context.CurrOp; - - Context.EmitLdintzr(Op.Rt); - Context.EmitLdc_I(0); - - EmitBranch(Context, ILOp); - } - - public static void Ret(AILEmitterCtx Context) - { - if (AOptimizations.GenerateCallStack) - { - Context.EmitLdarg(ATranslatedSub.StateArgIdx); - - Context.EmitPrivateCall(typeof(AThreadState), nameof(AThreadState.ExitMethod)); - } - - Context.EmitStoreState(); - Context.EmitLdint(AThreadState.LRIndex); - - Context.Emit(OpCodes.Ret); - } - - public static void Tbnz(AILEmitterCtx Context) => EmitTb(Context, OpCodes.Bne_Un); - public static void Tbz(AILEmitterCtx Context) => EmitTb(Context, OpCodes.Beq); - - private static void EmitTb(AILEmitterCtx Context, OpCode ILOp) - { - AOpCodeBImmTest Op = (AOpCodeBImmTest)Context.CurrOp; - - Context.EmitLdintzr(Op.Rt); - Context.EmitLdc_I(1L << Op.Pos); - - Context.Emit(OpCodes.And); - - Context.EmitLdc_I(0); - - EmitBranch(Context, ILOp); - } - - private static void EmitBranch(AILEmitterCtx Context, ACond Cond) - { - AOpCodeBImm Op = (AOpCodeBImm)Context.CurrOp; - - if (Context.CurrBlock.Next != null && - Context.CurrBlock.Branch != null) - { - Context.EmitCondBranch(Context.GetLabel(Op.Imm), Cond); - } - else - { - Context.EmitStoreState(); - - AILLabel LblTaken = new AILLabel(); - - Context.EmitCondBranch(LblTaken, Cond); - - Context.EmitLdc_I8(Op.Position + 4); - - Context.Emit(OpCodes.Ret); - - Context.MarkLabel(LblTaken); - - Context.EmitLdc_I8(Op.Imm); - - Context.Emit(OpCodes.Ret); - } - } - - private static void EmitBranch(AILEmitterCtx Context, OpCode ILOp) - { - AOpCodeBImm Op = (AOpCodeBImm)Context.CurrOp; - - if (Context.CurrBlock.Next != null && - Context.CurrBlock.Branch != null) - { - Context.Emit(ILOp, Context.GetLabel(Op.Imm)); - } - else - { - Context.EmitStoreState(); - - AILLabel LblTaken = new AILLabel(); - - Context.Emit(ILOp, LblTaken); - - Context.EmitLdc_I8(Op.Position + 4); - - Context.Emit(OpCodes.Ret); - - Context.MarkLabel(LblTaken); - - Context.EmitLdc_I8(Op.Imm); - - Context.Emit(OpCodes.Ret); - } - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Instruction/AInstEmitHash.cs b/ChocolArm64/Instruction/AInstEmitHash.cs deleted file mode 100644 index 69bdbc480d..0000000000 --- a/ChocolArm64/Instruction/AInstEmitHash.cs +++ /dev/null @@ -1,115 +0,0 @@ -using ChocolArm64.Decoder; -using ChocolArm64.State; -using ChocolArm64.Translation; -using System; -using System.Reflection.Emit; -using System.Runtime.Intrinsics.X86; - -namespace ChocolArm64.Instruction -{ - static partial class AInstEmit - { - public static void Crc32b(AILEmitterCtx Context) - { - EmitCrc32(Context, nameof(ASoftFallback.Crc32b)); - } - - public static void Crc32h(AILEmitterCtx Context) - { - EmitCrc32(Context, nameof(ASoftFallback.Crc32h)); - } - - public static void Crc32w(AILEmitterCtx Context) - { - EmitCrc32(Context, nameof(ASoftFallback.Crc32w)); - } - - public static void Crc32x(AILEmitterCtx Context) - { - EmitCrc32(Context, nameof(ASoftFallback.Crc32x)); - } - - public static void Crc32cb(AILEmitterCtx Context) - { - if (AOptimizations.UseSse42) - { - EmitSse42Crc32(Context, typeof(uint), typeof(byte)); - } - else - { - EmitCrc32(Context, nameof(ASoftFallback.Crc32cb)); - } - } - - public static void Crc32ch(AILEmitterCtx Context) - { - if (AOptimizations.UseSse42) - { - EmitSse42Crc32(Context, typeof(uint), typeof(ushort)); - } - else - { - EmitCrc32(Context, nameof(ASoftFallback.Crc32ch)); - } - } - - public static void Crc32cw(AILEmitterCtx Context) - { - if (AOptimizations.UseSse42) - { - EmitSse42Crc32(Context, typeof(uint), typeof(uint)); - } - else - { - EmitCrc32(Context, nameof(ASoftFallback.Crc32cw)); - } - } - - public static void Crc32cx(AILEmitterCtx Context) - { - if (AOptimizations.UseSse42) - { - EmitSse42Crc32(Context, typeof(ulong), typeof(ulong)); - } - else - { - EmitCrc32(Context, nameof(ASoftFallback.Crc32cx)); - } - } - - private static void EmitSse42Crc32(AILEmitterCtx Context, Type TCrc, Type TData) - { - AOpCodeAluRs Op = (AOpCodeAluRs)Context.CurrOp; - - Context.EmitLdintzr(Op.Rn); - Context.EmitLdintzr(Op.Rm); - - Context.EmitCall(typeof(Sse42).GetMethod(nameof(Sse42.Crc32), new Type[] { TCrc, TData })); - - Context.EmitStintzr(Op.Rd); - } - - private static void EmitCrc32(AILEmitterCtx Context, string Name) - { - AOpCodeAluRs Op = (AOpCodeAluRs)Context.CurrOp; - - Context.EmitLdintzr(Op.Rn); - - if (Op.RegisterSize != ARegisterSize.Int32) - { - Context.Emit(OpCodes.Conv_U4); - } - - Context.EmitLdintzr(Op.Rm); - - ASoftFallback.EmitCall(Context, Name); - - if (Op.RegisterSize != ARegisterSize.Int32) - { - Context.Emit(OpCodes.Conv_U8); - } - - Context.EmitStintzr(Op.Rd); - } - } -} diff --git a/ChocolArm64/Instruction/AInstEmitMemory.cs b/ChocolArm64/Instruction/AInstEmitMemory.cs deleted file mode 100644 index 67653ed06c..0000000000 --- a/ChocolArm64/Instruction/AInstEmitMemory.cs +++ /dev/null @@ -1,252 +0,0 @@ -using ChocolArm64.Decoder; -using ChocolArm64.Translation; -using System.Reflection.Emit; - -using static ChocolArm64.Instruction.AInstEmitMemoryHelper; - -namespace ChocolArm64.Instruction -{ - static partial class AInstEmit - { - public static void Adr(AILEmitterCtx Context) - { - AOpCodeAdr Op = (AOpCodeAdr)Context.CurrOp; - - Context.EmitLdc_I(Op.Position + Op.Imm); - Context.EmitStintzr(Op.Rd); - } - - public static void Adrp(AILEmitterCtx Context) - { - AOpCodeAdr Op = (AOpCodeAdr)Context.CurrOp; - - Context.EmitLdc_I((Op.Position & ~0xfffL) + (Op.Imm << 12)); - Context.EmitStintzr(Op.Rd); - } - - public static void Ldr(AILEmitterCtx Context) => EmitLdr(Context, false); - public static void Ldrs(AILEmitterCtx Context) => EmitLdr(Context, true); - - private static void EmitLdr(AILEmitterCtx Context, bool Signed) - { - AOpCodeMem Op = (AOpCodeMem)Context.CurrOp; - - Context.EmitLdarg(ATranslatedSub.MemoryArgIdx); - - EmitLoadAddress(Context); - - if (Signed && Op.Extend64) - { - EmitReadSx64Call(Context, Op.Size); - } - else if (Signed) - { - EmitReadSx32Call(Context, Op.Size); - } - else - { - EmitReadZxCall(Context, Op.Size); - } - - if (Op is IAOpCodeSimd) - { - Context.EmitStvec(Op.Rt); - } - else - { - Context.EmitStintzr(Op.Rt); - } - - EmitWBackIfNeeded(Context); - } - - public static void LdrLit(AILEmitterCtx Context) - { - IAOpCodeLit Op = (IAOpCodeLit)Context.CurrOp; - - if (Op.Prefetch) - { - return; - } - - Context.EmitLdarg(ATranslatedSub.MemoryArgIdx); - Context.EmitLdc_I8(Op.Imm); - - if (Op.Signed) - { - EmitReadSx64Call(Context, Op.Size); - } - else - { - EmitReadZxCall(Context, Op.Size); - } - - if (Op is IAOpCodeSimd) - { - Context.EmitStvec(Op.Rt); - } - else - { - Context.EmitStint(Op.Rt); - } - } - - public static void Ldp(AILEmitterCtx Context) - { - AOpCodeMemPair Op = (AOpCodeMemPair)Context.CurrOp; - - void EmitReadAndStore(int Rt) - { - if (Op.Extend64) - { - EmitReadSx64Call(Context, Op.Size); - } - else - { - EmitReadZxCall(Context, Op.Size); - } - - if (Op is IAOpCodeSimd) - { - Context.EmitStvec(Rt); - } - else - { - Context.EmitStintzr(Rt); - } - } - - Context.EmitLdarg(ATranslatedSub.MemoryArgIdx); - - EmitLoadAddress(Context); - - EmitReadAndStore(Op.Rt); - - Context.EmitLdarg(ATranslatedSub.MemoryArgIdx); - Context.EmitLdtmp(); - Context.EmitLdc_I8(1 << Op.Size); - - Context.Emit(OpCodes.Add); - - EmitReadAndStore(Op.Rt2); - - EmitWBackIfNeeded(Context); - } - - public static void Str(AILEmitterCtx Context) - { - AOpCodeMem Op = (AOpCodeMem)Context.CurrOp; - - Context.EmitLdarg(ATranslatedSub.MemoryArgIdx); - - EmitLoadAddress(Context); - - if (Op is IAOpCodeSimd) - { - Context.EmitLdvec(Op.Rt); - } - else - { - Context.EmitLdintzr(Op.Rt); - } - - EmitWriteCall(Context, Op.Size); - - EmitWBackIfNeeded(Context); - } - - public static void Stp(AILEmitterCtx Context) - { - AOpCodeMemPair Op = (AOpCodeMemPair)Context.CurrOp; - - Context.EmitLdarg(ATranslatedSub.MemoryArgIdx); - - EmitLoadAddress(Context); - - if (Op is IAOpCodeSimd) - { - Context.EmitLdvec(Op.Rt); - } - else - { - Context.EmitLdintzr(Op.Rt); - } - - EmitWriteCall(Context, Op.Size); - - Context.EmitLdarg(ATranslatedSub.MemoryArgIdx); - Context.EmitLdtmp(); - Context.EmitLdc_I8(1 << Op.Size); - - Context.Emit(OpCodes.Add); - - if (Op is IAOpCodeSimd) - { - Context.EmitLdvec(Op.Rt2); - } - else - { - Context.EmitLdintzr(Op.Rt2); - } - - EmitWriteCall(Context, Op.Size); - - EmitWBackIfNeeded(Context); - } - - private static void EmitLoadAddress(AILEmitterCtx Context) - { - switch (Context.CurrOp) - { - case AOpCodeMemImm Op: - Context.EmitLdint(Op.Rn); - - if (!Op.PostIdx) - { - //Pre-indexing. - Context.EmitLdc_I(Op.Imm); - - Context.Emit(OpCodes.Add); - } - break; - - case AOpCodeMemReg Op: - Context.EmitLdint(Op.Rn); - Context.EmitLdintzr(Op.Rm); - Context.EmitCast(Op.IntType); - - if (Op.Shift) - { - Context.EmitLsl(Op.Size); - } - - Context.Emit(OpCodes.Add); - break; - } - - //Save address to Scratch var since the register value may change. - Context.Emit(OpCodes.Dup); - - Context.EmitSttmp(); - } - - private static void EmitWBackIfNeeded(AILEmitterCtx Context) - { - //Check whenever the current OpCode has post-indexed write back, if so write it. - //Note: AOpCodeMemPair inherits from AOpCodeMemImm, so this works for both. - if (Context.CurrOp is AOpCodeMemImm Op && Op.WBack) - { - Context.EmitLdtmp(); - - if (Op.PostIdx) - { - Context.EmitLdc_I(Op.Imm); - - Context.Emit(OpCodes.Add); - } - - Context.EmitStint(Op.Rn); - } - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Instruction/AInstEmitMemoryEx.cs b/ChocolArm64/Instruction/AInstEmitMemoryEx.cs deleted file mode 100644 index ba8eeceb7b..0000000000 --- a/ChocolArm64/Instruction/AInstEmitMemoryEx.cs +++ /dev/null @@ -1,180 +0,0 @@ -using ChocolArm64.Decoder; -using ChocolArm64.Memory; -using ChocolArm64.Translation; -using System; -using System.Reflection.Emit; -using System.Threading; - -using static ChocolArm64.Instruction.AInstEmitMemoryHelper; - -namespace ChocolArm64.Instruction -{ - static partial class AInstEmit - { - [Flags] - private enum AccessType - { - None = 0, - Ordered = 1, - Exclusive = 2, - OrderedEx = Ordered | Exclusive - } - - public static void Clrex(AILEmitterCtx Context) - { - EmitMemoryCall(Context, nameof(AMemory.ClearExclusive)); - } - - public static void Dmb(AILEmitterCtx Context) => EmitBarrier(Context); - public static void Dsb(AILEmitterCtx Context) => EmitBarrier(Context); - - public static void Ldar(AILEmitterCtx Context) => EmitLdr(Context, AccessType.Ordered); - public static void Ldaxr(AILEmitterCtx Context) => EmitLdr(Context, AccessType.OrderedEx); - public static void Ldxr(AILEmitterCtx Context) => EmitLdr(Context, AccessType.Exclusive); - public static void Ldxp(AILEmitterCtx Context) => EmitLdp(Context, AccessType.Exclusive); - public static void Ldaxp(AILEmitterCtx Context) => EmitLdp(Context, AccessType.OrderedEx); - - private static void EmitLdr(AILEmitterCtx Context, AccessType AccType) - { - EmitLoad(Context, AccType, false); - } - - private static void EmitLdp(AILEmitterCtx Context, AccessType AccType) - { - EmitLoad(Context, AccType, true); - } - - private static void EmitLoad(AILEmitterCtx Context, AccessType AccType, bool Pair) - { - AOpCodeMemEx Op = (AOpCodeMemEx)Context.CurrOp; - - if (AccType.HasFlag(AccessType.Ordered)) - { - EmitBarrier(Context); - } - - if (AccType.HasFlag(AccessType.Exclusive)) - { - EmitMemoryCall(Context, nameof(AMemory.SetExclusive), Op.Rn); - } - - Context.EmitLdarg(ATranslatedSub.MemoryArgIdx); - Context.EmitLdint(Op.Rn); - - EmitReadZxCall(Context, Op.Size); - - Context.EmitStintzr(Op.Rt); - - if (Pair) - { - Context.EmitLdarg(ATranslatedSub.MemoryArgIdx); - Context.EmitLdint(Op.Rn); - Context.EmitLdc_I(8 << Op.Size); - - Context.Emit(OpCodes.Add); - - EmitReadZxCall(Context, Op.Size); - - Context.EmitStintzr(Op.Rt2); - } - } - - public static void Pfrm(AILEmitterCtx Context) - { - //Memory Prefetch, execute as no-op. - } - - public static void Stlr(AILEmitterCtx Context) => EmitStr(Context, AccessType.Ordered); - public static void Stlxr(AILEmitterCtx Context) => EmitStr(Context, AccessType.OrderedEx); - public static void Stxr(AILEmitterCtx Context) => EmitStr(Context, AccessType.Exclusive); - public static void Stxp(AILEmitterCtx Context) => EmitStp(Context, AccessType.Exclusive); - public static void Stlxp(AILEmitterCtx Context) => EmitStp(Context, AccessType.OrderedEx); - - private static void EmitStr(AILEmitterCtx Context, AccessType AccType) - { - EmitStore(Context, AccType, false); - } - - private static void EmitStp(AILEmitterCtx Context, AccessType AccType) - { - EmitStore(Context, AccType, true); - } - - private static void EmitStore(AILEmitterCtx Context, AccessType AccType, bool Pair) - { - AOpCodeMemEx Op = (AOpCodeMemEx)Context.CurrOp; - - if (AccType.HasFlag(AccessType.Ordered)) - { - EmitBarrier(Context); - } - - AILLabel LblEx = new AILLabel(); - AILLabel LblEnd = new AILLabel(); - - if (AccType.HasFlag(AccessType.Exclusive)) - { - EmitMemoryCall(Context, nameof(AMemory.TestExclusive), Op.Rn); - - Context.Emit(OpCodes.Brtrue_S, LblEx); - - Context.EmitLdc_I8(1); - Context.EmitStintzr(Op.Rs); - - Context.Emit(OpCodes.Br_S, LblEnd); - } - - Context.MarkLabel(LblEx); - - Context.EmitLdarg(ATranslatedSub.MemoryArgIdx); - Context.EmitLdint(Op.Rn); - Context.EmitLdintzr(Op.Rt); - - EmitWriteCall(Context, Op.Size); - - if (Pair) - { - Context.EmitLdarg(ATranslatedSub.MemoryArgIdx); - Context.EmitLdint(Op.Rn); - Context.EmitLdc_I(8 << Op.Size); - - Context.Emit(OpCodes.Add); - - Context.EmitLdintzr(Op.Rt2); - - EmitWriteCall(Context, Op.Size); - } - - if (AccType.HasFlag(AccessType.Exclusive)) - { - Context.EmitLdc_I8(0); - Context.EmitStintzr(Op.Rs); - - EmitMemoryCall(Context, nameof(AMemory.ClearExclusiveForStore)); - } - - Context.MarkLabel(LblEnd); - } - - private static void EmitMemoryCall(AILEmitterCtx Context, string Name, int Rn = -1) - { - Context.EmitLdarg(ATranslatedSub.MemoryArgIdx); - Context.EmitLdarg(ATranslatedSub.StateArgIdx); - - if (Rn != -1) - { - Context.EmitLdint(Rn); - } - - Context.EmitCall(typeof(AMemory), Name); - } - - private static void EmitBarrier(AILEmitterCtx 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. - Context.EmitCall(typeof(Thread), nameof(Thread.MemoryBarrier)); - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Instruction/AInstEmitMemoryHelper.cs b/ChocolArm64/Instruction/AInstEmitMemoryHelper.cs deleted file mode 100644 index df091bd514..0000000000 --- a/ChocolArm64/Instruction/AInstEmitMemoryHelper.cs +++ /dev/null @@ -1,188 +0,0 @@ -using ChocolArm64.Decoder; -using ChocolArm64.Memory; -using ChocolArm64.Translation; -using System; -using System.Reflection.Emit; - -namespace ChocolArm64.Instruction -{ - static class AInstEmitMemoryHelper - { - private enum Extension - { - Zx, - Sx32, - Sx64 - } - - public static void EmitReadZxCall(AILEmitterCtx Context, int Size) - { - EmitReadCall(Context, Extension.Zx, Size); - } - - public static void EmitReadSx32Call(AILEmitterCtx Context, int Size) - { - EmitReadCall(Context, Extension.Sx32, Size); - } - - public static void EmitReadSx64Call(AILEmitterCtx Context, int Size) - { - EmitReadCall(Context, Extension.Sx64, Size); - } - - private static void EmitReadCall(AILEmitterCtx Context, Extension Ext, int Size) - { - bool IsSimd = GetIsSimd(Context); - - string Name = null; - - if (Size < 0 || Size > (IsSimd ? 4 : 3)) - { - throw new ArgumentOutOfRangeException(nameof(Size)); - } - - if (IsSimd) - { - switch (Size) - { - case 0: Name = AOptimizations.DisableMemoryChecks - ? nameof(AMemory.ReadVector8Unchecked) - : nameof(AMemory.ReadVector8); break; - - case 1: Name = AOptimizations.DisableMemoryChecks - ? nameof(AMemory.ReadVector16Unchecked) - : nameof(AMemory.ReadVector16); break; - - case 2: Name = AOptimizations.DisableMemoryChecks - ? nameof(AMemory.ReadVector32Unchecked) - : nameof(AMemory.ReadVector32); break; - - case 3: Name = AOptimizations.DisableMemoryChecks - ? nameof(AMemory.ReadVector64Unchecked) - : nameof(AMemory.ReadVector64); break; - - case 4: Name = AOptimizations.DisableMemoryChecks - ? nameof(AMemory.ReadVector128Unchecked) - : nameof(AMemory.ReadVector128); break; - } - } - else - { - switch (Size) - { - case 0: Name = AOptimizations.DisableMemoryChecks - ? nameof(AMemory.ReadByteUnchecked) - : nameof(AMemory.ReadByte); break; - - case 1: Name = AOptimizations.DisableMemoryChecks - ? nameof(AMemory.ReadUInt16Unchecked) - : nameof(AMemory.ReadUInt16); break; - - case 2: Name = AOptimizations.DisableMemoryChecks - ? nameof(AMemory.ReadUInt32Unchecked) - : nameof(AMemory.ReadUInt32); break; - - case 3: Name = AOptimizations.DisableMemoryChecks - ? nameof(AMemory.ReadUInt64Unchecked) - : nameof(AMemory.ReadUInt64); break; - } - } - - Context.EmitCall(typeof(AMemory), Name); - - if (!IsSimd) - { - if (Ext == Extension.Sx32 || - Ext == Extension.Sx64) - { - switch (Size) - { - case 0: Context.Emit(OpCodes.Conv_I1); break; - case 1: Context.Emit(OpCodes.Conv_I2); break; - case 2: Context.Emit(OpCodes.Conv_I4); break; - } - } - - if (Size < 3) - { - Context.Emit(Ext == Extension.Sx64 - ? OpCodes.Conv_I8 - : OpCodes.Conv_U8); - } - } - } - - public static void EmitWriteCall(AILEmitterCtx Context, int Size) - { - bool IsSimd = GetIsSimd(Context); - - string Name = null; - - if (Size < 0 || Size > (IsSimd ? 4 : 3)) - { - throw new ArgumentOutOfRangeException(nameof(Size)); - } - - if (Size < 3 && !IsSimd) - { - Context.Emit(OpCodes.Conv_I4); - } - - if (IsSimd) - { - switch (Size) - { - case 0: Name = AOptimizations.DisableMemoryChecks - ? nameof(AMemory.WriteVector8Unchecked) - : nameof(AMemory.WriteVector8); break; - - case 1: Name = AOptimizations.DisableMemoryChecks - ? nameof(AMemory.WriteVector16Unchecked) - : nameof(AMemory.WriteVector16); break; - - case 2: Name = AOptimizations.DisableMemoryChecks - ? nameof(AMemory.WriteVector32Unchecked) - : nameof(AMemory.WriteVector32); break; - - case 3: Name = AOptimizations.DisableMemoryChecks - ? nameof(AMemory.WriteVector64Unchecked) - : nameof(AMemory.WriteVector64); break; - - case 4: Name = AOptimizations.DisableMemoryChecks - ? nameof(AMemory.WriteVector128Unchecked) - : nameof(AMemory.WriteVector128); break; - } - } - else - { - switch (Size) - { - case 0: Name = AOptimizations.DisableMemoryChecks - ? nameof(AMemory.WriteByteUnchecked) - : nameof(AMemory.WriteByte); break; - - case 1: Name = AOptimizations.DisableMemoryChecks - ? nameof(AMemory.WriteUInt16Unchecked) - : nameof(AMemory.WriteUInt16); break; - - case 2: Name = AOptimizations.DisableMemoryChecks - ? nameof(AMemory.WriteUInt32Unchecked) - : nameof(AMemory.WriteUInt32); break; - - case 3: Name = AOptimizations.DisableMemoryChecks - ? nameof(AMemory.WriteUInt64Unchecked) - : nameof(AMemory.WriteUInt64); break; - } - } - - Context.EmitCall(typeof(AMemory), Name); - } - - private static bool GetIsSimd(AILEmitterCtx Context) - { - return Context.CurrOp is IAOpCodeSimd && - !(Context.CurrOp is AOpCodeSimdMemMs || - Context.CurrOp is AOpCodeSimdMemSs); - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Instruction/AInstEmitMove.cs b/ChocolArm64/Instruction/AInstEmitMove.cs deleted file mode 100644 index 719b53d5d5..0000000000 --- a/ChocolArm64/Instruction/AInstEmitMove.cs +++ /dev/null @@ -1,41 +0,0 @@ -using ChocolArm64.Decoder; -using ChocolArm64.Translation; -using System.Reflection.Emit; - -namespace ChocolArm64.Instruction -{ - static partial class AInstEmit - { - public static void Movk(AILEmitterCtx Context) - { - AOpCodeMov Op = (AOpCodeMov)Context.CurrOp; - - Context.EmitLdintzr(Op.Rd); - Context.EmitLdc_I(~(0xffffL << Op.Pos)); - - Context.Emit(OpCodes.And); - - Context.EmitLdc_I(Op.Imm); - - Context.Emit(OpCodes.Or); - - Context.EmitStintzr(Op.Rd); - } - - public static void Movn(AILEmitterCtx Context) - { - AOpCodeMov Op = (AOpCodeMov)Context.CurrOp; - - Context.EmitLdc_I(~Op.Imm); - Context.EmitStintzr(Op.Rd); - } - - public static void Movz(AILEmitterCtx Context) - { - AOpCodeMov Op = (AOpCodeMov)Context.CurrOp; - - Context.EmitLdc_I(Op.Imm); - Context.EmitStintzr(Op.Rd); - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Instruction/AInstEmitMul.cs b/ChocolArm64/Instruction/AInstEmitMul.cs deleted file mode 100644 index 3713c81f6c..0000000000 --- a/ChocolArm64/Instruction/AInstEmitMul.cs +++ /dev/null @@ -1,80 +0,0 @@ -using ChocolArm64.Decoder; -using ChocolArm64.Translation; -using System.Reflection.Emit; - -namespace ChocolArm64.Instruction -{ - static partial class AInstEmit - { - public static void Madd(AILEmitterCtx Context) => EmitMul(Context, OpCodes.Add); - public static void Msub(AILEmitterCtx Context) => EmitMul(Context, OpCodes.Sub); - - private static void EmitMul(AILEmitterCtx Context, OpCode ILOp) - { - AOpCodeMul Op = (AOpCodeMul)Context.CurrOp; - - Context.EmitLdintzr(Op.Ra); - Context.EmitLdintzr(Op.Rn); - Context.EmitLdintzr(Op.Rm); - - Context.Emit(OpCodes.Mul); - Context.Emit(ILOp); - - Context.EmitStintzr(Op.Rd); - } - - public static void Smaddl(AILEmitterCtx Context) => EmitMull(Context, OpCodes.Add, true); - public static void Smsubl(AILEmitterCtx Context) => EmitMull(Context, OpCodes.Sub, true); - public static void Umaddl(AILEmitterCtx Context) => EmitMull(Context, OpCodes.Add, false); - public static void Umsubl(AILEmitterCtx Context) => EmitMull(Context, OpCodes.Sub, false); - - private static void EmitMull(AILEmitterCtx Context, OpCode AddSubOp, bool Signed) - { - AOpCodeMul Op = (AOpCodeMul)Context.CurrOp; - - OpCode CastOp = Signed - ? OpCodes.Conv_I8 - : OpCodes.Conv_U8; - - Context.EmitLdintzr(Op.Ra); - Context.EmitLdintzr(Op.Rn); - - Context.Emit(OpCodes.Conv_I4); - Context.Emit(CastOp); - - Context.EmitLdintzr(Op.Rm); - - Context.Emit(OpCodes.Conv_I4); - Context.Emit(CastOp); - Context.Emit(OpCodes.Mul); - - Context.Emit(AddSubOp); - - Context.EmitStintzr(Op.Rd); - } - - public static void Smulh(AILEmitterCtx Context) - { - AOpCodeMul Op = (AOpCodeMul)Context.CurrOp; - - Context.EmitLdintzr(Op.Rn); - Context.EmitLdintzr(Op.Rm); - - ASoftFallback.EmitCall(Context, nameof(ASoftFallback.SMulHi128)); - - Context.EmitStintzr(Op.Rd); - } - - public static void Umulh(AILEmitterCtx Context) - { - AOpCodeMul Op = (AOpCodeMul)Context.CurrOp; - - Context.EmitLdintzr(Op.Rn); - Context.EmitLdintzr(Op.Rm); - - ASoftFallback.EmitCall(Context, nameof(ASoftFallback.UMulHi128)); - - Context.EmitStintzr(Op.Rd); - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Instruction/AInstEmitSimdArithmetic.cs b/ChocolArm64/Instruction/AInstEmitSimdArithmetic.cs deleted file mode 100644 index 39331f965c..0000000000 --- a/ChocolArm64/Instruction/AInstEmitSimdArithmetic.cs +++ /dev/null @@ -1,1283 +0,0 @@ -using ChocolArm64.Decoder; -using ChocolArm64.State; -using ChocolArm64.Translation; -using System; -using System.Reflection; -using System.Reflection.Emit; -using System.Runtime.Intrinsics.X86; - -using static ChocolArm64.Instruction.AInstEmitSimdHelper; - -namespace ChocolArm64.Instruction -{ - static partial class AInstEmit - { - public static void Abs_S(AILEmitterCtx Context) - { - EmitScalarUnaryOpSx(Context, () => EmitAbs(Context)); - } - - public static void Abs_V(AILEmitterCtx Context) - { - EmitVectorUnaryOpSx(Context, () => EmitAbs(Context)); - } - - public static void Add_S(AILEmitterCtx Context) - { - EmitScalarBinaryOpZx(Context, () => Context.Emit(OpCodes.Add)); - } - - public static void Add_V(AILEmitterCtx Context) - { - if (AOptimizations.UseSse2) - { - EmitSse2Call(Context, nameof(Sse2.Add)); - } - else - { - EmitVectorBinaryOpZx(Context, () => Context.Emit(OpCodes.Add)); - } - } - - public static void Addhn_V(AILEmitterCtx Context) - { - EmitHighNarrow(Context, () => Context.Emit(OpCodes.Add), Round: false); - } - - public static void Addp_S(AILEmitterCtx Context) - { - AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; - - EmitVectorExtractZx(Context, Op.Rn, 0, Op.Size); - EmitVectorExtractZx(Context, Op.Rn, 1, Op.Size); - - Context.Emit(OpCodes.Add); - - EmitScalarSet(Context, Op.Rd, Op.Size); - } - - public static void Addp_V(AILEmitterCtx Context) - { - EmitVectorPairwiseOpZx(Context, () => Context.Emit(OpCodes.Add)); - } - - public static void Addv_V(AILEmitterCtx Context) - { - AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; - - int Bytes = Context.CurrOp.GetBitsCount() >> 3; - - EmitVectorExtractZx(Context, Op.Rn, 0, Op.Size); - - for (int Index = 1; Index < (Bytes >> Op.Size); Index++) - { - EmitVectorExtractZx(Context, Op.Rn, Index, Op.Size); - - Context.Emit(OpCodes.Add); - } - - EmitScalarSet(Context, Op.Rd, Op.Size); - } - - public static void Cls_V(AILEmitterCtx Context) - { - MethodInfo MthdInfo = typeof(ASoftFallback).GetMethod(nameof(ASoftFallback.CountLeadingSigns)); - - EmitCountLeadingBits(Context, () => Context.EmitCall(MthdInfo)); - } - - public static void Clz_V(AILEmitterCtx Context) - { - MethodInfo MthdInfo = typeof(ASoftFallback).GetMethod(nameof(ASoftFallback.CountLeadingZeros)); - - EmitCountLeadingBits(Context, () => Context.EmitCall(MthdInfo)); - } - - private static void EmitCountLeadingBits(AILEmitterCtx Context, Action Emit) - { - AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; - - int Bytes = Context.CurrOp.GetBitsCount() >> 3; - - for (int Index = 0; Index < (Bytes >> Op.Size); Index++) - { - EmitVectorExtractZx(Context, Op.Rn, Index, Op.Size); - - Context.EmitLdc_I4(8 << Op.Size); - - Emit(); - - EmitVectorInsert(Context, Op.Rd, Index, Op.Size); - } - - if (Op.RegisterSize == ARegisterSize.SIMD64) - { - EmitVectorZeroUpper(Context, Op.Rd); - } - } - - public static void Cnt_V(AILEmitterCtx Context) - { - AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; - - int Elems = Op.RegisterSize == ARegisterSize.SIMD128 ? 16 : 8; - - for (int Index = 0; Index < Elems; Index++) - { - EmitVectorExtractZx(Context, Op.Rn, Index, 0); - - Context.Emit(OpCodes.Conv_U4); - - ASoftFallback.EmitCall(Context, nameof(ASoftFallback.CountSetBits8)); - - Context.Emit(OpCodes.Conv_U8); - - EmitVectorInsert(Context, Op.Rd, Index, 0); - } - - if (Op.RegisterSize == ARegisterSize.SIMD64) - { - EmitVectorZeroUpper(Context, Op.Rd); - } - } - - private static void EmitAbs(AILEmitterCtx Context) - { - AILLabel LblTrue = new AILLabel(); - - Context.Emit(OpCodes.Dup); - Context.Emit(OpCodes.Ldc_I4_0); - Context.Emit(OpCodes.Bge_S, LblTrue); - - Context.Emit(OpCodes.Neg); - - Context.MarkLabel(LblTrue); - } - - private static void EmitHighNarrow(AILEmitterCtx Context, Action Emit, bool Round) - { - AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; - - int Elems = 8 >> Op.Size; - int ESize = 8 << Op.Size; - - int Part = Op.RegisterSize == ARegisterSize.SIMD128 ? Elems : 0; - - long RoundConst = 1L << (ESize - 1); - - for (int Index = 0; Index < Elems; Index++) - { - EmitVectorExtractZx(Context, Op.Rn, Index, Op.Size + 1); - EmitVectorExtractZx(Context, Op.Rm, Index, Op.Size + 1); - - Emit(); - - if (Round) - { - Context.EmitLdc_I8(RoundConst); - - Context.Emit(OpCodes.Add); - } - - Context.EmitLsr(ESize); - - EmitVectorInsert(Context, Op.Rd, Part + Index, Op.Size); - } - - if (Part == 0) - { - EmitVectorZeroUpper(Context, Op.Rd); - } - } - - private static void EmitSaturatingExtNarrow(AILEmitterCtx Context, bool SignedSrc, bool SignedDst, bool Scalar) - { - AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; - - int Elems = (!Scalar ? 8 >> Op.Size : 1); - int ESize = 8 << Op.Size; - - int Part = (!Scalar & (Op.RegisterSize == ARegisterSize.SIMD128) ? Elems : 0); - - int TMaxValue = (SignedDst ? (1 << (ESize - 1)) - 1 : (int)((1L << ESize) - 1L)); - int TMinValue = (SignedDst ? -((1 << (ESize - 1))) : 0); - - Context.EmitLdc_I8(0L); - Context.EmitSttmp(); - - for (int Index = 0; Index < Elems; Index++) - { - AILLabel LblLe = new AILLabel(); - AILLabel LblGeEnd = new AILLabel(); - - EmitVectorExtract(Context, Op.Rn, Index, Op.Size + 1, SignedSrc); - - Context.Emit(OpCodes.Dup); - - Context.EmitLdc_I4(TMaxValue); - Context.Emit(OpCodes.Conv_U8); - - Context.Emit(SignedSrc ? OpCodes.Ble_S : OpCodes.Ble_Un_S, LblLe); - - Context.Emit(OpCodes.Pop); - - Context.EmitLdc_I4(TMaxValue); - - Context.EmitLdc_I8(0x8000000L); - Context.EmitSttmp(); - - Context.Emit(OpCodes.Br_S, LblGeEnd); - - Context.MarkLabel(LblLe); - - Context.Emit(OpCodes.Dup); - - Context.EmitLdc_I4(TMinValue); - Context.Emit(OpCodes.Conv_I8); - - Context.Emit(SignedSrc ? OpCodes.Bge_S : OpCodes.Bge_Un_S, LblGeEnd); - - Context.Emit(OpCodes.Pop); - - Context.EmitLdc_I4(TMinValue); - - Context.EmitLdc_I8(0x8000000L); - Context.EmitSttmp(); - - Context.MarkLabel(LblGeEnd); - - if (Scalar) - { - EmitVectorZeroLower(Context, Op.Rd); - } - - EmitVectorInsert(Context, Op.Rd, Part + Index, Op.Size); - } - - if (Part == 0) - { - EmitVectorZeroUpper(Context, Op.Rd); - } - - Context.EmitLdarg(ATranslatedSub.StateArgIdx); - Context.EmitLdarg(ATranslatedSub.StateArgIdx); - Context.EmitCallPropGet(typeof(AThreadState), nameof(AThreadState.Fpsr)); - Context.EmitLdtmp(); - Context.Emit(OpCodes.Conv_I4); - Context.Emit(OpCodes.Or); - Context.EmitCallPropSet(typeof(AThreadState), nameof(AThreadState.Fpsr)); - } - - public static void Fabd_S(AILEmitterCtx Context) - { - EmitScalarBinaryOpF(Context, () => - { - Context.Emit(OpCodes.Sub); - - EmitUnaryMathCall(Context, nameof(Math.Abs)); - }); - } - - public static void Fabs_S(AILEmitterCtx Context) - { - EmitScalarUnaryOpF(Context, () => - { - EmitUnaryMathCall(Context, nameof(Math.Abs)); - }); - } - - public static void Fabs_V(AILEmitterCtx Context) - { - EmitVectorUnaryOpF(Context, () => - { - EmitUnaryMathCall(Context, nameof(Math.Abs)); - }); - } - - public static void Fadd_S(AILEmitterCtx Context) - { - if (AOptimizations.UseSse && AOptimizations.UseSse2) - { - EmitSseOrSse2CallF(Context, nameof(Sse.AddScalar)); - } - else - { - EmitScalarBinaryOpF(Context, () => Context.Emit(OpCodes.Add)); - } - } - - public static void Fadd_V(AILEmitterCtx Context) - { - if (AOptimizations.UseSse && AOptimizations.UseSse2) - { - EmitSseOrSse2CallF(Context, nameof(Sse.Add)); - } - else - { - EmitVectorBinaryOpF(Context, () => Context.Emit(OpCodes.Add)); - } - } - - public static void Faddp_S(AILEmitterCtx Context) - { - AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; - - int SizeF = Op.Size & 1; - - EmitVectorExtractF(Context, Op.Rn, 0, SizeF); - EmitVectorExtractF(Context, Op.Rn, 1, SizeF); - - Context.Emit(OpCodes.Add); - - EmitScalarSetF(Context, Op.Rd, SizeF); - } - - public static void Faddp_V(AILEmitterCtx Context) - { - AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; - - int SizeF = Op.Size & 1; - - int Bytes = Context.CurrOp.GetBitsCount() >> 3; - - int Elems = Bytes >> SizeF + 2; - int Half = Elems >> 1; - - for (int Index = 0; Index < Elems; Index++) - { - int Elem = (Index & (Half - 1)) << 1; - - EmitVectorExtractF(Context, Index < Half ? Op.Rn : Op.Rm, Elem + 0, SizeF); - EmitVectorExtractF(Context, Index < Half ? Op.Rn : Op.Rm, Elem + 1, SizeF); - - Context.Emit(OpCodes.Add); - - EmitVectorInsertTmpF(Context, Index, SizeF); - } - - Context.EmitLdvectmp(); - Context.EmitStvec(Op.Rd); - - if (Op.RegisterSize == ARegisterSize.SIMD64) - { - EmitVectorZeroUpper(Context, Op.Rd); - } - } - - public static void Fdiv_S(AILEmitterCtx Context) - { - if (AOptimizations.UseSse && AOptimizations.UseSse2) - { - EmitSseOrSse2CallF(Context, nameof(Sse.DivideScalar)); - } - else - { - EmitScalarBinaryOpF(Context, () => Context.Emit(OpCodes.Div)); - } - } - - public static void Fdiv_V(AILEmitterCtx Context) - { - if (AOptimizations.UseSse && AOptimizations.UseSse2) - { - EmitSseOrSse2CallF(Context, nameof(Sse.Divide)); - } - else - { - EmitVectorBinaryOpF(Context, () => Context.Emit(OpCodes.Div)); - } - } - - public static void Fmadd_S(AILEmitterCtx Context) - { - EmitScalarTernaryRaOpF(Context, () => - { - Context.Emit(OpCodes.Mul); - Context.Emit(OpCodes.Add); - }); - } - - public static void Fmax_S(AILEmitterCtx Context) - { - AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; - - EmitScalarBinaryOpF(Context, () => - { - if (Op.Size == 0) - { - AVectorHelper.EmitCall(Context, nameof(AVectorHelper.MaxF)); - } - else if (Op.Size == 1) - { - AVectorHelper.EmitCall(Context, nameof(AVectorHelper.Max)); - } - else - { - throw new InvalidOperationException(); - } - }); - } - - public static void Fmax_V(AILEmitterCtx Context) - { - AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; - - EmitVectorBinaryOpF(Context, () => - { - if (Op.Size == 0) - { - AVectorHelper.EmitCall(Context, nameof(AVectorHelper.MaxF)); - } - else if (Op.Size == 1) - { - AVectorHelper.EmitCall(Context, nameof(AVectorHelper.Max)); - } - else - { - throw new InvalidOperationException(); - } - }); - } - - public static void Fmin_S(AILEmitterCtx Context) - { - AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; - - EmitScalarBinaryOpF(Context, () => - { - if (Op.Size == 0) - { - AVectorHelper.EmitCall(Context, nameof(AVectorHelper.MinF)); - } - else if (Op.Size == 1) - { - AVectorHelper.EmitCall(Context, nameof(AVectorHelper.Min)); - } - else - { - throw new InvalidOperationException(); - } - }); - } - - public static void Fmin_V(AILEmitterCtx Context) - { - AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; - - int SizeF = Op.Size & 1; - - EmitVectorBinaryOpF(Context, () => - { - if (SizeF == 0) - { - AVectorHelper.EmitCall(Context, nameof(AVectorHelper.MinF)); - } - else if (SizeF == 1) - { - AVectorHelper.EmitCall(Context, nameof(AVectorHelper.Min)); - } - else - { - throw new InvalidOperationException(); - } - }); - } - - public static void Fmaxnm_S(AILEmitterCtx Context) - { - Fmax_S(Context); - } - - public static void Fminnm_S(AILEmitterCtx Context) - { - Fmin_S(Context); - } - - public static void Fmla_Se(AILEmitterCtx Context) - { - EmitScalarTernaryOpByElemF(Context, () => - { - Context.Emit(OpCodes.Mul); - Context.Emit(OpCodes.Add); - }); - } - - public static void Fmla_V(AILEmitterCtx Context) - { - EmitVectorTernaryOpF(Context, () => - { - Context.Emit(OpCodes.Mul); - Context.Emit(OpCodes.Add); - }); - } - - public static void Fmla_Ve(AILEmitterCtx Context) - { - EmitVectorTernaryOpByElemF(Context, () => - { - Context.Emit(OpCodes.Mul); - Context.Emit(OpCodes.Add); - }); - } - - public static void Fmls_V(AILEmitterCtx Context) - { - EmitVectorTernaryOpF(Context, () => - { - Context.Emit(OpCodes.Mul); - Context.Emit(OpCodes.Sub); - }); - } - - public static void Fmls_Ve(AILEmitterCtx Context) - { - EmitVectorTernaryOpByElemF(Context, () => - { - Context.Emit(OpCodes.Mul); - Context.Emit(OpCodes.Sub); - }); - } - - public static void Fmsub_S(AILEmitterCtx Context) - { - EmitScalarTernaryRaOpF(Context, () => - { - Context.Emit(OpCodes.Mul); - Context.Emit(OpCodes.Sub); - }); - } - - public static void Fmul_S(AILEmitterCtx Context) - { - if (AOptimizations.UseSse && AOptimizations.UseSse2) - { - EmitSseOrSse2CallF(Context, nameof(Sse.MultiplyScalar)); - } - else - { - EmitScalarBinaryOpF(Context, () => Context.Emit(OpCodes.Mul)); - } - } - - public static void Fmul_Se(AILEmitterCtx Context) - { - EmitScalarBinaryOpByElemF(Context, () => Context.Emit(OpCodes.Mul)); - } - - public static void Fmul_V(AILEmitterCtx Context) - { - if (AOptimizations.UseSse && AOptimizations.UseSse2) - { - EmitSseOrSse2CallF(Context, nameof(Sse.Multiply)); - } - else - { - EmitVectorBinaryOpF(Context, () => Context.Emit(OpCodes.Mul)); - } - } - - public static void Fmul_Ve(AILEmitterCtx Context) - { - EmitVectorBinaryOpByElemF(Context, () => Context.Emit(OpCodes.Mul)); - } - - public static void Fneg_S(AILEmitterCtx Context) - { - EmitScalarUnaryOpF(Context, () => Context.Emit(OpCodes.Neg)); - } - - public static void Fneg_V(AILEmitterCtx Context) - { - EmitVectorUnaryOpF(Context, () => Context.Emit(OpCodes.Neg)); - } - - public static void Fnmadd_S(AILEmitterCtx Context) - { - AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; - - int SizeF = Op.Size & 1; - - EmitVectorExtractF(Context, Op.Rn, 0, SizeF); - - Context.Emit(OpCodes.Neg); - - EmitVectorExtractF(Context, Op.Rm, 0, SizeF); - - Context.Emit(OpCodes.Mul); - - EmitVectorExtractF(Context, Op.Ra, 0, SizeF); - - Context.Emit(OpCodes.Sub); - - EmitScalarSetF(Context, Op.Rd, SizeF); - } - - public static void Fnmsub_S(AILEmitterCtx Context) - { - AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; - - int SizeF = Op.Size & 1; - - EmitVectorExtractF(Context, Op.Rn, 0, SizeF); - EmitVectorExtractF(Context, Op.Rm, 0, SizeF); - - Context.Emit(OpCodes.Mul); - - EmitVectorExtractF(Context, Op.Ra, 0, SizeF); - - Context.Emit(OpCodes.Sub); - - EmitScalarSetF(Context, Op.Rd, SizeF); - } - - public static void Fnmul_S(AILEmitterCtx Context) - { - EmitScalarBinaryOpF(Context, () => - { - Context.Emit(OpCodes.Mul); - Context.Emit(OpCodes.Neg); - }); - } - - public static void Frecpe_S(AILEmitterCtx Context) - { - EmitScalarUnaryOpF(Context, () => - { - EmitUnarySoftFloatCall(Context, nameof(ASoftFloat.RecipEstimate)); - }); - } - - public static void Frecpe_V(AILEmitterCtx Context) - { - EmitVectorUnaryOpF(Context, () => - { - EmitUnarySoftFloatCall(Context, nameof(ASoftFloat.RecipEstimate)); - }); - } - - public static void Frecps_S(AILEmitterCtx Context) - { - EmitScalarBinaryOpF(Context, () => - { - EmitBinarySoftFloatCall(Context, nameof(ASoftFloat.RecipStep)); - }); - } - - public static void Frecps_V(AILEmitterCtx Context) - { - EmitVectorBinaryOpF(Context, () => - { - EmitBinarySoftFloatCall(Context, nameof(ASoftFloat.RecipStep)); - }); - } - - public static void Frinta_S(AILEmitterCtx Context) - { - AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; - - EmitVectorExtractF(Context, Op.Rn, 0, Op.Size); - - EmitRoundMathCall(Context, MidpointRounding.AwayFromZero); - - EmitScalarSetF(Context, Op.Rd, Op.Size); - } - - public static void Frinta_V(AILEmitterCtx Context) - { - EmitVectorUnaryOpF(Context, () => - { - EmitRoundMathCall(Context, MidpointRounding.AwayFromZero); - }); - } - - public static void Frinti_S(AILEmitterCtx Context) - { - AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; - - EmitScalarUnaryOpF(Context, () => - { - Context.EmitLdarg(ATranslatedSub.StateArgIdx); - - Context.EmitCallPropGet(typeof(AThreadState), nameof(AThreadState.Fpcr)); - - if (Op.Size == 0) - { - AVectorHelper.EmitCall(Context, nameof(AVectorHelper.RoundF)); - } - else if (Op.Size == 1) - { - AVectorHelper.EmitCall(Context, nameof(AVectorHelper.Round)); - } - else - { - throw new InvalidOperationException(); - } - }); - } - - public static void Frinti_V(AILEmitterCtx Context) - { - AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; - - int SizeF = Op.Size & 1; - - EmitVectorUnaryOpF(Context, () => - { - Context.EmitLdarg(ATranslatedSub.StateArgIdx); - - Context.EmitCallPropGet(typeof(AThreadState), nameof(AThreadState.Fpcr)); - - if (SizeF == 0) - { - AVectorHelper.EmitCall(Context, nameof(AVectorHelper.RoundF)); - } - else if (SizeF == 1) - { - AVectorHelper.EmitCall(Context, nameof(AVectorHelper.Round)); - } - else - { - throw new InvalidOperationException(); - } - }); - } - - public static void Frintm_S(AILEmitterCtx Context) - { - EmitScalarUnaryOpF(Context, () => - { - EmitUnaryMathCall(Context, nameof(Math.Floor)); - }); - } - - public static void Frintm_V(AILEmitterCtx Context) - { - EmitVectorUnaryOpF(Context, () => - { - EmitUnaryMathCall(Context, nameof(Math.Floor)); - }); - } - - public static void Frintn_S(AILEmitterCtx Context) - { - AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; - - EmitVectorExtractF(Context, Op.Rn, 0, Op.Size); - - EmitRoundMathCall(Context, MidpointRounding.ToEven); - - EmitScalarSetF(Context, Op.Rd, Op.Size); - } - - public static void Frintn_V(AILEmitterCtx Context) - { - EmitVectorUnaryOpF(Context, () => - { - EmitRoundMathCall(Context, MidpointRounding.ToEven); - }); - } - - public static void Frintp_S(AILEmitterCtx Context) - { - EmitScalarUnaryOpF(Context, () => - { - EmitUnaryMathCall(Context, nameof(Math.Ceiling)); - }); - } - - public static void Frintp_V(AILEmitterCtx Context) - { - EmitVectorUnaryOpF(Context, () => - { - EmitUnaryMathCall(Context, nameof(Math.Ceiling)); - }); - } - - public static void Frintx_S(AILEmitterCtx Context) - { - AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; - - EmitScalarUnaryOpF(Context, () => - { - Context.EmitLdarg(ATranslatedSub.StateArgIdx); - - Context.EmitCallPropGet(typeof(AThreadState), nameof(AThreadState.Fpcr)); - - if (Op.Size == 0) - { - AVectorHelper.EmitCall(Context, nameof(AVectorHelper.RoundF)); - } - else if (Op.Size == 1) - { - AVectorHelper.EmitCall(Context, nameof(AVectorHelper.Round)); - } - else - { - throw new InvalidOperationException(); - } - }); - } - - public static void Frintx_V(AILEmitterCtx Context) - { - AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; - - EmitVectorUnaryOpF(Context, () => - { - Context.EmitLdarg(ATranslatedSub.StateArgIdx); - - Context.EmitCallPropGet(typeof(AThreadState), nameof(AThreadState.Fpcr)); - - if (Op.Size == 0) - { - AVectorHelper.EmitCall(Context, nameof(AVectorHelper.RoundF)); - } - else if (Op.Size == 1) - { - AVectorHelper.EmitCall(Context, nameof(AVectorHelper.Round)); - } - else - { - throw new InvalidOperationException(); - } - }); - } - - public static void Frsqrte_S(AILEmitterCtx Context) - { - EmitScalarUnaryOpF(Context, () => - { - EmitUnarySoftFloatCall(Context, nameof(ASoftFloat.InvSqrtEstimate)); - }); - } - - public static void Frsqrte_V(AILEmitterCtx Context) - { - EmitVectorUnaryOpF(Context, () => - { - EmitUnarySoftFloatCall(Context, nameof(ASoftFloat.InvSqrtEstimate)); - }); - } - - public static void Frsqrts_S(AILEmitterCtx Context) - { - EmitFrsqrts(Context, 0, Scalar: true); - } - - public static void Frsqrts_V(AILEmitterCtx Context) - { - AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; - - int SizeF = Op.Size & 1; - - int Bytes = Context.CurrOp.GetBitsCount() >> 3; - - for (int Index = 0; Index < Bytes >> SizeF + 2; Index++) - { - EmitFrsqrts(Context, Index, Scalar: false); - } - - if (Op.RegisterSize == ARegisterSize.SIMD64) - { - EmitVectorZeroUpper(Context, Op.Rd); - } - } - - private static void EmitFrsqrts(AILEmitterCtx Context, int Index, bool Scalar) - { - AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; - - int SizeF = Op.Size & 1; - - if (SizeF == 0) - { - Context.EmitLdc_R4(3); - } - else /* if (SizeF == 1) */ - { - Context.EmitLdc_R8(3); - } - - EmitVectorExtractF(Context, Op.Rn, Index, SizeF); - EmitVectorExtractF(Context, Op.Rm, Index, SizeF); - - Context.Emit(OpCodes.Mul); - Context.Emit(OpCodes.Sub); - - if (SizeF == 0) - { - Context.EmitLdc_R4(0.5f); - } - else /* if (SizeF == 1) */ - { - Context.EmitLdc_R8(0.5); - } - - Context.Emit(OpCodes.Mul); - - if (Scalar) - { - EmitVectorZeroAll(Context, Op.Rd); - } - - EmitVectorInsertF(Context, Op.Rd, Index, SizeF); - } - - public static void Fsqrt_S(AILEmitterCtx Context) - { - EmitScalarUnaryOpF(Context, () => - { - EmitUnaryMathCall(Context, nameof(Math.Sqrt)); - }); - } - - public static void Fsub_S(AILEmitterCtx Context) - { - if (AOptimizations.UseSse && AOptimizations.UseSse2) - { - EmitSseOrSse2CallF(Context, nameof(Sse.SubtractScalar)); - } - else - { - EmitScalarBinaryOpF(Context, () => Context.Emit(OpCodes.Sub)); - } - } - - public static void Fsub_V(AILEmitterCtx Context) - { - if (AOptimizations.UseSse && AOptimizations.UseSse2) - { - EmitSseOrSse2CallF(Context, nameof(Sse.Subtract)); - } - else - { - EmitVectorBinaryOpF(Context, () => Context.Emit(OpCodes.Sub)); - } - } - - public static void Mla_V(AILEmitterCtx Context) - { - EmitVectorTernaryOpZx(Context, () => - { - Context.Emit(OpCodes.Mul); - Context.Emit(OpCodes.Add); - }); - } - - public static void Mla_Ve(AILEmitterCtx Context) - { - EmitVectorTernaryOpByElemZx(Context, () => - { - Context.Emit(OpCodes.Mul); - Context.Emit(OpCodes.Add); - }); - } - - public static void Mls_V(AILEmitterCtx Context) - { - EmitVectorTernaryOpZx(Context, () => - { - Context.Emit(OpCodes.Mul); - Context.Emit(OpCodes.Sub); - }); - } - - public static void Mul_V(AILEmitterCtx Context) - { - EmitVectorBinaryOpZx(Context, () => Context.Emit(OpCodes.Mul)); - } - - public static void Mul_Ve(AILEmitterCtx Context) - { - EmitVectorBinaryOpByElemZx(Context, () => Context.Emit(OpCodes.Mul)); - } - - public static void Neg_S(AILEmitterCtx Context) - { - EmitScalarUnaryOpSx(Context, () => Context.Emit(OpCodes.Neg)); - } - - public static void Neg_V(AILEmitterCtx Context) - { - EmitVectorUnaryOpSx(Context, () => Context.Emit(OpCodes.Neg)); - } - - public static void Raddhn_V(AILEmitterCtx Context) - { - EmitHighNarrow(Context, () => Context.Emit(OpCodes.Add), Round: true); - } - - public static void Rsubhn_V(AILEmitterCtx Context) - { - EmitHighNarrow(Context, () => Context.Emit(OpCodes.Sub), Round: true); - } - - public static void Saba_V(AILEmitterCtx Context) - { - EmitVectorTernaryOpSx(Context, () => - { - Context.Emit(OpCodes.Sub); - EmitAbs(Context); - - Context.Emit(OpCodes.Add); - }); - } - - public static void Sabal_V(AILEmitterCtx Context) - { - EmitVectorWidenRnRmTernaryOpSx(Context, () => - { - Context.Emit(OpCodes.Sub); - EmitAbs(Context); - - Context.Emit(OpCodes.Add); - }); - } - - public static void Sabd_V(AILEmitterCtx Context) - { - EmitVectorBinaryOpSx(Context, () => - { - Context.Emit(OpCodes.Sub); - EmitAbs(Context); - }); - } - - public static void Sabdl_V(AILEmitterCtx Context) - { - EmitVectorWidenRnRmBinaryOpSx(Context, () => - { - Context.Emit(OpCodes.Sub); - EmitAbs(Context); - }); - } - - public static void Saddw_V(AILEmitterCtx Context) - { - EmitVectorWidenRmBinaryOpSx(Context, () => Context.Emit(OpCodes.Add)); - } - - public static void Smax_V(AILEmitterCtx Context) - { - Type[] Types = new Type[] { typeof(long), typeof(long) }; - - MethodInfo MthdInfo = typeof(Math).GetMethod(nameof(Math.Max), Types); - - EmitVectorBinaryOpSx(Context, () => Context.EmitCall(MthdInfo)); - } - - public static void Smaxp_V(AILEmitterCtx Context) - { - Type[] Types = new Type[] { typeof(long), typeof(long) }; - - MethodInfo MthdInfo = typeof(Math).GetMethod(nameof(Math.Max), Types); - - EmitVectorPairwiseOpSx(Context, () => Context.EmitCall(MthdInfo)); - } - - public static void Smin_V(AILEmitterCtx Context) - { - Type[] Types = new Type[] { typeof(long), typeof(long) }; - - MethodInfo MthdInfo = typeof(Math).GetMethod(nameof(Math.Min), Types); - - EmitVectorBinaryOpSx(Context, () => Context.EmitCall(MthdInfo)); - } - - public static void Sminp_V(AILEmitterCtx Context) - { - Type[] Types = new Type[] { typeof(long), typeof(long) }; - - MethodInfo MthdInfo = typeof(Math).GetMethod(nameof(Math.Min), Types); - - EmitVectorPairwiseOpSx(Context, () => Context.EmitCall(MthdInfo)); - } - - public static void Smlal_V(AILEmitterCtx Context) - { - EmitVectorWidenRnRmTernaryOpSx(Context, () => - { - Context.Emit(OpCodes.Mul); - Context.Emit(OpCodes.Add); - }); - } - - public static void Smull_V(AILEmitterCtx Context) - { - EmitVectorWidenRnRmBinaryOpSx(Context, () => Context.Emit(OpCodes.Mul)); - } - - public static void Sqxtn_S(AILEmitterCtx Context) - { - EmitSaturatingExtNarrow(Context, SignedSrc: true, SignedDst: true, Scalar: true); - } - - public static void Sqxtn_V(AILEmitterCtx Context) - { - EmitSaturatingExtNarrow(Context, SignedSrc: true, SignedDst: true, Scalar: false); - } - - public static void Sqxtun_S(AILEmitterCtx Context) - { - EmitSaturatingExtNarrow(Context, SignedSrc: true, SignedDst: false, Scalar: true); - } - - public static void Sqxtun_V(AILEmitterCtx Context) - { - EmitSaturatingExtNarrow(Context, SignedSrc: true, SignedDst: false, Scalar: false); - } - - public static void Sub_S(AILEmitterCtx Context) - { - EmitScalarBinaryOpZx(Context, () => Context.Emit(OpCodes.Sub)); - } - - public static void Sub_V(AILEmitterCtx Context) - { - if (AOptimizations.UseSse2) - { - EmitSse2Call(Context, nameof(Sse2.Subtract)); - } - else - { - EmitVectorBinaryOpZx(Context, () => Context.Emit(OpCodes.Sub)); - } - } - - public static void Subhn_V(AILEmitterCtx Context) - { - EmitHighNarrow(Context, () => Context.Emit(OpCodes.Sub), Round: false); - } - - public static void Uaba_V(AILEmitterCtx Context) - { - EmitVectorTernaryOpZx(Context, () => - { - Context.Emit(OpCodes.Sub); - EmitAbs(Context); - - Context.Emit(OpCodes.Add); - }); - } - - public static void Uabal_V(AILEmitterCtx Context) - { - EmitVectorWidenRnRmTernaryOpZx(Context, () => - { - Context.Emit(OpCodes.Sub); - EmitAbs(Context); - - Context.Emit(OpCodes.Add); - }); - } - - public static void Uabd_V(AILEmitterCtx Context) - { - EmitVectorBinaryOpZx(Context, () => - { - Context.Emit(OpCodes.Sub); - EmitAbs(Context); - }); - } - - public static void Uabdl_V(AILEmitterCtx Context) - { - EmitVectorWidenRnRmBinaryOpZx(Context, () => - { - Context.Emit(OpCodes.Sub); - EmitAbs(Context); - }); - } - - public static void Uaddl_V(AILEmitterCtx Context) - { - EmitVectorWidenRnRmBinaryOpZx(Context, () => Context.Emit(OpCodes.Add)); - } - - public static void Uaddlv_V(AILEmitterCtx Context) - { - AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; - - int Bytes = Context.CurrOp.GetBitsCount() >> 3; - - EmitVectorExtractZx(Context, Op.Rn, 0, Op.Size); - - for (int Index = 1; Index < (Bytes >> Op.Size); Index++) - { - EmitVectorExtractZx(Context, Op.Rn, Index, Op.Size); - - Context.Emit(OpCodes.Add); - } - - EmitScalarSet(Context, Op.Rd, Op.Size + 1); - } - - public static void Uaddw_V(AILEmitterCtx Context) - { - EmitVectorWidenRmBinaryOpZx(Context, () => Context.Emit(OpCodes.Add)); - } - - public static void Uhadd_V(AILEmitterCtx Context) - { - EmitVectorBinaryOpZx(Context, () => - { - Context.Emit(OpCodes.Add); - - Context.EmitLdc_I4(1); - - Context.Emit(OpCodes.Shr_Un); - }); - } - - public static void Umin_V(AILEmitterCtx Context) - { - Type[] Types = new Type[] { typeof(ulong), typeof(ulong) }; - - MethodInfo MthdInfo = typeof(Math).GetMethod(nameof(Math.Min), Types); - - EmitVectorBinaryOpZx(Context, () => Context.EmitCall(MthdInfo)); - } - - public static void Uminp_V(AILEmitterCtx Context) - { - Type[] Types = new Type[] { typeof(ulong), typeof(ulong) }; - - MethodInfo MthdInfo = typeof(Math).GetMethod(nameof(Math.Min), Types); - - EmitVectorPairwiseOpZx(Context, () => Context.EmitCall(MthdInfo)); - } - - public static void Umax_V(AILEmitterCtx Context) - { - Type[] Types = new Type[] { typeof(ulong), typeof(ulong) }; - - MethodInfo MthdInfo = typeof(Math).GetMethod(nameof(Math.Max), Types); - - EmitVectorBinaryOpZx(Context, () => Context.EmitCall(MthdInfo)); - } - - public static void Umaxp_V(AILEmitterCtx Context) - { - Type[] Types = new Type[] { typeof(ulong), typeof(ulong) }; - - MethodInfo MthdInfo = typeof(Math).GetMethod(nameof(Math.Max), Types); - - EmitVectorPairwiseOpZx(Context, () => Context.EmitCall(MthdInfo)); - } - - public static void Umull_V(AILEmitterCtx Context) - { - EmitVectorWidenRnRmBinaryOpZx(Context, () => Context.Emit(OpCodes.Mul)); - } - - public static void Uqxtn_S(AILEmitterCtx Context) - { - EmitSaturatingExtNarrow(Context, SignedSrc: false, SignedDst: false, Scalar: true); - } - - public static void Uqxtn_V(AILEmitterCtx Context) - { - EmitSaturatingExtNarrow(Context, SignedSrc: false, SignedDst: false, Scalar: false); - } - } -} diff --git a/ChocolArm64/Instruction/AInstEmitSimdCmp.cs b/ChocolArm64/Instruction/AInstEmitSimdCmp.cs deleted file mode 100644 index 68a7ab8808..0000000000 --- a/ChocolArm64/Instruction/AInstEmitSimdCmp.cs +++ /dev/null @@ -1,525 +0,0 @@ -using ChocolArm64.Decoder; -using ChocolArm64.State; -using ChocolArm64.Translation; -using System; -using System.Reflection.Emit; -using System.Runtime.Intrinsics.X86; - -using static ChocolArm64.Instruction.AInstEmitAluHelper; -using static ChocolArm64.Instruction.AInstEmitSimdHelper; - -namespace ChocolArm64.Instruction -{ - static partial class AInstEmit - { - public static void Cmeq_S(AILEmitterCtx Context) - { - EmitCmp(Context, OpCodes.Beq_S, Scalar: true); - } - - public static void Cmeq_V(AILEmitterCtx Context) - { - if (Context.CurrOp is AOpCodeSimdReg Op) - { - if (Op.Size < 3 && AOptimizations.UseSse2) - { - EmitSse2Call(Context, nameof(Sse2.CompareEqual)); - } - else if (Op.Size == 3 && AOptimizations.UseSse41) - { - EmitSse41Call(Context, nameof(Sse41.CompareEqual)); - } - else - { - EmitCmp(Context, OpCodes.Beq_S, Scalar: false); - } - } - else - { - EmitCmp(Context, OpCodes.Beq_S, Scalar: false); - } - } - - public static void Cmge_S(AILEmitterCtx Context) - { - EmitCmp(Context, OpCodes.Bge_S, Scalar: true); - } - - public static void Cmge_V(AILEmitterCtx Context) - { - EmitCmp(Context, OpCodes.Bge_S, Scalar: false); - } - - public static void Cmgt_S(AILEmitterCtx Context) - { - EmitCmp(Context, OpCodes.Bgt_S, Scalar: true); - } - - public static void Cmgt_V(AILEmitterCtx Context) - { - if (Context.CurrOp is AOpCodeSimdReg Op) - { - if (Op.Size < 3 && AOptimizations.UseSse2) - { - EmitSse2Call(Context, nameof(Sse2.CompareGreaterThan)); - } - else if (Op.Size == 3 && AOptimizations.UseSse42) - { - EmitSse42Call(Context, nameof(Sse42.CompareGreaterThan)); - } - else - { - EmitCmp(Context, OpCodes.Bgt_S, Scalar: false); - } - } - else - { - EmitCmp(Context, OpCodes.Bgt_S, Scalar: false); - } - } - - public static void Cmhi_S(AILEmitterCtx Context) - { - EmitCmp(Context, OpCodes.Bgt_Un_S, Scalar: true); - } - - public static void Cmhi_V(AILEmitterCtx Context) - { - EmitCmp(Context, OpCodes.Bgt_Un_S, Scalar: false); - } - - public static void Cmhs_S(AILEmitterCtx Context) - { - EmitCmp(Context, OpCodes.Bge_Un_S, Scalar: true); - } - - public static void Cmhs_V(AILEmitterCtx Context) - { - EmitCmp(Context, OpCodes.Bge_Un_S, Scalar: false); - } - - public static void Cmle_S(AILEmitterCtx Context) - { - EmitCmp(Context, OpCodes.Ble_S, Scalar: true); - } - - public static void Cmle_V(AILEmitterCtx Context) - { - EmitCmp(Context, OpCodes.Ble_S, Scalar: false); - } - - public static void Cmlt_S(AILEmitterCtx Context) - { - EmitCmp(Context, OpCodes.Blt_S, Scalar: true); - } - - public static void Cmlt_V(AILEmitterCtx Context) - { - EmitCmp(Context, OpCodes.Blt_S, Scalar: false); - } - - public static void Cmtst_S(AILEmitterCtx Context) - { - EmitCmtst(Context, Scalar: true); - } - - public static void Cmtst_V(AILEmitterCtx Context) - { - EmitCmtst(Context, Scalar: false); - } - - public static void Fccmp_S(AILEmitterCtx Context) - { - AOpCodeSimdFcond Op = (AOpCodeSimdFcond)Context.CurrOp; - - AILLabel LblTrue = new AILLabel(); - AILLabel LblEnd = new AILLabel(); - - Context.EmitCondBranch(LblTrue, Op.Cond); - - EmitSetNZCV(Context, Op.NZCV); - - Context.Emit(OpCodes.Br, LblEnd); - - Context.MarkLabel(LblTrue); - - Fcmp_S(Context); - - Context.MarkLabel(LblEnd); - } - - public static void Fccmpe_S(AILEmitterCtx Context) - { - Fccmp_S(Context); - } - - public static void Fcmeq_S(AILEmitterCtx Context) - { - if (Context.CurrOp is AOpCodeSimdReg && AOptimizations.UseSse - && AOptimizations.UseSse2) - { - EmitSseOrSse2CallF(Context, nameof(Sse.CompareEqualScalar)); - } - else - { - EmitScalarFcmp(Context, OpCodes.Beq_S); - } - } - - public static void Fcmeq_V(AILEmitterCtx Context) - { - if (Context.CurrOp is AOpCodeSimdReg && AOptimizations.UseSse - && AOptimizations.UseSse2) - { - EmitSseOrSse2CallF(Context, nameof(Sse.CompareEqual)); - } - else - { - EmitVectorFcmp(Context, OpCodes.Beq_S); - } - } - - public static void Fcmge_S(AILEmitterCtx Context) - { - if (Context.CurrOp is AOpCodeSimdReg && AOptimizations.UseSse - && AOptimizations.UseSse2) - { - EmitSseOrSse2CallF(Context, nameof(Sse.CompareGreaterThanOrEqualScalar)); - } - else - { - EmitScalarFcmp(Context, OpCodes.Bge_S); - } - } - - public static void Fcmge_V(AILEmitterCtx Context) - { - if (Context.CurrOp is AOpCodeSimdReg && AOptimizations.UseSse - && AOptimizations.UseSse2) - { - EmitSseOrSse2CallF(Context, nameof(Sse.CompareGreaterThanOrEqual)); - } - else - { - EmitVectorFcmp(Context, OpCodes.Bge_S); - } - } - - public static void Fcmgt_S(AILEmitterCtx Context) - { - if (Context.CurrOp is AOpCodeSimdReg && AOptimizations.UseSse - && AOptimizations.UseSse2) - { - EmitSseOrSse2CallF(Context, nameof(Sse.CompareGreaterThanScalar)); - } - else - { - EmitScalarFcmp(Context, OpCodes.Bgt_S); - } - } - - public static void Fcmgt_V(AILEmitterCtx Context) - { - if (Context.CurrOp is AOpCodeSimdReg && AOptimizations.UseSse - && AOptimizations.UseSse2) - { - EmitSseOrSse2CallF(Context, nameof(Sse.CompareGreaterThan)); - } - else - { - EmitVectorFcmp(Context, OpCodes.Bgt_S); - } - } - - public static void Fcmle_S(AILEmitterCtx Context) - { - EmitScalarFcmp(Context, OpCodes.Ble_S); - } - - public static void Fcmle_V(AILEmitterCtx Context) - { - EmitVectorFcmp(Context, OpCodes.Ble_S); - } - - public static void Fcmlt_S(AILEmitterCtx Context) - { - EmitScalarFcmp(Context, OpCodes.Blt_S); - } - - public static void Fcmlt_V(AILEmitterCtx Context) - { - EmitVectorFcmp(Context, OpCodes.Blt_S); - } - - public static void Fcmp_S(AILEmitterCtx Context) - { - AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; - - bool CmpWithZero = !(Op is AOpCodeSimdFcond) ? Op.Bit3 : false; - - //Handle NaN case. - //If any number is NaN, then NZCV = 0011. - if (CmpWithZero) - { - EmitNaNCheck(Context, Op.Rn); - } - else - { - EmitNaNCheck(Context, Op.Rn); - EmitNaNCheck(Context, Op.Rm); - - Context.Emit(OpCodes.Or); - } - - AILLabel LblNaN = new AILLabel(); - AILLabel LblEnd = new AILLabel(); - - Context.Emit(OpCodes.Brtrue_S, LblNaN); - - void EmitLoadOpers() - { - EmitVectorExtractF(Context, Op.Rn, 0, Op.Size); - - if (CmpWithZero) - { - if (Op.Size == 0) - { - Context.EmitLdc_R4(0); - } - else /* if (SizeF == 1) */ - { - Context.EmitLdc_R8(0); - } - } - else - { - EmitVectorExtractF(Context, Op.Rm, 0, Op.Size); - } - } - - //Z = Rn == Rm - EmitLoadOpers(); - - Context.Emit(OpCodes.Ceq); - Context.Emit(OpCodes.Dup); - - Context.EmitStflg((int)APState.ZBit); - - //C = Rn >= Rm - EmitLoadOpers(); - - Context.Emit(OpCodes.Cgt); - Context.Emit(OpCodes.Or); - - Context.EmitStflg((int)APState.CBit); - - //N = Rn < Rm - EmitLoadOpers(); - - Context.Emit(OpCodes.Clt); - - Context.EmitStflg((int)APState.NBit); - - //V = 0 - Context.EmitLdc_I4(0); - - Context.EmitStflg((int)APState.VBit); - - Context.Emit(OpCodes.Br_S, LblEnd); - - Context.MarkLabel(LblNaN); - - EmitSetNZCV(Context, 0b0011); - - Context.MarkLabel(LblEnd); - } - - public static void Fcmpe_S(AILEmitterCtx Context) - { - Fcmp_S(Context); - } - - private static void EmitNaNCheck(AILEmitterCtx Context, int Reg) - { - IAOpCodeSimd Op = (IAOpCodeSimd)Context.CurrOp; - - EmitVectorExtractF(Context, Reg, 0, Op.Size); - - if (Op.Size == 0) - { - Context.EmitCall(typeof(float), nameof(float.IsNaN)); - } - else if (Op.Size == 1) - { - Context.EmitCall(typeof(double), nameof(double.IsNaN)); - } - else - { - throw new InvalidOperationException(); - } - } - - private static void EmitCmp(AILEmitterCtx Context, OpCode ILOp, bool Scalar) - { - AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; - - int Bytes = Context.CurrOp.GetBitsCount() >> 3; - int Elems = (!Scalar ? Bytes >> Op.Size : 1); - - ulong SzMask = ulong.MaxValue >> (64 - (8 << Op.Size)); - - for (int Index = 0; Index < Elems; Index++) - { - EmitVectorExtractSx(Context, Op.Rn, Index, Op.Size); - - if (Op is AOpCodeSimdReg BinOp) - { - EmitVectorExtractSx(Context, BinOp.Rm, Index, Op.Size); - } - else - { - Context.EmitLdc_I8(0); - } - - AILLabel LblTrue = new AILLabel(); - AILLabel LblEnd = new AILLabel(); - - Context.Emit(ILOp, LblTrue); - - EmitVectorInsert(Context, Op.Rd, Index, Op.Size, 0); - - Context.Emit(OpCodes.Br_S, LblEnd); - - Context.MarkLabel(LblTrue); - - EmitVectorInsert(Context, Op.Rd, Index, Op.Size, (long)SzMask); - - Context.MarkLabel(LblEnd); - } - - if ((Op.RegisterSize == ARegisterSize.SIMD64) || Scalar) - { - EmitVectorZeroUpper(Context, Op.Rd); - } - } - - private static void EmitCmtst(AILEmitterCtx Context, bool Scalar) - { - AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; - - int Bytes = Context.CurrOp.GetBitsCount() >> 3; - int Elems = (!Scalar ? Bytes >> Op.Size : 1); - - ulong SzMask = ulong.MaxValue >> (64 - (8 << Op.Size)); - - for (int Index = 0; Index < Elems; Index++) - { - EmitVectorExtractZx(Context, Op.Rn, Index, Op.Size); - EmitVectorExtractZx(Context, Op.Rm, Index, Op.Size); - - AILLabel LblTrue = new AILLabel(); - AILLabel LblEnd = new AILLabel(); - - Context.Emit(OpCodes.And); - - Context.EmitLdc_I8(0); - - Context.Emit(OpCodes.Bne_Un_S, LblTrue); - - EmitVectorInsert(Context, Op.Rd, Index, Op.Size, 0); - - Context.Emit(OpCodes.Br_S, LblEnd); - - Context.MarkLabel(LblTrue); - - EmitVectorInsert(Context, Op.Rd, Index, Op.Size, (long)SzMask); - - Context.MarkLabel(LblEnd); - } - - if ((Op.RegisterSize == ARegisterSize.SIMD64) || Scalar) - { - EmitVectorZeroUpper(Context, Op.Rd); - } - } - - private static void EmitScalarFcmp(AILEmitterCtx Context, OpCode ILOp) - { - EmitFcmp(Context, ILOp, 0, Scalar: true); - } - - private static void EmitVectorFcmp(AILEmitterCtx Context, OpCode ILOp) - { - AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; - - int SizeF = Op.Size & 1; - - int Bytes = Context.CurrOp.GetBitsCount() >> 3; - - for (int Index = 0; Index < Bytes >> SizeF + 2; Index++) - { - EmitFcmp(Context, ILOp, Index, Scalar: false); - } - - if (Op.RegisterSize == ARegisterSize.SIMD64) - { - EmitVectorZeroUpper(Context, Op.Rd); - } - } - - private static void EmitFcmp(AILEmitterCtx Context, OpCode ILOp, int Index, bool Scalar) - { - AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; - - int SizeF = Op.Size & 1; - - ulong SzMask = ulong.MaxValue >> (64 - (32 << SizeF)); - - EmitVectorExtractF(Context, Op.Rn, Index, SizeF); - - if (Op is AOpCodeSimdReg BinOp) - { - EmitVectorExtractF(Context, BinOp.Rm, Index, SizeF); - } - else if (SizeF == 0) - { - Context.EmitLdc_R4(0); - } - else /* if (SizeF == 1) */ - { - Context.EmitLdc_R8(0); - } - - AILLabel LblTrue = new AILLabel(); - AILLabel LblEnd = new AILLabel(); - - Context.Emit(ILOp, LblTrue); - - if (Scalar) - { - EmitVectorZeroAll(Context, Op.Rd); - } - else - { - EmitVectorInsert(Context, Op.Rd, Index, SizeF + 2, 0); - } - - Context.Emit(OpCodes.Br_S, LblEnd); - - Context.MarkLabel(LblTrue); - - if (Scalar) - { - EmitVectorInsert(Context, Op.Rd, Index, 3, (long)SzMask); - - EmitVectorZeroUpper(Context, Op.Rd); - } - else - { - EmitVectorInsert(Context, Op.Rd, Index, SizeF + 2, (long)SzMask); - } - - Context.MarkLabel(LblEnd); - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Instruction/AInstEmitSimdCvt.cs b/ChocolArm64/Instruction/AInstEmitSimdCvt.cs deleted file mode 100644 index 98bb972a2d..0000000000 --- a/ChocolArm64/Instruction/AInstEmitSimdCvt.cs +++ /dev/null @@ -1,572 +0,0 @@ -using ChocolArm64.Decoder; -using ChocolArm64.State; -using ChocolArm64.Translation; -using System; -using System.Reflection.Emit; - -using static ChocolArm64.Instruction.AInstEmitSimdHelper; - -namespace ChocolArm64.Instruction -{ - static partial class AInstEmit - { - public static void Fcvt_S(AILEmitterCtx Context) - { - AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; - - EmitVectorExtractF(Context, Op.Rn, 0, Op.Size); - - EmitFloatCast(Context, Op.Opc); - - EmitScalarSetF(Context, Op.Rd, Op.Opc); - } - - public static void Fcvtas_Gp(AILEmitterCtx Context) - { - EmitFcvt_s_Gp(Context, () => EmitRoundMathCall(Context, MidpointRounding.AwayFromZero)); - } - - public static void Fcvtau_Gp(AILEmitterCtx Context) - { - EmitFcvt_u_Gp(Context, () => EmitRoundMathCall(Context, MidpointRounding.AwayFromZero)); - } - - public static void Fcvtl_V(AILEmitterCtx Context) - { - AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; - - int SizeF = Op.Size & 1; - - int Elems = 4 >> SizeF; - - int Part = Context.CurrOp.RegisterSize == ARegisterSize.SIMD128 ? Elems : 0; - - for (int Index = 0; Index < Elems; Index++) - { - if (SizeF == 0) - { - //TODO: This need the half precision floating point type, - //that is not yet supported on .NET. We should probably - //do our own implementation on the meantime. - throw new NotImplementedException(); - } - else /* if (SizeF == 1) */ - { - EmitVectorExtractF(Context, Op.Rn, Part + Index, 0); - - Context.Emit(OpCodes.Conv_R8); - } - - EmitVectorInsertF(Context, Op.Rd, Index, SizeF); - } - } - - public static void Fcvtms_Gp(AILEmitterCtx Context) - { - EmitFcvt_s_Gp(Context, () => EmitUnaryMathCall(Context, nameof(Math.Floor))); - } - - public static void Fcvtmu_Gp(AILEmitterCtx Context) - { - EmitFcvt_u_Gp(Context, () => EmitUnaryMathCall(Context, nameof(Math.Floor))); - } - - public static void Fcvtn_V(AILEmitterCtx Context) - { - AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; - - int SizeF = Op.Size & 1; - - int Elems = 4 >> SizeF; - - int Part = Context.CurrOp.RegisterSize == ARegisterSize.SIMD128 ? Elems : 0; - - for (int Index = 0; Index < Elems; Index++) - { - EmitVectorExtractF(Context, Op.Rd, Index, SizeF); - - if (SizeF == 0) - { - //TODO: This need the half precision floating point type, - //that is not yet supported on .NET. We should probably - //do our own implementation on the meantime. - throw new NotImplementedException(); - } - else /* if (SizeF == 1) */ - { - Context.Emit(OpCodes.Conv_R4); - - EmitVectorInsertF(Context, Op.Rd, Part + Index, 0); - } - } - - if (Op.RegisterSize == ARegisterSize.SIMD64) - { - EmitVectorZeroUpper(Context, Op.Rd); - } - } - - public static void Fcvtps_Gp(AILEmitterCtx Context) - { - EmitFcvt_s_Gp(Context, () => EmitUnaryMathCall(Context, nameof(Math.Ceiling))); - } - - public static void Fcvtpu_Gp(AILEmitterCtx Context) - { - EmitFcvt_u_Gp(Context, () => EmitUnaryMathCall(Context, nameof(Math.Ceiling))); - } - - public static void Fcvtzs_Gp(AILEmitterCtx Context) - { - EmitFcvt_s_Gp(Context, () => { }); - } - - public static void Fcvtzs_Gp_Fix(AILEmitterCtx Context) - { - EmitFcvtzs_Gp_Fix(Context); - } - - public static void Fcvtzs_S(AILEmitterCtx Context) - { - EmitScalarFcvtzs(Context); - } - - public static void Fcvtzs_V(AILEmitterCtx Context) - { - EmitVectorFcvtzs(Context); - } - - public static void Fcvtzu_Gp(AILEmitterCtx Context) - { - EmitFcvt_u_Gp(Context, () => { }); - } - - public static void Fcvtzu_Gp_Fix(AILEmitterCtx Context) - { - EmitFcvtzu_Gp_Fix(Context); - } - - public static void Fcvtzu_S(AILEmitterCtx Context) - { - EmitScalarFcvtzu(Context); - } - - public static void Fcvtzu_V(AILEmitterCtx Context) - { - EmitVectorFcvtzu(Context); - } - - public static void Scvtf_Gp(AILEmitterCtx Context) - { - AOpCodeSimdCvt Op = (AOpCodeSimdCvt)Context.CurrOp; - - Context.EmitLdintzr(Op.Rn); - - if (Context.CurrOp.RegisterSize == ARegisterSize.Int32) - { - Context.Emit(OpCodes.Conv_U4); - } - - EmitFloatCast(Context, Op.Size); - - EmitScalarSetF(Context, Op.Rd, Op.Size); - } - - public static void Scvtf_S(AILEmitterCtx Context) - { - AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; - - EmitVectorExtractSx(Context, Op.Rn, 0, Op.Size + 2); - - EmitFloatCast(Context, Op.Size); - - EmitScalarSetF(Context, Op.Rd, Op.Size); - } - - public static void Scvtf_V(AILEmitterCtx Context) - { - EmitVectorCvtf(Context, Signed: true); - } - - public static void Ucvtf_Gp(AILEmitterCtx Context) - { - AOpCodeSimdCvt Op = (AOpCodeSimdCvt)Context.CurrOp; - - Context.EmitLdintzr(Op.Rn); - - if (Context.CurrOp.RegisterSize == ARegisterSize.Int32) - { - Context.Emit(OpCodes.Conv_U4); - } - - Context.Emit(OpCodes.Conv_R_Un); - - EmitFloatCast(Context, Op.Size); - - EmitScalarSetF(Context, Op.Rd, Op.Size); - } - - public static void Ucvtf_S(AILEmitterCtx Context) - { - AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; - - EmitVectorExtractZx(Context, Op.Rn, 0, Op.Size + 2); - - Context.Emit(OpCodes.Conv_R_Un); - - EmitFloatCast(Context, Op.Size); - - EmitScalarSetF(Context, Op.Rd, Op.Size); - } - - public static void Ucvtf_V(AILEmitterCtx Context) - { - EmitVectorCvtf(Context, Signed: false); - } - - private static int GetFBits(AILEmitterCtx Context) - { - if (Context.CurrOp is AOpCodeSimdShImm Op) - { - return GetImmShr(Op); - } - - return 0; - } - - private static void EmitFloatCast(AILEmitterCtx Context, int Size) - { - if (Size == 0) - { - Context.Emit(OpCodes.Conv_R4); - } - else if (Size == 1) - { - Context.Emit(OpCodes.Conv_R8); - } - else - { - throw new ArgumentOutOfRangeException(nameof(Size)); - } - } - - private static void EmitFcvt_s_Gp(AILEmitterCtx Context, Action Emit) - { - EmitFcvt___Gp(Context, Emit, true); - } - - private static void EmitFcvt_u_Gp(AILEmitterCtx Context, Action Emit) - { - EmitFcvt___Gp(Context, Emit, false); - } - - private static void EmitFcvt___Gp(AILEmitterCtx Context, Action Emit, bool Signed) - { - AOpCodeSimdCvt Op = (AOpCodeSimdCvt)Context.CurrOp; - - EmitVectorExtractF(Context, Op.Rn, 0, Op.Size); - - Emit(); - - if (Signed) - { - EmitScalarFcvts(Context, Op.Size, 0); - } - else - { - EmitScalarFcvtu(Context, Op.Size, 0); - } - - if (Context.CurrOp.RegisterSize == ARegisterSize.Int32) - { - Context.Emit(OpCodes.Conv_U8); - } - - Context.EmitStintzr(Op.Rd); - } - - private static void EmitFcvtzs_Gp_Fix(AILEmitterCtx Context) - { - EmitFcvtz__Gp_Fix(Context, true); - } - - private static void EmitFcvtzu_Gp_Fix(AILEmitterCtx Context) - { - EmitFcvtz__Gp_Fix(Context, false); - } - - private static void EmitFcvtz__Gp_Fix(AILEmitterCtx Context, bool Signed) - { - AOpCodeSimdCvt Op = (AOpCodeSimdCvt)Context.CurrOp; - - EmitVectorExtractF(Context, Op.Rn, 0, Op.Size); - - if (Signed) - { - EmitScalarFcvts(Context, Op.Size, Op.FBits); - } - else - { - EmitScalarFcvtu(Context, Op.Size, Op.FBits); - } - - if (Context.CurrOp.RegisterSize == ARegisterSize.Int32) - { - Context.Emit(OpCodes.Conv_U8); - } - - Context.EmitStintzr(Op.Rd); - } - - private static void EmitVectorScvtf(AILEmitterCtx Context) - { - EmitVectorCvtf(Context, true); - } - - private static void EmitVectorUcvtf(AILEmitterCtx Context) - { - EmitVectorCvtf(Context, false); - } - - private static void EmitVectorCvtf(AILEmitterCtx Context, bool Signed) - { - AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; - - int SizeF = Op.Size & 1; - int SizeI = SizeF + 2; - - int FBits = GetFBits(Context); - - int Bytes = Context.CurrOp.GetBitsCount() >> 3; - - for (int Index = 0; Index < (Bytes >> SizeI); Index++) - { - EmitVectorExtract(Context, Op.Rn, Index, SizeI, Signed); - - if (!Signed) - { - Context.Emit(OpCodes.Conv_R_Un); - } - - Context.Emit(SizeF == 0 - ? OpCodes.Conv_R4 - : OpCodes.Conv_R8); - - EmitI2fFBitsMul(Context, SizeF, FBits); - - EmitVectorInsertF(Context, Op.Rd, Index, SizeF); - } - - if (Op.RegisterSize == ARegisterSize.SIMD64) - { - EmitVectorZeroUpper(Context, Op.Rd); - } - } - - private static void EmitScalarFcvtzs(AILEmitterCtx Context) - { - EmitScalarFcvtz(Context, true); - } - - private static void EmitScalarFcvtzu(AILEmitterCtx Context) - { - EmitScalarFcvtz(Context, false); - } - - private static void EmitScalarFcvtz(AILEmitterCtx Context, bool Signed) - { - AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; - - int SizeF = Op.Size & 1; - int SizeI = SizeF + 2; - - int FBits = GetFBits(Context); - - EmitVectorExtractF(Context, Op.Rn, 0, SizeF); - - EmitF2iFBitsMul(Context, SizeF, FBits); - - if (SizeF == 0) - { - AVectorHelper.EmitCall(Context, Signed - ? nameof(AVectorHelper.SatF32ToS32) - : nameof(AVectorHelper.SatF32ToU32)); - } - else /* if (SizeF == 1) */ - { - AVectorHelper.EmitCall(Context, Signed - ? nameof(AVectorHelper.SatF64ToS64) - : nameof(AVectorHelper.SatF64ToU64)); - } - - if (SizeF == 0) - { - Context.Emit(OpCodes.Conv_U8); - } - - EmitScalarSet(Context, Op.Rd, SizeI); - } - - private static void EmitVectorFcvtzs(AILEmitterCtx Context) - { - EmitVectorFcvtz(Context, true); - } - - private static void EmitVectorFcvtzu(AILEmitterCtx Context) - { - EmitVectorFcvtz(Context, false); - } - - private static void EmitVectorFcvtz(AILEmitterCtx Context, bool Signed) - { - AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; - - int SizeF = Op.Size & 1; - int SizeI = SizeF + 2; - - int FBits = GetFBits(Context); - - int Bytes = Context.CurrOp.GetBitsCount() >> 3; - - for (int Index = 0; Index < (Bytes >> SizeI); Index++) - { - EmitVectorExtractF(Context, Op.Rn, Index, SizeF); - - EmitF2iFBitsMul(Context, SizeF, FBits); - - if (SizeF == 0) - { - AVectorHelper.EmitCall(Context, Signed - ? nameof(AVectorHelper.SatF32ToS32) - : nameof(AVectorHelper.SatF32ToU32)); - } - else /* if (SizeF == 1) */ - { - AVectorHelper.EmitCall(Context, Signed - ? nameof(AVectorHelper.SatF64ToS64) - : nameof(AVectorHelper.SatF64ToU64)); - } - - if (SizeF == 0) - { - Context.Emit(OpCodes.Conv_U8); - } - - EmitVectorInsert(Context, Op.Rd, Index, SizeI); - } - - if (Op.RegisterSize == ARegisterSize.SIMD64) - { - EmitVectorZeroUpper(Context, Op.Rd); - } - } - - private static void EmitScalarFcvts(AILEmitterCtx Context, int Size, int FBits) - { - if (Size < 0 || Size > 1) - { - throw new ArgumentOutOfRangeException(nameof(Size)); - } - - EmitF2iFBitsMul(Context, Size, FBits); - - if (Context.CurrOp.RegisterSize == ARegisterSize.Int32) - { - if (Size == 0) - { - AVectorHelper.EmitCall(Context, nameof(AVectorHelper.SatF32ToS32)); - } - else /* if (Size == 1) */ - { - AVectorHelper.EmitCall(Context, nameof(AVectorHelper.SatF64ToS32)); - } - } - else - { - if (Size == 0) - { - AVectorHelper.EmitCall(Context, nameof(AVectorHelper.SatF32ToS64)); - } - else /* if (Size == 1) */ - { - AVectorHelper.EmitCall(Context, nameof(AVectorHelper.SatF64ToS64)); - } - } - } - - private static void EmitScalarFcvtu(AILEmitterCtx Context, int Size, int FBits) - { - if (Size < 0 || Size > 1) - { - throw new ArgumentOutOfRangeException(nameof(Size)); - } - - EmitF2iFBitsMul(Context, Size, FBits); - - if (Context.CurrOp.RegisterSize == ARegisterSize.Int32) - { - if (Size == 0) - { - AVectorHelper.EmitCall(Context, nameof(AVectorHelper.SatF32ToU32)); - } - else /* if (Size == 1) */ - { - AVectorHelper.EmitCall(Context, nameof(AVectorHelper.SatF64ToU32)); - } - } - else - { - if (Size == 0) - { - AVectorHelper.EmitCall(Context, nameof(AVectorHelper.SatF32ToU64)); - } - else /* if (Size == 1) */ - { - AVectorHelper.EmitCall(Context, nameof(AVectorHelper.SatF64ToU64)); - } - } - } - - private static void EmitF2iFBitsMul(AILEmitterCtx Context, int Size, int FBits) - { - if (FBits != 0) - { - if (Size == 0) - { - Context.EmitLdc_R4(MathF.Pow(2, FBits)); - } - else if (Size == 1) - { - Context.EmitLdc_R8(Math.Pow(2, FBits)); - } - else - { - throw new ArgumentOutOfRangeException(nameof(Size)); - } - - Context.Emit(OpCodes.Mul); - } - } - - private static void EmitI2fFBitsMul(AILEmitterCtx Context, int Size, int FBits) - { - if (FBits != 0) - { - if (Size == 0) - { - Context.EmitLdc_R4(1f / MathF.Pow(2, FBits)); - } - else if (Size == 1) - { - Context.EmitLdc_R8(1 / Math.Pow(2, FBits)); - } - else - { - throw new ArgumentOutOfRangeException(nameof(Size)); - } - - Context.Emit(OpCodes.Mul); - } - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Instruction/AInstEmitSimdHelper.cs b/ChocolArm64/Instruction/AInstEmitSimdHelper.cs deleted file mode 100644 index d895ec9c7c..0000000000 --- a/ChocolArm64/Instruction/AInstEmitSimdHelper.cs +++ /dev/null @@ -1,958 +0,0 @@ -using ChocolArm64.Decoder; -using ChocolArm64.State; -using ChocolArm64.Translation; -using System; -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; - -namespace ChocolArm64.Instruction -{ - static class AInstEmitSimdHelper - { - [Flags] - public enum OperFlags - { - Rd = 1 << 0, - Rn = 1 << 1, - Rm = 1 << 2, - Ra = 1 << 3, - - RnRm = Rn | Rm, - RdRn = Rd | Rn, - RaRnRm = Ra | Rn | Rm, - RdRnRm = Rd | Rn | Rm - } - - public static int GetImmShl(AOpCodeSimdShImm Op) - { - return Op.Imm - (8 << Op.Size); - } - - public static int GetImmShr(AOpCodeSimdShImm Op) - { - return (8 << (Op.Size + 1)) - Op.Imm; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void EmitSse2Call(AILEmitterCtx Context, string Name) - { - EmitSseCall(Context, Name, typeof(Sse2)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void EmitSse41Call(AILEmitterCtx Context, string Name) - { - EmitSseCall(Context, Name, typeof(Sse41)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void EmitSse42Call(AILEmitterCtx Context, string Name) - { - EmitSseCall(Context, Name, typeof(Sse42)); - } - - private static void EmitSseCall(AILEmitterCtx Context, string Name, Type Type) - { - AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; - - void Ldvec(int Reg) - { - Context.EmitLdvec(Reg); - - switch (Op.Size) - { - case 0: AVectorHelper.EmitCall(Context, nameof(AVectorHelper.VectorSingleToSByte)); break; - case 1: AVectorHelper.EmitCall(Context, nameof(AVectorHelper.VectorSingleToInt16)); break; - case 2: AVectorHelper.EmitCall(Context, nameof(AVectorHelper.VectorSingleToInt32)); break; - case 3: AVectorHelper.EmitCall(Context, nameof(AVectorHelper.VectorSingleToInt64)); break; - } - } - - Ldvec(Op.Rn); - - Type BaseType = null; - - switch (Op.Size) - { - case 0: BaseType = typeof(Vector128); break; - case 1: BaseType = typeof(Vector128); break; - case 2: BaseType = typeof(Vector128); break; - case 3: BaseType = typeof(Vector128); break; - } - - if (Op is AOpCodeSimdReg BinOp) - { - Ldvec(BinOp.Rm); - - Context.EmitCall(Type.GetMethod(Name, new Type[] { BaseType, BaseType })); - } - else - { - Context.EmitCall(Type.GetMethod(Name, new Type[] { BaseType })); - } - - switch (Op.Size) - { - case 0: AVectorHelper.EmitCall(Context, nameof(AVectorHelper.VectorSByteToSingle)); break; - case 1: AVectorHelper.EmitCall(Context, nameof(AVectorHelper.VectorInt16ToSingle)); break; - case 2: AVectorHelper.EmitCall(Context, nameof(AVectorHelper.VectorInt32ToSingle)); break; - case 3: AVectorHelper.EmitCall(Context, nameof(AVectorHelper.VectorInt64ToSingle)); break; - } - - Context.EmitStvec(Op.Rd); - - if (Op.RegisterSize == ARegisterSize.SIMD64) - { - EmitVectorZeroUpper(Context, Op.Rd); - } - } - - public static void EmitSseOrSse2CallF(AILEmitterCtx Context, string Name) - { - AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; - - int SizeF = Op.Size & 1; - - void Ldvec(int Reg) - { - Context.EmitLdvec(Reg); - - if (SizeF == 1) - { - AVectorHelper.EmitCall(Context, nameof(AVectorHelper.VectorSingleToDouble)); - } - } - - Ldvec(Op.Rn); - - Type Type; - Type BaseType; - - if (SizeF == 0) - { - Type = typeof(Sse); - BaseType = typeof(Vector128); - } - else /* if (SizeF == 1) */ - { - Type = typeof(Sse2); - BaseType = typeof(Vector128); - } - - if (Op is AOpCodeSimdReg BinOp) - { - Ldvec(BinOp.Rm); - - Context.EmitCall(Type.GetMethod(Name, new Type[] { BaseType, BaseType })); - } - else - { - Context.EmitCall(Type.GetMethod(Name, new Type[] { BaseType })); - } - - if (SizeF == 1) - { - AVectorHelper.EmitCall(Context, nameof(AVectorHelper.VectorDoubleToSingle)); - } - - Context.EmitStvec(Op.Rd); - - if (Op.RegisterSize == ARegisterSize.SIMD64) - { - EmitVectorZeroUpper(Context, Op.Rd); - } - } - - public static void EmitUnaryMathCall(AILEmitterCtx Context, string Name) - { - IAOpCodeSimd Op = (IAOpCodeSimd)Context.CurrOp; - - int SizeF = Op.Size & 1; - - MethodInfo MthdInfo; - - if (SizeF == 0) - { - MthdInfo = typeof(MathF).GetMethod(Name, new Type[] { typeof(float) }); - } - else /* if (SizeF == 1) */ - { - MthdInfo = typeof(Math).GetMethod(Name, new Type[] { typeof(double) }); - } - - Context.EmitCall(MthdInfo); - } - - public static void EmitBinaryMathCall(AILEmitterCtx Context, string Name) - { - IAOpCodeSimd Op = (IAOpCodeSimd)Context.CurrOp; - - int SizeF = Op.Size & 1; - - MethodInfo MthdInfo; - - if (SizeF == 0) - { - MthdInfo = typeof(MathF).GetMethod(Name, new Type[] { typeof(float), typeof(float) }); - } - else /* if (SizeF == 1) */ - { - MthdInfo = typeof(Math).GetMethod(Name, new Type[] { typeof(double), typeof(double) }); - } - - Context.EmitCall(MthdInfo); - } - - public static void EmitRoundMathCall(AILEmitterCtx Context, MidpointRounding RoundMode) - { - IAOpCodeSimd Op = (IAOpCodeSimd)Context.CurrOp; - - int SizeF = Op.Size & 1; - - Context.EmitLdc_I4((int)RoundMode); - - MethodInfo MthdInfo; - - Type[] Types = new Type[] { null, typeof(MidpointRounding) }; - - Types[0] = SizeF == 0 - ? typeof(float) - : typeof(double); - - if (SizeF == 0) - { - MthdInfo = typeof(MathF).GetMethod(nameof(MathF.Round), Types); - } - else /* if (SizeF == 1) */ - { - MthdInfo = typeof(Math).GetMethod(nameof(Math.Round), Types); - } - - Context.EmitCall(MthdInfo); - } - - public static void EmitUnarySoftFloatCall(AILEmitterCtx Context, string Name) - { - IAOpCodeSimd Op = (IAOpCodeSimd)Context.CurrOp; - - int SizeF = Op.Size & 1; - - MethodInfo MthdInfo; - - if (SizeF == 0) - { - MthdInfo = typeof(ASoftFloat).GetMethod(Name, new Type[] { typeof(float) }); - } - else /* if (SizeF == 1) */ - { - MthdInfo = typeof(ASoftFloat).GetMethod(Name, new Type[] { typeof(double) }); - } - - Context.EmitCall(MthdInfo); - } - - public static void EmitBinarySoftFloatCall(AILEmitterCtx Context, string Name) - { - IAOpCodeSimd Op = (IAOpCodeSimd)Context.CurrOp; - - int SizeF = Op.Size & 1; - - MethodInfo MthdInfo; - - if (SizeF == 0) - { - MthdInfo = typeof(ASoftFloat).GetMethod(Name, new Type[] { typeof(float), typeof(float) }); - } - else /* if (SizeF == 1) */ - { - MthdInfo = typeof(ASoftFloat).GetMethod(Name, new Type[] { typeof(double), typeof(double) }); - } - - Context.EmitCall(MthdInfo); - } - - public static void EmitScalarBinaryOpByElemF(AILEmitterCtx Context, Action Emit) - { - AOpCodeSimdRegElemF Op = (AOpCodeSimdRegElemF)Context.CurrOp; - - EmitScalarOpByElemF(Context, Emit, Op.Index, Ternary: false); - } - - public static void EmitScalarTernaryOpByElemF(AILEmitterCtx Context, Action Emit) - { - AOpCodeSimdRegElemF Op = (AOpCodeSimdRegElemF)Context.CurrOp; - - EmitScalarOpByElemF(Context, Emit, Op.Index, Ternary: true); - } - - public static void EmitScalarOpByElemF(AILEmitterCtx Context, Action Emit, int Elem, bool Ternary) - { - AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; - - int SizeF = Op.Size & 1; - - if (Ternary) - { - EmitVectorExtractF(Context, Op.Rd, 0, SizeF); - } - - EmitVectorExtractF(Context, Op.Rn, 0, SizeF); - EmitVectorExtractF(Context, Op.Rm, Elem, SizeF); - - Emit(); - - EmitScalarSetF(Context, Op.Rd, SizeF); - } - - public static void EmitScalarUnaryOpSx(AILEmitterCtx Context, Action Emit) - { - EmitScalarOp(Context, Emit, OperFlags.Rn, true); - } - - public static void EmitScalarBinaryOpSx(AILEmitterCtx Context, Action Emit) - { - EmitScalarOp(Context, Emit, OperFlags.RnRm, true); - } - - public static void EmitScalarUnaryOpZx(AILEmitterCtx Context, Action Emit) - { - EmitScalarOp(Context, Emit, OperFlags.Rn, false); - } - - public static void EmitScalarBinaryOpZx(AILEmitterCtx Context, Action Emit) - { - EmitScalarOp(Context, Emit, OperFlags.RnRm, false); - } - - public static void EmitScalarTernaryOpZx(AILEmitterCtx Context, Action Emit) - { - EmitScalarOp(Context, Emit, OperFlags.RdRnRm, false); - } - - public static void EmitScalarOp(AILEmitterCtx Context, Action Emit, OperFlags Opers, bool Signed) - { - AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; - - if (Opers.HasFlag(OperFlags.Rd)) - { - EmitVectorExtract(Context, Op.Rd, 0, Op.Size, Signed); - } - - if (Opers.HasFlag(OperFlags.Rn)) - { - EmitVectorExtract(Context, Op.Rn, 0, Op.Size, Signed); - } - - if (Opers.HasFlag(OperFlags.Rm)) - { - EmitVectorExtract(Context, ((AOpCodeSimdReg)Op).Rm, 0, Op.Size, Signed); - } - - Emit(); - - EmitScalarSet(Context, Op.Rd, Op.Size); - } - - public static void EmitScalarUnaryOpF(AILEmitterCtx Context, Action Emit) - { - EmitScalarOpF(Context, Emit, OperFlags.Rn); - } - - public static void EmitScalarBinaryOpF(AILEmitterCtx Context, Action Emit) - { - EmitScalarOpF(Context, Emit, OperFlags.RnRm); - } - - public static void EmitScalarTernaryRaOpF(AILEmitterCtx Context, Action Emit) - { - EmitScalarOpF(Context, Emit, OperFlags.RaRnRm); - } - - public static void EmitScalarOpF(AILEmitterCtx Context, Action Emit, OperFlags Opers) - { - AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; - - int SizeF = Op.Size & 1; - - if (Opers.HasFlag(OperFlags.Ra)) - { - EmitVectorExtractF(Context, ((AOpCodeSimdReg)Op).Ra, 0, SizeF); - } - - if (Opers.HasFlag(OperFlags.Rn)) - { - EmitVectorExtractF(Context, Op.Rn, 0, SizeF); - } - - if (Opers.HasFlag(OperFlags.Rm)) - { - EmitVectorExtractF(Context, ((AOpCodeSimdReg)Op).Rm, 0, SizeF); - } - - Emit(); - - EmitScalarSetF(Context, Op.Rd, SizeF); - } - - public static void EmitVectorUnaryOpF(AILEmitterCtx Context, Action Emit) - { - EmitVectorOpF(Context, Emit, OperFlags.Rn); - } - - public static void EmitVectorBinaryOpF(AILEmitterCtx Context, Action Emit) - { - EmitVectorOpF(Context, Emit, OperFlags.RnRm); - } - - public static void EmitVectorTernaryOpF(AILEmitterCtx Context, Action Emit) - { - EmitVectorOpF(Context, Emit, OperFlags.RdRnRm); - } - - public static void EmitVectorOpF(AILEmitterCtx Context, Action Emit, OperFlags Opers) - { - AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; - - int SizeF = Op.Size & 1; - - int Bytes = Context.CurrOp.GetBitsCount() >> 3; - - for (int Index = 0; Index < (Bytes >> SizeF + 2); Index++) - { - if (Opers.HasFlag(OperFlags.Rd)) - { - EmitVectorExtractF(Context, Op.Rd, Index, SizeF); - } - - if (Opers.HasFlag(OperFlags.Rn)) - { - EmitVectorExtractF(Context, Op.Rn, Index, SizeF); - } - - if (Opers.HasFlag(OperFlags.Rm)) - { - EmitVectorExtractF(Context, ((AOpCodeSimdReg)Op).Rm, Index, SizeF); - } - - Emit(); - - EmitVectorInsertF(Context, Op.Rd, Index, SizeF); - } - - if (Op.RegisterSize == ARegisterSize.SIMD64) - { - EmitVectorZeroUpper(Context, Op.Rd); - } - } - - public static void EmitVectorBinaryOpByElemF(AILEmitterCtx Context, Action Emit) - { - AOpCodeSimdRegElemF Op = (AOpCodeSimdRegElemF)Context.CurrOp; - - EmitVectorOpByElemF(Context, Emit, Op.Index, Ternary: false); - } - - public static void EmitVectorTernaryOpByElemF(AILEmitterCtx Context, Action Emit) - { - AOpCodeSimdRegElemF Op = (AOpCodeSimdRegElemF)Context.CurrOp; - - EmitVectorOpByElemF(Context, Emit, Op.Index, Ternary: true); - } - - public static void EmitVectorOpByElemF(AILEmitterCtx Context, Action Emit, int Elem, bool Ternary) - { - AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; - - int SizeF = Op.Size & 1; - - int Bytes = Context.CurrOp.GetBitsCount() >> 3; - - for (int Index = 0; Index < (Bytes >> SizeF + 2); Index++) - { - if (Ternary) - { - EmitVectorExtractF(Context, Op.Rd, Index, SizeF); - } - - EmitVectorExtractF(Context, Op.Rn, Index, SizeF); - EmitVectorExtractF(Context, Op.Rm, Elem, SizeF); - - Emit(); - - EmitVectorInsertTmpF(Context, Index, SizeF); - } - - Context.EmitLdvectmp(); - Context.EmitStvec(Op.Rd); - - if (Op.RegisterSize == ARegisterSize.SIMD64) - { - EmitVectorZeroUpper(Context, Op.Rd); - } - } - - public static void EmitVectorUnaryOpSx(AILEmitterCtx Context, Action Emit) - { - EmitVectorOp(Context, Emit, OperFlags.Rn, true); - } - - public static void EmitVectorBinaryOpSx(AILEmitterCtx Context, Action Emit) - { - EmitVectorOp(Context, Emit, OperFlags.RnRm, true); - } - - public static void EmitVectorTernaryOpSx(AILEmitterCtx Context, Action Emit) - { - EmitVectorOp(Context, Emit, OperFlags.RdRnRm, true); - } - - public static void EmitVectorUnaryOpZx(AILEmitterCtx Context, Action Emit) - { - EmitVectorOp(Context, Emit, OperFlags.Rn, false); - } - - public static void EmitVectorBinaryOpZx(AILEmitterCtx Context, Action Emit) - { - EmitVectorOp(Context, Emit, OperFlags.RnRm, false); - } - - public static void EmitVectorTernaryOpZx(AILEmitterCtx Context, Action Emit) - { - EmitVectorOp(Context, Emit, OperFlags.RdRnRm, false); - } - - public static void EmitVectorOp(AILEmitterCtx Context, Action Emit, OperFlags Opers, bool Signed) - { - AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; - - int Bytes = Context.CurrOp.GetBitsCount() >> 3; - - for (int Index = 0; Index < (Bytes >> Op.Size); Index++) - { - if (Opers.HasFlag(OperFlags.Rd)) - { - EmitVectorExtract(Context, Op.Rd, Index, Op.Size, Signed); - } - - if (Opers.HasFlag(OperFlags.Rn)) - { - EmitVectorExtract(Context, Op.Rn, Index, Op.Size, Signed); - } - - if (Opers.HasFlag(OperFlags.Rm)) - { - EmitVectorExtract(Context, ((AOpCodeSimdReg)Op).Rm, Index, Op.Size, Signed); - } - - Emit(); - - EmitVectorInsert(Context, Op.Rd, Index, Op.Size); - } - - if (Op.RegisterSize == ARegisterSize.SIMD64) - { - EmitVectorZeroUpper(Context, Op.Rd); - } - } - - public static void EmitVectorBinaryOpByElemSx(AILEmitterCtx Context, Action Emit) - { - AOpCodeSimdRegElem Op = (AOpCodeSimdRegElem)Context.CurrOp; - - EmitVectorOpByElem(Context, Emit, Op.Index, false, true); - } - - public static void EmitVectorBinaryOpByElemZx(AILEmitterCtx Context, Action Emit) - { - AOpCodeSimdRegElem Op = (AOpCodeSimdRegElem)Context.CurrOp; - - EmitVectorOpByElem(Context, Emit, Op.Index, false, false); - } - - public static void EmitVectorTernaryOpByElemZx(AILEmitterCtx Context, Action Emit) - { - AOpCodeSimdRegElem Op = (AOpCodeSimdRegElem)Context.CurrOp; - - EmitVectorOpByElem(Context, Emit, Op.Index, true, false); - } - - public static void EmitVectorOpByElem(AILEmitterCtx Context, Action Emit, int Elem, bool Ternary, bool Signed) - { - AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; - - int Bytes = Context.CurrOp.GetBitsCount() >> 3; - - for (int Index = 0; Index < (Bytes >> Op.Size); Index++) - { - if (Ternary) - { - EmitVectorExtract(Context, Op.Rd, Index, Op.Size, Signed); - } - - EmitVectorExtract(Context, Op.Rn, Index, Op.Size, Signed); - EmitVectorExtract(Context, Op.Rm, Elem, Op.Size, Signed); - - Emit(); - - EmitVectorInsertTmp(Context, Index, Op.Size); - } - - Context.EmitLdvectmp(); - Context.EmitStvec(Op.Rd); - - if (Op.RegisterSize == ARegisterSize.SIMD64) - { - EmitVectorZeroUpper(Context, Op.Rd); - } - } - - public static void EmitVectorImmUnaryOp(AILEmitterCtx Context, Action Emit) - { - EmitVectorImmOp(Context, Emit, false); - } - - public static void EmitVectorImmBinaryOp(AILEmitterCtx Context, Action Emit) - { - EmitVectorImmOp(Context, Emit, true); - } - - public static void EmitVectorImmOp(AILEmitterCtx Context, Action Emit, bool Binary) - { - AOpCodeSimdImm Op = (AOpCodeSimdImm)Context.CurrOp; - - int Bytes = Context.CurrOp.GetBitsCount() >> 3; - - for (int Index = 0; Index < (Bytes >> Op.Size); Index++) - { - if (Binary) - { - EmitVectorExtractZx(Context, Op.Rd, Index, Op.Size); - } - - Context.EmitLdc_I8(Op.Imm); - - Emit(); - - EmitVectorInsert(Context, Op.Rd, Index, Op.Size); - } - - if (Op.RegisterSize == ARegisterSize.SIMD64) - { - EmitVectorZeroUpper(Context, Op.Rd); - } - } - - public static void EmitVectorWidenRmBinaryOpSx(AILEmitterCtx Context, Action Emit) - { - EmitVectorWidenRmBinaryOp(Context, Emit, true); - } - - public static void EmitVectorWidenRmBinaryOpZx(AILEmitterCtx Context, Action Emit) - { - EmitVectorWidenRmBinaryOp(Context, Emit, false); - } - - public static void EmitVectorWidenRmBinaryOp(AILEmitterCtx Context, Action Emit, bool Signed) - { - AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; - - Context.EmitLdvec(Op.Rd); - Context.EmitStvectmp(); - - int Elems = 8 >> Op.Size; - - int Part = Op.RegisterSize == ARegisterSize.SIMD128 ? Elems : 0; - - for (int Index = 0; Index < Elems; Index++) - { - EmitVectorExtract(Context, Op.Rn, Index, Op.Size + 1, Signed); - EmitVectorExtract(Context, Op.Rm, Part + Index, Op.Size, Signed); - - Emit(); - - EmitVectorInsertTmp(Context, Index, Op.Size + 1); - } - - Context.EmitLdvectmp(); - Context.EmitStvec(Op.Rd); - } - - public static void EmitVectorWidenRnRmBinaryOpSx(AILEmitterCtx Context, Action Emit) - { - EmitVectorWidenRnRmOp(Context, Emit, false, true); - } - - public static void EmitVectorWidenRnRmBinaryOpZx(AILEmitterCtx Context, Action Emit) - { - EmitVectorWidenRnRmOp(Context, Emit, false, false); - } - - public static void EmitVectorWidenRnRmTernaryOpSx(AILEmitterCtx Context, Action Emit) - { - EmitVectorWidenRnRmOp(Context, Emit, true, true); - } - - public static void EmitVectorWidenRnRmTernaryOpZx(AILEmitterCtx Context, Action Emit) - { - EmitVectorWidenRnRmOp(Context, Emit, true, false); - } - - public static void EmitVectorWidenRnRmOp(AILEmitterCtx Context, Action Emit, bool Ternary, bool Signed) - { - AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; - - Context.EmitLdvec(Op.Rd); - Context.EmitStvectmp(); - - int Elems = 8 >> Op.Size; - - int Part = Op.RegisterSize == ARegisterSize.SIMD128 ? Elems : 0; - - for (int Index = 0; Index < Elems; Index++) - { - if (Ternary) - { - EmitVectorExtract(Context, Op.Rd, Index, Op.Size + 1, Signed); - } - - EmitVectorExtract(Context, Op.Rn, Part + Index, Op.Size, Signed); - EmitVectorExtract(Context, Op.Rm, Part + Index, Op.Size, Signed); - - Emit(); - - EmitVectorInsertTmp(Context, Index, Op.Size + 1); - } - - Context.EmitLdvectmp(); - Context.EmitStvec(Op.Rd); - } - - public static void EmitVectorPairwiseOpSx(AILEmitterCtx Context, Action Emit) - { - EmitVectorPairwiseOp(Context, Emit, true); - } - - public static void EmitVectorPairwiseOpZx(AILEmitterCtx Context, Action Emit) - { - EmitVectorPairwiseOp(Context, Emit, false); - } - - private static void EmitVectorPairwiseOp(AILEmitterCtx Context, Action Emit, bool Signed) - { - AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; - - int Bytes = Context.CurrOp.GetBitsCount() >> 3; - - int Elems = Bytes >> Op.Size; - int Half = Elems >> 1; - - for (int Index = 0; Index < Elems; Index++) - { - int Elem = (Index & (Half - 1)) << 1; - - EmitVectorExtract(Context, Index < Half ? Op.Rn : Op.Rm, Elem + 0, Op.Size, Signed); - EmitVectorExtract(Context, Index < Half ? Op.Rn : Op.Rm, Elem + 1, Op.Size, Signed); - - Emit(); - - EmitVectorInsertTmp(Context, Index, Op.Size); - } - - Context.EmitLdvectmp(); - Context.EmitStvec(Op.Rd); - - if (Op.RegisterSize == ARegisterSize.SIMD64) - { - EmitVectorZeroUpper(Context, Op.Rd); - } - } - - public static void EmitScalarSet(AILEmitterCtx Context, int Reg, int Size) - { - EmitVectorZeroAll(Context, Reg); - EmitVectorInsert(Context, Reg, 0, Size); - } - - public static void EmitScalarSetF(AILEmitterCtx Context, int Reg, int Size) - { - EmitVectorZeroAll(Context, Reg); - EmitVectorInsertF(Context, Reg, 0, Size); - } - - public static void EmitVectorExtractSx(AILEmitterCtx Context, int Reg, int Index, int Size) - { - EmitVectorExtract(Context, Reg, Index, Size, true); - } - - public static void EmitVectorExtractZx(AILEmitterCtx Context, int Reg, int Index, int Size) - { - EmitVectorExtract(Context, Reg, Index, Size, false); - } - - public static void EmitVectorExtract(AILEmitterCtx Context, int Reg, int Index, int Size, bool Signed) - { - ThrowIfInvalid(Index, Size); - - IAOpCodeSimd Op = (IAOpCodeSimd)Context.CurrOp; - - Context.EmitLdvec(Reg); - Context.EmitLdc_I4(Index); - Context.EmitLdc_I4(Size); - - AVectorHelper.EmitCall(Context, Signed - ? nameof(AVectorHelper.VectorExtractIntSx) - : nameof(AVectorHelper.VectorExtractIntZx)); - } - - public static void EmitVectorExtractF(AILEmitterCtx Context, int Reg, int Index, int Size) - { - ThrowIfInvalidF(Index, Size); - - Context.EmitLdvec(Reg); - Context.EmitLdc_I4(Index); - - if (Size == 0) - { - AVectorHelper.EmitCall(Context, nameof(AVectorHelper.VectorExtractSingle)); - } - else if (Size == 1) - { - AVectorHelper.EmitCall(Context, nameof(AVectorHelper.VectorExtractDouble)); - } - else - { - throw new ArgumentOutOfRangeException(nameof(Size)); - } - } - - public static void EmitVectorZeroAll(AILEmitterCtx Context, int Rd) - { - EmitVectorZeroLower(Context, Rd); - EmitVectorZeroUpper(Context, Rd); - } - - public static void EmitVectorZeroLower(AILEmitterCtx Context, int Rd) - { - EmitVectorInsert(Context, Rd, 0, 3, 0); - } - - public static void EmitVectorZeroUpper(AILEmitterCtx Context, int Rd) - { - EmitVectorInsert(Context, Rd, 1, 3, 0); - } - - public static void EmitVectorInsert(AILEmitterCtx Context, int Reg, int Index, int Size) - { - ThrowIfInvalid(Index, Size); - - Context.EmitLdvec(Reg); - Context.EmitLdc_I4(Index); - Context.EmitLdc_I4(Size); - - AVectorHelper.EmitCall(Context, nameof(AVectorHelper.VectorInsertInt)); - - Context.EmitStvec(Reg); - } - - public static void EmitVectorInsertTmp(AILEmitterCtx Context, int Index, int Size) - { - ThrowIfInvalid(Index, Size); - - Context.EmitLdvectmp(); - Context.EmitLdc_I4(Index); - Context.EmitLdc_I4(Size); - - AVectorHelper.EmitCall(Context, nameof(AVectorHelper.VectorInsertInt)); - - Context.EmitStvectmp(); - } - - public static void EmitVectorInsert(AILEmitterCtx Context, int Reg, int Index, int Size, long Value) - { - ThrowIfInvalid(Index, Size); - - Context.EmitLdc_I8(Value); - Context.EmitLdvec(Reg); - Context.EmitLdc_I4(Index); - Context.EmitLdc_I4(Size); - - AVectorHelper.EmitCall(Context, nameof(AVectorHelper.VectorInsertInt)); - - Context.EmitStvec(Reg); - } - - public static void EmitVectorInsertF(AILEmitterCtx Context, int Reg, int Index, int Size) - { - ThrowIfInvalidF(Index, Size); - - Context.EmitLdvec(Reg); - Context.EmitLdc_I4(Index); - - if (Size == 0) - { - AVectorHelper.EmitCall(Context, nameof(AVectorHelper.VectorInsertSingle)); - } - else if (Size == 1) - { - AVectorHelper.EmitCall(Context, nameof(AVectorHelper.VectorInsertDouble)); - } - else - { - throw new ArgumentOutOfRangeException(nameof(Size)); - } - - Context.EmitStvec(Reg); - } - - public static void EmitVectorInsertTmpF(AILEmitterCtx Context, int Index, int Size) - { - ThrowIfInvalidF(Index, Size); - - Context.EmitLdvectmp(); - Context.EmitLdc_I4(Index); - - if (Size == 0) - { - AVectorHelper.EmitCall(Context, nameof(AVectorHelper.VectorInsertSingle)); - } - else if (Size == 1) - { - AVectorHelper.EmitCall(Context, nameof(AVectorHelper.VectorInsertDouble)); - } - else - { - throw new ArgumentOutOfRangeException(nameof(Size)); - } - - Context.EmitStvectmp(); - } - - private static void ThrowIfInvalid(int Index, int Size) - { - if ((uint)Size > 3) - { - throw new ArgumentOutOfRangeException(nameof(Size)); - } - - if ((uint)Index >= 16 >> Size) - { - throw new ArgumentOutOfRangeException(nameof(Index)); - } - } - - private static void ThrowIfInvalidF(int Index, int Size) - { - if ((uint)Size > 1) - { - throw new ArgumentOutOfRangeException(nameof(Size)); - } - - if ((uint)Index >= 4 >> Size) - { - throw new ArgumentOutOfRangeException(nameof(Index)); - } - } - } -} diff --git a/ChocolArm64/Instruction/AInstEmitSimdLogical.cs b/ChocolArm64/Instruction/AInstEmitSimdLogical.cs deleted file mode 100644 index 8475a8a474..0000000000 --- a/ChocolArm64/Instruction/AInstEmitSimdLogical.cs +++ /dev/null @@ -1,221 +0,0 @@ -using ChocolArm64.Decoder; -using ChocolArm64.State; -using ChocolArm64.Translation; -using System; -using System.Reflection.Emit; -using System.Runtime.Intrinsics.X86; - -using static ChocolArm64.Instruction.AInstEmitSimdHelper; - -namespace ChocolArm64.Instruction -{ - static partial class AInstEmit - { - public static void And_V(AILEmitterCtx Context) - { - if (AOptimizations.UseSse2) - { - EmitSse2Call(Context, nameof(Sse2.And)); - } - else - { - EmitVectorBinaryOpZx(Context, () => Context.Emit(OpCodes.And)); - } - } - - public static void Bic_V(AILEmitterCtx Context) - { - EmitVectorBinaryOpZx(Context, () => - { - Context.Emit(OpCodes.Not); - Context.Emit(OpCodes.And); - }); - } - - public static void Bic_Vi(AILEmitterCtx Context) - { - EmitVectorImmBinaryOp(Context, () => - { - Context.Emit(OpCodes.Not); - Context.Emit(OpCodes.And); - }); - } - - public static void Bif_V(AILEmitterCtx Context) - { - EmitBitBif(Context, true); - } - - public static void Bit_V(AILEmitterCtx Context) - { - EmitBitBif(Context, false); - } - - private static void EmitBitBif(AILEmitterCtx Context, bool NotRm) - { - AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; - - int Bytes = Context.CurrOp.GetBitsCount() >> 3; - int Elems = Bytes >> Op.Size; - - for (int Index = 0; Index < Elems; Index++) - { - EmitVectorExtractZx(Context, Op.Rd, Index, Op.Size); - EmitVectorExtractZx(Context, Op.Rn, Index, Op.Size); - - Context.Emit(OpCodes.Xor); - - EmitVectorExtractZx(Context, Op.Rm, Index, Op.Size); - - if (NotRm) - { - Context.Emit(OpCodes.Not); - } - - Context.Emit(OpCodes.And); - - EmitVectorExtractZx(Context, Op.Rd, Index, Op.Size); - - Context.Emit(OpCodes.Xor); - - EmitVectorInsert(Context, Op.Rd, Index, Op.Size); - } - - if (Op.RegisterSize == ARegisterSize.SIMD64) - { - EmitVectorZeroUpper(Context, Op.Rd); - } - } - - public static void Bsl_V(AILEmitterCtx Context) - { - EmitVectorTernaryOpZx(Context, () => - { - Context.EmitSttmp(); - Context.EmitLdtmp(); - - Context.Emit(OpCodes.Xor); - Context.Emit(OpCodes.And); - - Context.EmitLdtmp(); - - Context.Emit(OpCodes.Xor); - }); - } - - public static void Eor_V(AILEmitterCtx Context) - { - if (AOptimizations.UseSse2) - { - EmitSse2Call(Context, nameof(Sse2.Xor)); - } - else - { - EmitVectorBinaryOpZx(Context, () => Context.Emit(OpCodes.Xor)); - } - } - - public static void Not_V(AILEmitterCtx Context) - { - EmitVectorUnaryOpZx(Context, () => Context.Emit(OpCodes.Not)); - } - - public static void Orn_V(AILEmitterCtx Context) - { - EmitVectorBinaryOpZx(Context, () => - { - Context.Emit(OpCodes.Not); - Context.Emit(OpCodes.Or); - }); - } - - public static void Orr_V(AILEmitterCtx Context) - { - if (AOptimizations.UseSse2) - { - EmitSse2Call(Context, nameof(Sse2.Or)); - } - else - { - EmitVectorBinaryOpZx(Context, () => Context.Emit(OpCodes.Or)); - } - } - - public static void Orr_Vi(AILEmitterCtx Context) - { - EmitVectorImmBinaryOp(Context, () => Context.Emit(OpCodes.Or)); - } - - public static void Rbit_V(AILEmitterCtx Context) - { - AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; - - int Elems = Op.RegisterSize == ARegisterSize.SIMD128 ? 16 : 8; - - for (int Index = 0; Index < Elems; Index++) - { - EmitVectorExtractZx(Context, Op.Rn, Index, 0); - - Context.Emit(OpCodes.Conv_U4); - - ASoftFallback.EmitCall(Context, nameof(ASoftFallback.ReverseBits8)); - - Context.Emit(OpCodes.Conv_U8); - - EmitVectorInsert(Context, Op.Rd, Index, 0); - } - - if (Op.RegisterSize == ARegisterSize.SIMD64) - { - EmitVectorZeroUpper(Context, Op.Rd); - } - } - - public static void Rev16_V(AILEmitterCtx Context) - { - EmitRev_V(Context, ContainerSize: 1); - } - - public static void Rev32_V(AILEmitterCtx Context) - { - EmitRev_V(Context, ContainerSize: 2); - } - - public static void Rev64_V(AILEmitterCtx Context) - { - EmitRev_V(Context, ContainerSize: 3); - } - - private static void EmitRev_V(AILEmitterCtx Context, int ContainerSize) - { - AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; - - if (Op.Size >= ContainerSize) - { - throw new InvalidOperationException(); - } - - int Bytes = Context.CurrOp.GetBitsCount() >> 3; - int Elems = Bytes >> Op.Size; - - int ContainerMask = (1 << (ContainerSize - Op.Size)) - 1; - - for (int Index = 0; Index < Elems; Index++) - { - int RevIndex = Index ^ ContainerMask; - - EmitVectorExtractZx(Context, Op.Rn, RevIndex, Op.Size); - - EmitVectorInsertTmp(Context, Index, Op.Size); - } - - Context.EmitLdvectmp(); - Context.EmitStvec(Op.Rd); - - if (Op.RegisterSize == ARegisterSize.SIMD64) - { - EmitVectorZeroUpper(Context, Op.Rd); - } - } - } -} diff --git a/ChocolArm64/Instruction/AInstEmitSimdMemory.cs b/ChocolArm64/Instruction/AInstEmitSimdMemory.cs deleted file mode 100644 index d98ec012e4..0000000000 --- a/ChocolArm64/Instruction/AInstEmitSimdMemory.cs +++ /dev/null @@ -1,184 +0,0 @@ -using ChocolArm64.Decoder; -using ChocolArm64.State; -using ChocolArm64.Translation; -using System; -using System.Reflection.Emit; - -using static ChocolArm64.Instruction.AInstEmitMemoryHelper; -using static ChocolArm64.Instruction.AInstEmitSimdHelper; - -namespace ChocolArm64.Instruction -{ - static partial class AInstEmit - { - public static void Ld__Vms(AILEmitterCtx Context) - { - EmitSimdMemMs(Context, IsLoad: true); - } - - public static void Ld__Vss(AILEmitterCtx Context) - { - EmitSimdMemSs(Context, IsLoad: true); - } - - public static void St__Vms(AILEmitterCtx Context) - { - EmitSimdMemMs(Context, IsLoad: false); - } - - public static void St__Vss(AILEmitterCtx Context) - { - EmitSimdMemSs(Context, IsLoad: false); - } - - private static void EmitSimdMemMs(AILEmitterCtx Context, bool IsLoad) - { - AOpCodeSimdMemMs Op = (AOpCodeSimdMemMs)Context.CurrOp; - - int 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; - - if (IsLoad) - { - Context.EmitLdarg(ATranslatedSub.MemoryArgIdx); - Context.EmitLdint(Op.Rn); - Context.EmitLdc_I8(Offset); - - Context.Emit(OpCodes.Add); - - EmitReadZxCall(Context, Op.Size); - - EmitVectorInsert(Context, Rtt, Elem, Op.Size); - - if (Op.RegisterSize == ARegisterSize.SIMD64 && Elem == Op.Elems - 1) - { - EmitVectorZeroUpper(Context, Rtt); - } - } - else - { - Context.EmitLdarg(ATranslatedSub.MemoryArgIdx); - Context.EmitLdint(Op.Rn); - Context.EmitLdc_I8(Offset); - - Context.Emit(OpCodes.Add); - - EmitVectorExtractZx(Context, Rtt, Elem, Op.Size); - - EmitWriteCall(Context, Op.Size); - } - - Offset += 1 << Op.Size; - } - - if (Op.WBack) - { - EmitSimdMemWBack(Context, Offset); - } - } - - private static void EmitSimdMemSs(AILEmitterCtx Context, bool IsLoad) - { - AOpCodeSimdMemSs Op = (AOpCodeSimdMemSs)Context.CurrOp; - - int Offset = 0; - - void EmitMemAddress() - { - Context.EmitLdarg(ATranslatedSub.MemoryArgIdx); - Context.EmitLdint(Op.Rn); - Context.EmitLdc_I8(Offset); - - Context.Emit(OpCodes.Add); - } - - if (Op.Replicate) - { - //Only loads uses the replicate mode. - if (!IsLoad) - { - throw new InvalidOperationException(); - } - - int Bytes = Context.CurrOp.GetBitsCount() >> 3; - - for (int SElem = 0; SElem < Op.SElems; SElem++) - { - int Rt = (Op.Rt + SElem) & 0x1f; - - for (int Index = 0; Index < (Bytes >> Op.Size); Index++) - { - EmitMemAddress(); - - EmitReadZxCall(Context, Op.Size); - - EmitVectorInsert(Context, Rt, Index, Op.Size); - } - - if (Op.RegisterSize == ARegisterSize.SIMD64) - { - EmitVectorZeroUpper(Context, Rt); - } - - Offset += 1 << Op.Size; - } - } - else - { - for (int SElem = 0; SElem < Op.SElems; SElem++) - { - int Rt = (Op.Rt + SElem) & 0x1f; - - if (IsLoad) - { - EmitMemAddress(); - - EmitReadZxCall(Context, Op.Size); - - EmitVectorInsert(Context, Rt, Op.Index, Op.Size); - } - else - { - EmitMemAddress(); - - EmitVectorExtractZx(Context, Rt, Op.Index, Op.Size); - - EmitWriteCall(Context, Op.Size); - } - - Offset += 1 << Op.Size; - } - } - - if (Op.WBack) - { - EmitSimdMemWBack(Context, Offset); - } - } - - private static void EmitSimdMemWBack(AILEmitterCtx Context, int Offset) - { - AOpCodeMemReg Op = (AOpCodeMemReg)Context.CurrOp; - - Context.EmitLdint(Op.Rn); - - if (Op.Rm != AThreadState.ZRIndex) - { - Context.EmitLdint(Op.Rm); - } - else - { - Context.EmitLdc_I8(Offset); - } - - Context.Emit(OpCodes.Add); - - Context.EmitStint(Op.Rn); - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Instruction/AInstEmitSimdMove.cs b/ChocolArm64/Instruction/AInstEmitSimdMove.cs deleted file mode 100644 index d67946a977..0000000000 --- a/ChocolArm64/Instruction/AInstEmitSimdMove.cs +++ /dev/null @@ -1,408 +0,0 @@ -using ChocolArm64.Decoder; -using ChocolArm64.State; -using ChocolArm64.Translation; -using System; -using System.Reflection.Emit; - -using static ChocolArm64.Instruction.AInstEmitSimdHelper; - -namespace ChocolArm64.Instruction -{ - static partial class AInstEmit - { - public static void Dup_Gp(AILEmitterCtx Context) - { - AOpCodeSimdIns Op = (AOpCodeSimdIns)Context.CurrOp; - - int Bytes = Context.CurrOp.GetBitsCount() >> 3; - - for (int Index = 0; Index < (Bytes >> Op.Size); Index++) - { - Context.EmitLdintzr(Op.Rn); - - EmitVectorInsert(Context, Op.Rd, Index, Op.Size); - } - - if (Op.RegisterSize == ARegisterSize.SIMD64) - { - EmitVectorZeroUpper(Context, Op.Rd); - } - } - - public static void Dup_S(AILEmitterCtx Context) - { - AOpCodeSimdIns Op = (AOpCodeSimdIns)Context.CurrOp; - - EmitVectorExtractZx(Context, Op.Rn, Op.DstIndex, Op.Size); - - EmitScalarSet(Context, Op.Rd, Op.Size); - } - - public static void Dup_V(AILEmitterCtx Context) - { - AOpCodeSimdIns Op = (AOpCodeSimdIns)Context.CurrOp; - - int Bytes = Context.CurrOp.GetBitsCount() >> 3; - - for (int Index = 0; Index < (Bytes >> Op.Size); Index++) - { - EmitVectorExtractZx(Context, Op.Rn, Op.DstIndex, Op.Size); - - EmitVectorInsert(Context, Op.Rd, Index, Op.Size); - } - - if (Op.RegisterSize == ARegisterSize.SIMD64) - { - EmitVectorZeroUpper(Context, Op.Rd); - } - } - - public static void Ext_V(AILEmitterCtx Context) - { - AOpCodeSimdExt Op = (AOpCodeSimdExt)Context.CurrOp; - - Context.EmitLdvec(Op.Rd); - Context.EmitStvectmp(); - - int Bytes = Context.CurrOp.GetBitsCount() >> 3; - - int Position = Op.Imm4; - - for (int Index = 0; Index < Bytes; Index++) - { - int Reg = Op.Imm4 + Index < Bytes ? Op.Rn : Op.Rm; - - if (Position == Bytes) - { - Position = 0; - } - - EmitVectorExtractZx(Context, Reg, Position++, 0); - EmitVectorInsertTmp(Context, Index, 0); - } - - Context.EmitLdvectmp(); - Context.EmitStvec(Op.Rd); - - if (Op.RegisterSize == ARegisterSize.SIMD64) - { - EmitVectorZeroUpper(Context, Op.Rd); - } - } - - public static void Fcsel_S(AILEmitterCtx Context) - { - AOpCodeSimdFcond Op = (AOpCodeSimdFcond)Context.CurrOp; - - AILLabel LblTrue = new AILLabel(); - AILLabel LblEnd = new AILLabel(); - - Context.EmitCondBranch(LblTrue, Op.Cond); - - EmitVectorExtractF(Context, Op.Rm, 0, Op.Size); - - Context.Emit(OpCodes.Br_S, LblEnd); - - Context.MarkLabel(LblTrue); - - EmitVectorExtractF(Context, Op.Rn, 0, Op.Size); - - Context.MarkLabel(LblEnd); - - EmitScalarSetF(Context, Op.Rd, Op.Size); - } - - public static void Fmov_Ftoi(AILEmitterCtx Context) - { - AOpCodeSimdCvt Op = (AOpCodeSimdCvt)Context.CurrOp; - - EmitVectorExtractZx(Context, Op.Rn, 0, 3); - - EmitIntZeroUpperIfNeeded(Context); - - Context.EmitStintzr(Op.Rd); - } - - public static void Fmov_Ftoi1(AILEmitterCtx Context) - { - AOpCodeSimdCvt Op = (AOpCodeSimdCvt)Context.CurrOp; - - EmitVectorExtractZx(Context, Op.Rn, 1, 3); - - EmitIntZeroUpperIfNeeded(Context); - - Context.EmitStintzr(Op.Rd); - } - - public static void Fmov_Itof(AILEmitterCtx Context) - { - AOpCodeSimdCvt Op = (AOpCodeSimdCvt)Context.CurrOp; - - Context.EmitLdintzr(Op.Rn); - - EmitIntZeroUpperIfNeeded(Context); - - EmitScalarSet(Context, Op.Rd, 3); - } - - public static void Fmov_Itof1(AILEmitterCtx Context) - { - AOpCodeSimdCvt Op = (AOpCodeSimdCvt)Context.CurrOp; - - Context.EmitLdintzr(Op.Rn); - - EmitIntZeroUpperIfNeeded(Context); - - EmitVectorInsert(Context, Op.Rd, 1, 3); - } - - public static void Fmov_S(AILEmitterCtx Context) - { - AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; - - EmitVectorExtractF(Context, Op.Rn, 0, Op.Size); - - EmitScalarSetF(Context, Op.Rd, Op.Size); - } - - public static void Fmov_Si(AILEmitterCtx Context) - { - AOpCodeSimdFmov Op = (AOpCodeSimdFmov)Context.CurrOp; - - Context.EmitLdc_I8(Op.Imm); - - EmitScalarSet(Context, Op.Rd, Op.Size + 2); - } - - public static void Fmov_V(AILEmitterCtx Context) - { - AOpCodeSimdImm Op = (AOpCodeSimdImm)Context.CurrOp; - - int Elems = Op.RegisterSize == ARegisterSize.SIMD128 ? 4 : 2; - - for (int Index = 0; Index < (Elems >> Op.Size); Index++) - { - Context.EmitLdc_I8(Op.Imm); - - EmitVectorInsert(Context, Op.Rd, Index, Op.Size + 2); - } - - if (Op.RegisterSize == ARegisterSize.SIMD64) - { - EmitVectorZeroUpper(Context, Op.Rd); - } - } - - public static void Ins_Gp(AILEmitterCtx Context) - { - AOpCodeSimdIns Op = (AOpCodeSimdIns)Context.CurrOp; - - Context.EmitLdintzr(Op.Rn); - - EmitVectorInsert(Context, Op.Rd, Op.DstIndex, Op.Size); - } - - public static void Ins_V(AILEmitterCtx Context) - { - AOpCodeSimdIns Op = (AOpCodeSimdIns)Context.CurrOp; - - EmitVectorExtractZx(Context, Op.Rn, Op.SrcIndex, Op.Size); - - EmitVectorInsert(Context, Op.Rd, Op.DstIndex, Op.Size); - } - - public static void Movi_V(AILEmitterCtx Context) - { - EmitVectorImmUnaryOp(Context, () => { }); - } - - public static void Mvni_V(AILEmitterCtx Context) - { - EmitVectorImmUnaryOp(Context, () => Context.Emit(OpCodes.Not)); - } - - public static void Tbl_V(AILEmitterCtx Context) - { - AOpCodeSimdTbl Op = (AOpCodeSimdTbl)Context.CurrOp; - - Context.EmitLdvec(Op.Rm); - - for (int Index = 0; Index < Op.Size; Index++) - { - Context.EmitLdvec((Op.Rn + Index) & 0x1f); - } - - switch (Op.Size) - { - case 1: AVectorHelper.EmitCall(Context, - nameof(AVectorHelper.Tbl1_V64), - nameof(AVectorHelper.Tbl1_V128)); break; - - case 2: AVectorHelper.EmitCall(Context, - nameof(AVectorHelper.Tbl2_V64), - nameof(AVectorHelper.Tbl2_V128)); break; - - case 3: AVectorHelper.EmitCall(Context, - nameof(AVectorHelper.Tbl3_V64), - nameof(AVectorHelper.Tbl3_V128)); break; - - case 4: AVectorHelper.EmitCall(Context, - nameof(AVectorHelper.Tbl4_V64), - nameof(AVectorHelper.Tbl4_V128)); break; - - default: throw new InvalidOperationException(); - } - - Context.EmitStvec(Op.Rd); - } - - public static void Trn1_V(AILEmitterCtx Context) - { - EmitVectorTranspose(Context, Part: 0); - } - - public static void Trn2_V(AILEmitterCtx Context) - { - EmitVectorTranspose(Context, Part: 1); - } - - public static void Umov_S(AILEmitterCtx Context) - { - AOpCodeSimdIns Op = (AOpCodeSimdIns)Context.CurrOp; - - EmitVectorExtractZx(Context, Op.Rn, Op.DstIndex, Op.Size); - - Context.EmitStintzr(Op.Rd); - } - - public static void Uzp1_V(AILEmitterCtx Context) - { - EmitVectorUnzip(Context, Part: 0); - } - - public static void Uzp2_V(AILEmitterCtx Context) - { - EmitVectorUnzip(Context, Part: 1); - } - - public static void Xtn_V(AILEmitterCtx Context) - { - AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; - - int Elems = 8 >> Op.Size; - - int Part = Op.RegisterSize == ARegisterSize.SIMD128 ? Elems : 0; - - for (int Index = 0; Index < Elems; Index++) - { - EmitVectorExtractZx(Context, Op.Rn, Index, Op.Size + 1); - - EmitVectorInsert(Context, Op.Rd, Part + Index, Op.Size); - } - - if (Part == 0) - { - EmitVectorZeroUpper(Context, Op.Rd); - } - } - - public static void Zip1_V(AILEmitterCtx Context) - { - EmitVectorZip(Context, Part: 0); - } - - public static void Zip2_V(AILEmitterCtx Context) - { - EmitVectorZip(Context, Part: 1); - } - - private static void EmitIntZeroUpperIfNeeded(AILEmitterCtx Context) - { - if (Context.CurrOp.RegisterSize == ARegisterSize.Int32) - { - Context.Emit(OpCodes.Conv_U4); - Context.Emit(OpCodes.Conv_U8); - } - } - - private static void EmitVectorTranspose(AILEmitterCtx Context, int Part) - { - AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; - - int Bytes = Context.CurrOp.GetBitsCount() >> 3; - - int Elems = Bytes >> Op.Size; - - for (int Index = 0; Index < Elems; Index++) - { - int Elem = (Index & ~1) + Part; - - EmitVectorExtractZx(Context, (Index & 1) == 0 ? Op.Rn : Op.Rm, Elem, Op.Size); - - EmitVectorInsertTmp(Context, Index, Op.Size); - } - - Context.EmitLdvectmp(); - Context.EmitStvec(Op.Rd); - - if (Op.RegisterSize == ARegisterSize.SIMD64) - { - EmitVectorZeroUpper(Context, Op.Rd); - } - } - - private static void EmitVectorUnzip(AILEmitterCtx Context, int Part) - { - AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; - - int Bytes = Context.CurrOp.GetBitsCount() >> 3; - - int Elems = Bytes >> Op.Size; - int Half = Elems >> 1; - - for (int Index = 0; Index < Elems; Index++) - { - int Elem = Part + ((Index & (Half - 1)) << 1); - - EmitVectorExtractZx(Context, Index < Half ? Op.Rn : Op.Rm, Elem, Op.Size); - - EmitVectorInsertTmp(Context, Index, Op.Size); - } - - Context.EmitLdvectmp(); - Context.EmitStvec(Op.Rd); - - if (Op.RegisterSize == ARegisterSize.SIMD64) - { - EmitVectorZeroUpper(Context, Op.Rd); - } - } - - private static void EmitVectorZip(AILEmitterCtx Context, int Part) - { - AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp; - - int Bytes = Context.CurrOp.GetBitsCount() >> 3; - - int Elems = Bytes >> Op.Size; - int Half = Elems >> 1; - - for (int Index = 0; Index < Elems; Index++) - { - int Elem = Part * Half + (Index >> 1); - - EmitVectorExtractZx(Context, (Index & 1) == 0 ? Op.Rn : Op.Rm, Elem, Op.Size); - - EmitVectorInsertTmp(Context, Index, Op.Size); - } - - Context.EmitLdvectmp(); - Context.EmitStvec(Op.Rd); - - if (Op.RegisterSize == ARegisterSize.SIMD64) - { - EmitVectorZeroUpper(Context, Op.Rd); - } - } - } -} diff --git a/ChocolArm64/Instruction/AInstEmitSimdShift.cs b/ChocolArm64/Instruction/AInstEmitSimdShift.cs deleted file mode 100644 index 24d35abe4c..0000000000 --- a/ChocolArm64/Instruction/AInstEmitSimdShift.cs +++ /dev/null @@ -1,365 +0,0 @@ -using ChocolArm64.Decoder; -using ChocolArm64.State; -using ChocolArm64.Translation; -using System; -using System.Reflection.Emit; - -using static ChocolArm64.Instruction.AInstEmitSimdHelper; - -namespace ChocolArm64.Instruction -{ - static partial class AInstEmit - { - public static void Shl_S(AILEmitterCtx Context) - { - AOpCodeSimdShImm Op = (AOpCodeSimdShImm)Context.CurrOp; - - EmitVectorExtractZx(Context, Op.Rn, 0, Op.Size); - - Context.EmitLdc_I4(GetImmShl(Op)); - - Context.Emit(OpCodes.Shl); - - EmitScalarSet(Context, Op.Rd, Op.Size); - } - - public static void Shl_V(AILEmitterCtx Context) - { - AOpCodeSimdShImm Op = (AOpCodeSimdShImm)Context.CurrOp; - - int Shift = Op.Imm - (8 << Op.Size); - - EmitVectorShImmBinaryZx(Context, () => Context.Emit(OpCodes.Shl), Shift); - } - - public static void Shll_V(AILEmitterCtx Context) - { - AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; - - int Shift = 8 << Op.Size; - - EmitVectorShImmWidenBinaryZx(Context, () => Context.Emit(OpCodes.Shl), Shift); - } - - public static void Shrn_V(AILEmitterCtx Context) - { - AOpCodeSimdShImm Op = (AOpCodeSimdShImm)Context.CurrOp; - - int Shift = (8 << (Op.Size + 1)) - Op.Imm; - - EmitVectorShImmNarrowBinaryZx(Context, () => Context.Emit(OpCodes.Shr_Un), Shift); - } - - public static void Sli_V(AILEmitterCtx Context) - { - AOpCodeSimdShImm Op = (AOpCodeSimdShImm)Context.CurrOp; - - int Bytes = Context.CurrOp.GetBitsCount() >> 3; - - int Shift = Op.Imm - (8 << Op.Size); - - ulong Mask = Shift != 0 ? ulong.MaxValue >> (64 - Shift) : 0; - - for (int Index = 0; Index < (Bytes >> Op.Size); Index++) - { - EmitVectorExtractZx(Context, Op.Rn, Index, Op.Size); - - Context.EmitLdc_I4(Shift); - - Context.Emit(OpCodes.Shl); - - EmitVectorExtractZx(Context, Op.Rd, Index, Op.Size); - - Context.EmitLdc_I8((long)Mask); - - Context.Emit(OpCodes.And); - Context.Emit(OpCodes.Or); - - EmitVectorInsert(Context, Op.Rd, Index, Op.Size); - } - - if (Op.RegisterSize == ARegisterSize.SIMD64) - { - EmitVectorZeroUpper(Context, Op.Rd); - } - } - - public static void Sshl_V(AILEmitterCtx Context) - { - EmitVectorShl(Context, Signed: true); - } - - public static void Sshll_V(AILEmitterCtx Context) - { - AOpCodeSimdShImm Op = (AOpCodeSimdShImm)Context.CurrOp; - - int Shift = Op.Imm - (8 << Op.Size); - - EmitVectorShImmWidenBinarySx(Context, () => Context.Emit(OpCodes.Shl), Shift); - } - - public static void Sshr_S(AILEmitterCtx Context) - { - AOpCodeSimdShImm Op = (AOpCodeSimdShImm)Context.CurrOp; - - EmitVectorExtractSx(Context, Op.Rn, 0, Op.Size); - - Context.EmitLdc_I4(GetImmShr(Op)); - - Context.Emit(OpCodes.Shr); - - EmitScalarSet(Context, Op.Rd, Op.Size); - } - - public static void Sshr_V(AILEmitterCtx Context) - { - AOpCodeSimdShImm Op = (AOpCodeSimdShImm)Context.CurrOp; - - int Shift = (8 << (Op.Size + 1)) - Op.Imm; - - EmitVectorShImmBinarySx(Context, () => Context.Emit(OpCodes.Shr), Shift); - } - - public static void Ssra_V(AILEmitterCtx Context) - { - AOpCodeSimdShImm Op = (AOpCodeSimdShImm)Context.CurrOp; - - int Shift = (8 << (Op.Size + 1)) - Op.Imm; - - Action Emit = () => - { - Context.Emit(OpCodes.Shr); - Context.Emit(OpCodes.Add); - }; - - EmitVectorShImmTernarySx(Context, Emit, Shift); - } - - public static void Ushl_V(AILEmitterCtx Context) - { - EmitVectorShl(Context, Signed: false); - } - - public static void Ushll_V(AILEmitterCtx Context) - { - AOpCodeSimdShImm Op = (AOpCodeSimdShImm)Context.CurrOp; - - int Shift = Op.Imm - (8 << Op.Size); - - EmitVectorShImmWidenBinaryZx(Context, () => Context.Emit(OpCodes.Shl), Shift); - } - - public static void Ushr_S(AILEmitterCtx Context) - { - AOpCodeSimdShImm Op = (AOpCodeSimdShImm)Context.CurrOp; - - EmitScalarUnaryOpZx(Context, () => - { - Context.EmitLdc_I4(GetImmShr(Op)); - - Context.Emit(OpCodes.Shr_Un); - }); - } - - public static void Ushr_V(AILEmitterCtx Context) - { - AOpCodeSimdShImm Op = (AOpCodeSimdShImm)Context.CurrOp; - - EmitVectorUnaryOpZx(Context, () => - { - Context.EmitLdc_I4(GetImmShr(Op)); - - Context.Emit(OpCodes.Shr_Un); - }); - } - - public static void Usra_V(AILEmitterCtx Context) - { - AOpCodeSimdShImm Op = (AOpCodeSimdShImm)Context.CurrOp; - - Action Emit = () => - { - Context.EmitLdc_I4(GetImmShr(Op)); - - Context.Emit(OpCodes.Shr_Un); - Context.Emit(OpCodes.Add); - }; - - EmitVectorOp(Context, Emit, OperFlags.RdRn, Signed: false); - } - - private static void EmitVectorShl(AILEmitterCtx Context, bool Signed) - { - //This instruction shifts the value on vector A by the number of bits - //specified on the signed, lower 8 bits of vector B. If the shift value - //is greater or equal to the data size of each lane, then the result is zero. - //Additionally, negative shifts produces right shifts by the negated shift value. - AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; - - int MaxShift = 8 << Op.Size; - - Action Emit = () => - { - AILLabel LblShl = new AILLabel(); - AILLabel LblZero = new AILLabel(); - AILLabel LblEnd = new AILLabel(); - - void EmitShift(OpCode ILOp) - { - Context.Emit(OpCodes.Dup); - - Context.EmitLdc_I4(MaxShift); - - Context.Emit(OpCodes.Bge_S, LblZero); - Context.Emit(ILOp); - Context.Emit(OpCodes.Br_S, LblEnd); - } - - Context.Emit(OpCodes.Conv_I1); - Context.Emit(OpCodes.Dup); - - Context.EmitLdc_I4(0); - - Context.Emit(OpCodes.Bge_S, LblShl); - Context.Emit(OpCodes.Neg); - - EmitShift(Signed - ? OpCodes.Shr - : OpCodes.Shr_Un); - - Context.MarkLabel(LblShl); - - EmitShift(OpCodes.Shl); - - Context.MarkLabel(LblZero); - - Context.Emit(OpCodes.Pop); - Context.Emit(OpCodes.Pop); - - Context.EmitLdc_I8(0); - - Context.MarkLabel(LblEnd); - }; - - if (Signed) - { - EmitVectorBinaryOpSx(Context, Emit); - } - else - { - EmitVectorBinaryOpZx(Context, Emit); - } - } - - private static void EmitVectorShImmBinarySx(AILEmitterCtx Context, Action Emit, int Imm) - { - EmitVectorShImmOp(Context, Emit, Imm, false, true); - } - - private static void EmitVectorShImmTernarySx(AILEmitterCtx Context, Action Emit, int Imm) - { - EmitVectorShImmOp(Context, Emit, Imm, true, true); - } - - private static void EmitVectorShImmBinaryZx(AILEmitterCtx Context, Action Emit, int Imm) - { - EmitVectorShImmOp(Context, Emit, Imm, false, false); - } - - private static void EmitVectorShImmOp(AILEmitterCtx Context, Action Emit, int Imm, bool Ternary, bool Signed) - { - AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; - - int Bytes = Context.CurrOp.GetBitsCount() >> 3; - - for (int Index = 0; Index < (Bytes >> Op.Size); Index++) - { - if (Ternary) - { - EmitVectorExtract(Context, Op.Rd, Index, Op.Size, Signed); - } - - EmitVectorExtract(Context, Op.Rn, Index, Op.Size, Signed); - - Context.EmitLdc_I4(Imm); - - Emit(); - - EmitVectorInsert(Context, Op.Rd, Index, Op.Size); - } - - if (Op.RegisterSize == ARegisterSize.SIMD64) - { - EmitVectorZeroUpper(Context, Op.Rd); - } - } - - private static void EmitVectorShImmNarrowBinarySx(AILEmitterCtx Context, Action Emit, int Imm) - { - EmitVectorShImmNarrowBinaryOp(Context, Emit, Imm, true); - } - - private static void EmitVectorShImmNarrowBinaryZx(AILEmitterCtx Context, Action Emit, int Imm) - { - EmitVectorShImmNarrowBinaryOp(Context, Emit, Imm, false); - } - - private static void EmitVectorShImmNarrowBinaryOp(AILEmitterCtx Context, Action Emit, int Imm, bool Signed) - { - AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; - - int Elems = 8 >> Op.Size; - - int Part = Op.RegisterSize == ARegisterSize.SIMD128 ? Elems : 0; - - for (int Index = 0; Index < Elems; Index++) - { - EmitVectorExtract(Context, Op.Rn, Index, Op.Size + 1, Signed); - - Context.EmitLdc_I4(Imm); - - Emit(); - - EmitVectorInsert(Context, Op.Rd, Part + Index, Op.Size); - } - - if (Part == 0) - { - EmitVectorZeroUpper(Context, Op.Rd); - } - } - - private static void EmitVectorShImmWidenBinarySx(AILEmitterCtx Context, Action Emit, int Imm) - { - EmitVectorShImmWidenBinaryOp(Context, Emit, Imm, true); - } - - private static void EmitVectorShImmWidenBinaryZx(AILEmitterCtx Context, Action Emit, int Imm) - { - EmitVectorShImmWidenBinaryOp(Context, Emit, Imm, false); - } - - private static void EmitVectorShImmWidenBinaryOp(AILEmitterCtx Context, Action Emit, int Imm, bool Signed) - { - AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; - - int Elems = 8 >> Op.Size; - - int Part = Op.RegisterSize == ARegisterSize.SIMD128 ? Elems : 0; - - for (int Index = 0; Index < Elems; Index++) - { - EmitVectorExtract(Context, Op.Rn, Part + Index, Op.Size, Signed); - - Context.EmitLdc_I4(Imm); - - Emit(); - - EmitVectorInsertTmp(Context, Index, Op.Size + 1); - } - - Context.EmitLdvectmp(); - Context.EmitStvec(Op.Rd); - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Instruction/AInstEmitSystem.cs b/ChocolArm64/Instruction/AInstEmitSystem.cs deleted file mode 100644 index 1c5d02634f..0000000000 --- a/ChocolArm64/Instruction/AInstEmitSystem.cs +++ /dev/null @@ -1,133 +0,0 @@ -using ChocolArm64.Decoder; -using ChocolArm64.State; -using ChocolArm64.Translation; -using System; -using System.Reflection; -using System.Reflection.Emit; - -namespace ChocolArm64.Instruction -{ - static partial class AInstEmit - { - public static void Hint(AILEmitterCtx Context) - { - //Execute as no-op. - } - - public static void Mrs(AILEmitterCtx Context) - { - AOpCodeSystem Op = (AOpCodeSystem)Context.CurrOp; - - Context.EmitLdarg(ATranslatedSub.StateArgIdx); - - string PropName; - - switch (GetPackedId(Op)) - { - case 0b11_011_0000_0000_001: PropName = nameof(AThreadState.CtrEl0); break; - case 0b11_011_0000_0000_111: PropName = nameof(AThreadState.DczidEl0); break; - case 0b11_011_0100_0100_000: PropName = nameof(AThreadState.Fpcr); break; - case 0b11_011_0100_0100_001: PropName = nameof(AThreadState.Fpsr); break; - case 0b11_011_1101_0000_010: PropName = nameof(AThreadState.TpidrEl0); break; - case 0b11_011_1101_0000_011: PropName = nameof(AThreadState.Tpidr); break; - case 0b11_011_1110_0000_000: PropName = nameof(AThreadState.CntfrqEl0); break; - case 0b11_011_1110_0000_001: PropName = nameof(AThreadState.CntpctEl0); break; - - default: throw new NotImplementedException($"Unknown MRS at {Op.Position:x16}"); - } - - Context.EmitCallPropGet(typeof(AThreadState), PropName); - - PropertyInfo PropInfo = typeof(AThreadState).GetProperty(PropName); - - if (PropInfo.PropertyType != typeof(long) && - PropInfo.PropertyType != typeof(ulong)) - { - Context.Emit(OpCodes.Conv_U8); - } - - Context.EmitStintzr(Op.Rt); - } - - public static void Msr(AILEmitterCtx Context) - { - AOpCodeSystem Op = (AOpCodeSystem)Context.CurrOp; - - Context.EmitLdarg(ATranslatedSub.StateArgIdx); - Context.EmitLdintzr(Op.Rt); - - string PropName; - - switch (GetPackedId(Op)) - { - case 0b11_011_0100_0100_000: PropName = nameof(AThreadState.Fpcr); break; - case 0b11_011_0100_0100_001: PropName = nameof(AThreadState.Fpsr); break; - case 0b11_011_1101_0000_010: PropName = nameof(AThreadState.TpidrEl0); break; - - default: throw new NotImplementedException($"Unknown MSR at {Op.Position:x16}"); - } - - PropertyInfo PropInfo = typeof(AThreadState).GetProperty(PropName); - - if (PropInfo.PropertyType != typeof(long) && - PropInfo.PropertyType != typeof(ulong)) - { - Context.Emit(OpCodes.Conv_U4); - } - - Context.EmitCallPropSet(typeof(AThreadState), PropName); - } - - public static void Nop(AILEmitterCtx Context) - { - //Do nothing. - } - - public static void Sys(AILEmitterCtx 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. - AOpCodeSystem Op = (AOpCodeSystem)Context.CurrOp; - - switch (GetPackedId(Op)) - { - case 0b11_011_0111_0100_001: - { - //DC ZVA - for (int Offs = 0; Offs < (4 << AThreadState.DczSizeLog2); Offs += 8) - { - Context.EmitLdarg(ATranslatedSub.MemoryArgIdx); - Context.EmitLdintzr(Op.Rt); - Context.EmitLdc_I(Offs); - - Context.Emit(OpCodes.Add); - - Context.EmitLdc_I8(0); - - AInstEmitMemoryHelper.EmitWriteCall(Context, 3); - } - - break; - } - - //No-op - case 0b11_011_0111_1110_001: //DC CIVAC - break; - } - } - - private static int GetPackedId(AOpCodeSystem 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; - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Instruction/AInstEmitter.cs b/ChocolArm64/Instruction/AInstEmitter.cs deleted file mode 100644 index 8712a7367c..0000000000 --- a/ChocolArm64/Instruction/AInstEmitter.cs +++ /dev/null @@ -1,6 +0,0 @@ -using ChocolArm64.Translation; - -namespace ChocolArm64.Instruction -{ - delegate void AInstEmitter(AILEmitterCtx Context); -} \ No newline at end of file diff --git a/ChocolArm64/Instruction/AInstInterpreter.cs b/ChocolArm64/Instruction/AInstInterpreter.cs deleted file mode 100644 index 6a855aecb0..0000000000 --- a/ChocolArm64/Instruction/AInstInterpreter.cs +++ /dev/null @@ -1,8 +0,0 @@ -using ChocolArm64.Decoder; -using ChocolArm64.Memory; -using ChocolArm64.State; - -namespace ChocolArm64.Instruction -{ - delegate void AInstInterpreter(AThreadState State, AMemory Memory, AOpCode OpCode); -} \ No newline at end of file diff --git a/ChocolArm64/Instruction/ASoftFallback.cs b/ChocolArm64/Instruction/ASoftFallback.cs deleted file mode 100644 index 5c0a9c8e3a..0000000000 --- a/ChocolArm64/Instruction/ASoftFallback.cs +++ /dev/null @@ -1,193 +0,0 @@ -using ChocolArm64.Translation; -using System; - -namespace ChocolArm64.Instruction -{ - static class ASoftFallback - { - public static void EmitCall(AILEmitterCtx Context, string MthdName) - { - Context.EmitCall(typeof(ASoftFallback), MthdName); - } - - public static ulong CountLeadingSigns(ulong Value, int Size) - { - return CountLeadingZeros((Value >> 1) ^ Value, Size - 1); - } - - public static ulong CountLeadingZeros(ulong Value, int Size) - { - int HighBit = Size - 1; - - for (int Bit = HighBit; Bit >= 0; Bit--) - { - if (((Value >> Bit) & 1) != 0) - { - return (ulong)(HighBit - Bit); - } - } - - return (ulong)Size; - } - - public static uint CountSetBits8(uint Value) - { - Value = ((Value >> 1) & 0x55) + (Value & 0x55); - Value = ((Value >> 2) & 0x33) + (Value & 0x33); - - return (Value >> 4) + (Value & 0x0f); - } - - private const uint Crc32RevPoly = 0xedb88320; - private const uint Crc32cRevPoly = 0x82f63b78; - - public static uint Crc32b(uint Crc, byte Val) => Crc32 (Crc, Crc32RevPoly, Val); - public static uint Crc32h(uint Crc, ushort Val) => Crc32h(Crc, Crc32RevPoly, Val); - public static uint Crc32w(uint Crc, uint Val) => Crc32w(Crc, Crc32RevPoly, Val); - public static uint Crc32x(uint Crc, ulong Val) => Crc32x(Crc, Crc32RevPoly, Val); - - public static uint Crc32cb(uint Crc, byte Val) => Crc32 (Crc, Crc32cRevPoly, Val); - public static uint Crc32ch(uint Crc, ushort Val) => Crc32h(Crc, Crc32cRevPoly, Val); - public static uint Crc32cw(uint Crc, uint Val) => Crc32w(Crc, Crc32cRevPoly, Val); - public static uint Crc32cx(uint Crc, ulong Val) => Crc32x(Crc, Crc32cRevPoly, Val); - - 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; - } - - public static uint ReverseBits8(uint Value) - { - Value = ((Value & 0xaa) >> 1) | ((Value & 0x55) << 1); - Value = ((Value & 0xcc) >> 2) | ((Value & 0x33) << 2); - - return (Value >> 4) | ((Value & 0x0f) << 4); - } - - public static uint ReverseBits32(uint Value) - { - Value = ((Value & 0xaaaaaaaa) >> 1) | ((Value & 0x55555555) << 1); - Value = ((Value & 0xcccccccc) >> 2) | ((Value & 0x33333333) << 2); - Value = ((Value & 0xf0f0f0f0) >> 4) | ((Value & 0x0f0f0f0f) << 4); - Value = ((Value & 0xff00ff00) >> 8) | ((Value & 0x00ff00ff) << 8); - - return (Value >> 16) | (Value << 16); - } - - public static ulong ReverseBits64(ulong Value) - { - Value = ((Value & 0xaaaaaaaaaaaaaaaa) >> 1 ) | ((Value & 0x5555555555555555) << 1 ); - Value = ((Value & 0xcccccccccccccccc) >> 2 ) | ((Value & 0x3333333333333333) << 2 ); - Value = ((Value & 0xf0f0f0f0f0f0f0f0) >> 4 ) | ((Value & 0x0f0f0f0f0f0f0f0f) << 4 ); - Value = ((Value & 0xff00ff00ff00ff00) >> 8 ) | ((Value & 0x00ff00ff00ff00ff) << 8 ); - Value = ((Value & 0xffff0000ffff0000) >> 16) | ((Value & 0x0000ffff0000ffff) << 16); - - return (Value >> 32) | (Value << 32); - } - - public static uint ReverseBytes16_32(uint Value) => (uint)ReverseBytes16_64(Value); - public static uint ReverseBytes32_32(uint Value) => (uint)ReverseBytes32_64(Value); - - public static ulong ReverseBytes16_64(ulong Value) => ReverseBytes(Value, RevSize.Rev16); - public static ulong ReverseBytes32_64(ulong Value) => ReverseBytes(Value, RevSize.Rev32); - public static ulong ReverseBytes64(ulong Value) => ReverseBytes(Value, RevSize.Rev64); - - private enum RevSize - { - Rev16, - Rev32, - Rev64 - } - - private static ulong ReverseBytes(ulong Value, RevSize Size) - { - Value = ((Value & 0xff00ff00ff00ff00) >> 8) | ((Value & 0x00ff00ff00ff00ff) << 8); - - if (Size == RevSize.Rev16) - { - return Value; - } - - Value = ((Value & 0xffff0000ffff0000) >> 16) | ((Value & 0x0000ffff0000ffff) << 16); - - if (Size == RevSize.Rev32) - { - return Value; - } - - Value = ((Value & 0xffffffff00000000) >> 32) | ((Value & 0x00000000ffffffff) << 32); - - if (Size == RevSize.Rev64) - { - return Value; - } - - throw new ArgumentException(nameof(Size)); - } - - public static long SMulHi128(long LHS, long RHS) - { - long Result = (long)UMulHi128((ulong)(LHS), (ulong)(RHS)); - if (LHS < 0) Result -= RHS; - if (RHS < 0) Result -= LHS; - return Result; - } - - public static ulong UMulHi128(ulong LHS, ulong RHS) - { - //long multiplication - //multiply 32 bits at a time in 64 bit, the result is what's carried over 64 bits. - ulong LHigh = LHS >> 32; - ulong LLow = LHS & 0xFFFFFFFF; - ulong RHigh = RHS >> 32; - ulong RLow = RHS & 0xFFFFFFFF; - ulong Z2 = LLow * RLow; - ulong T = LHigh * RLow + (Z2 >> 32); - ulong Z1 = T & 0xFFFFFFFF; - ulong Z0 = T >> 32; - Z1 += LLow * RHigh; - return LHigh * RHigh + Z0 + (Z1 >> 32); - } - } -} diff --git a/ChocolArm64/Instruction/ASoftFloat.cs b/ChocolArm64/Instruction/ASoftFloat.cs deleted file mode 100644 index e63c82beea..0000000000 --- a/ChocolArm64/Instruction/ASoftFloat.cs +++ /dev/null @@ -1,229 +0,0 @@ -using System; - -namespace ChocolArm64.Instruction -{ - static class ASoftFloat - { - static ASoftFloat() - { - InvSqrtEstimateTable = BuildInvSqrtEstimateTable(); - RecipEstimateTable = BuildRecipEstimateTable(); - } - - private static readonly byte[] RecipEstimateTable; - private static readonly byte[] InvSqrtEstimateTable; - - private static byte[] BuildInvSqrtEstimateTable() - { - byte[] Table = new byte[512]; - for (ulong index = 128; index < 512; index++) - { - ulong a = index; - if (a < 256) - { - a = (a << 1) + 1; - } - else - { - a = (a | 1) << 1; - } - - ulong b = 256; - while (a * (b + 1) * (b + 1) < (1ul << 28)) - { - b++; - } - b = (b + 1) >> 1; - - Table[index] = (byte)(b & 0xFF); - } - return Table; - } - - private static byte[] BuildRecipEstimateTable() - { - byte[] Table = new byte[256]; - for (ulong index = 0; index < 256; index++) - { - ulong a = index | 0x100; - - a = (a << 1) + 1; - ulong b = 0x80000 / a; - b = (b + 1) >> 1; - - Table[index] = (byte)(b & 0xFF); - } - return Table; - } - - public static float InvSqrtEstimate(float x) - { - return (float)InvSqrtEstimate((double)x); - } - - public static double InvSqrtEstimate(double x) - { - ulong x_bits = (ulong)BitConverter.DoubleToInt64Bits(x); - ulong x_sign = x_bits & 0x8000000000000000; - long x_exp = (long)((x_bits >> 52) & 0x7FF); - ulong scaled = x_bits & ((1ul << 52) - 1); - - if (x_exp == 0x7FF && scaled != 0) - { - // NaN - return BitConverter.Int64BitsToDouble((long)(x_bits | 0x0008000000000000)); - } - - if (x_exp == 0) - { - if (scaled == 0) - { - // Zero -> Infinity - return BitConverter.Int64BitsToDouble((long)(x_sign | 0x7ff0000000000000)); - } - - // Denormal - while ((scaled & (1 << 51)) == 0) - { - scaled <<= 1; - x_exp--; - } - scaled <<= 1; - } - - if (x_sign != 0) - { - // Negative -> NaN - return BitConverter.Int64BitsToDouble((long)0x7ff8000000000000); - } - - if (x_exp == 0x7ff && scaled == 0) - { - // Infinity -> Zero - return BitConverter.Int64BitsToDouble((long)x_sign); - } - - if (((ulong)x_exp & 1) == 1) - { - scaled >>= 45; - scaled &= 0xFF; - scaled |= 0x80; - } - else - { - scaled >>= 44; - scaled &= 0xFF; - scaled |= 0x100; - } - - ulong result_exp = ((ulong)(3068 - x_exp) / 2) & 0x7FF; - ulong estimate = (ulong)InvSqrtEstimateTable[scaled]; - ulong fraction = estimate << 44; - - ulong result = x_sign | (result_exp << 52) | fraction; - return BitConverter.Int64BitsToDouble((long)result); - } - - public static float RecipEstimate(float x) - { - return (float)RecipEstimate((double)x); - } - - public static double RecipEstimate(double x) - { - ulong x_bits = (ulong)BitConverter.DoubleToInt64Bits(x); - ulong x_sign = x_bits & 0x8000000000000000; - ulong x_exp = (x_bits >> 52) & 0x7FF; - ulong scaled = x_bits & ((1ul << 52) - 1); - - if (x_exp >= 2045) - { - if (x_exp == 0x7ff && scaled != 0) - { - // NaN - return BitConverter.Int64BitsToDouble((long)(x_bits | 0x0008000000000000)); - } - - // Infinity, or Out of range -> Zero - return BitConverter.Int64BitsToDouble((long)x_sign); - } - - if (x_exp == 0) - { - if (scaled == 0) - { - // Zero -> Infinity - return BitConverter.Int64BitsToDouble((long)(x_sign | 0x7ff0000000000000)); - } - - // Denormal - if ((scaled & (1ul << 51)) == 0) - { - x_exp = ~0ul; - scaled <<= 2; - } - else - { - scaled <<= 1; - } - } - - scaled >>= 44; - scaled &= 0xFF; - - ulong result_exp = (2045 - x_exp) & 0x7FF; - ulong estimate = (ulong)RecipEstimateTable[scaled]; - ulong fraction = estimate << 44; - - if (result_exp == 0) - { - fraction >>= 1; - fraction |= 1ul << 51; - } - else if (result_exp == 0x7FF) - { - result_exp = 0; - fraction >>= 2; - fraction |= 1ul << 50; - } - - ulong result = x_sign | (result_exp << 52) | fraction; - return BitConverter.Int64BitsToDouble((long)result); - } - - public static float RecipStep(float op1, float op2) - { - return (float)RecipStep((double)op1, (double)op2); - } - - public static double RecipStep(double op1, double op2) - { - op1 = -op1; - - ulong op1_bits = (ulong)BitConverter.DoubleToInt64Bits(op1); - ulong op2_bits = (ulong)BitConverter.DoubleToInt64Bits(op2); - - ulong op1_sign = op1_bits & 0x8000000000000000; - ulong op2_sign = op2_bits & 0x8000000000000000; - ulong op1_other = op1_bits & 0x7FFFFFFFFFFFFFFF; - ulong op2_other = op2_bits & 0x7FFFFFFFFFFFFFFF; - - bool inf1 = op1_other == 0x7ff0000000000000; - bool inf2 = op2_other == 0x7ff0000000000000; - bool zero1 = op1_other == 0; - bool zero2 = op2_other == 0; - - if ((inf1 && zero2) || (zero1 && inf2)) - { - return 2.0; - } - else if (inf1 || inf2) - { - // Infinity - return BitConverter.Int64BitsToDouble((long)(0x7ff0000000000000 | (op1_sign ^ op2_sign))); - } - - return 2.0 + op1 * op2; - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Instruction/AVectorHelper.cs b/ChocolArm64/Instruction/AVectorHelper.cs deleted file mode 100644 index a0f887b043..0000000000 --- a/ChocolArm64/Instruction/AVectorHelper.cs +++ /dev/null @@ -1,641 +0,0 @@ -using ChocolArm64.State; -using ChocolArm64.Translation; -using System; -using System.Runtime.CompilerServices; -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; - -namespace ChocolArm64.Instruction -{ - static class AVectorHelper - { - public static void EmitCall(AILEmitterCtx Context, string Name64, string Name128) - { - bool IsSimd64 = Context.CurrOp.RegisterSize == ARegisterSize.SIMD64; - - Context.EmitCall(typeof(AVectorHelper), IsSimd64 ? Name64 : Name128); - } - - public static void EmitCall(AILEmitterCtx Context, string MthdName) - { - Context.EmitCall(typeof(AVectorHelper), MthdName); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - 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; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - 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; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - 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; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - 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; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - 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; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - 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; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - 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; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - 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; - } - - public static double Max(double LHS, double RHS) - { - if (LHS == 0.0 && RHS == 0.0) - { - if (BitConverter.DoubleToInt64Bits(LHS) < 0 && - BitConverter.DoubleToInt64Bits(RHS) < 0) - return -0.0; - - return 0.0; - } - - if (LHS > RHS) - return LHS; - - if (double.IsNaN(LHS)) - return LHS; - - return RHS; - } - - public static float MaxF(float LHS, float RHS) - { - if (LHS == 0.0 && RHS == 0.0) - { - if (BitConverter.SingleToInt32Bits(LHS) < 0 && - BitConverter.SingleToInt32Bits(RHS) < 0) - return -0.0f; - - return 0.0f; - } - - if (LHS > RHS) - return LHS; - - if (float.IsNaN(LHS)) - return LHS; - - return RHS; - } - - public static double Min(double LHS, double RHS) - { - if (LHS == 0.0 && RHS == 0.0) - { - if (BitConverter.DoubleToInt64Bits(LHS) < 0 || - BitConverter.DoubleToInt64Bits(RHS) < 0) - return -0.0; - - return 0.0; - } - - if (LHS < RHS) - return LHS; - - if (double.IsNaN(LHS)) - return LHS; - - return RHS; - } - - public static float MinF(float LHS, float RHS) - { - if (LHS == 0.0 && RHS == 0.0) - { - if (BitConverter.SingleToInt32Bits(LHS) < 0 || - BitConverter.SingleToInt32Bits(RHS) < 0) - return -0.0f; - - return 0.0f; - } - - if (LHS < RHS) - return LHS; - - if (float.IsNaN(LHS)) - return LHS; - - return RHS; - } - - public static double Round(double Value, int Fpcr) - { - switch ((ARoundMode)((Fpcr >> 22) & 3)) - { - case ARoundMode.ToNearest: return Math.Round (Value); - case ARoundMode.TowardsPlusInfinity: return Math.Ceiling (Value); - case ARoundMode.TowardsMinusInfinity: return Math.Floor (Value); - case ARoundMode.TowardsZero: return Math.Truncate(Value); - } - - throw new InvalidOperationException(); - } - - public static float RoundF(float Value, int Fpcr) - { - switch ((ARoundMode)((Fpcr >> 22) & 3)) - { - case ARoundMode.ToNearest: return MathF.Round (Value); - case ARoundMode.TowardsPlusInfinity: return MathF.Ceiling (Value); - case ARoundMode.TowardsMinusInfinity: return MathF.Floor (Value); - case ARoundMode.TowardsZero: return MathF.Truncate(Value); - } - - throw new InvalidOperationException(); - } - - public static Vector128 Tbl1_V64( - Vector128 Vector, - Vector128 Tb0) - { - return Tbl(Vector, 8, Tb0); - } - - public static Vector128 Tbl1_V128( - Vector128 Vector, - Vector128 Tb0) - { - return Tbl(Vector, 16, Tb0); - } - - public static Vector128 Tbl2_V64( - Vector128 Vector, - Vector128 Tb0, - Vector128 Tb1) - { - return Tbl(Vector, 8, Tb0, Tb1); - } - - public static Vector128 Tbl2_V128( - Vector128 Vector, - Vector128 Tb0, - Vector128 Tb1) - { - return Tbl(Vector, 16, Tb0, Tb1); - } - - public static Vector128 Tbl3_V64( - Vector128 Vector, - Vector128 Tb0, - Vector128 Tb1, - Vector128 Tb2) - { - return Tbl(Vector, 8, Tb0, Tb1, Tb2); - } - - public static Vector128 Tbl3_V128( - Vector128 Vector, - Vector128 Tb0, - Vector128 Tb1, - Vector128 Tb2) - { - return Tbl(Vector, 16, Tb0, Tb1, Tb2); - } - - public static Vector128 Tbl4_V64( - Vector128 Vector, - Vector128 Tb0, - Vector128 Tb1, - Vector128 Tb2, - Vector128 Tb3) - { - return Tbl(Vector, 8, Tb0, Tb1, Tb2, Tb3); - } - - public static Vector128 Tbl4_V128( - Vector128 Vector, - Vector128 Tb0, - Vector128 Tb1, - Vector128 Tb2, - Vector128 Tb3) - { - return Tbl(Vector, 16, Tb0, Tb1, Tb2, Tb3); - } - - private static Vector128 Tbl(Vector128 Vector, int Bytes, params Vector128[] Tb) - { - Vector128 Res = new Vector128(); - - byte[] Table = new byte[Tb.Length * 16]; - - for (byte Index = 0; Index < Tb.Length; Index++) - for (byte Index2 = 0; Index2 < 16; Index2++) - { - Table[Index * 16 + Index2] = (byte)VectorExtractIntZx(Tb[Index], Index2, 0); - } - - for (byte Index = 0; Index < Bytes; Index++) - { - byte TblIdx = (byte)VectorExtractIntZx(Vector, Index, 0); - - if (TblIdx < Table.Length) - { - Res = VectorInsertInt(Table[TblIdx], Res, Index, 0); - } - } - - return Res; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static double VectorExtractDouble(Vector128 Vector, byte Index) - { - return BitConverter.Int64BitsToDouble(VectorExtractIntSx(Vector, Index, 3)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static long VectorExtractIntSx(Vector128 Vector, byte Index, int Size) - { - if (Sse41.IsSupported) - { - switch (Size) - { - case 0: - return (sbyte)Sse41.Extract(Sse.StaticCast(Vector), Index); - - case 1: - return (short)Sse2.Extract(Sse.StaticCast(Vector), Index); - - case 2: - return Sse41.Extract(Sse.StaticCast(Vector), Index); - - case 3: - return Sse41.Extract(Sse.StaticCast(Vector), Index); - } - - throw new ArgumentOutOfRangeException(nameof(Size)); - } - else if (Sse2.IsSupported) - { - switch (Size) - { - case 0: - return (sbyte)VectorExtractIntZx(Vector, Index, Size); - - case 1: - return (short)VectorExtractIntZx(Vector, Index, Size); - - case 2: - return (int)VectorExtractIntZx(Vector, Index, Size); - - case 3: - return (long)VectorExtractIntZx(Vector, Index, Size); - } - - throw new ArgumentOutOfRangeException(nameof(Size)); - } - - throw new PlatformNotSupportedException(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ulong VectorExtractIntZx(Vector128 Vector, byte Index, int Size) - { - if (Sse41.IsSupported) - { - switch (Size) - { - case 0: - return Sse41.Extract(Sse.StaticCast(Vector), Index); - - case 1: - return Sse2.Extract(Sse.StaticCast(Vector), Index); - - case 2: - return Sse41.Extract(Sse.StaticCast(Vector), Index); - - case 3: - return Sse41.Extract(Sse.StaticCast(Vector), Index); - } - - throw new ArgumentOutOfRangeException(nameof(Size)); - } - else if (Sse2.IsSupported) - { - int ShortIdx = Size == 0 - ? Index >> 1 - : Index << (Size - 1); - - ushort Value = Sse2.Extract(Sse.StaticCast(Vector), (byte)ShortIdx); - - switch (Size) - { - case 0: - return (byte)(Value >> (Index & 1) * 8); - - case 1: - return Value; - - case 2: - case 3: - { - ushort Value1 = Sse2.Extract(Sse.StaticCast(Vector), (byte)(ShortIdx + 1)); - - if (Size == 2) - { - return (uint)(Value | (Value1 << 16)); - } - - ushort Value2 = Sse2.Extract(Sse.StaticCast(Vector), (byte)(ShortIdx + 2)); - ushort Value3 = Sse2.Extract(Sse.StaticCast(Vector), (byte)(ShortIdx + 3)); - - return ((ulong)Value << 0) | - ((ulong)Value1 << 16) | - ((ulong)Value2 << 32) | - ((ulong)Value3 << 48); - } - } - - throw new ArgumentOutOfRangeException(nameof(Size)); - } - - throw new PlatformNotSupportedException(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static float VectorExtractSingle(Vector128 Vector, byte Index) - { - if (Sse41.IsSupported) - { - return Sse41.Extract(Vector, Index); - } - else if (Sse2.IsSupported) - { - Vector128 ShortVector = Sse.StaticCast(Vector); - - int Low = Sse2.Extract(ShortVector, (byte)(Index * 2 + 0)); - int High = Sse2.Extract(ShortVector, (byte)(Index * 2 + 1)); - - return BitConverter.Int32BitsToSingle(Low | (High << 16)); - } - - throw new PlatformNotSupportedException(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 VectorInsertDouble(double Value, Vector128 Vector, byte Index) - { - return VectorInsertInt((ulong)BitConverter.DoubleToInt64Bits(Value), Vector, Index, 3); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 VectorInsertInt(ulong Value, Vector128 Vector, byte Index, int Size) - { - if (Sse41.IsSupported) - { - switch (Size) - { - case 0: - return Sse.StaticCast(Sse41.Insert(Sse.StaticCast(Vector), (byte)Value, Index)); - - case 1: - return Sse.StaticCast(Sse2.Insert(Sse.StaticCast(Vector), (ushort)Value, Index)); - - case 2: - return Sse.StaticCast(Sse41.Insert(Sse.StaticCast(Vector), (uint)Value, Index)); - - case 3: - return Sse.StaticCast(Sse41.Insert(Sse.StaticCast(Vector), Value, Index)); - } - - throw new ArgumentOutOfRangeException(nameof(Size)); - } - else if (Sse2.IsSupported) - { - Vector128 ShortVector = Sse.StaticCast(Vector); - - int ShortIdx = Size == 0 - ? Index >> 1 - : Index << (Size - 1); - - switch (Size) - { - case 0: - { - ushort ShortVal = Sse2.Extract(Sse.StaticCast(Vector), (byte)ShortIdx); - - int Shift = (Index & 1) * 8; - - ShortVal &= (ushort)(0xff00 >> Shift); - - ShortVal |= (ushort)((byte)Value << Shift); - - return Sse.StaticCast(Sse2.Insert(ShortVector, ShortVal, (byte)ShortIdx)); - } - - case 1: - return Sse.StaticCast(Sse2.Insert(Sse.StaticCast(Vector), (ushort)Value, Index)); - - case 2: - case 3: - { - ShortVector = Sse2.Insert(ShortVector, (ushort)(Value >> 0), (byte)(ShortIdx + 0)); - ShortVector = Sse2.Insert(ShortVector, (ushort)(Value >> 16), (byte)(ShortIdx + 1)); - - if (Size == 3) - { - ShortVector = Sse2.Insert(ShortVector, (ushort)(Value >> 32), (byte)(ShortIdx + 2)); - ShortVector = Sse2.Insert(ShortVector, (ushort)(Value >> 48), (byte)(ShortIdx + 3)); - } - - return Sse.StaticCast(ShortVector); - } - } - - throw new ArgumentOutOfRangeException(nameof(Size)); - } - - throw new PlatformNotSupportedException(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 VectorInsertSingle(float Value, Vector128 Vector, byte Index) - { - if (Sse41.IsSupported) - { - return Sse41.Insert(Vector, Value, (byte)(Index << 4)); - } - else if (Sse2.IsSupported) - { - int IntValue = BitConverter.SingleToInt32Bits(Value); - - ushort Low = (ushort)(IntValue >> 0); - ushort High = (ushort)(IntValue >> 16); - - Vector128 ShortVector = Sse.StaticCast(Vector); - - ShortVector = Sse2.Insert(ShortVector, Low, (byte)(Index * 2 + 0)); - ShortVector = Sse2.Insert(ShortVector, High, (byte)(Index * 2 + 1)); - - return Sse.StaticCast(ShortVector); - } - - throw new PlatformNotSupportedException(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 VectorSingleToSByte(Vector128 Vector) - { - if (Sse.IsSupported) - { - return Sse.StaticCast(Vector); - } - - throw new PlatformNotSupportedException(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 VectorSingleToInt16(Vector128 Vector) - { - if (Sse.IsSupported) - { - return Sse.StaticCast(Vector); - } - - throw new PlatformNotSupportedException(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 VectorSingleToInt32(Vector128 Vector) - { - if (Sse.IsSupported) - { - return Sse.StaticCast(Vector); - } - - throw new PlatformNotSupportedException(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 VectorSingleToInt64(Vector128 Vector) - { - if (Sse.IsSupported) - { - return Sse.StaticCast(Vector); - } - - throw new PlatformNotSupportedException(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 VectorSingleToDouble(Vector128 Vector) - { - if (Sse.IsSupported) - { - return Sse.StaticCast(Vector); - } - - throw new PlatformNotSupportedException(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 VectorSByteToSingle(Vector128 Vector) - { - if (Sse.IsSupported) - { - return Sse.StaticCast(Vector); - } - - throw new PlatformNotSupportedException(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 VectorInt16ToSingle(Vector128 Vector) - { - if (Sse.IsSupported) - { - return Sse.StaticCast(Vector); - } - - throw new PlatformNotSupportedException(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 VectorInt32ToSingle(Vector128 Vector) - { - if (Sse.IsSupported) - { - return Sse.StaticCast(Vector); - } - - throw new PlatformNotSupportedException(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 VectorInt64ToSingle(Vector128 Vector) - { - if (Sse.IsSupported) - { - return Sse.StaticCast(Vector); - } - - throw new PlatformNotSupportedException(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 VectorDoubleToSingle(Vector128 Vector) - { - if (Sse.IsSupported) - { - return Sse.StaticCast(Vector); - } - - throw new PlatformNotSupportedException(); - } - } -} diff --git a/ChocolArm64/Instruction32/A32InstInterpretAlu.cs b/ChocolArm64/Instruction32/A32InstInterpretAlu.cs deleted file mode 100644 index 41b9d22aeb..0000000000 --- a/ChocolArm64/Instruction32/A32InstInterpretAlu.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace ChocolArm64.Instruction32 -{ - static partial class A32InstInterpret - { - - } -} \ No newline at end of file diff --git a/ChocolArm64/Instruction32/A32InstInterpretFlow.cs b/ChocolArm64/Instruction32/A32InstInterpretFlow.cs deleted file mode 100644 index 223fd186ca..0000000000 --- a/ChocolArm64/Instruction32/A32InstInterpretFlow.cs +++ /dev/null @@ -1,70 +0,0 @@ -using ChocolArm64.Decoder; -using ChocolArm64.Decoder32; -using ChocolArm64.Memory; -using ChocolArm64.State; - -using static ChocolArm64.Instruction32.A32InstInterpretHelper; - -namespace ChocolArm64.Instruction32 -{ - static partial class A32InstInterpret - { - public static void B(AThreadState State, AMemory Memory, AOpCode OpCode) - { - A32OpCodeBImmAl Op = (A32OpCodeBImmAl)OpCode; - - if (IsConditionTrue(State, Op.Cond)) - { - BranchWritePc(State, GetPc(State) + (uint)Op.Imm); - } - } - - public static void Bl(AThreadState State, AMemory Memory, AOpCode OpCode) - { - Blx(State, Memory, OpCode, false); - } - - public static void Blx(AThreadState State, AMemory Memory, AOpCode OpCode) - { - Blx(State, Memory, OpCode, true); - } - - public static void Blx(AThreadState State, AMemory Memory, AOpCode OpCode, bool X) - { - A32OpCodeBImmAl Op = (A32OpCodeBImmAl)OpCode; - - if (IsConditionTrue(State, Op.Cond)) - { - uint Pc = GetPc(State); - - if (State.Thumb) - { - State.R14 = Pc | 1; - } - else - { - State.R14 = Pc - 4U; - } - - if (X) - { - State.Thumb = !State.Thumb; - } - - if (!State.Thumb) - { - Pc &= ~3U; - } - - BranchWritePc(State, Pc + (uint)Op.Imm); - } - } - - private static void BranchWritePc(AThreadState State, uint Pc) - { - State.R15 = State.Thumb - ? Pc & ~1U - : Pc & ~3U; - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Instruction32/A32InstInterpretHelper.cs b/ChocolArm64/Instruction32/A32InstInterpretHelper.cs deleted file mode 100644 index 9c3c098e8e..0000000000 --- a/ChocolArm64/Instruction32/A32InstInterpretHelper.cs +++ /dev/null @@ -1,65 +0,0 @@ -using ChocolArm64.Decoder; -using ChocolArm64.State; -using System; - -namespace ChocolArm64.Instruction32 -{ - static class A32InstInterpretHelper - { - public static bool IsConditionTrue(AThreadState State, ACond Cond) - { - switch (Cond) - { - case ACond.Eq: return State.Zero; - case ACond.Ne: return !State.Zero; - case ACond.Ge_Un: return State.Carry; - case ACond.Lt_Un: return !State.Carry; - case ACond.Mi: return State.Negative; - case ACond.Pl: return !State.Negative; - case ACond.Vs: return State.Overflow; - case ACond.Vc: return !State.Overflow; - case ACond.Gt_Un: return State.Carry && !State.Zero; - case ACond.Le_Un: return !State.Carry && State.Zero; - case ACond.Ge: return State.Negative == State.Overflow; - case ACond.Lt: return State.Negative != State.Overflow; - case ACond.Gt: return State.Negative == State.Overflow && !State.Zero; - case ACond.Le: return State.Negative != State.Overflow && State.Zero; - } - - return true; - } - - public unsafe static uint GetReg(AThreadState State, int Reg) - { - if ((uint)Reg > 15) - { - throw new ArgumentOutOfRangeException(nameof(Reg)); - } - - fixed (uint* Ptr = &State.R0) - { - return *(Ptr + Reg); - } - } - - public unsafe static void SetReg(AThreadState State, int Reg, uint Value) - { - if ((uint)Reg > 15) - { - throw new ArgumentOutOfRangeException(nameof(Reg)); - } - - fixed (uint* Ptr = &State.R0) - { - *(Ptr + Reg) = Value; - } - } - - public static uint GetPc(AThreadState State) - { - //Due to the old fetch-decode-execute pipeline of old ARM CPUs, - //the PC is 4 or 8 bytes (2 instructions) ahead of the current instruction. - return State.R15 + (State.Thumb ? 2U : 4U); - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Memory/AMemory.cs b/ChocolArm64/Memory/AMemory.cs deleted file mode 100644 index da5cf00749..0000000000 --- a/ChocolArm64/Memory/AMemory.cs +++ /dev/null @@ -1,736 +0,0 @@ -using ChocolArm64.Exceptions; -using ChocolArm64.State; -using System; -using System.Collections.Generic; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; -using System.Threading; - -namespace ChocolArm64.Memory -{ - public unsafe class AMemory : IAMemory, IDisposable - { - private const long ErgMask = (4 << AThreadState.ErgSizeLog2) - 1; - - public AMemoryMgr Manager { get; private set; } - - private class ArmMonitor - { - public long Position; - public bool ExState; - - public bool HasExclusiveAccess(long Position) - { - return this.Position == Position && ExState; - } - } - - private Dictionary Monitors; - - public IntPtr Ram { get; private set; } - - private byte* RamPtr; - - private int HostPageSize; - - public AMemory() - { - Manager = new AMemoryMgr(); - - Monitors = new Dictionary(); - - IntPtr Size = (IntPtr)AMemoryMgr.RamSize + AMemoryMgr.PageSize; - - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - Ram = AMemoryWin32.Allocate(Size); - - HostPageSize = AMemoryWin32.GetPageSize(Ram, Size); - } - else - { - Ram = Marshal.AllocHGlobal(Size); - } - - RamPtr = (byte*)Ram; - } - - public void RemoveMonitor(AThreadState State) - { - lock (Monitors) - { - ClearExclusive(State); - - Monitors.Remove(State.ThreadId); - } - } - - public void SetExclusive(AThreadState ThreadState, long Position) - { - Position &= ~ErgMask; - - lock (Monitors) - { - foreach (ArmMonitor Mon in Monitors.Values) - { - if (Mon.Position == Position && Mon.ExState) - { - Mon.ExState = false; - } - } - - if (!Monitors.TryGetValue(ThreadState.ThreadId, out ArmMonitor ThreadMon)) - { - ThreadMon = new ArmMonitor(); - - Monitors.Add(ThreadState.ThreadId, ThreadMon); - } - - ThreadMon.Position = Position; - ThreadMon.ExState = true; - } - } - - public bool TestExclusive(AThreadState ThreadState, long Position) - { - //Note: Any call to this method also should be followed by a - //call to ClearExclusiveForStore if this method returns true. - Position &= ~ErgMask; - - Monitor.Enter(Monitors); - - if (!Monitors.TryGetValue(ThreadState.ThreadId, out ArmMonitor ThreadMon)) - { - return false; - } - - bool ExState = ThreadMon.HasExclusiveAccess(Position); - - if (!ExState) - { - Monitor.Exit(Monitors); - } - - return ExState; - } - - public void ClearExclusiveForStore(AThreadState ThreadState) - { - if (Monitors.TryGetValue(ThreadState.ThreadId, out ArmMonitor ThreadMon)) - { - ThreadMon.ExState = false; - } - - Monitor.Exit(Monitors); - } - - public void ClearExclusive(AThreadState ThreadState) - { - lock (Monitors) - { - if (Monitors.TryGetValue(ThreadState.ThreadId, out ArmMonitor ThreadMon)) - { - ThreadMon.ExState = false; - } - } - } - - public void WriteInt32ToSharedAddr(long Position, int Value) - { - long MaskedPosition = Position & ~ErgMask; - - lock (Monitors) - { - foreach (ArmMonitor Mon in Monitors.Values) - { - if (Mon.Position == MaskedPosition && Mon.ExState) - { - Mon.ExState = false; - } - } - - WriteInt32(Position, Value); - } - } - - public int GetHostPageSize() - { - return HostPageSize; - } - - public bool[] IsRegionModified(long Position, long Size) - { - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - return null; - } - - long EndPos = Position + Size; - - if ((ulong)EndPos < (ulong)Position) - { - return null; - } - - if ((ulong)EndPos > AMemoryMgr.RamSize) - { - return null; - } - - IntPtr MemAddress = new IntPtr(RamPtr + Position); - IntPtr MemSize = new IntPtr(Size); - - int HostPageMask = HostPageSize - 1; - - Position &= ~HostPageMask; - - Size = EndPos - Position; - - IntPtr[] Addresses = new IntPtr[(Size + HostPageMask) / HostPageSize]; - - AMemoryWin32.IsRegionModified(MemAddress, MemSize, Addresses, out int Count); - - bool[] Modified = new bool[Addresses.Length]; - - for (int Index = 0; Index < Count; Index++) - { - long VA = Addresses[Index].ToInt64() - Ram.ToInt64(); - - Modified[(VA - Position) / HostPageSize] = true; - } - - return Modified; - } - - public sbyte ReadSByte(long Position) - { - return (sbyte)ReadByte(Position); - } - - public short ReadInt16(long Position) - { - return (short)ReadUInt16(Position); - } - - public int ReadInt32(long Position) - { - return (int)ReadUInt32(Position); - } - - public long ReadInt64(long Position) - { - return (long)ReadUInt64(Position); - } - - public byte ReadByte(long Position) - { - EnsureAccessIsValid(Position, AMemoryPerm.Read); - - return ReadByteUnchecked(Position); - } - - public ushort ReadUInt16(long Position) - { - EnsureAccessIsValid(Position + 0, AMemoryPerm.Read); - EnsureAccessIsValid(Position + 1, AMemoryPerm.Read); - - return ReadUInt16Unchecked(Position); - } - - public uint ReadUInt32(long Position) - { - EnsureAccessIsValid(Position + 0, AMemoryPerm.Read); - EnsureAccessIsValid(Position + 3, AMemoryPerm.Read); - - return ReadUInt32Unchecked(Position); - } - - public ulong ReadUInt64(long Position) - { - EnsureAccessIsValid(Position + 0, AMemoryPerm.Read); - EnsureAccessIsValid(Position + 7, AMemoryPerm.Read); - - return ReadUInt64Unchecked(Position); - } - - public Vector128 ReadVector8(long Position) - { - if (Sse2.IsSupported) - { - return Sse.StaticCast(Sse2.SetVector128(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ReadByte(Position))); - } - else - { - throw new PlatformNotSupportedException(); - } - } - - public Vector128 ReadVector16(long Position) - { - if (Sse2.IsSupported) - { - return Sse.StaticCast(Sse2.Insert(Sse2.SetZeroVector128(), ReadUInt16(Position), 0)); - } - else - { - throw new PlatformNotSupportedException(); - } - } - - public Vector128 ReadVector32(long Position) - { - EnsureAccessIsValid(Position + 0, AMemoryPerm.Read); - EnsureAccessIsValid(Position + 3, AMemoryPerm.Read); - - if (Sse.IsSupported) - { - return Sse.LoadScalarVector128((float*)(RamPtr + (uint)Position)); - } - else - { - throw new PlatformNotSupportedException(); - } - } - - public Vector128 ReadVector64(long Position) - { - EnsureAccessIsValid(Position + 0, AMemoryPerm.Read); - EnsureAccessIsValid(Position + 7, AMemoryPerm.Read); - - if (Sse2.IsSupported) - { - return Sse.StaticCast(Sse2.LoadScalarVector128((double*)(RamPtr + (uint)Position))); - } - else - { - throw new PlatformNotSupportedException(); - } - } - - public Vector128 ReadVector128(long Position) - { - EnsureAccessIsValid(Position + 0, AMemoryPerm.Read); - EnsureAccessIsValid(Position + 15, AMemoryPerm.Read); - - if (Sse.IsSupported) - { - return Sse.LoadVector128((float*)(RamPtr + (uint)Position)); - } - else - { - throw new PlatformNotSupportedException(); - } - } - - public sbyte ReadSByteUnchecked(long Position) - { - return (sbyte)ReadByteUnchecked(Position); - } - - public short ReadInt16Unchecked(long Position) - { - return (short)ReadUInt16Unchecked(Position); - } - - public int ReadInt32Unchecked(long Position) - { - return (int)ReadUInt32Unchecked(Position); - } - - public long ReadInt64Unchecked(long Position) - { - return (long)ReadUInt64Unchecked(Position); - } - - public byte ReadByteUnchecked(long Position) - { - return *((byte*)(RamPtr + (uint)Position)); - } - - public ushort ReadUInt16Unchecked(long Position) - { - return *((ushort*)(RamPtr + (uint)Position)); - } - - public uint ReadUInt32Unchecked(long Position) - { - return *((uint*)(RamPtr + (uint)Position)); - } - - public ulong ReadUInt64Unchecked(long Position) - { - return *((ulong*)(RamPtr + (uint)Position)); - } - - public Vector128 ReadVector8Unchecked(long Position) - { - if (Sse2.IsSupported) - { - return Sse.StaticCast(Sse2.SetVector128(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ReadByte(Position))); - } - else - { - throw new PlatformNotSupportedException(); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector128 ReadVector16Unchecked(long Position) - { - if (Sse2.IsSupported) - { - return Sse.StaticCast(Sse2.Insert(Sse2.SetZeroVector128(), ReadUInt16Unchecked(Position), 0)); - } - else - { - throw new PlatformNotSupportedException(); - } - } - - [MethodImpl(MethodImplOptions.NoInlining)] - public Vector128 ReadVector32Unchecked(long Position) - { - if (Sse.IsSupported) - { - return Sse.LoadScalarVector128((float*)(RamPtr + (uint)Position)); - } - else - { - throw new PlatformNotSupportedException(); - } - } - - [MethodImpl(MethodImplOptions.NoInlining)] - public Vector128 ReadVector64Unchecked(long Position) - { - if (Sse2.IsSupported) - { - return Sse.StaticCast(Sse2.LoadScalarVector128((double*)(RamPtr + (uint)Position))); - } - else - { - throw new PlatformNotSupportedException(); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector128 ReadVector128Unchecked(long Position) - { - if (Sse.IsSupported) - { - return Sse.LoadVector128((float*)(RamPtr + (uint)Position)); - } - else - { - throw new PlatformNotSupportedException(); - } - } - - public byte[] ReadBytes(long Position, long Size) - { - if ((uint)Size > int.MaxValue) - { - throw new ArgumentOutOfRangeException(nameof(Size)); - } - - EnsureRangeIsValid(Position, Size, AMemoryPerm.Read); - - byte[] Data = new byte[Size]; - - Marshal.Copy((IntPtr)(RamPtr + (uint)Position), Data, 0, (int)Size); - - return Data; - } - - public void WriteSByte(long Position, sbyte Value) - { - WriteByte(Position, (byte)Value); - } - - public void WriteInt16(long Position, short Value) - { - WriteUInt16(Position, (ushort)Value); - } - - public void WriteInt32(long Position, int Value) - { - WriteUInt32(Position, (uint)Value); - } - - public void WriteInt64(long Position, long Value) - { - WriteUInt64(Position, (ulong)Value); - } - - public void WriteByte(long Position, byte Value) - { - EnsureAccessIsValid(Position, AMemoryPerm.Write); - - WriteByteUnchecked(Position, Value); - } - - public void WriteUInt16(long Position, ushort Value) - { - EnsureAccessIsValid(Position + 0, AMemoryPerm.Write); - EnsureAccessIsValid(Position + 1, AMemoryPerm.Write); - - WriteUInt16Unchecked(Position, Value); - } - - public void WriteUInt32(long Position, uint Value) - { - EnsureAccessIsValid(Position + 0, AMemoryPerm.Write); - EnsureAccessIsValid(Position + 3, AMemoryPerm.Write); - - WriteUInt32Unchecked(Position, Value); - } - - public void WriteUInt64(long Position, ulong Value) - { - EnsureAccessIsValid(Position + 0, AMemoryPerm.Write); - EnsureAccessIsValid(Position + 7, AMemoryPerm.Write); - - WriteUInt64Unchecked(Position, Value); - } - - public void WriteVector8(long Position, Vector128 Value) - { - if (Sse41.IsSupported) - { - WriteByte(Position, Sse41.Extract(Sse.StaticCast(Value), 0)); - } - else if (Sse2.IsSupported) - { - WriteByte(Position, (byte)Sse2.Extract(Sse.StaticCast(Value), 0)); - } - else - { - throw new PlatformNotSupportedException(); - } - } - - public void WriteVector16(long Position, Vector128 Value) - { - if (Sse2.IsSupported) - { - WriteUInt16(Position, Sse2.Extract(Sse.StaticCast(Value), 0)); - } - else - { - throw new PlatformNotSupportedException(); - } - } - - public void WriteVector32(long Position, Vector128 Value) - { - EnsureAccessIsValid(Position + 0, AMemoryPerm.Write); - EnsureAccessIsValid(Position + 3, AMemoryPerm.Write); - - if (Sse.IsSupported) - { - Sse.StoreScalar((float*)(RamPtr + (uint)Position), Value); - } - else - { - throw new PlatformNotSupportedException(); - } - } - - public void WriteVector64(long Position, Vector128 Value) - { - EnsureAccessIsValid(Position + 0, AMemoryPerm.Write); - EnsureAccessIsValid(Position + 7, AMemoryPerm.Write); - - if (Sse2.IsSupported) - { - Sse2.StoreScalar((double*)(RamPtr + (uint)Position), Sse.StaticCast(Value)); - } - else - { - throw new PlatformNotSupportedException(); - } - } - - public void WriteVector128(long Position, Vector128 Value) - { - EnsureAccessIsValid(Position + 0, AMemoryPerm.Write); - EnsureAccessIsValid(Position + 15, AMemoryPerm.Write); - - if (Sse.IsSupported) - { - Sse.Store((float*)(RamPtr + (uint)Position), Value); - } - else - { - throw new PlatformNotSupportedException(); - } - } - - public void WriteSByteUnchecked(long Position, sbyte Value) - { - WriteByteUnchecked(Position, (byte)Value); - } - - public void WriteInt16Unchecked(long Position, short Value) - { - WriteUInt16Unchecked(Position, (ushort)Value); - } - - public void WriteInt32Unchecked(long Position, int Value) - { - WriteUInt32Unchecked(Position, (uint)Value); - } - - public void WriteInt64Unchecked(long Position, long Value) - { - WriteUInt64Unchecked(Position, (ulong)Value); - } - - public void WriteByteUnchecked(long Position, byte Value) - { - *((byte*)(RamPtr + (uint)Position)) = Value; - } - - public void WriteUInt16Unchecked(long Position, ushort Value) - { - *((ushort*)(RamPtr + (uint)Position)) = Value; - } - - public void WriteUInt32Unchecked(long Position, uint Value) - { - *((uint*)(RamPtr + (uint)Position)) = Value; - } - - public void WriteUInt64Unchecked(long Position, ulong Value) - { - *((ulong*)(RamPtr + (uint)Position)) = Value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteVector8Unchecked(long Position, Vector128 Value) - { - if (Sse41.IsSupported) - { - WriteByteUnchecked(Position, Sse41.Extract(Sse.StaticCast(Value), 0)); - } - else if (Sse2.IsSupported) - { - WriteByteUnchecked(Position, (byte)Sse2.Extract(Sse.StaticCast(Value), 0)); - } - else - { - throw new PlatformNotSupportedException(); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteVector16Unchecked(long Position, Vector128 Value) - { - if (Sse2.IsSupported) - { - WriteUInt16Unchecked(Position, Sse2.Extract(Sse.StaticCast(Value), 0)); - } - else - { - throw new PlatformNotSupportedException(); - } - } - - [MethodImpl(MethodImplOptions.NoInlining)] - public void WriteVector32Unchecked(long Position, Vector128 Value) - { - if (Sse.IsSupported) - { - Sse.StoreScalar((float*)(RamPtr + (uint)Position), Value); - } - else - { - throw new PlatformNotSupportedException(); - } - } - - [MethodImpl(MethodImplOptions.NoInlining)] - public void WriteVector64Unchecked(long Position, Vector128 Value) - { - if (Sse2.IsSupported) - { - Sse2.StoreScalar((double*)(RamPtr + (uint)Position), Sse.StaticCast(Value)); - } - else - { - throw new PlatformNotSupportedException(); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteVector128Unchecked(long Position, Vector128 Value) - { - if (Sse.IsSupported) - { - Sse.Store((float*)(RamPtr + (uint)Position), Value); - } - else - { - throw new PlatformNotSupportedException(); - } - } - - public void WriteBytes(long Position, byte[] Data) - { - EnsureRangeIsValid(Position, (uint)Data.Length, AMemoryPerm.Write); - - Marshal.Copy(Data, 0, (IntPtr)(RamPtr + (uint)Position), Data.Length); - } - - private void EnsureRangeIsValid(long Position, long Size, AMemoryPerm Perm) - { - long EndPos = Position + Size; - - Position &= ~AMemoryMgr.PageMask; - - while ((ulong)Position < (ulong)EndPos) - { - EnsureAccessIsValid(Position, Perm); - - Position += AMemoryMgr.PageSize; - } - } - - private void EnsureAccessIsValid(long Position, AMemoryPerm Perm) - { - if (!Manager.IsMapped(Position)) - { - throw new VmmPageFaultException(Position); - } - - if (!Manager.HasPermission(Position, Perm)) - { - throw new VmmAccessViolationException(Position, Perm); - } - } - - public void Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool disposing) - { - if (Ram != IntPtr.Zero) - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - AMemoryWin32.Free(Ram); - } - else - { - Marshal.FreeHGlobal(Ram); - } - - Ram = IntPtr.Zero; - } - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Memory/AMemoryHelper.cs b/ChocolArm64/Memory/AMemoryHelper.cs deleted file mode 100644 index 0a23a2f89d..0000000000 --- a/ChocolArm64/Memory/AMemoryHelper.cs +++ /dev/null @@ -1,83 +0,0 @@ -using System; -using System.IO; -using System.Runtime.InteropServices; -using System.Text; - -namespace ChocolArm64.Memory -{ - public static class AMemoryHelper - { - public static void FillWithZeros(AMemory Memory, long Position, int Size) - { - int Size8 = Size & ~(8 - 1); - - for (int Offs = 0; Offs < Size8; Offs += 8) - { - Memory.WriteInt64(Position + Offs, 0); - } - - for (int Offs = Size8; Offs < (Size - Size8); Offs++) - { - Memory.WriteByte(Position + Offs, 0); - } - } - - public unsafe static T Read(AMemory Memory, long Position) where T : struct - { - long Size = Marshal.SizeOf(); - - if ((ulong)(Position + Size) > AMemoryMgr.AddrSize) - { - throw new ArgumentOutOfRangeException(nameof(Position)); - } - - IntPtr Ptr = new IntPtr((byte*)Memory.Ram + Position); - - return Marshal.PtrToStructure(Ptr); - } - - public unsafe static void Write(AMemory Memory, long Position, T Value) where T : struct - { - long Size = Marshal.SizeOf(); - - if ((ulong)(Position + Size) > AMemoryMgr.AddrSize) - { - throw new ArgumentOutOfRangeException(nameof(Position)); - } - - IntPtr Ptr = new IntPtr((byte*)Memory.Ram + Position); - - Marshal.StructureToPtr(Value, Ptr, false); - } - - public static string ReadAsciiString(AMemory Memory, long Position, long MaxSize = -1) - { - using (MemoryStream MS = new MemoryStream()) - { - for (long Offs = 0; Offs < MaxSize || MaxSize == -1; Offs++) - { - byte Value = (byte)Memory.ReadByte(Position + Offs); - - if (Value == 0) - { - break; - } - - MS.WriteByte(Value); - } - - return Encoding.ASCII.GetString(MS.ToArray()); - } - } - - public static long PageRoundUp(long Value) - { - return (Value + AMemoryMgr.PageMask) & ~AMemoryMgr.PageMask; - } - - public static long PageRoundDown(long Value) - { - return Value & ~AMemoryMgr.PageMask; - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Memory/AMemoryMapInfo.cs b/ChocolArm64/Memory/AMemoryMapInfo.cs deleted file mode 100644 index 02dd3055cc..0000000000 --- a/ChocolArm64/Memory/AMemoryMapInfo.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace ChocolArm64.Memory -{ - public class AMemoryMapInfo - { - public long Position { get; private set; } - public long Size { get; private set; } - public int Type { get; private set; } - public int Attr { get; private set; } - - public AMemoryPerm Perm { get; private set; } - - public AMemoryMapInfo(long Position, long Size, int Type, int Attr, AMemoryPerm Perm) - { - this.Position = Position; - this.Size = Size; - this.Type = Type; - this.Attr = Attr; - this.Perm = Perm; - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Memory/AMemoryMgr.cs b/ChocolArm64/Memory/AMemoryMgr.cs deleted file mode 100644 index 8a165b0796..0000000000 --- a/ChocolArm64/Memory/AMemoryMgr.cs +++ /dev/null @@ -1,258 +0,0 @@ -using System; - -namespace ChocolArm64.Memory -{ - public class AMemoryMgr - { - public const long RamSize = 4L * 1024 * 1024 * 1024; - public const long AddrSize = RamSize; - - private const int PTLvl0Bits = 10; - private const int PTLvl1Bits = 10; - private const int PTPageBits = 12; - - private const int PTLvl0Size = 1 << PTLvl0Bits; - private const int PTLvl1Size = 1 << PTLvl1Bits; - public const int PageSize = 1 << PTPageBits; - - private const int PTLvl0Mask = PTLvl0Size - 1; - private const int PTLvl1Mask = PTLvl1Size - 1; - public const int PageMask = PageSize - 1; - - private const int PTLvl0Bit = PTPageBits + PTLvl1Bits; - private const int PTLvl1Bit = PTPageBits; - - private enum PTMap - { - Unmapped, - Mapped - } - - private struct PTEntry - { - public PTMap Map; - public AMemoryPerm Perm; - - public int Type; - public int Attr; - - public PTEntry(PTMap Map, AMemoryPerm Perm, int Type, int Attr) - { - this.Map = Map; - this.Perm = Perm; - this.Type = Type; - this.Attr = Attr; - } - } - - private PTEntry[][] PageTable; - - public AMemoryMgr() - { - PageTable = new PTEntry[PTLvl0Size][]; - } - - public void Map(long Position, long Size, int Type, AMemoryPerm Perm) - { - SetPTEntry(Position, Size, new PTEntry(PTMap.Mapped, Perm, Type, 0)); - } - - public void Unmap(long Position, long Size) - { - SetPTEntry(Position, Size, new PTEntry(PTMap.Unmapped, 0, 0, 0)); - } - - public void Unmap(long Position, long Size, int Type) - { - SetPTEntry(Position, Size, Type, new PTEntry(PTMap.Unmapped, 0, 0, 0)); - } - - public void Reprotect(long Position, long Size, AMemoryPerm Perm) - { - Position = AMemoryHelper.PageRoundDown(Position); - - Size = AMemoryHelper.PageRoundUp(Size); - - long PagesCount = Size / PageSize; - - while (PagesCount-- > 0) - { - PTEntry Entry = GetPTEntry(Position); - - Entry.Perm = Perm; - - SetPTEntry(Position, Entry); - - Position += PageSize; - } - } - - public AMemoryMapInfo GetMapInfo(long Position) - { - if (!IsValidPosition(Position)) - { - return null; - } - - Position = AMemoryHelper.PageRoundDown(Position); - - PTEntry BaseEntry = GetPTEntry(Position); - - bool IsSameSegment(long Pos) - { - if (!IsValidPosition(Pos)) - { - return false; - } - - PTEntry Entry = GetPTEntry(Pos); - - return Entry.Map == BaseEntry.Map && - Entry.Perm == BaseEntry.Perm && - Entry.Type == BaseEntry.Type && - Entry.Attr == BaseEntry.Attr; - } - - long Start = Position; - long End = Position + PageSize; - - while (Start > 0 && IsSameSegment(Start - PageSize)) - { - Start -= PageSize; - } - - while (End < AddrSize && IsSameSegment(End)) - { - End += PageSize; - } - - long Size = End - Start; - - return new AMemoryMapInfo( - Start, - Size, - BaseEntry.Type, - BaseEntry.Attr, - BaseEntry.Perm); - } - - public void ClearAttrBit(long Position, long Size, int Bit) - { - while (Size > 0) - { - PTEntry Entry = GetPTEntry(Position); - - Entry.Attr &= ~(1 << Bit); - - SetPTEntry(Position, Entry); - - Position += PageSize; - Size -= PageSize; - } - } - - public void SetAttrBit(long Position, long Size, int Bit) - { - while (Size > 0) - { - PTEntry Entry = GetPTEntry(Position); - - Entry.Attr |= (1 << Bit); - - SetPTEntry(Position, Entry); - - Position += PageSize; - Size -= PageSize; - } - } - - public bool HasPermission(long Position, AMemoryPerm Perm) - { - return GetPTEntry(Position).Perm.HasFlag(Perm); - } - - public bool IsValidPosition(long Position) - { - if (Position >> PTLvl0Bits + PTLvl1Bits + PTPageBits != 0) - { - return false; - } - - return true; - } - - public bool IsMapped(long Position) - { - if (Position >> PTLvl0Bits + PTLvl1Bits + PTPageBits != 0) - { - return false; - } - - long L0 = (Position >> PTLvl0Bit) & PTLvl0Mask; - long L1 = (Position >> PTLvl1Bit) & PTLvl1Mask; - - if (PageTable[L0] == null) - { - return false; - } - - return PageTable[L0][L1].Map != PTMap.Unmapped; - } - - private PTEntry GetPTEntry(long Position) - { - long L0 = (Position >> PTLvl0Bit) & PTLvl0Mask; - long L1 = (Position >> PTLvl1Bit) & PTLvl1Mask; - - if (PageTable[L0] == null) - { - return default(PTEntry); - } - - return PageTable[L0][L1]; - } - - private void SetPTEntry(long Position, long Size, PTEntry Entry) - { - while (Size > 0) - { - SetPTEntry(Position, Entry); - - Position += PageSize; - Size -= PageSize; - } - } - - private void SetPTEntry(long Position, long Size, int Type, PTEntry Entry) - { - while (Size > 0) - { - if (GetPTEntry(Position).Type == Type) - { - SetPTEntry(Position, Entry); - } - - Position += PageSize; - Size -= PageSize; - } - } - - private void SetPTEntry(long Position, PTEntry Entry) - { - if (!IsValidPosition(Position)) - { - throw new ArgumentOutOfRangeException(nameof(Position)); - } - - long L0 = (Position >> PTLvl0Bit) & PTLvl0Mask; - long L1 = (Position >> PTLvl1Bit) & PTLvl1Mask; - - if (PageTable[L0] == null) - { - PageTable[L0] = new PTEntry[PTLvl1Size]; - } - - PageTable[L0][L1] = Entry; - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Memory/AMemoryPerm.cs b/ChocolArm64/Memory/AMemoryPerm.cs deleted file mode 100644 index b425eb94b2..0000000000 --- a/ChocolArm64/Memory/AMemoryPerm.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace ChocolArm64.Memory -{ - [Flags] - public enum AMemoryPerm - { - None = 0, - Read = 1 << 0, - Write = 1 << 1, - Execute = 1 << 2, - RW = Read | Write, - RX = Read | Execute - } -} \ No newline at end of file diff --git a/ChocolArm64/Memory/AMemoryWin32.cs b/ChocolArm64/Memory/AMemoryWin32.cs deleted file mode 100644 index 387ca32c2e..0000000000 --- a/ChocolArm64/Memory/AMemoryWin32.cs +++ /dev/null @@ -1,92 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace ChocolArm64.Memory -{ - static class AMemoryWin32 - { - private const int MEM_COMMIT = 0x00001000; - private const int MEM_RESERVE = 0x00002000; - private const int MEM_WRITE_WATCH = 0x00200000; - - private const int PAGE_READWRITE = 0x04; - - private const int MEM_RELEASE = 0x8000; - - private const int WRITE_WATCH_FLAG_RESET = 1; - - [DllImport("kernel32.dll")] - private static extern IntPtr VirtualAlloc(IntPtr lpAddress, IntPtr dwSize, int flAllocationType, int flProtect); - - [DllImport("kernel32.dll")] - private static extern bool VirtualFree(IntPtr lpAddress, IntPtr dwSize, int dwFreeType); - - [DllImport("kernel32.dll")] - private unsafe static extern int GetWriteWatch( - int dwFlags, - IntPtr lpBaseAddress, - IntPtr dwRegionSize, - IntPtr[] lpAddresses, - long* lpdwCount, - long* lpdwGranularity); - - public static IntPtr Allocate(IntPtr Size) - { - const int Flags = MEM_COMMIT | MEM_RESERVE | MEM_WRITE_WATCH; - - IntPtr Address = VirtualAlloc(IntPtr.Zero, Size, Flags, PAGE_READWRITE); - - if (Address == IntPtr.Zero) - { - throw new InvalidOperationException(); - } - - return Address; - } - - public static void Free(IntPtr Address) - { - VirtualFree(Address, IntPtr.Zero, MEM_RELEASE); - } - - public unsafe static int GetPageSize(IntPtr Address, IntPtr Size) - { - IntPtr[] Addresses = new IntPtr[1]; - - long Count = Addresses.Length; - - long Granularity; - - GetWriteWatch( - 0, - Address, - Size, - Addresses, - &Count, - &Granularity); - - return (int)Granularity; - } - - public unsafe static void IsRegionModified( - IntPtr Address, - IntPtr Size, - IntPtr[] Addresses, - out int AddrCount) - { - long Count = Addresses.Length; - - long Granularity; - - GetWriteWatch( - WRITE_WATCH_FLAG_RESET, - Address, - Size, - Addresses, - &Count, - &Granularity); - - AddrCount = (int)Count; - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Memory/IAMemory.cs b/ChocolArm64/Memory/IAMemory.cs deleted file mode 100644 index 5b7d17bb8b..0000000000 --- a/ChocolArm64/Memory/IAMemory.cs +++ /dev/null @@ -1,37 +0,0 @@ -namespace ChocolArm64.Memory -{ - public interface IAMemory - { - sbyte ReadSByte(long Position); - - short ReadInt16(long Position); - - int ReadInt32(long Position); - - long ReadInt64(long Position); - - byte ReadByte(long Position); - - ushort ReadUInt16(long Position); - - uint ReadUInt32(long Position); - - ulong ReadUInt64(long Position); - - void WriteSByte(long Position, sbyte Value); - - void WriteInt16(long Position, short Value); - - void WriteInt32(long Position, int Value); - - void WriteInt64(long Position, long Value); - - void WriteByte(long Position, byte Value); - - void WriteUInt16(long Position, ushort Value); - - void WriteUInt32(long Position, uint Value); - - void WriteUInt64(long Position, ulong Value); - } -} \ No newline at end of file diff --git a/ChocolArm64/State/AExecutionMode.cs b/ChocolArm64/State/AExecutionMode.cs deleted file mode 100644 index 8632da7747..0000000000 --- a/ChocolArm64/State/AExecutionMode.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace ChocolArm64.State -{ - enum AExecutionMode - { - AArch32, - AArch64 - } -} \ No newline at end of file diff --git a/ChocolArm64/State/APState.cs b/ChocolArm64/State/APState.cs deleted file mode 100644 index f55431a661..0000000000 --- a/ChocolArm64/State/APState.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; - -namespace ChocolArm64.State -{ - [Flags] - public enum APState - { - VBit = 28, - CBit = 29, - ZBit = 30, - NBit = 31, - - V = 1 << VBit, - C = 1 << CBit, - Z = 1 << ZBit, - N = 1 << NBit, - - NZ = N | Z, - CV = C | V, - - NZCV = NZ | CV - } -} \ No newline at end of file diff --git a/ChocolArm64/State/ARegister.cs b/ChocolArm64/State/ARegister.cs deleted file mode 100644 index 5861db8c64..0000000000 --- a/ChocolArm64/State/ARegister.cs +++ /dev/null @@ -1,142 +0,0 @@ -using System; -using System.Reflection; - -namespace ChocolArm64.State -{ - struct ARegister - { - public int Index; - - public ARegisterType Type; - - public ARegister(int Index, ARegisterType Type) - { - this.Index = Index; - this.Type = Type; - } - - public override int GetHashCode() - { - return (ushort)Index | ((ushort)Type << 16); - } - - public override bool Equals(object Obj) - { - return Obj is ARegister Reg && - Reg.Index == Index && - Reg.Type == Type; - } - - public FieldInfo GetField() - { - switch (Type) - { - case ARegisterType.Flag: return GetFieldFlag(); - case ARegisterType.Int: return GetFieldInt(); - case ARegisterType.Vector: return GetFieldVector(); - } - - throw new InvalidOperationException(); - } - - private FieldInfo GetFieldFlag() - { - switch ((APState)Index) - { - case APState.VBit: return GetField(nameof(AThreadState.Overflow)); - case APState.CBit: return GetField(nameof(AThreadState.Carry)); - case APState.ZBit: return GetField(nameof(AThreadState.Zero)); - case APState.NBit: return GetField(nameof(AThreadState.Negative)); - } - - throw new InvalidOperationException(); - } - - private FieldInfo GetFieldInt() - { - switch (Index) - { - case 0: return GetField(nameof(AThreadState.X0)); - case 1: return GetField(nameof(AThreadState.X1)); - case 2: return GetField(nameof(AThreadState.X2)); - case 3: return GetField(nameof(AThreadState.X3)); - case 4: return GetField(nameof(AThreadState.X4)); - case 5: return GetField(nameof(AThreadState.X5)); - case 6: return GetField(nameof(AThreadState.X6)); - case 7: return GetField(nameof(AThreadState.X7)); - case 8: return GetField(nameof(AThreadState.X8)); - case 9: return GetField(nameof(AThreadState.X9)); - case 10: return GetField(nameof(AThreadState.X10)); - case 11: return GetField(nameof(AThreadState.X11)); - case 12: return GetField(nameof(AThreadState.X12)); - case 13: return GetField(nameof(AThreadState.X13)); - case 14: return GetField(nameof(AThreadState.X14)); - case 15: return GetField(nameof(AThreadState.X15)); - case 16: return GetField(nameof(AThreadState.X16)); - case 17: return GetField(nameof(AThreadState.X17)); - case 18: return GetField(nameof(AThreadState.X18)); - case 19: return GetField(nameof(AThreadState.X19)); - case 20: return GetField(nameof(AThreadState.X20)); - case 21: return GetField(nameof(AThreadState.X21)); - case 22: return GetField(nameof(AThreadState.X22)); - case 23: return GetField(nameof(AThreadState.X23)); - case 24: return GetField(nameof(AThreadState.X24)); - case 25: return GetField(nameof(AThreadState.X25)); - case 26: return GetField(nameof(AThreadState.X26)); - case 27: return GetField(nameof(AThreadState.X27)); - case 28: return GetField(nameof(AThreadState.X28)); - case 29: return GetField(nameof(AThreadState.X29)); - case 30: return GetField(nameof(AThreadState.X30)); - case 31: return GetField(nameof(AThreadState.X31)); - } - - throw new InvalidOperationException(); - } - - private FieldInfo GetFieldVector() - { - switch (Index) - { - case 0: return GetField(nameof(AThreadState.V0)); - case 1: return GetField(nameof(AThreadState.V1)); - case 2: return GetField(nameof(AThreadState.V2)); - case 3: return GetField(nameof(AThreadState.V3)); - case 4: return GetField(nameof(AThreadState.V4)); - case 5: return GetField(nameof(AThreadState.V5)); - case 6: return GetField(nameof(AThreadState.V6)); - case 7: return GetField(nameof(AThreadState.V7)); - case 8: return GetField(nameof(AThreadState.V8)); - case 9: return GetField(nameof(AThreadState.V9)); - case 10: return GetField(nameof(AThreadState.V10)); - case 11: return GetField(nameof(AThreadState.V11)); - case 12: return GetField(nameof(AThreadState.V12)); - case 13: return GetField(nameof(AThreadState.V13)); - case 14: return GetField(nameof(AThreadState.V14)); - case 15: return GetField(nameof(AThreadState.V15)); - case 16: return GetField(nameof(AThreadState.V16)); - case 17: return GetField(nameof(AThreadState.V17)); - case 18: return GetField(nameof(AThreadState.V18)); - case 19: return GetField(nameof(AThreadState.V19)); - case 20: return GetField(nameof(AThreadState.V20)); - case 21: return GetField(nameof(AThreadState.V21)); - case 22: return GetField(nameof(AThreadState.V22)); - case 23: return GetField(nameof(AThreadState.V23)); - case 24: return GetField(nameof(AThreadState.V24)); - case 25: return GetField(nameof(AThreadState.V25)); - case 26: return GetField(nameof(AThreadState.V26)); - case 27: return GetField(nameof(AThreadState.V27)); - case 28: return GetField(nameof(AThreadState.V28)); - case 29: return GetField(nameof(AThreadState.V29)); - case 30: return GetField(nameof(AThreadState.V30)); - case 31: return GetField(nameof(AThreadState.V31)); - } - - throw new InvalidOperationException(); - } - - private FieldInfo GetField(string Name) - { - return typeof(AThreadState).GetField(Name); - } - } -} \ No newline at end of file diff --git a/ChocolArm64/State/ARegisterSize.cs b/ChocolArm64/State/ARegisterSize.cs deleted file mode 100644 index 144f36b929..0000000000 --- a/ChocolArm64/State/ARegisterSize.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace ChocolArm64.State -{ - enum ARegisterSize - { - Int32, - Int64, - SIMD64, - SIMD128 - } -} \ No newline at end of file diff --git a/ChocolArm64/State/ARegisterType.cs b/ChocolArm64/State/ARegisterType.cs deleted file mode 100644 index f9776bb7dd..0000000000 --- a/ChocolArm64/State/ARegisterType.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace ChocolArm64.State -{ - enum ARegisterType - { - Flag, - Int, - Vector - } -} \ No newline at end of file diff --git a/ChocolArm64/State/AThreadState.cs b/ChocolArm64/State/AThreadState.cs deleted file mode 100644 index a84e3242bf..0000000000 --- a/ChocolArm64/State/AThreadState.cs +++ /dev/null @@ -1,140 +0,0 @@ -using ChocolArm64.Events; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Runtime.Intrinsics; - -namespace ChocolArm64.State -{ - public class AThreadState - { - internal const int LRIndex = 30; - internal const int ZRIndex = 31; - - internal const int ErgSizeLog2 = 4; - internal const int DczSizeLog2 = 4; - - internal AExecutionMode ExecutionMode; - - //AArch32 state. - public uint R0, R1, R2, R3, - R4, R5, R6, R7, - R8, R9, R10, R11, - R12, R13, R14, R15; - - public bool Thumb; - - //AArch64 state. - public ulong X0, X1, X2, X3, X4, X5, X6, X7, - X8, X9, X10, X11, X12, X13, X14, X15, - X16, X17, X18, X19, X20, X21, X22, X23, - X24, X25, X26, X27, X28, X29, X30, X31; - - public Vector128 V0, V1, V2, V3, V4, V5, V6, V7, - V8, V9, V10, V11, V12, V13, V14, V15, - V16, V17, V18, V19, V20, V21, V22, V23, - V24, V25, V26, V27, V28, V29, V30, V31; - - public bool Overflow; - public bool Carry; - public bool Zero; - public bool Negative; - - public int ProcessId; - public int ThreadId; - - public bool Running { get; set; } - - public long TpidrEl0 { get; set; } - public long Tpidr { get; set; } - - public int Fpcr { get; set; } - public int Fpsr { get; set; } - - public int Psr - { - get - { - return (Negative ? (int)APState.N : 0) | - (Zero ? (int)APState.Z : 0) | - (Carry ? (int)APState.C : 0) | - (Overflow ? (int)APState.V : 0); - } - } - - public uint CtrEl0 => 0x8444c004; - public uint DczidEl0 => 0x00000004; - - public ulong CntfrqEl0 { get; set; } - public ulong CntpctEl0 - { - get - { - double Ticks = TickCounter.ElapsedTicks * HostTickFreq; - - return (ulong)(Ticks * CntfrqEl0); - } - } - - public event EventHandler Break; - public event EventHandler SvcCall; - public event EventHandler Undefined; - - private Stack CallStack; - - private static Stopwatch TickCounter; - - private static double HostTickFreq; - - public AThreadState() - { - CallStack = new Stack(); - } - - static AThreadState() - { - HostTickFreq = 1.0 / Stopwatch.Frequency; - - TickCounter = new Stopwatch(); - - TickCounter.Start(); - } - - internal void OnBreak(long Position, int Imm) - { - Break?.Invoke(this, new AInstExceptionEventArgs(Position, Imm)); - } - - internal void OnSvcCall(long Position, int Imm) - { - SvcCall?.Invoke(this, new AInstExceptionEventArgs(Position, Imm)); - } - - internal void OnUndefined(long Position, int RawOpCode) - { - Undefined?.Invoke(this, new AInstUndefinedEventArgs(Position, RawOpCode)); - } - - internal void EnterMethod(long Position) - { - CallStack.Push(Position); - } - - internal void ExitMethod() - { - CallStack.TryPop(out _); - } - - internal void JumpMethod(long Position) - { - CallStack.TryPop(out _); - - CallStack.Push(Position); - } - - public long[] GetCallStack() - { - return CallStack.ToArray(); - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Translation/AILBarrier.cs b/ChocolArm64/Translation/AILBarrier.cs deleted file mode 100644 index 25b08de31d..0000000000 --- a/ChocolArm64/Translation/AILBarrier.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace ChocolArm64.Translation -{ - struct AILBarrier : IAILEmit - { - public void Emit(AILEmitter Context) { } - } -} \ No newline at end of file diff --git a/ChocolArm64/Translation/AILBlock.cs b/ChocolArm64/Translation/AILBlock.cs deleted file mode 100644 index e580e09c9b..0000000000 --- a/ChocolArm64/Translation/AILBlock.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System.Collections.Generic; - -namespace ChocolArm64.Translation -{ - class AILBlock : IAILEmit - { - public long IntInputs { get; private set; } - public long IntOutputs { get; private set; } - public long IntAwOutputs { get; private set; } - - public long VecInputs { get; private set; } - public long VecOutputs { get; private set; } - public long VecAwOutputs { get; private set; } - - public bool HasStateStore { get; private set; } - - public List ILEmitters { get; private set; } - - public AILBlock Next { get; set; } - public AILBlock Branch { get; set; } - - public AILBlock() - { - ILEmitters = new List(); - } - - public void Add(IAILEmit ILEmitter) - { - if (ILEmitter is AILBarrier) - { - //Those barriers are used to separate the groups of CIL - //opcodes emitted by each ARM instruction. - //We can only consider the new outputs for doing input elimination - //after all the CIL opcodes used by the instruction being emitted. - IntAwOutputs = IntOutputs; - VecAwOutputs = VecOutputs; - } - else if (ILEmitter is AILOpCodeLoad Ld && AILEmitter.IsRegIndex(Ld.Index)) - { - switch (Ld.IoType) - { - case AIoType.Flag: IntInputs |= ((1L << Ld.Index) << 32) & ~IntAwOutputs; break; - case AIoType.Int: IntInputs |= (1L << Ld.Index) & ~IntAwOutputs; break; - case AIoType.Vector: VecInputs |= (1L << Ld.Index) & ~VecAwOutputs; break; - } - } - else if (ILEmitter is AILOpCodeStore St) - { - if (AILEmitter.IsRegIndex(St.Index)) - { - switch (St.IoType) - { - case AIoType.Flag: IntOutputs |= (1L << St.Index) << 32; break; - case AIoType.Int: IntOutputs |= 1L << St.Index; break; - case AIoType.Vector: VecOutputs |= 1L << St.Index; break; - } - } - - if (St.IoType == AIoType.Fields) - { - HasStateStore = true; - } - } - - ILEmitters.Add(ILEmitter); - } - - public void Emit(AILEmitter Context) - { - foreach (IAILEmit ILEmitter in ILEmitters) - { - ILEmitter.Emit(Context); - } - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Translation/AILEmitter.cs b/ChocolArm64/Translation/AILEmitter.cs deleted file mode 100644 index 8c7805353b..0000000000 --- a/ChocolArm64/Translation/AILEmitter.cs +++ /dev/null @@ -1,188 +0,0 @@ -using ChocolArm64.Decoder; -using ChocolArm64.State; -using System; -using System.Collections.Generic; -using System.Reflection.Emit; -using System.Runtime.Intrinsics; - -namespace ChocolArm64.Translation -{ - class AILEmitter - { - public ALocalAlloc LocalAlloc { get; private set; } - - public ILGenerator Generator { get; private set; } - - private Dictionary Locals; - - private AILBlock[] ILBlocks; - - private AILBlock Root; - - private ATranslatedSub Subroutine; - - private string SubName; - - private int LocalsCount; - - public AILEmitter(ABlock[] Graph, ABlock Root, string SubName) - { - this.SubName = SubName; - - Locals = new Dictionary(); - - ILBlocks = new AILBlock[Graph.Length]; - - AILBlock GetBlock(int Index) - { - if (Index < 0 || Index >= ILBlocks.Length) - { - return null; - } - - if (ILBlocks[Index] == null) - { - ILBlocks[Index] = new AILBlock(); - } - - return ILBlocks[Index]; - } - - for (int Index = 0; Index < ILBlocks.Length; Index++) - { - AILBlock Block = GetBlock(Index); - - Block.Next = GetBlock(Array.IndexOf(Graph, Graph[Index].Next)); - Block.Branch = GetBlock(Array.IndexOf(Graph, Graph[Index].Branch)); - } - - this.Root = ILBlocks[Array.IndexOf(Graph, Root)]; - } - - public AILBlock GetILBlock(int Index) => ILBlocks[Index]; - - public ATranslatedSub GetSubroutine() - { - LocalAlloc = new ALocalAlloc(ILBlocks, Root); - - InitSubroutine(); - InitLocals(); - - foreach (AILBlock ILBlock in ILBlocks) - { - ILBlock.Emit(this); - } - - return Subroutine; - } - - private void InitSubroutine() - { - List Params = new List(); - - void SetParams(long Inputs, ARegisterType BaseType) - { - for (int Bit = 0; Bit < 64; Bit++) - { - long Mask = 1L << Bit; - - if ((Inputs & Mask) != 0) - { - Params.Add(GetRegFromBit(Bit, BaseType)); - } - } - } - - SetParams(LocalAlloc.GetIntInputs(Root), ARegisterType.Int); - SetParams(LocalAlloc.GetVecInputs(Root), ARegisterType.Vector); - - DynamicMethod Mthd = new DynamicMethod(SubName, typeof(long), GetParamTypes(Params)); - - Generator = Mthd.GetILGenerator(); - - Subroutine = new ATranslatedSub(Mthd, Params); - } - - private void InitLocals() - { - int ParamsStart = ATranslatedSub.FixedArgTypes.Length; - - Locals = new Dictionary(); - - for (int Index = 0; Index < Subroutine.Params.Count; Index++) - { - ARegister Reg = Subroutine.Params[Index]; - - Generator.EmitLdarg(Index + ParamsStart); - Generator.EmitStloc(GetLocalIndex(Reg)); - } - } - - private Type[] GetParamTypes(IList Params) - { - Type[] FixedArgs = ATranslatedSub.FixedArgTypes; - - Type[] Output = new Type[Params.Count + FixedArgs.Length]; - - FixedArgs.CopyTo(Output, 0); - - int TypeIdx = FixedArgs.Length; - - for (int Index = 0; Index < Params.Count; Index++) - { - Output[TypeIdx++] = GetFieldType(Params[Index].Type); - } - - return Output; - } - - public int GetLocalIndex(ARegister Reg) - { - if (!Locals.TryGetValue(Reg, out int Index)) - { - Generator.DeclareLocal(GetLocalType(Reg)); - - Index = LocalsCount++; - - Locals.Add(Reg, Index); - } - - return Index; - } - - public Type GetLocalType(ARegister Reg) => GetFieldType(Reg.Type); - - public Type GetFieldType(ARegisterType RegType) - { - switch (RegType) - { - case ARegisterType.Flag: return typeof(bool); - case ARegisterType.Int: return typeof(ulong); - case ARegisterType.Vector: return typeof(Vector128); - } - - throw new ArgumentException(nameof(RegType)); - } - - public static ARegister GetRegFromBit(int Bit, ARegisterType BaseType) - { - if (Bit < 32) - { - return new ARegister(Bit, BaseType); - } - else if (BaseType == ARegisterType.Int) - { - return new ARegister(Bit & 0x1f, ARegisterType.Flag); - } - else - { - throw new ArgumentOutOfRangeException(nameof(Bit)); - } - } - - public static bool IsRegIndex(int Index) - { - return Index >= 0 && Index < 32; - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Translation/AILEmitterCtx.cs b/ChocolArm64/Translation/AILEmitterCtx.cs deleted file mode 100644 index 3fa46e96d6..0000000000 --- a/ChocolArm64/Translation/AILEmitterCtx.cs +++ /dev/null @@ -1,537 +0,0 @@ -using ChocolArm64.Decoder; -using ChocolArm64.Instruction; -using ChocolArm64.State; -using System; -using System.Collections.Generic; -using System.Reflection; -using System.Reflection.Emit; - -namespace ChocolArm64.Translation -{ - class AILEmitterCtx - { - private ATranslator Translator; - - private Dictionary Labels; - - private int BlkIndex; - private int OpcIndex; - - private ABlock[] Graph; - private ABlock Root; - public ABlock CurrBlock => Graph[BlkIndex]; - public AOpCode CurrOp => Graph[BlkIndex].OpCodes[OpcIndex]; - - private AILEmitter Emitter; - - private AILBlock ILBlock; - - private AOpCode OptOpLastCompare; - private AOpCode OptOpLastFlagSet; - - //This is the index of the temporary register, used to store temporary - //values needed by some functions, since IL doesn't have a swap instruction. - //You can use any value here as long it doesn't conflict with the indices - //for the other registers. Any value >= 64 or < 0 will do. - private const int Tmp1Index = -1; - private const int Tmp2Index = -2; - private const int Tmp3Index = -3; - private const int Tmp4Index = -4; - private const int Tmp5Index = -5; - - public AILEmitterCtx( - ATranslator Translator, - ABlock[] Graph, - ABlock Root, - string SubName) - { - if (Translator == null) - { - throw new ArgumentNullException(nameof(Translator)); - } - - if (Graph == null) - { - throw new ArgumentNullException(nameof(Graph)); - } - - if (Root == null) - { - throw new ArgumentNullException(nameof(Root)); - } - - this.Translator = Translator; - this.Graph = Graph; - this.Root = Root; - - Labels = new Dictionary(); - - Emitter = new AILEmitter(Graph, Root, SubName); - - ILBlock = Emitter.GetILBlock(0); - - OpcIndex = -1; - - if (Graph.Length == 0 || !AdvanceOpCode()) - { - throw new ArgumentException(nameof(Graph)); - } - } - - public ATranslatedSub GetSubroutine() - { - return Emitter.GetSubroutine(); - } - - public bool AdvanceOpCode() - { - if (OpcIndex + 1 == CurrBlock.OpCodes.Count && - BlkIndex + 1 == Graph.Length) - { - return false; - } - - while (++OpcIndex >= (CurrBlock?.OpCodes.Count ?? 0)) - { - BlkIndex++; - OpcIndex = -1; - - OptOpLastFlagSet = null; - OptOpLastCompare = null; - - ILBlock = Emitter.GetILBlock(BlkIndex); - } - - return true; - } - - public void EmitOpCode() - { - if (OpcIndex == 0) - { - MarkLabel(GetLabel(CurrBlock.Position)); - } - - CurrOp.Emitter(this); - - ILBlock.Add(new AILBarrier()); - } - - public bool TryOptEmitSubroutineCall() - { - if (CurrBlock.Next == null) - { - return false; - } - - if (!Translator.TryGetCachedSub(CurrOp, out ATranslatedSub Sub)) - { - return false; - } - - for (int Index = 0; Index < ATranslatedSub.FixedArgTypes.Length; Index++) - { - EmitLdarg(Index); - } - - foreach (ARegister Reg in Sub.Params) - { - switch (Reg.Type) - { - case ARegisterType.Flag: Ldloc(Reg.Index, AIoType.Flag); break; - case ARegisterType.Int: Ldloc(Reg.Index, AIoType.Int); break; - case ARegisterType.Vector: Ldloc(Reg.Index, AIoType.Vector); break; - } - } - - EmitCall(Sub.Method); - - Sub.AddCaller(Root.Position); - - return true; - } - - public void TryOptMarkCondWithoutCmp() - { - OptOpLastCompare = CurrOp; - - AInstEmitAluHelper.EmitDataLoadOpers(this); - - Stloc(Tmp4Index, AIoType.Int); - Stloc(Tmp3Index, AIoType.Int); - } - - private Dictionary BranchOps = new Dictionary() - { - { ACond.Eq, OpCodes.Beq }, - { ACond.Ne, OpCodes.Bne_Un }, - { ACond.Ge_Un, OpCodes.Bge_Un }, - { ACond.Lt_Un, OpCodes.Blt_Un }, - { ACond.Gt_Un, OpCodes.Bgt_Un }, - { ACond.Le_Un, OpCodes.Ble_Un }, - { ACond.Ge, OpCodes.Bge }, - { ACond.Lt, OpCodes.Blt }, - { ACond.Gt, OpCodes.Bgt }, - { ACond.Le, OpCodes.Ble } - }; - - public void EmitCondBranch(AILLabel Target, ACond Cond) - { - OpCode ILOp; - - int IntCond = (int)Cond; - - if (OptOpLastCompare != null && - OptOpLastCompare == OptOpLastFlagSet && BranchOps.ContainsKey(Cond)) - { - Ldloc(Tmp3Index, AIoType.Int, OptOpLastCompare.RegisterSize); - Ldloc(Tmp4Index, AIoType.Int, OptOpLastCompare.RegisterSize); - - ILOp = BranchOps[Cond]; - } - else if (IntCond < 14) - { - int CondTrue = IntCond >> 1; - - switch (CondTrue) - { - case 0: EmitLdflg((int)APState.ZBit); break; - case 1: EmitLdflg((int)APState.CBit); break; - case 2: EmitLdflg((int)APState.NBit); break; - case 3: EmitLdflg((int)APState.VBit); break; - - case 4: - EmitLdflg((int)APState.CBit); - EmitLdflg((int)APState.ZBit); - - Emit(OpCodes.Not); - Emit(OpCodes.And); - break; - - case 5: - case 6: - EmitLdflg((int)APState.NBit); - EmitLdflg((int)APState.VBit); - - Emit(OpCodes.Ceq); - - if (CondTrue == 6) - { - EmitLdflg((int)APState.ZBit); - - Emit(OpCodes.Not); - Emit(OpCodes.And); - } - break; - } - - ILOp = (IntCond & 1) != 0 - ? OpCodes.Brfalse - : OpCodes.Brtrue; - } - else - { - ILOp = OpCodes.Br; - } - - Emit(ILOp, Target); - } - - public void EmitCast(AIntType IntType) - { - switch (IntType) - { - case AIntType.UInt8: Emit(OpCodes.Conv_U1); break; - case AIntType.UInt16: Emit(OpCodes.Conv_U2); break; - case AIntType.UInt32: Emit(OpCodes.Conv_U4); break; - case AIntType.UInt64: Emit(OpCodes.Conv_U8); break; - case AIntType.Int8: Emit(OpCodes.Conv_I1); break; - case AIntType.Int16: Emit(OpCodes.Conv_I2); break; - case AIntType.Int32: Emit(OpCodes.Conv_I4); break; - case AIntType.Int64: Emit(OpCodes.Conv_I8); break; - } - - bool Sz64 = CurrOp.RegisterSize != ARegisterSize.Int32; - - if (Sz64 == (IntType == AIntType.UInt64 || - IntType == AIntType.Int64)) - { - return; - } - - if (Sz64) - { - Emit(IntType >= AIntType.Int8 - ? OpCodes.Conv_I8 - : OpCodes.Conv_U8); - } - else - { - Emit(OpCodes.Conv_U4); - } - } - - public void EmitLsl(int Amount) => EmitILShift(Amount, OpCodes.Shl); - public void EmitLsr(int Amount) => EmitILShift(Amount, OpCodes.Shr_Un); - public void EmitAsr(int Amount) => EmitILShift(Amount, OpCodes.Shr); - - private void EmitILShift(int Amount, OpCode ILOp) - { - if (Amount > 0) - { - EmitLdc_I4(Amount); - - Emit(ILOp); - } - } - - public void EmitRor(int Amount) - { - if (Amount > 0) - { - Stloc(Tmp2Index, AIoType.Int); - Ldloc(Tmp2Index, AIoType.Int); - - EmitLdc_I4(Amount); - - Emit(OpCodes.Shr_Un); - - Ldloc(Tmp2Index, AIoType.Int); - - EmitLdc_I4(CurrOp.GetBitsCount() - Amount); - - Emit(OpCodes.Shl); - Emit(OpCodes.Or); - } - } - - public AILLabel GetLabel(long Position) - { - if (!Labels.TryGetValue(Position, out AILLabel Output)) - { - Output = new AILLabel(); - - Labels.Add(Position, Output); - } - - return Output; - } - - public void MarkLabel(AILLabel Label) - { - ILBlock.Add(Label); - } - - public void Emit(OpCode ILOp) - { - ILBlock.Add(new AILOpCode(ILOp)); - } - - public void Emit(OpCode ILOp, AILLabel Label) - { - ILBlock.Add(new AILOpCodeBranch(ILOp, Label)); - } - - public void Emit(string Text) - { - ILBlock.Add(new AILOpCodeLog(Text)); - } - - public void EmitLdarg(int Index) - { - ILBlock.Add(new AILOpCodeLoad(Index, AIoType.Arg)); - } - - public void EmitLdintzr(int Index) - { - if (Index != AThreadState.ZRIndex) - { - EmitLdint(Index); - } - else - { - EmitLdc_I(0); - } - } - - public void EmitStintzr(int Index) - { - if (Index != AThreadState.ZRIndex) - { - EmitStint(Index); - } - else - { - Emit(OpCodes.Pop); - } - } - - public void EmitLoadState(ABlock RetBlk) - { - ILBlock.Add(new AILOpCodeLoad(Array.IndexOf(Graph, RetBlk), AIoType.Fields)); - } - - public void EmitStoreState() - { - ILBlock.Add(new AILOpCodeStore(Array.IndexOf(Graph, CurrBlock), AIoType.Fields)); - } - - public void EmitLdtmp() => EmitLdint(Tmp1Index); - public void EmitSttmp() => EmitStint(Tmp1Index); - - public void EmitLdvectmp() => EmitLdvec(Tmp5Index); - public void EmitStvectmp() => EmitStvec(Tmp5Index); - - public void EmitLdint(int Index) => Ldloc(Index, AIoType.Int); - public void EmitStint(int Index) => Stloc(Index, AIoType.Int); - - public void EmitLdvec(int Index) => Ldloc(Index, AIoType.Vector); - public void EmitStvec(int Index) => Stloc(Index, AIoType.Vector); - - public void EmitLdflg(int Index) => Ldloc(Index, AIoType.Flag); - public void EmitStflg(int Index) - { - OptOpLastFlagSet = CurrOp; - - Stloc(Index, AIoType.Flag); - } - - private void Ldloc(int Index, AIoType IoType) - { - ILBlock.Add(new AILOpCodeLoad(Index, IoType, CurrOp.RegisterSize)); - } - - private void Ldloc(int Index, AIoType IoType, ARegisterSize RegisterSize) - { - ILBlock.Add(new AILOpCodeLoad(Index, IoType, RegisterSize)); - } - - private void Stloc(int Index, AIoType IoType) - { - ILBlock.Add(new AILOpCodeStore(Index, IoType, CurrOp.RegisterSize)); - } - - public void EmitCallPropGet(Type ObjType, string PropName) - { - if (ObjType == null) - { - throw new ArgumentNullException(nameof(ObjType)); - } - - if (PropName == null) - { - throw new ArgumentNullException(nameof(PropName)); - } - - EmitCall(ObjType.GetMethod($"get_{PropName}")); - } - - public void EmitCallPropSet(Type ObjType, string PropName) - { - if (ObjType == null) - { - throw new ArgumentNullException(nameof(ObjType)); - } - - if (PropName == null) - { - throw new ArgumentNullException(nameof(PropName)); - } - - EmitCall(ObjType.GetMethod($"set_{PropName}")); - } - - public void EmitCall(Type ObjType, string MthdName) - { - if (ObjType == null) - { - throw new ArgumentNullException(nameof(ObjType)); - } - - if (MthdName == null) - { - throw new ArgumentNullException(nameof(MthdName)); - } - - EmitCall(ObjType.GetMethod(MthdName)); - } - - public void EmitPrivateCall(Type ObjType, string MthdName) - { - if (ObjType == null) - { - throw new ArgumentNullException(nameof(ObjType)); - } - - if (MthdName == null) - { - throw new ArgumentNullException(nameof(MthdName)); - } - - EmitCall(ObjType.GetMethod(MthdName, BindingFlags.Instance | BindingFlags.NonPublic)); - } - - public void EmitCall(MethodInfo MthdInfo) - { - if (MthdInfo == null) - { - throw new ArgumentNullException(nameof(MthdInfo)); - } - - ILBlock.Add(new AILOpCodeCall(MthdInfo)); - } - - public void EmitLdc_I(long Value) - { - if (CurrOp.RegisterSize == ARegisterSize.Int32) - { - EmitLdc_I4((int)Value); - } - else - { - EmitLdc_I8(Value); - } - } - - public void EmitLdc_I4(int Value) - { - ILBlock.Add(new AILOpCodeConst(Value)); - } - - public void EmitLdc_I8(long Value) - { - ILBlock.Add(new AILOpCodeConst(Value)); - } - - public void EmitLdc_R4(float Value) - { - ILBlock.Add(new AILOpCodeConst(Value)); - } - - public void EmitLdc_R8(double Value) - { - ILBlock.Add(new AILOpCodeConst(Value)); - } - - public void EmitZNFlagCheck() - { - EmitZNCheck(OpCodes.Ceq, (int)APState.ZBit); - EmitZNCheck(OpCodes.Clt, (int)APState.NBit); - } - - private void EmitZNCheck(OpCode ILCmpOp, int Flag) - { - Emit(OpCodes.Dup); - Emit(OpCodes.Ldc_I4_0); - - if (CurrOp.RegisterSize != ARegisterSize.Int32) - { - Emit(OpCodes.Conv_I8); - } - - Emit(ILCmpOp); - - EmitStflg(Flag); - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Translation/AILLabel.cs b/ChocolArm64/Translation/AILLabel.cs deleted file mode 100644 index 0ee39ad7e2..0000000000 --- a/ChocolArm64/Translation/AILLabel.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Reflection.Emit; - -namespace ChocolArm64.Translation -{ - class AILLabel : IAILEmit - { - private bool HasLabel; - - private Label Lbl; - - public void Emit(AILEmitter Context) - { - Context.Generator.MarkLabel(GetLabel(Context)); - } - - public Label GetLabel(AILEmitter Context) - { - if (!HasLabel) - { - Lbl = Context.Generator.DefineLabel(); - - HasLabel = true; - } - - return Lbl; - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Translation/AILOpCode.cs b/ChocolArm64/Translation/AILOpCode.cs deleted file mode 100644 index a4bc93a065..0000000000 --- a/ChocolArm64/Translation/AILOpCode.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Reflection.Emit; - -namespace ChocolArm64.Translation -{ - struct AILOpCode : IAILEmit - { - private OpCode ILOp; - - public AILOpCode(OpCode ILOp) - { - this.ILOp = ILOp; - } - - public void Emit(AILEmitter Context) - { - Context.Generator.Emit(ILOp); - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Translation/AILOpCodeBranch.cs b/ChocolArm64/Translation/AILOpCodeBranch.cs deleted file mode 100644 index e4caad1ffa..0000000000 --- a/ChocolArm64/Translation/AILOpCodeBranch.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Reflection.Emit; - -namespace ChocolArm64.Translation -{ - struct AILOpCodeBranch : IAILEmit - { - private OpCode ILOp; - private AILLabel Label; - - public AILOpCodeBranch(OpCode ILOp, AILLabel Label) - { - this.ILOp = ILOp; - this.Label = Label; - } - - public void Emit(AILEmitter Context) - { - Context.Generator.Emit(ILOp, Label.GetLabel(Context)); - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Translation/AILOpCodeCall.cs b/ChocolArm64/Translation/AILOpCodeCall.cs deleted file mode 100644 index 8cd944eb01..0000000000 --- a/ChocolArm64/Translation/AILOpCodeCall.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Reflection; -using System.Reflection.Emit; - -namespace ChocolArm64.Translation -{ - struct AILOpCodeCall : IAILEmit - { - private MethodInfo MthdInfo; - - public AILOpCodeCall(MethodInfo MthdInfo) - { - this.MthdInfo = MthdInfo; - } - - public void Emit(AILEmitter Context) - { - Context.Generator.Emit(OpCodes.Call, MthdInfo); - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Translation/AILOpCodeConst.cs b/ChocolArm64/Translation/AILOpCodeConst.cs deleted file mode 100644 index fee8640768..0000000000 --- a/ChocolArm64/Translation/AILOpCodeConst.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System.Reflection.Emit; -using System.Runtime.InteropServices; - -namespace ChocolArm64.Translation -{ - class AILOpCodeConst : IAILEmit - { - [StructLayout(LayoutKind.Explicit, Size = 8)] - private struct ImmVal - { - [FieldOffset(0)] public int I4; - [FieldOffset(0)] public long I8; - [FieldOffset(0)] public float R4; - [FieldOffset(0)] public double R8; - } - - private ImmVal Value; - - private enum ConstType - { - Int32, - Int64, - Single, - Double - } - - private ConstType Type; - - private AILOpCodeConst(ConstType Type) - { - this.Type = Type; - } - - public AILOpCodeConst(int Value) : this(ConstType.Int32) - { - this.Value = new ImmVal { I4 = Value }; - } - - public AILOpCodeConst(long Value) : this(ConstType.Int64) - { - this.Value = new ImmVal { I8 = Value }; - } - - public AILOpCodeConst(float Value) : this(ConstType.Single) - { - this.Value = new ImmVal { R4 = Value }; - } - - public AILOpCodeConst(double Value) : this(ConstType.Double) - { - this.Value = new ImmVal { R8 = Value }; - } - - public void Emit(AILEmitter Context) - { - switch (Type) - { - case ConstType.Int32: Context.Generator.EmitLdc_I4(Value.I4); break; - case ConstType.Int64: Context.Generator.Emit(OpCodes.Ldc_I8, Value.I8); break; - case ConstType.Single: Context.Generator.Emit(OpCodes.Ldc_R4, Value.R4); break; - case ConstType.Double: Context.Generator.Emit(OpCodes.Ldc_R8, Value.R8); break; - } - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Translation/AILOpCodeLoad.cs b/ChocolArm64/Translation/AILOpCodeLoad.cs deleted file mode 100644 index d60ce539f7..0000000000 --- a/ChocolArm64/Translation/AILOpCodeLoad.cs +++ /dev/null @@ -1,75 +0,0 @@ -using ChocolArm64.State; -using System.Reflection.Emit; - -namespace ChocolArm64.Translation -{ - struct AILOpCodeLoad : IAILEmit - { - public int Index { get; private set; } - - public AIoType IoType { get; private set; } - - public ARegisterSize RegisterSize { get; private set; } - - public AILOpCodeLoad(int Index, AIoType IoType, ARegisterSize RegisterSize = 0) - { - this.Index = Index; - this.IoType = IoType; - this.RegisterSize = RegisterSize; - } - - public void Emit(AILEmitter Context) - { - switch (IoType) - { - case AIoType.Arg: Context.Generator.EmitLdarg(Index); break; - - case AIoType.Fields: - { - long IntInputs = Context.LocalAlloc.GetIntInputs(Context.GetILBlock(Index)); - long VecInputs = Context.LocalAlloc.GetVecInputs(Context.GetILBlock(Index)); - - LoadLocals(Context, IntInputs, ARegisterType.Int); - LoadLocals(Context, VecInputs, ARegisterType.Vector); - - break; - } - - case AIoType.Flag: EmitLdloc(Context, Index, ARegisterType.Flag); break; - case AIoType.Int: EmitLdloc(Context, Index, ARegisterType.Int); break; - case AIoType.Vector: EmitLdloc(Context, Index, ARegisterType.Vector); break; - } - } - - private void LoadLocals(AILEmitter Context, long Inputs, ARegisterType BaseType) - { - for (int Bit = 0; Bit < 64; Bit++) - { - long Mask = 1L << Bit; - - if ((Inputs & Mask) != 0) - { - ARegister Reg = AILEmitter.GetRegFromBit(Bit, BaseType); - - Context.Generator.EmitLdarg(ATranslatedSub.StateArgIdx); - Context.Generator.Emit(OpCodes.Ldfld, Reg.GetField()); - - Context.Generator.EmitStloc(Context.GetLocalIndex(Reg)); - } - } - } - - private void EmitLdloc(AILEmitter Context, int Index, ARegisterType RegisterType) - { - ARegister Reg = new ARegister(Index, RegisterType); - - Context.Generator.EmitLdloc(Context.GetLocalIndex(Reg)); - - if (RegisterType == ARegisterType.Int && - RegisterSize == ARegisterSize.Int32) - { - Context.Generator.Emit(OpCodes.Conv_U4); - } - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Translation/AILOpCodeLog.cs b/ChocolArm64/Translation/AILOpCodeLog.cs deleted file mode 100644 index 1338ca1f3a..0000000000 --- a/ChocolArm64/Translation/AILOpCodeLog.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace ChocolArm64.Translation -{ - struct AILOpCodeLog : IAILEmit - { - private string Text; - - public AILOpCodeLog(string Text) - { - this.Text = Text; - } - - public void Emit(AILEmitter Context) - { - Context.Generator.EmitWriteLine(Text); - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Translation/AILOpCodeStore.cs b/ChocolArm64/Translation/AILOpCodeStore.cs deleted file mode 100644 index a0feb43773..0000000000 --- a/ChocolArm64/Translation/AILOpCodeStore.cs +++ /dev/null @@ -1,75 +0,0 @@ -using ChocolArm64.State; -using System.Reflection.Emit; - -namespace ChocolArm64.Translation -{ - struct AILOpCodeStore : IAILEmit - { - public int Index { get; private set; } - - public AIoType IoType { get; private set; } - - public ARegisterSize RegisterSize { get; private set; } - - public AILOpCodeStore(int Index, AIoType IoType, ARegisterSize RegisterSize = 0) - { - this.Index = Index; - this.IoType = IoType; - this.RegisterSize = RegisterSize; - } - - public void Emit(AILEmitter Context) - { - switch (IoType) - { - case AIoType.Arg: Context.Generator.EmitStarg(Index); break; - - case AIoType.Fields: - { - long IntOutputs = Context.LocalAlloc.GetIntOutputs(Context.GetILBlock(Index)); - long VecOutputs = Context.LocalAlloc.GetVecOutputs(Context.GetILBlock(Index)); - - StoreLocals(Context, IntOutputs, ARegisterType.Int); - StoreLocals(Context, VecOutputs, ARegisterType.Vector); - - break; - } - - case AIoType.Flag: EmitStloc(Context, Index, ARegisterType.Flag); break; - case AIoType.Int: EmitStloc(Context, Index, ARegisterType.Int); break; - case AIoType.Vector: EmitStloc(Context, Index, ARegisterType.Vector); break; - } - } - - private void StoreLocals(AILEmitter Context, long Outputs, ARegisterType BaseType) - { - for (int Bit = 0; Bit < 64; Bit++) - { - long Mask = 1L << Bit; - - if ((Outputs & Mask) != 0) - { - ARegister Reg = AILEmitter.GetRegFromBit(Bit, BaseType); - - Context.Generator.EmitLdarg(ATranslatedSub.StateArgIdx); - Context.Generator.EmitLdloc(Context.GetLocalIndex(Reg)); - - Context.Generator.Emit(OpCodes.Stfld, Reg.GetField()); - } - } - } - - private void EmitStloc(AILEmitter Context, int Index, ARegisterType RegisterType) - { - ARegister Reg = new ARegister(Index, RegisterType); - - if (RegisterType == ARegisterType.Int && - RegisterSize == ARegisterSize.Int32) - { - Context.Generator.Emit(OpCodes.Conv_U8); - } - - Context.Generator.EmitStloc(Context.GetLocalIndex(Reg)); - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Translation/AIoType.cs b/ChocolArm64/Translation/AIoType.cs deleted file mode 100644 index 94f8908142..0000000000 --- a/ChocolArm64/Translation/AIoType.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace ChocolArm64.Translation -{ - [Flags] - enum AIoType - { - Arg, - Fields, - Flag, - Int, - Float, - Vector - } -} \ No newline at end of file diff --git a/ChocolArm64/Translation/ALocalAlloc.cs b/ChocolArm64/Translation/ALocalAlloc.cs deleted file mode 100644 index 8e9047804d..0000000000 --- a/ChocolArm64/Translation/ALocalAlloc.cs +++ /dev/null @@ -1,229 +0,0 @@ -using System.Collections.Generic; - -namespace ChocolArm64.Translation -{ - class ALocalAlloc - { - private class PathIo - { - private Dictionary AllInputs; - private Dictionary CmnOutputs; - - private long AllOutputs; - - public PathIo() - { - AllInputs = new Dictionary(); - CmnOutputs = new Dictionary(); - } - - public PathIo(AILBlock Root, long Inputs, long Outputs) : this() - { - Set(Root, Inputs, Outputs); - } - - public void Set(AILBlock Root, long Inputs, long Outputs) - { - if (!AllInputs.TryAdd(Root, Inputs)) - { - AllInputs[Root] |= Inputs; - } - - if (!CmnOutputs.TryAdd(Root, Outputs)) - { - CmnOutputs[Root] &= Outputs; - } - - AllOutputs |= Outputs; - } - - public long GetInputs(AILBlock Root) - { - if (AllInputs.TryGetValue(Root, out long Inputs)) - { - return Inputs | (AllOutputs & ~CmnOutputs[Root]); - } - - return 0; - } - - public long GetOutputs() - { - return AllOutputs; - } - } - - private Dictionary IntPaths; - private Dictionary VecPaths; - - private struct BlockIo - { - public AILBlock Block; - public AILBlock Entry; - - public long IntInputs; - public long VecInputs; - public long IntOutputs; - public long VecOutputs; - } - - private const int MaxOptGraphLength = 40; - - public ALocalAlloc(AILBlock[] Graph, AILBlock Root) - { - IntPaths = new Dictionary(); - VecPaths = new Dictionary(); - - if (Graph.Length > 1 && - Graph.Length < MaxOptGraphLength) - { - InitializeOptimal(Graph, Root); - } - else - { - InitializeFast(Graph); - } - } - - private void InitializeOptimal(AILBlock[] Graph, AILBlock Root) - { - //This will go through all possible paths on the graph, - //and store all inputs/outputs for each block. A register - //that was previously written to already is not considered an input. - //When a block can be reached by more than one path, then the - //output from all paths needs to be set for this block, and - //only outputs present in all of the parent blocks can be considered - //when doing input elimination. Each block chain have a root, that's where - //the code starts executing. They are present on the subroutine start point, - //and on call return points too (address written to X30 by BL). - HashSet Visited = new HashSet(); - - Queue Unvisited = new Queue(); - - void Enqueue(BlockIo Block) - { - if (!Visited.Contains(Block)) - { - Unvisited.Enqueue(Block); - - Visited.Add(Block); - } - } - - Enqueue(new BlockIo() - { - Block = Root, - Entry = Root - }); - - while (Unvisited.Count > 0) - { - BlockIo Current = Unvisited.Dequeue(); - - Current.IntInputs |= Current.Block.IntInputs & ~Current.IntOutputs; - Current.VecInputs |= Current.Block.VecInputs & ~Current.VecOutputs; - Current.IntOutputs |= Current.Block.IntOutputs; - Current.VecOutputs |= Current.Block.VecOutputs; - - //Check if this is a exit block - //(a block that returns or calls another sub). - if ((Current.Block.Next == null && - Current.Block.Branch == null) || Current.Block.HasStateStore) - { - if (!IntPaths.TryGetValue(Current.Block, out PathIo IntPath)) - { - IntPaths.Add(Current.Block, IntPath = new PathIo()); - } - - if (!VecPaths.TryGetValue(Current.Block, out PathIo VecPath)) - { - VecPaths.Add(Current.Block, VecPath = new PathIo()); - } - - IntPath.Set(Current.Entry, Current.IntInputs, Current.IntOutputs); - VecPath.Set(Current.Entry, Current.VecInputs, Current.VecOutputs); - } - - void EnqueueFromCurrent(AILBlock Block, bool RetTarget) - { - BlockIo BlkIO = new BlockIo() { Block = Block }; - - if (RetTarget) - { - BlkIO.Entry = Block; - } - else - { - BlkIO.Entry = Current.Entry; - BlkIO.IntInputs = Current.IntInputs; - BlkIO.VecInputs = Current.VecInputs; - BlkIO.IntOutputs = Current.IntOutputs; - BlkIO.VecOutputs = Current.VecOutputs; - } - - Enqueue(BlkIO); - } - - if (Current.Block.Next != null) - { - EnqueueFromCurrent(Current.Block.Next, Current.Block.HasStateStore); - } - - if (Current.Block.Branch != null) - { - EnqueueFromCurrent(Current.Block.Branch, false); - } - } - } - - private void InitializeFast(AILBlock[] Graph) - { - //This is WAY faster than InitializeOptimal, but results in - //uneeded loads and stores, so the resulting code will be slower. - long IntInputs = 0, IntOutputs = 0; - long VecInputs = 0, VecOutputs = 0; - - foreach (AILBlock Block in Graph) - { - IntInputs |= Block.IntInputs; - IntOutputs |= Block.IntOutputs; - VecInputs |= Block.VecInputs; - VecOutputs |= Block.VecOutputs; - } - - //It's possible that not all code paths writes to those output registers, - //in those cases if we attempt to write an output registers that was - //not written, we will be just writing zero and messing up the old register value. - //So we just need to ensure that all outputs are loaded. - if (Graph.Length > 1) - { - IntInputs |= IntOutputs; - VecInputs |= VecOutputs; - } - - foreach (AILBlock Block in Graph) - { - IntPaths.Add(Block, new PathIo(Block, IntInputs, IntOutputs)); - VecPaths.Add(Block, new PathIo(Block, VecInputs, VecOutputs)); - } - } - - public long GetIntInputs(AILBlock Root) => GetInputsImpl(Root, IntPaths.Values); - public long GetVecInputs(AILBlock Root) => GetInputsImpl(Root, VecPaths.Values); - - private long GetInputsImpl(AILBlock Root, IEnumerable Values) - { - long Inputs = 0; - - foreach (PathIo Path in Values) - { - Inputs |= Path.GetInputs(Root); - } - - return Inputs; - } - - public long GetIntOutputs(AILBlock Block) => IntPaths[Block].GetOutputs(); - public long GetVecOutputs(AILBlock Block) => VecPaths[Block].GetOutputs(); - } -} \ No newline at end of file diff --git a/ChocolArm64/Translation/IAILEmit.cs b/ChocolArm64/Translation/IAILEmit.cs deleted file mode 100644 index 6e4e9a7855..0000000000 --- a/ChocolArm64/Translation/IAILEmit.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace ChocolArm64.Translation -{ - interface IAILEmit - { - void Emit(AILEmitter Context); - } -} \ No newline at end of file diff --git a/ChocolArm64/Translation/ILGeneratorEx.cs b/ChocolArm64/Translation/ILGeneratorEx.cs deleted file mode 100644 index 40c6efa4d7..0000000000 --- a/ChocolArm64/Translation/ILGeneratorEx.cs +++ /dev/null @@ -1,129 +0,0 @@ -using System; - -namespace ChocolArm64 -{ - using System.Reflection.Emit; - - static class ILGeneratorEx - { - public static void EmitLdc_I4(this ILGenerator Generator, int Value) - { - switch (Value) - { - case 0: Generator.Emit(OpCodes.Ldc_I4_0); break; - case 1: Generator.Emit(OpCodes.Ldc_I4_1); break; - case 2: Generator.Emit(OpCodes.Ldc_I4_2); break; - case 3: Generator.Emit(OpCodes.Ldc_I4_3); break; - case 4: Generator.Emit(OpCodes.Ldc_I4_4); break; - case 5: Generator.Emit(OpCodes.Ldc_I4_5); break; - case 6: Generator.Emit(OpCodes.Ldc_I4_6); break; - case 7: Generator.Emit(OpCodes.Ldc_I4_7); break; - case 8: Generator.Emit(OpCodes.Ldc_I4_8); break; - case -1: Generator.Emit(OpCodes.Ldc_I4_M1); break; - default: Generator.Emit(OpCodes.Ldc_I4, Value); break; - } - } - - public static void EmitLdarg(this ILGenerator Generator, int Index) - { - switch (Index) - { - case 0: Generator.Emit(OpCodes.Ldarg_0); break; - case 1: Generator.Emit(OpCodes.Ldarg_1); break; - case 2: Generator.Emit(OpCodes.Ldarg_2); break; - case 3: Generator.Emit(OpCodes.Ldarg_3); break; - - default: - if ((uint)Index <= byte.MaxValue) - { - Generator.Emit(OpCodes.Ldarg_S, (byte)Index); - } - else if ((uint)Index < ushort.MaxValue) - { - Generator.Emit(OpCodes.Ldarg, (short)Index); - } - else - { - throw new ArgumentOutOfRangeException(nameof(Index)); - } - break; - } - } - - public static void EmitStarg(this ILGenerator Generator, int Index) - { - if ((uint)Index <= byte.MaxValue) - { - Generator.Emit(OpCodes.Starg_S, (byte)Index); - } - else if ((uint)Index < ushort.MaxValue) - { - Generator.Emit(OpCodes.Starg, (short)Index); - } - else - { - throw new ArgumentOutOfRangeException(nameof(Index)); - } - } - - public static void EmitLdloc(this ILGenerator Generator, int Index) - { - switch (Index) - { - case 0: Generator.Emit(OpCodes.Ldloc_0); break; - case 1: Generator.Emit(OpCodes.Ldloc_1); break; - case 2: Generator.Emit(OpCodes.Ldloc_2); break; - case 3: Generator.Emit(OpCodes.Ldloc_3); break; - - default: - if ((uint)Index <= byte.MaxValue) - { - Generator.Emit(OpCodes.Ldloc_S, (byte)Index); - } - else if ((uint)Index < ushort.MaxValue) - { - Generator.Emit(OpCodes.Ldloc, (short)Index); - } - else - { - throw new ArgumentOutOfRangeException(nameof(Index)); - } - break; - } - } - - public static void EmitStloc(this ILGenerator Generator, int Index) - { - switch (Index) - { - case 0: Generator.Emit(OpCodes.Stloc_0); break; - case 1: Generator.Emit(OpCodes.Stloc_1); break; - case 2: Generator.Emit(OpCodes.Stloc_2); break; - case 3: Generator.Emit(OpCodes.Stloc_3); break; - - default: - if ((uint)Index <= byte.MaxValue) - { - Generator.Emit(OpCodes.Stloc_S, (byte)Index); - } - else if ((uint)Index < ushort.MaxValue) - { - Generator.Emit(OpCodes.Stloc, (short)Index); - } - else - { - throw new ArgumentOutOfRangeException(nameof(Index)); - } - break; - } - } - - public static void EmitLdargSeq(this ILGenerator Generator, int Count) - { - for (int Index = 0; Index < Count; Index++) - { - Generator.EmitLdarg(Index); - } - } - } -} diff --git a/KEYS.md b/KEYS.md new file mode 100644 index 0000000000..868e1f06a4 --- /dev/null +++ b/KEYS.md @@ -0,0 +1,40 @@ +# Keys + +Keys are required for decrypting most of the file formats used by the Nintendo Switch. + + Keysets are stored as text files. These 2 filenames are automatically read: +* `prod.keys` - Contains common keys used by all Nintendo Switch devices. +* `title.keys` - Contains game-specific keys. + +Ryujinx will first look for keys in `Ryujinx/system`, and if it doesn't find any there it will look in `$HOME/.switch`. +To dump your `prod.keys` and `title.keys` please follow these following steps. +1. First off learn how to boot into RCM mode and inject payloads if you haven't already. This can be done [here](https://nh-server.github.io/switch-guide/). +2. Make sure you have an SD card with the latest release of [Atmosphere](https://github.com/Atmosphere-NX/Atmosphere/releases) inserted into your Nintendo Switch. +3. Download the latest release of [Lockpick_RCM](https://github.com/shchmue/Lockpick_RCM/releases). +4. Boot into RCM mode. +5. Inject the `Lockpick_RCM.bin` that you have downloaded at `Step 3.` using your preferred payload injector. We recommend [TegraRCMGUI](https://github.com/eliboa/TegraRcmGUI/releases) as it is easy to use and has a decent feature set. +6. Using the `Vol+/-` buttons to navigate and the `Power` button to select, select `Dump from SysNAND | Key generation: X` ("X" depends on your Nintendo Switch's firmware version) +7. The dumping process may take a while depending on how many titles you have installed. +8. After its completion press any button to return to the main menu of Lockpick_RCM. +9. Navigate to and select `Power off` if you have an SD card reader. Or you could Navigate and select `Reboot (RCM)` if you want to mount your SD card using `TegraRCMGUI > Tools > Memloader V3 > MMC - SD Card`. +10. You can find your keys in `sd:/switch/prod.keys` and `sd:/switch/title.keys` respectively. +11. Copy these files and paste them in `Ryujinx/system`. +And you're done! + +## Title keys + +These are only used for games that are not dumped from cartridges but from games downloaded from the Nintendo eShop, these are also only used if the eShop dump does *not* have a `ticket`. If the game does have a ticket, Ryujinx will read the key directly from that ticket. + +Title keys are stored in the format `rights_id = key`. + +For example: + +``` +01000000000100000000000000000003 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +01000000000108000000000000000003 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +01000000000108000000000000000004 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +``` + +## Prod keys + +These are typically used to decrypt system files and encrypted game files. These keys get changed in about every major system update, so make sure to keep your keys up-to-date if you want to play newer games! \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE.txt index 00d2e135a7..818ddd7605 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,24 +1,9 @@ -This is free and unencumbered software released into the public domain. +MIT License -Anyone is free to copy, modify, publish, use, compile, sell, or -distribute this software, either in source code form or as a compiled -binary, for any purpose, commercial or non-commercial, and by any -means. +Copyright (c) Ryujinx Team and Contributors -In jurisdictions that recognize copyright laws, the author or authors -of this software dedicate any and all copyright interest in the -software to the public domain. We make this dedication for the benefit -of the public at large and to the detriment of our heirs and -successors. We intend this dedication to be an overt act of -relinquishment in perpetuity of all present and future rights to this -software under copyright law. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR -OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -For more information, please refer to \ No newline at end of file +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index 71dad9ce29..3d415d1be3 100644 --- a/README.md +++ b/README.md @@ -1,100 +1,98 @@ -![](https://ryujinx.github.io/static/img/Ryujinx_logo_128.png) -# Ryujinx [![Build status](https://ci.appveyor.com/api/projects/status/ssg4jwu6ve3k594s?svg=true)](https://ci.appveyor.com/project/gdkchan/ryujinx) -Experimental Switch emulator written in C# +

+ Ryujinx + + + + + + +

-Don't expect much from this. Some homebrew apps work, Puyo Puyo Tetris shows the intro logo (sometimes), and a handful of games boot / work; but that's about it for now. -Contributions are always welcome. +

+ An Experimental Switch emulator written in C#
+
+ +

-**Building** +
+ A lot of games boot, but only some are playable. See the compatiblity list here. +
-To build this emulator, you will need the .NET Core 2.1 (RC1) (or higher) SDK. https://www.microsoft.com/net/download/ -In release builds, memory checks are disabled to improve performances. +## Usage -Or just drag'n'drop the *.NRO / *.NSO or the game folder on the executable if you have a pre-build version. +To run this emulator, you need the [.NET Core 3.0 (or higher) SDK](https://dotnet.microsoft.com/download/dotnet-core). -**Features** +If you use a pre-built version, you can use the graphical interface to run your games and homebrew. - - Audio is partially supported (glitched) on Windows but you need to install the OpenAL Core SDK. -https://openal.org/downloads/OpenAL11CoreSDK.zip +If you build it yourself you will need to: +Run `dotnet run -c Release -- path\to\homebrew.nro` inside the Ryujinx project folder to run homebrew apps. +Run `dotnet run -c Release -- path\to\game.nsp/xci` to run official games. - - Keyboard Input is partially supported: - - Left Joycon: - - Stick Up = W - - Stick Down = S - - Stick Left = A - - Stick Right = D - - Stick Button = F - - DPad Up = Up - - DPad Down = Down - - DPad Left = Left - - DPad Right = Right - - Minus = - - - L = E - - ZL = Q +Every file related to Ryujinx is stored in the `Ryujinx` folder. Located in `C:\Users\USERNAME\AppData\Roaming\` for Windows, `/home/USERNAME/.config` for Linux or `/Users/USERNAME/Library/Application Support/` for macOS. It can also be accessed by clicking `Open Ryujinx Folder` under the File menu in the GUI. - - Right Joycon: - - Stick Up = I - - Stick Down = K - - Stick Left = J - - Stick Right = L - - Stick Button = H - - A = Z - - B = X - - X = C - - Y = V - - Plus = + - - R = U - - ZR = O - - For more information on how to configure these buttons see [CONFIG.md](CONFIG.md) +## Latest build - - Controller Input is partially supported: - - Left Joycon: - - Analog Stick = Left Analog Stick - - DPad Up = DPad Up - - DPad Down = DPad Down - - DPad Left = DPad Left - - DPad Right = DPad Right - - Minus = Select / Back / Share - - L = Left Shoulder Button - - ZL = Left Trigger - - - Right Joycon: - - Analog Stick = Right Analog Stick - - A = B / Circle - - B = A / Cross - - X = Y / Triangle - - Y = X / Square - - Plus = Start / Options - - R = Right Shoulder Button - - ZR = Right Trigger - - For more information on how to configure these buttons see [CONFIG.md](CONFIG.md) +These builds are compiled automatically for each commit on the master branch, **and may be unstable or completely broken.** - - Config File: `Ryujinx.conf` should be present in executable folder. - For more information [you can go here](CONFIG.md). +The latest automatic build for Windows, macOS, and Linux can be found on the [Official Website](https://ryujinx.org/#/Build). - - If you are a Windows user, you can configure your keys, the logs, install OpenAL, etc... with Ryujinx-Setting. - [Download it, right here](https://github.com/AcK77/Ryujinx-Settings) +## Requirements -**Help** + - **Switch Keys** -If you have some homebrew that currently doesn't work within the emulator, you can contact us through our Discord with the compiled NRO/NSO (and source code if possible) and then we'll make changes in order to make the requested app / game work. + Everything on the Switch is encrypted, so if you want to run anything other than homebrew, you have to dump encryption keys from your console. To get more information please take a look at our [Keys Documentation](KEYS.md). -**Contact** + - **System Titles** -For help, support, suggestions, or if you just want to get in touch with the team; join our Discord server! -https://discord.gg/VkQYXAZ + Some of our System Module implementations, like `time`, require [System Data Archives](https://switchbrew.org/wiki/Title_list#System_Data_Archives). You can install them by mounting your nand partition using [HacDiskMount](https://switchtools.sshnuke.net/) and copying the content to `Ryujinx/bis/system`. -**Running** + - **Executables** -To run this emulator, you need the .NET Core 2.1 (or higher) SDK *and* the OpenAL 11 Core SDK. -Run `dotnet run -c Release -- path\to\homebrew.nro` inside the Ryujinx solution folder to run homebrew apps. -Run `dotnet run -c Release -- path\to\game_exefs_and_romfs_folder` to run official games (they need to be decrypted and extracted first!) + Ryujinx is able to run both official games and homebrew. -**Compatibility** -You can check out the compatibility list within the Wiki. Only a handful of games actually work. + Homebrew is available on many websites, such as the [Switch Appstore](https://www.switchbru.com/appstore/). -**Latest build** + A hacked Nintendo Switch is needed to dump games, which you can learn how to do [here](https://nh-server.github.io/switch-guide/). Once you have hacked your Nintendo Switch, you will need to dump your own games with [NxDumpTool](https://github.com/DarkMatterCore/nxdumptool/releases) to get an XCI or NSP dump. -These builds are compiled automatically for each commit on the master branch. They may be unstable or might not work at all. -The latest automatic build for Windows (64-bit) can be found on the [official website](https://ryujinx.org/#/Build). +## Features + + - **Audio** + + Everything for audio is partially supported. We currently use a C# wrapper for [libsoundio](http://libsound.io/), and we support [OpenAL](https://openal.org/downloads/OpenAL11CoreSDK.zip) (installation needed) too as a fallback. Our current Opus implementation is pretty incomplete. + +- **CPU** + + The CPU emulator, ARMeilleure, emulates an ARMv8 CPU, and currently only has support for the new 64-bit ARMv8 instructions (with a few instructions still missing). It translates the ARM code to a custom IR, performs a few optimizations, and turns that into x86 code. To handle that, we use our own JIT called ARMeilleure, which uses the custom IR and compiles the code to x86. + +- **GPU** + + The GPU emulator emulates the Switch's Maxwell GPU using the OpenGL API (version 4.2 minimum) through a custom build of OpenTK. + +- **Input** + + We currently have support for keyboard, mouse, touch input, JoyCon input support emulated through the keyboard, and some controllers too. You can set up everything inside the configuration menu. + +- **Configuration** + + The emulator has settings for dumping shaders, enabling or disabling some logging, remapping controllers, and more. You can configure all of them through the graphical interface or manually through the config file, `Config.json`. + + For more information [you can go here](CONFIG.md) *(Outdated)*. + +## Compatibility + +You can check out the compatibility list [here](https://github.com/Ryujinx/Ryujinx-Games-List/issues). + +Don't hesitate to open a new issue if a game isn't already on there. + +## Help + +If you have homebrew that currently doesn't work within the emulator, you can contact us through our Discord with the .NRO/.NSO and source code, if possible. We'll take note of whatever is causing the app/game to not work, on the watch list and fix it at a later date. + +If you need help with setting up Ryujinx, you can ask questions in the support channel of our Discord server. + +## Contact + +If you have contributions, need support, have suggestions, or just want to get in touch with the team, join our [Discord server](https://discord.gg/N2FmfVc)! + +If you'd like to donate, please take a look at our [Patreon](https://www.patreon.com/ryujinx). diff --git a/Ryujinx.Audio/Decoders/Adpcm/AdpcmDecoder.cs b/Ryujinx.Audio/Decoders/Adpcm/AdpcmDecoder.cs new file mode 100644 index 0000000000..24455b4187 --- /dev/null +++ b/Ryujinx.Audio/Decoders/Adpcm/AdpcmDecoder.cs @@ -0,0 +1,91 @@ +namespace Ryujinx.Audio.Adpcm +{ + public static class AdpcmDecoder + { + private const int SamplesPerFrame = 14; + private const int BytesPerFrame = 8; + + public static int[] Decode(byte[] Buffer, AdpcmDecoderContext Context) + { + int Samples = GetSamplesCountFromSize(Buffer.Length); + + int[] Pcm = new int[Samples * 2]; + + short History0 = Context.History0; + short History1 = Context.History1; + + int InputOffset = 0; + int OutputOffset = 0; + + while (InputOffset < Buffer.Length) + { + byte Header = Buffer[InputOffset++]; + + int Scale = 0x800 << (Header & 0xf); + + int CoeffIndex = (Header >> 4) & 7; + + short Coeff0 = Context.Coefficients[CoeffIndex * 2 + 0]; + short Coeff1 = Context.Coefficients[CoeffIndex * 2 + 1]; + + int FrameSamples = SamplesPerFrame; + + if (FrameSamples > Samples) + { + FrameSamples = Samples; + } + + int Value = 0; + + for (int SampleIndex = 0; SampleIndex < FrameSamples; SampleIndex++) + { + int Sample; + + if ((SampleIndex & 1) == 0) + { + Value = Buffer[InputOffset++]; + + Sample = (Value << 24) >> 28; + } + else + { + Sample = (Value << 28) >> 28; + } + + int Prediction = Coeff0 * History0 + Coeff1 * History1; + + Sample = (Sample * Scale + Prediction + 0x400) >> 11; + + short SaturatedSample = DspUtils.Saturate(Sample); + + History1 = History0; + History0 = SaturatedSample; + + Pcm[OutputOffset++] = SaturatedSample; + Pcm[OutputOffset++] = SaturatedSample; + } + + Samples -= FrameSamples; + } + + Context.History0 = History0; + Context.History1 = History1; + + return Pcm; + } + + public static long GetSizeFromSamplesCount(int SamplesCount) + { + int Frames = SamplesCount / SamplesPerFrame; + + return Frames * BytesPerFrame; + } + + public static int GetSamplesCountFromSize(long Size) + { + int Frames = (int)(Size / BytesPerFrame); + + return Frames * SamplesPerFrame; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Audio/Decoders/Adpcm/AdpcmDecoderContext.cs b/Ryujinx.Audio/Decoders/Adpcm/AdpcmDecoderContext.cs new file mode 100644 index 0000000000..91730333c8 --- /dev/null +++ b/Ryujinx.Audio/Decoders/Adpcm/AdpcmDecoderContext.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Audio.Adpcm +{ + public class AdpcmDecoderContext + { + public short[] Coefficients; + + public short History0; + public short History1; + } +} \ No newline at end of file diff --git a/Ryujinx.Audio/DspUtils.cs b/Ryujinx.Audio/DspUtils.cs new file mode 100644 index 0000000000..c048161dae --- /dev/null +++ b/Ryujinx.Audio/DspUtils.cs @@ -0,0 +1,16 @@ +namespace Ryujinx.Audio.Adpcm +{ + public static class DspUtils + { + public static short Saturate(int Value) + { + if (Value > short.MaxValue) + Value = short.MaxValue; + + if (Value < short.MinValue) + Value = short.MinValue; + + return (short)Value; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Audio/IAalOutput.cs b/Ryujinx.Audio/IAalOutput.cs index f9978ee4d9..489f90028e 100644 --- a/Ryujinx.Audio/IAalOutput.cs +++ b/Ryujinx.Audio/IAalOutput.cs @@ -1,24 +1,27 @@ +using System; + namespace Ryujinx.Audio { - public interface IAalOutput + public interface IAalOutput : IDisposable { - int OpenTrack( - int SampleRate, - int Channels, - ReleaseCallback Callback, - out AudioFormat Format); + int OpenTrack(int sampleRate, int channels, ReleaseCallback callback); - void CloseTrack(int Track); + void CloseTrack(int trackId); - bool ContainsBuffer(int Track, long Tag); + bool ContainsBuffer(int trackId, long bufferTag); - long[] GetReleasedBuffers(int Track, int MaxCount); + long[] GetReleasedBuffers(int trackId, int maxCount); - void AppendBuffer(int Track, long Tag, byte[] Buffer); + void AppendBuffer(int trackId, long bufferTag, T[] buffer) where T : struct; - void Start(int Track); - void Stop(int Track); + void Start(int trackId); - PlaybackState GetState(int Track); + void Stop(int trackId); + + float GetVolume(); + + void SetVolume(float volume); + + PlaybackState GetState(int trackId); } } \ No newline at end of file diff --git a/Ryujinx.Audio/Native/libsoundio/MarshalExtensions.cs b/Ryujinx.Audio/Native/libsoundio/MarshalExtensions.cs new file mode 100644 index 0000000000..dfa699c82a --- /dev/null +++ b/Ryujinx.Audio/Native/libsoundio/MarshalExtensions.cs @@ -0,0 +1,38 @@ +using System; +using System.Runtime.InteropServices; + +namespace SoundIOSharp +{ + public static class MarshalEx + { + public static double ReadDouble(IntPtr handle, int offset = 0) + { + return BitConverter.Int64BitsToDouble(Marshal.ReadInt64(handle, offset)); + } + + public static void WriteDouble(IntPtr handle, double value) + { + WriteDouble(handle, 0, value); + } + + public static void WriteDouble(IntPtr handle, int offset, double value) + { + Marshal.WriteInt64(handle, offset, BitConverter.DoubleToInt64Bits(value)); + } + + public static float ReadFloat(IntPtr handle, int offset = 0) + { + return BitConverter.Int32BitsToSingle(Marshal.ReadInt32(handle, offset)); + } + + public static void WriteFloat(IntPtr handle, float value) + { + WriteFloat(handle, 0, value); + } + + public static void WriteFloat(IntPtr handle, int offset, float value) + { + Marshal.WriteInt32(handle, offset, BitConverter.SingleToInt32Bits(value)); + } + } +} diff --git a/Ryujinx.Audio/Native/libsoundio/SoundIO.cs b/Ryujinx.Audio/Native/libsoundio/SoundIO.cs new file mode 100644 index 0000000000..e9ab9e6e1a --- /dev/null +++ b/Ryujinx.Audio/Native/libsoundio/SoundIO.cs @@ -0,0 +1,311 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; + +namespace SoundIOSharp +{ + public class SoundIO : IDisposable + { + Pointer handle; + + public SoundIO () + { + handle = Natives.soundio_create (); + } + + internal SoundIO (Pointer handle) + { + this.handle = handle; + } + + public void Dispose () + { + foreach (var h in allocated_hglobals) + Marshal.FreeHGlobal (h); + Natives.soundio_destroy (handle); + } + + // Equality (based on handle) + + public override bool Equals (object other) + { + var d = other as SoundIO; + return d != null && this.handle == d.handle; + } + + public override int GetHashCode () + { + return (int) (IntPtr) handle; + } + + public static bool operator == (SoundIO obj1, SoundIO obj2) + { + return (object)obj1 == null ? (object)obj2 == null : obj1.Equals (obj2); + } + + public static bool operator != (SoundIO obj1, SoundIO obj2) + { + return (object)obj1 == null ? (object)obj2 != null : !obj1.Equals (obj2); + } + + // fields + + // FIXME: this should be taken care in more centralized/decent manner... we don't want to write + // this kind of code anywhere we need string marshaling. + List allocated_hglobals = new List (); + + public string ApplicationName { + get { return Marshal.PtrToStringAnsi (Marshal.ReadIntPtr (handle, app_name_offset)); } + set { + unsafe { + var existing = Marshal.ReadIntPtr (handle, app_name_offset); + if (allocated_hglobals.Contains (existing)) { + allocated_hglobals.Remove (existing); + Marshal.FreeHGlobal (existing); + } + var ptr = Marshal.StringToHGlobalAnsi (value); + Marshal.WriteIntPtr (handle, app_name_offset, ptr); + allocated_hglobals.Add (ptr); + } + } + } + static readonly int app_name_offset = (int)Marshal.OffsetOf ("app_name"); + + public SoundIOBackend CurrentBackend { + get { return (SoundIOBackend) Marshal.ReadInt32 (handle, current_backend_offset); } + } + static readonly int current_backend_offset = (int)Marshal.OffsetOf ("current_backend"); + + // emit_rtprio_warning + public Action EmitRealtimePriorityWarning { + get { return emit_rtprio_warning; } + set { + emit_rtprio_warning = value; + var ptr = Marshal.GetFunctionPointerForDelegate (on_devices_change); + Marshal.WriteIntPtr (handle, emit_rtprio_warning_offset, ptr); + } + } + static readonly int emit_rtprio_warning_offset = (int)Marshal.OffsetOf ("emit_rtprio_warning"); + Action emit_rtprio_warning; + + // jack_error_callback + public Action JackErrorCallback { + get { return jack_error_callback; } + set { + jack_error_callback = value; + if (value == null) + jack_error_callback = null; + else + jack_error_callback_native = msg => jack_error_callback (msg); + var ptr = Marshal.GetFunctionPointerForDelegate (jack_error_callback_native); + Marshal.WriteIntPtr (handle, jack_error_callback_offset, ptr); + } + } + static readonly int jack_error_callback_offset = (int)Marshal.OffsetOf ("jack_error_callback"); + Action jack_error_callback; + delegate void jack_error_delegate (string message); + jack_error_delegate jack_error_callback_native; + + // jack_info_callback + public Action JackInfoCallback { + get { return jack_info_callback; } + set { + jack_info_callback = value; + if (value == null) + jack_info_callback = null; + else + jack_info_callback_native = msg => jack_info_callback (msg); + var ptr = Marshal.GetFunctionPointerForDelegate (jack_info_callback_native); + Marshal.WriteIntPtr (handle, jack_info_callback_offset, ptr); + } + } + static readonly int jack_info_callback_offset = (int)Marshal.OffsetOf ("jack_info_callback"); + Action jack_info_callback; + delegate void jack_info_delegate (string message); + jack_info_delegate jack_info_callback_native; + + // on_backend_disconnect + public Action OnBackendDisconnect { + get { return on_backend_disconnect; } + set { + on_backend_disconnect = value; + if (value == null) + on_backend_disconnect_native = null; + else + on_backend_disconnect_native = (sio, err) => on_backend_disconnect (err); + var ptr = Marshal.GetFunctionPointerForDelegate (on_backend_disconnect_native); + Marshal.WriteIntPtr (handle, on_backend_disconnect_offset, ptr); + } + } + static readonly int on_backend_disconnect_offset = (int)Marshal.OffsetOf ("on_backend_disconnect"); + Action on_backend_disconnect; + delegate void on_backend_disconnect_delegate (IntPtr handle, int errorCode); + on_backend_disconnect_delegate on_backend_disconnect_native; + + // on_devices_change + public Action OnDevicesChange { + get { return on_devices_change; } + set { + on_devices_change = value; + if (value == null) + on_devices_change_native = null; + else + on_devices_change_native = sio => on_devices_change (); + var ptr = Marshal.GetFunctionPointerForDelegate (on_devices_change_native); + Marshal.WriteIntPtr (handle, on_devices_change_offset, ptr); + } + } + static readonly int on_devices_change_offset = (int)Marshal.OffsetOf ("on_devices_change"); + Action on_devices_change; + delegate void on_devices_change_delegate (IntPtr handle); + on_devices_change_delegate on_devices_change_native; + + // on_events_signal + public Action OnEventsSignal { + get { return on_events_signal; } + set { + on_events_signal = value; + if (value == null) + on_events_signal_native = null; + else + on_events_signal_native = sio => on_events_signal (); + var ptr = Marshal.GetFunctionPointerForDelegate (on_events_signal_native); + Marshal.WriteIntPtr (handle, on_events_signal_offset, ptr); + } + } + static readonly int on_events_signal_offset = (int)Marshal.OffsetOf ("on_events_signal"); + Action on_events_signal; + delegate void on_events_signal_delegate (IntPtr handle); + on_events_signal_delegate on_events_signal_native; + + + // functions + + public int BackendCount { + get { return Natives.soundio_backend_count (handle); } + } + + public int InputDeviceCount { + get { return Natives.soundio_input_device_count (handle); } + } + + public int OutputDeviceCount { + get { return Natives.soundio_output_device_count (handle); } + } + + public int DefaultInputDeviceIndex { + get { return Natives.soundio_default_input_device_index (handle); } + } + + public int DefaultOutputDeviceIndex { + get { return Natives.soundio_default_output_device_index (handle); } + } + + public SoundIOBackend GetBackend (int index) + { + return (SoundIOBackend) Natives.soundio_get_backend (handle, index); + } + + public SoundIODevice GetInputDevice (int index) + { + return new SoundIODevice (Natives.soundio_get_input_device (handle, index)); + } + + public SoundIODevice GetOutputDevice (int index) + { + return new SoundIODevice (Natives.soundio_get_output_device (handle, index)); + } + + public void Connect () + { + var ret = (SoundIoError) Natives.soundio_connect (handle); + if (ret != SoundIoError.SoundIoErrorNone) + throw new SoundIOException (ret); + } + + public void ConnectBackend (SoundIOBackend backend) + { + var ret = (SoundIoError) Natives.soundio_connect_backend (handle, (SoundIoBackend) backend); + if (ret != SoundIoError.SoundIoErrorNone) + throw new SoundIOException (ret); + } + + public void Disconnect () + { + Natives.soundio_disconnect (handle); + } + + public void FlushEvents () + { + Natives.soundio_flush_events (handle); + } + + public void WaitEvents () + { + Natives.soundio_wait_events (handle); + } + + public void Wakeup () + { + Natives.soundio_wakeup (handle); + } + + public void ForceDeviceScan () + { + Natives.soundio_force_device_scan (handle); + } + + public SoundIORingBuffer CreateRingBuffer (int capacity) + { + return new SoundIORingBuffer (Natives.soundio_ring_buffer_create (handle, capacity)); + } + + // static methods + + public static string VersionString { + get { return Marshal.PtrToStringAnsi (Natives.soundio_version_string ()); } + } + + public static int VersionMajor { + get { return Natives.soundio_version_major (); } + } + + public static int VersionMinor { + get { return Natives.soundio_version_minor (); } + } + + public static int VersionPatch { + get { return Natives.soundio_version_patch (); } + } + + public static string GetBackendName (SoundIOBackend backend) + { + return Marshal.PtrToStringAnsi (Natives.soundio_backend_name ((SoundIoBackend) backend)); + } + + public static bool HaveBackend (SoundIOBackend backend) + { + return Natives.soundio_have_backend ((SoundIoBackend) backend); + } + + public static int GetBytesPerSample (SoundIOFormat format) + { + return Natives.soundio_get_bytes_per_sample ((SoundIoFormat) format); + } + + public static int GetBytesPerFrame (SoundIOFormat format, int channelCount) + { + return Natives.soundio_get_bytes_per_frame ((SoundIoFormat) format, channelCount); + } + + public static int GetBytesPerSecond (SoundIOFormat format, int channelCount, int sampleRate) + { + return Natives.soundio_get_bytes_per_second ((SoundIoFormat) format, channelCount, sampleRate); + } + + public static string GetSoundFormatName (SoundIOFormat format) + { + return Marshal.PtrToStringAnsi (Natives.soundio_format_string ((SoundIoFormat) format)); + } + } +} diff --git a/Ryujinx.Audio/Native/libsoundio/SoundIOBackend.cs b/Ryujinx.Audio/Native/libsoundio/SoundIOBackend.cs new file mode 100644 index 0000000000..dfcb0a3f03 --- /dev/null +++ b/Ryujinx.Audio/Native/libsoundio/SoundIOBackend.cs @@ -0,0 +1,15 @@ +using System; +namespace SoundIOSharp +{ + public enum SoundIOBackend + { + None = 0, + Jack = 1, + PulseAudio = 2, + Alsa = 3, + CoreAudio = 4, + Wasapi = 5, + Dummy = 6, + } + +} diff --git a/Ryujinx.Audio/Native/libsoundio/SoundIOChannelArea.cs b/Ryujinx.Audio/Native/libsoundio/SoundIOChannelArea.cs new file mode 100644 index 0000000000..f30e2bbb44 --- /dev/null +++ b/Ryujinx.Audio/Native/libsoundio/SoundIOChannelArea.cs @@ -0,0 +1,26 @@ +using System; +using System.Runtime.InteropServices; + +namespace SoundIOSharp +{ + public struct SoundIOChannelArea + { + internal SoundIOChannelArea (Pointer handle) + { + this.handle = handle; + } + + Pointer handle; + + public IntPtr Pointer { + get { return Marshal.ReadIntPtr (handle, ptr_offset); } + set { Marshal.WriteIntPtr (handle, ptr_offset, value); } + } + static readonly int ptr_offset = (int) Marshal.OffsetOf ("ptr"); + + public int Step { + get { return Marshal.ReadInt32 (handle, step_offset); } + } + static readonly int step_offset = (int)Marshal.OffsetOf ("step"); + } +} diff --git a/Ryujinx.Audio/Native/libsoundio/SoundIOChannelAreas.cs b/Ryujinx.Audio/Native/libsoundio/SoundIOChannelAreas.cs new file mode 100644 index 0000000000..776d657acb --- /dev/null +++ b/Ryujinx.Audio/Native/libsoundio/SoundIOChannelAreas.cs @@ -0,0 +1,33 @@ +using System; +using System.Runtime.InteropServices; + +namespace SoundIOSharp +{ + public struct SoundIOChannelAreas + { + static readonly int native_size = Marshal.SizeOf (); + + internal SoundIOChannelAreas (IntPtr head, int channelCount, int frameCount) + { + this.head = head; + this.channel_count = channelCount; + this.frame_count = frameCount; + } + + IntPtr head; + int channel_count; + int frame_count; + + public bool IsEmpty { + get { return head == IntPtr.Zero; } + } + + public SoundIOChannelArea GetArea (int channel) + { + return new SoundIOChannelArea (head + native_size * channel); + } + + public int ChannelCount => channel_count; + public int FrameCount => frame_count; + } +} diff --git a/Ryujinx.Audio/Native/libsoundio/SoundIOChannelId.cs b/Ryujinx.Audio/Native/libsoundio/SoundIOChannelId.cs new file mode 100644 index 0000000000..d24508a1f1 --- /dev/null +++ b/Ryujinx.Audio/Native/libsoundio/SoundIOChannelId.cs @@ -0,0 +1,77 @@ +using System; +namespace SoundIOSharp +{ + + 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/Ryujinx.Audio/Native/libsoundio/SoundIOChannelLayout.cs b/Ryujinx.Audio/Native/libsoundio/SoundIOChannelLayout.cs new file mode 100644 index 0000000000..ee63454200 --- /dev/null +++ b/Ryujinx.Audio/Native/libsoundio/SoundIOChannelLayout.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; + +namespace SoundIOSharp +{ + public struct SoundIOChannelLayout + { + public static int BuiltInCount { + get { return Natives.soundio_channel_layout_builtin_count (); } + } + + public static SoundIOChannelLayout GetBuiltIn (int index) + { + return new SoundIOChannelLayout (Natives.soundio_channel_layout_get_builtin (index)); + } + + public static SoundIOChannelLayout GetDefault (int channelCount) + { + var handle = Natives.soundio_channel_layout_get_default (channelCount); + return new SoundIOChannelLayout (handle); + } + + public static SoundIOChannelId ParseChannelId (string name) + { + var ptr = Marshal.StringToHGlobalAnsi (name); + try { + return (SoundIOChannelId)Natives.soundio_parse_channel_id (ptr, name.Length); + } finally { + Marshal.FreeHGlobal (ptr); + } + } + + // instance members + + internal SoundIOChannelLayout (Pointer handle) + { + this.handle = handle; + } + + readonly Pointer handle; + + public bool IsNull { + get { return handle.Handle == IntPtr.Zero; } + } + + internal IntPtr Handle { + get { return handle; } + } + + public int ChannelCount { + get { return IsNull ? 0 : Marshal.ReadInt32 ((IntPtr) handle + channel_count_offset); } + } + static readonly int channel_count_offset = (int) Marshal.OffsetOf ("channel_count"); + + public string Name { + get { return IsNull ? null : Marshal.PtrToStringAnsi (Marshal.ReadIntPtr ((IntPtr) handle + name_offset)); } + } + static readonly int name_offset = (int)Marshal.OffsetOf ("name"); + + public IEnumerable Channels { + get { + if (IsNull) + yield break; + for (int i = 0; i < 24; i++) + yield return (SoundIOChannelId) Marshal.ReadInt32 ((IntPtr) handle + channels_offset + sizeof (SoundIoChannelId) * i); + } + } + static readonly int channels_offset = (int)Marshal.OffsetOf ("channels"); + + public override bool Equals (object other) + { + if (!(other is SoundIOChannelLayout)) + return false; + var s = (SoundIOChannelLayout) other; + return handle == s.handle || Natives.soundio_channel_layout_equal (handle, s.handle); + } + + public override int GetHashCode () + { + return handle.GetHashCode (); + } + + public string DetectBuiltInName () + { + if (IsNull) + throw new InvalidOperationException (); + return Natives.soundio_channel_layout_detect_builtin (handle) ? Name : null; + } + + public int FindChannel (SoundIOChannelId channel) + { + if (IsNull) + throw new InvalidOperationException (); + return Natives.soundio_channel_layout_find_channel (handle, (SoundIoChannelId) channel); + } + } +} diff --git a/Ryujinx.Audio/Native/libsoundio/SoundIODevice.cs b/Ryujinx.Audio/Native/libsoundio/SoundIODevice.cs new file mode 100644 index 0000000000..81b78b679f --- /dev/null +++ b/Ryujinx.Audio/Native/libsoundio/SoundIODevice.cs @@ -0,0 +1,215 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; + +namespace SoundIOSharp +{ + public class SoundIODevice + { + public static SoundIOChannelLayout BestMatchingChannelLayout (SoundIODevice device1, SoundIODevice device2) + { + var ptr1 = Marshal.ReadIntPtr (device1.handle, layouts_offset); + var ptr2 = Marshal.ReadIntPtr (device2.handle, layouts_offset); + return new SoundIOChannelLayout (Natives.soundio_best_matching_channel_layout (ptr1, device1.LayoutCount, ptr2, device2.LayoutCount)); + } + + internal SoundIODevice (Pointer handle) + { + this.handle = handle; + } + + readonly Pointer handle; + + // Equality (based on handle and native func) + + public override bool Equals (object other) + { + var d = other as SoundIODevice; + return d != null && (this.handle == d.handle || Natives.soundio_device_equal (this.handle, d.handle)); + } + + public override int GetHashCode () + { + return (int) (IntPtr) handle; + } + + public static bool operator == (SoundIODevice obj1, SoundIODevice obj2) + { + return (object)obj1 == null ? (object)obj2 == null : obj1.Equals (obj2); + } + + public static bool operator != (SoundIODevice obj1, SoundIODevice obj2) + { + return (object)obj1 == null ? (object) obj2 != null : !obj1.Equals (obj2); + } + + // fields + + public SoundIODeviceAim Aim { + get { return (SoundIODeviceAim) Marshal.ReadInt32 (handle, aim_offset); } + } + static readonly int aim_offset = (int)Marshal.OffsetOf ("aim"); + + public SoundIOFormat CurrentFormat { + get { return (SoundIOFormat) Marshal.ReadInt32 (handle, current_format_offset); } + } + static readonly int current_format_offset = (int)Marshal.OffsetOf ("current_format"); + + public SoundIOChannelLayout CurrentLayout { + get { return new SoundIOChannelLayout ((IntPtr) handle + current_layout_offset); + } + } + static readonly int current_layout_offset = (int)Marshal.OffsetOf ("current_layout"); + + public int FormatCount { + get { return Marshal.ReadInt32 (handle, format_count_offset); } + } + static readonly int format_count_offset = (int)Marshal.OffsetOf ("format_count"); + + public IEnumerable Formats { + get { + var ptr = Marshal.ReadIntPtr (handle, formats_offset); + for (int i = 0; i < FormatCount; i++) + yield return (SoundIOFormat) Marshal.ReadInt32 (ptr, i); + } + } + static readonly int formats_offset = (int)Marshal.OffsetOf ("formats"); + + public string Id { + get { return Marshal.PtrToStringAnsi (Marshal.ReadIntPtr (handle, id_offset)); } + } + static readonly int id_offset = (int)Marshal.OffsetOf ("id"); + + public bool IsRaw { + get { return Marshal.ReadInt32 (handle, is_raw_offset) != 0; } + } + static readonly int is_raw_offset = (int)Marshal.OffsetOf ("is_raw"); + + public int LayoutCount { + get { return Marshal.ReadInt32 (handle, layout_count_offset); } + } + static readonly int layout_count_offset = (int)Marshal.OffsetOf ("layout_count"); + + public IEnumerable Layouts { + get { + var ptr = Marshal.ReadIntPtr (handle, layouts_offset); + for (int i = 0; i < LayoutCount; i++) + yield return new SoundIOChannelLayout (ptr + i * Marshal.SizeOf ()); + } + } + static readonly int layouts_offset = (int) Marshal.OffsetOf ("layouts"); + + public string Name { + get { return Marshal.PtrToStringAnsi (Marshal.ReadIntPtr (handle, name_offset)); } + } + static readonly int name_offset = (int)Marshal.OffsetOf ("name"); + + public int ProbeError { + get { return Marshal.ReadInt32 (handle, probe_error_offset); } + } + static readonly int probe_error_offset = (int)Marshal.OffsetOf ("probe_error"); + + public int ReferenceCount { + get { return Marshal.ReadInt32 (handle, ref_count_offset); } + } + static readonly int ref_count_offset = (int)Marshal.OffsetOf ("ref_count"); + + public int SampleRateCount { + get { return Marshal.ReadInt32 (handle, sample_rate_count_offset); } + } + static readonly int sample_rate_count_offset = (int)Marshal.OffsetOf ("sample_rate_count"); + + public IEnumerable SampleRates { + get { + var ptr = Marshal.ReadIntPtr (handle, sample_rates_offset); + for (int i = 0; i < SampleRateCount; i++) + yield return new SoundIOSampleRateRange ( + Marshal.ReadInt32 (ptr, i * 2), + Marshal.ReadInt32 (ptr, i * 2 + 1)); + } + } + static readonly int sample_rates_offset = (int)Marshal.OffsetOf ("sample_rates"); + + public double SoftwareLatencyCurrent { + get { return MarshalEx.ReadDouble (handle, software_latency_current_offset); } + set { MarshalEx.WriteDouble (handle, software_latency_current_offset, value); } + } + static readonly int software_latency_current_offset = (int)Marshal.OffsetOf ("software_latency_current"); + + public double SoftwareLatencyMin { + get { return MarshalEx.ReadDouble (handle, software_latency_min_offset); } + set { MarshalEx.WriteDouble (handle, software_latency_min_offset, value); } + } + static readonly int software_latency_min_offset = (int)Marshal.OffsetOf ("software_latency_min"); + + public double SoftwareLatencyMax { + get { return MarshalEx.ReadDouble (handle, software_latency_max_offset); } + set { MarshalEx.WriteDouble (handle, software_latency_max_offset, value); } + } + static readonly int software_latency_max_offset = (int)Marshal.OffsetOf ("software_latency_max"); + + public SoundIO SoundIO { + get { return new SoundIO (Marshal.ReadIntPtr (handle, soundio_offset)); } + } + static readonly int soundio_offset = (int)Marshal.OffsetOf ("soundio"); + + // functions + + public void AddReference () + { + Natives.soundio_device_ref (handle); + } + + public void RemoveReference () + { + Natives.soundio_device_unref (handle); + } + + public void SortDeviceChannelLayouts () + { + Natives.soundio_device_sort_channel_layouts (handle); + } + + public static readonly SoundIOFormat S16NE = BitConverter.IsLittleEndian ? SoundIOFormat.S16LE : SoundIOFormat.S16BE; + public static readonly SoundIOFormat U16NE = BitConverter.IsLittleEndian ? SoundIOFormat.U16LE : SoundIOFormat.U16BE; + public static readonly SoundIOFormat S24NE = BitConverter.IsLittleEndian ? SoundIOFormat.S24LE : SoundIOFormat.S24BE; + public static readonly SoundIOFormat U24NE = BitConverter.IsLittleEndian ? SoundIOFormat.U24LE : SoundIOFormat.U24BE; + public static readonly SoundIOFormat S32NE = BitConverter.IsLittleEndian ? SoundIOFormat.S32LE : SoundIOFormat.S32BE; + public static readonly SoundIOFormat U32NE = BitConverter.IsLittleEndian ? SoundIOFormat.U32LE : SoundIOFormat.U32BE; + public static readonly SoundIOFormat Float32NE = BitConverter.IsLittleEndian ? SoundIOFormat.Float32LE : SoundIOFormat.Float32BE; + public static readonly SoundIOFormat Float64NE = BitConverter.IsLittleEndian ? SoundIOFormat.Float64LE : SoundIOFormat.Float64BE; + public static readonly SoundIOFormat S16FE = !BitConverter.IsLittleEndian ? SoundIOFormat.S16LE : SoundIOFormat.S16BE; + public static readonly SoundIOFormat U16FE = !BitConverter.IsLittleEndian ? SoundIOFormat.U16LE : SoundIOFormat.U16BE; + public static readonly SoundIOFormat S24FE = !BitConverter.IsLittleEndian ? SoundIOFormat.S24LE : SoundIOFormat.S24BE; + public static readonly SoundIOFormat U24FE = !BitConverter.IsLittleEndian ? SoundIOFormat.U24LE : SoundIOFormat.U24BE; + public static readonly SoundIOFormat S32FE = !BitConverter.IsLittleEndian ? SoundIOFormat.S32LE : SoundIOFormat.S32BE; + public static readonly SoundIOFormat U32FE = !BitConverter.IsLittleEndian ? SoundIOFormat.U32LE : SoundIOFormat.U32BE; + public static readonly SoundIOFormat Float32FE = !BitConverter.IsLittleEndian ? SoundIOFormat.Float32LE : SoundIOFormat.Float32BE; + public static readonly SoundIOFormat Float64FE = !BitConverter.IsLittleEndian ? SoundIOFormat.Float64LE : SoundIOFormat.Float64BE; + + public bool SupportsFormat (SoundIOFormat format) + { + return Natives.soundio_device_supports_format (handle, (SoundIoFormat) format); + } + + public bool SupportsSampleRate (int sampleRate) + { + return Natives.soundio_device_supports_sample_rate (handle, sampleRate); + } + + public int GetNearestSampleRate (int sampleRate) + { + return Natives.soundio_device_nearest_sample_rate (handle, sampleRate); + } + + public SoundIOInStream CreateInStream () + { + return new SoundIOInStream (Natives.soundio_instream_create (handle)); + } + + public SoundIOOutStream CreateOutStream () + { + return new SoundIOOutStream (Natives.soundio_outstream_create (handle)); + } + } +} diff --git a/Ryujinx.Audio/Native/libsoundio/SoundIODeviceAim.cs b/Ryujinx.Audio/Native/libsoundio/SoundIODeviceAim.cs new file mode 100644 index 0000000000..9cd45f36f8 --- /dev/null +++ b/Ryujinx.Audio/Native/libsoundio/SoundIODeviceAim.cs @@ -0,0 +1,9 @@ +using System; +namespace SoundIOSharp +{ + public enum SoundIODeviceAim // soundio.h (228, 6) + { + Input = 0, + Output = 1, + } +} diff --git a/Ryujinx.Audio/Native/libsoundio/SoundIOException.cs b/Ryujinx.Audio/Native/libsoundio/SoundIOException.cs new file mode 100644 index 0000000000..ff6a0337b7 --- /dev/null +++ b/Ryujinx.Audio/Native/libsoundio/SoundIOException.cs @@ -0,0 +1,13 @@ +using System; +using System.Runtime.InteropServices; + +namespace SoundIOSharp +{ + public class SoundIOException : Exception + { + internal SoundIOException (SoundIoError errorCode) + : base (Marshal.PtrToStringAnsi (Natives.soundio_strerror ((int) errorCode))) + { + } + } +} diff --git a/Ryujinx.Audio/Native/libsoundio/SoundIOFormat.cs b/Ryujinx.Audio/Native/libsoundio/SoundIOFormat.cs new file mode 100644 index 0000000000..59434e1e57 --- /dev/null +++ b/Ryujinx.Audio/Native/libsoundio/SoundIOFormat.cs @@ -0,0 +1,26 @@ +using System; +namespace SoundIOSharp +{ + 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/Ryujinx.Audio/Native/libsoundio/SoundIOInStream.cs b/Ryujinx.Audio/Native/libsoundio/SoundIOInStream.cs new file mode 100644 index 0000000000..fb0b310450 --- /dev/null +++ b/Ryujinx.Audio/Native/libsoundio/SoundIOInStream.cs @@ -0,0 +1,228 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; + +namespace SoundIOSharp +{ + public class SoundIOInStream : IDisposable + { + internal SoundIOInStream (Pointer handle) + { + this.handle = handle; + } + + Pointer handle; + + public void Dispose () + { + Natives.soundio_instream_destroy (handle); + } + + // Equality (based on handle) + + public override bool Equals (object other) + { + var d = other as SoundIOInStream; + return d != null && (this.handle == d.handle); + } + + public override int GetHashCode () + { + return (int)(IntPtr)handle; + } + + public static bool operator == (SoundIOInStream obj1, SoundIOInStream obj2) + { + return (object)obj1 == null ? (object)obj2 == null : obj1.Equals (obj2); + } + + public static bool operator != (SoundIOInStream obj1, SoundIOInStream obj2) + { + return (object)obj1 == null ? (object)obj2 != null : !obj1.Equals (obj2); + } + + // fields + + public SoundIODevice Device { + get { return new SoundIODevice (Marshal.ReadIntPtr (handle, device_offset)); } + } + static readonly int device_offset = (int)Marshal.OffsetOf ("device"); + + public SoundIOFormat Format { + get { return (SoundIOFormat) Marshal.ReadInt32 (handle, format_offset); } + set { Marshal.WriteInt32 (handle, format_offset, (int) value); } + } + static readonly int format_offset = (int)Marshal.OffsetOf ("format"); + + public int SampleRate { + get { return Marshal.ReadInt32 (handle, sample_rate_offset); } + set { Marshal.WriteInt32 (handle, sample_rate_offset, value); } + } + static readonly int sample_rate_offset = (int)Marshal.OffsetOf ("sample_rate"); + + public SoundIOChannelLayout Layout { + get { return new SoundIOChannelLayout ((IntPtr) handle + layout_offset); } + set { + unsafe { + Buffer.MemoryCopy ((void*) ((IntPtr) handle + layout_offset), (void*)value.Handle, + Marshal.SizeOf (), Marshal.SizeOf ()); + } + } + } + static readonly int layout_offset = (int)Marshal.OffsetOf ("layout"); + + + public double SoftwareLatency { + get { return MarshalEx.ReadDouble (handle, software_latency_offset); } + set { MarshalEx.WriteDouble (handle, software_latency_offset, value); } + } + static readonly int software_latency_offset = (int)Marshal.OffsetOf ("software_latency"); + + // error_callback + public Action ErrorCallback { + get { return error_callback; } + set { + error_callback = value; + error_callback_native = _ => error_callback (); + var ptr = Marshal.GetFunctionPointerForDelegate (error_callback_native); + Marshal.WriteIntPtr (handle, error_callback_offset, ptr); + } + } + static readonly int error_callback_offset = (int)Marshal.OffsetOf ("error_callback"); + Action error_callback; + delegate void error_callback_delegate (IntPtr handle); + error_callback_delegate error_callback_native; + + // read_callback + public Action ReadCallback { + get { return read_callback; } + set { + read_callback = value; + read_callback_native = (_, minFrameCount, maxFrameCount) => read_callback (minFrameCount, maxFrameCount); + var ptr = Marshal.GetFunctionPointerForDelegate (read_callback_native); + Marshal.WriteIntPtr (handle, read_callback_offset, ptr); + } + } + static readonly int read_callback_offset = (int)Marshal.OffsetOf ("read_callback"); + Action read_callback; + delegate void read_callback_delegate (IntPtr handle, int min, int max); + read_callback_delegate read_callback_native; + + // overflow_callback + public Action OverflowCallback { + get { return overflow_callback; } + set { + overflow_callback = value; + overflow_callback_native = _ => overflow_callback (); + var ptr = Marshal.GetFunctionPointerForDelegate (overflow_callback_native); + Marshal.WriteIntPtr (handle, overflow_callback_offset, ptr); + } + } + static readonly int overflow_callback_offset = (int)Marshal.OffsetOf ("overflow_callback"); + Action overflow_callback; + delegate void overflow_callback_delegate (IntPtr handle); + overflow_callback_delegate overflow_callback_native; + + // FIXME: this should be taken care in more centralized/decent manner... we don't want to write + // this kind of code anywhere we need string marshaling. + List allocated_hglobals = new List (); + + public string Name { + get { return Marshal.PtrToStringAnsi (Marshal.ReadIntPtr (handle, name_offset)); } + set { + unsafe { + var existing = Marshal.ReadIntPtr (handle, name_offset); + if (allocated_hglobals.Contains (existing)) { + allocated_hglobals.Remove (existing); + Marshal.FreeHGlobal (existing); + } + var ptr = Marshal.StringToHGlobalAnsi (value); + Marshal.WriteIntPtr (handle, name_offset, ptr); + allocated_hglobals.Add (ptr); + } + } + } + static readonly int name_offset = (int)Marshal.OffsetOf ("name"); + + public bool NonTerminalHint { + get { return Marshal.ReadInt32 (handle, non_terminal_hint_offset) != 0; } + } + static readonly int non_terminal_hint_offset = (int)Marshal.OffsetOf ("non_terminal_hint"); + + public int BytesPerFrame { + get { return Marshal.ReadInt32 (handle, bytes_per_frame_offset); } + } + static readonly int bytes_per_frame_offset = (int)Marshal.OffsetOf ("bytes_per_frame"); + + public int BytesPerSample { + get { return Marshal.ReadInt32 (handle, bytes_per_sample_offset); } + } + static readonly int bytes_per_sample_offset = (int)Marshal.OffsetOf ("bytes_per_sample"); + + public string LayoutErrorMessage { + get { + var code = (SoundIoError) Marshal.ReadInt32 (handle, layout_error_offset); + return code == SoundIoError.SoundIoErrorNone ? null : Marshal.PtrToStringAnsi (Natives.soundio_strerror ((int) code)); + } + } + static readonly int layout_error_offset = (int)Marshal.OffsetOf ("layout_error"); + + // functions + + public void Open () + { + var ret = (SoundIoError) Natives.soundio_instream_open (handle); + if (ret != SoundIoError.SoundIoErrorNone) + throw new SoundIOException (ret); + } + + public void Start () + { + var ret = (SoundIoError)Natives.soundio_instream_start (handle); + if (ret != SoundIoError.SoundIoErrorNone) + throw new SoundIOException (ret); + } + + public SoundIOChannelAreas BeginRead (ref int frameCount) + { + IntPtr ptrs = default (IntPtr); + int nativeFrameCount = frameCount; + unsafe { + var frameCountPtr = &nativeFrameCount; + var ptrptr = &ptrs; + var ret = (SoundIoError) Natives.soundio_instream_begin_read (handle, (IntPtr)ptrptr, (IntPtr)frameCountPtr); + frameCount = *frameCountPtr; + if (ret != SoundIoError.SoundIoErrorNone) + throw new SoundIOException (ret); + return new SoundIOChannelAreas (ptrs, Layout.ChannelCount, frameCount); + } + } + + public void EndRead () + { + var ret = (SoundIoError) Natives.soundio_instream_end_read (handle); + if (ret != SoundIoError.SoundIoErrorNone) + throw new SoundIOException (ret); + } + + public void Pause (bool pause) + { + var ret = (SoundIoError) Natives.soundio_instream_pause (handle, pause); + if (ret != SoundIoError.SoundIoErrorNone) + throw new SoundIOException (ret); + } + + public double GetLatency () + { + unsafe { + double* dptr = null; + IntPtr p = new IntPtr (dptr); + var ret = (SoundIoError) Natives.soundio_instream_get_latency (handle, p); + if (ret != SoundIoError.SoundIoErrorNone) + throw new SoundIOException (ret); + dptr = (double*) p; + return *dptr; + } + } + } +} diff --git a/Ryujinx.Audio/Native/libsoundio/SoundIOOutStream.cs b/Ryujinx.Audio/Native/libsoundio/SoundIOOutStream.cs new file mode 100644 index 0000000000..346e6afbc7 --- /dev/null +++ b/Ryujinx.Audio/Native/libsoundio/SoundIOOutStream.cs @@ -0,0 +1,254 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; + +namespace SoundIOSharp +{ + public class SoundIOOutStream : IDisposable + { + internal SoundIOOutStream (Pointer handle) + { + this.handle = handle; + } + + Pointer handle; + + public void Dispose () + { + Natives.soundio_outstream_destroy (handle); + } + // Equality (based on handle) + + public override bool Equals (object other) + { + var d = other as SoundIOOutStream; + return d != null && (this.handle == d.handle); + } + + public override int GetHashCode () + { + return (int)(IntPtr)handle; + } + + public static bool operator == (SoundIOOutStream obj1, SoundIOOutStream obj2) + { + return (object)obj1 == null ? (object)obj2 == null : obj1.Equals (obj2); + } + + public static bool operator != (SoundIOOutStream obj1, SoundIOOutStream obj2) + { + return (object)obj1 == null ? (object)obj2 != null : !obj1.Equals (obj2); + } + + // fields + + public SoundIODevice Device { + get { return new SoundIODevice (Marshal.ReadIntPtr (handle, device_offset)); } + } + static readonly int device_offset = (int)Marshal.OffsetOf ("device"); + + public SoundIOFormat Format { + get { return (SoundIOFormat) Marshal.ReadInt32 (handle, format_offset); } + set { Marshal.WriteInt32 (handle, format_offset, (int) value); } + } + static readonly int format_offset = (int)Marshal.OffsetOf ("format"); + + public int SampleRate { + get { return Marshal.ReadInt32 (handle, sample_rate_offset); } + set { Marshal.WriteInt32 (handle, sample_rate_offset, value); } + } + static readonly int sample_rate_offset = (int)Marshal.OffsetOf ("sample_rate"); + + + public SoundIOChannelLayout Layout { + get { unsafe { return new SoundIOChannelLayout ((IntPtr) ((void*) ((IntPtr) handle + layout_offset))); } } + set { + unsafe { + Buffer.MemoryCopy ((void*)value.Handle, (void*)((IntPtr)handle + layout_offset), + Marshal.SizeOf (), Marshal.SizeOf ()); + } + } + } + static readonly int layout_offset = (int)Marshal.OffsetOf ("layout"); + + public double SoftwareLatency { + get { return MarshalEx.ReadDouble (handle, software_latency_offset); } + set { MarshalEx.WriteDouble (handle, software_latency_offset, value); } + } + static readonly int software_latency_offset = (int)Marshal.OffsetOf ("software_latency"); + + public float Volume { + get { return MarshalEx.ReadFloat (handle, volume_offset); } + set { MarshalEx.WriteFloat (handle, volume_offset, value); } + } + static readonly int volume_offset = (int)Marshal.OffsetOf ("volume"); + + // error_callback + public Action ErrorCallback { + get { return error_callback; } + set { + error_callback = value; + if (value == null) + error_callback_native = null; + else + error_callback_native = stream => error_callback (); + var ptr = Marshal.GetFunctionPointerForDelegate (error_callback_native); + Marshal.WriteIntPtr (handle, error_callback_offset, ptr); + } + } + static readonly int error_callback_offset = (int)Marshal.OffsetOf ("error_callback"); + Action error_callback; + delegate void error_callback_delegate (IntPtr handle); + error_callback_delegate error_callback_native; + + // write_callback + public Action WriteCallback { + get { return write_callback; } + set { + write_callback = value; + if (value == null) + write_callback_native = null; + else + write_callback_native = (h, frame_count_min, frame_count_max) => write_callback (frame_count_min, frame_count_max); + var ptr = Marshal.GetFunctionPointerForDelegate (write_callback_native); + Marshal.WriteIntPtr (handle, write_callback_offset, ptr); + } + } + static readonly int write_callback_offset = (int)Marshal.OffsetOf ("write_callback"); + Action write_callback; + delegate void write_callback_delegate (IntPtr handle, int min, int max); + write_callback_delegate write_callback_native; + + // underflow_callback + public Action UnderflowCallback { + get { return underflow_callback; } + set { + underflow_callback = value; + if (value == null) + underflow_callback_native = null; + else + underflow_callback_native = h => underflow_callback (); + var ptr = Marshal.GetFunctionPointerForDelegate (underflow_callback_native); + Marshal.WriteIntPtr (handle, underflow_callback_offset, ptr); + } + } + static readonly int underflow_callback_offset = (int)Marshal.OffsetOf ("underflow_callback"); + Action underflow_callback; + delegate void underflow_callback_delegate (IntPtr handle); + underflow_callback_delegate underflow_callback_native; + + // FIXME: this should be taken care in more centralized/decent manner... we don't want to write + // this kind of code anywhere we need string marshaling. + List allocated_hglobals = new List (); + + public string Name { + get { return Marshal.PtrToStringAnsi (Marshal.ReadIntPtr (handle, name_offset)); } + set { + unsafe { + var existing = Marshal.ReadIntPtr (handle, name_offset); + if (allocated_hglobals.Contains (existing)) { + allocated_hglobals.Remove (existing); + Marshal.FreeHGlobal (existing); + } + var ptr = Marshal.StringToHGlobalAnsi (value); + Marshal.WriteIntPtr (handle, name_offset, ptr); + allocated_hglobals.Add (ptr); + } + } + } + static readonly int name_offset = (int)Marshal.OffsetOf ("name"); + + public bool NonTerminalHint { + get { return Marshal.ReadInt32 (handle, non_terminal_hint_offset) != 0; } + } + static readonly int non_terminal_hint_offset = (int)Marshal.OffsetOf ("non_terminal_hint"); + + public int BytesPerFrame { + get { return Marshal.ReadInt32 (handle, bytes_per_frame_offset); } + } + static readonly int bytes_per_frame_offset = (int)Marshal.OffsetOf ("bytes_per_frame"); + + public int BytesPerSample { + get { return Marshal.ReadInt32 (handle, bytes_per_sample_offset); } + } + static readonly int bytes_per_sample_offset = (int)Marshal.OffsetOf ("bytes_per_sample"); + + public string LayoutErrorMessage { + get { + var code = (SoundIoError) Marshal.ReadInt32 (handle, layout_error_offset); + return code == SoundIoError.SoundIoErrorNone ? null : Marshal.PtrToStringAnsi (Natives.soundio_strerror ((int) code)); + } + } + static readonly int layout_error_offset = (int)Marshal.OffsetOf ("layout_error"); + + // functions + + public void Open () + { + var ret = (SoundIoError) Natives.soundio_outstream_open (handle); + if (ret != SoundIoError.SoundIoErrorNone) + throw new SoundIOException (ret); + } + + public void Start () + { + var ret = (SoundIoError)Natives.soundio_outstream_start (handle); + if (ret != SoundIoError.SoundIoErrorNone) + throw new SoundIOException (ret); + } + + public SoundIOChannelAreas BeginWrite (ref int frameCount) + { + IntPtr ptrs = default (IntPtr); + int nativeFrameCount = frameCount; + unsafe { + var frameCountPtr = &nativeFrameCount; + var ptrptr = &ptrs; + var ret = (SoundIoError)Natives.soundio_outstream_begin_write (handle, (IntPtr) ptrptr, (IntPtr) frameCountPtr); + frameCount = *frameCountPtr; + if (ret != SoundIoError.SoundIoErrorNone) + throw new SoundIOException (ret); + return new SoundIOChannelAreas (ptrs, Layout.ChannelCount, frameCount); + } + } + + public void EndWrite () + { + var ret = (SoundIoError) Natives.soundio_outstream_end_write (handle); + if (ret != SoundIoError.SoundIoErrorNone) + throw new SoundIOException (ret); + } + + public void ClearBuffer () + { + Natives.soundio_outstream_clear_buffer (handle); + } + + public void Pause (bool pause) + { + var ret = (SoundIoError) Natives.soundio_outstream_pause (handle, pause); + if (ret != SoundIoError.SoundIoErrorNone) + throw new SoundIOException (ret); + } + + public double GetLatency () + { + unsafe { + double* dptr = null; + IntPtr p = new IntPtr (dptr); + var ret = (SoundIoError) Natives.soundio_outstream_get_latency (handle, p); + if (ret != SoundIoError.SoundIoErrorNone) + throw new SoundIOException (ret); + dptr = (double*) p; + return *dptr; + } + } + + public void SetVolume (double volume) + { + var ret = (SoundIoError) Natives.soundio_outstream_set_volume (handle, volume); + if (ret != SoundIoError.SoundIoErrorNone) + throw new SoundIOException (ret); + } + } +} diff --git a/Ryujinx.Audio/Native/libsoundio/SoundIORingBuffer.cs b/Ryujinx.Audio/Native/libsoundio/SoundIORingBuffer.cs new file mode 100644 index 0000000000..63d796fd5b --- /dev/null +++ b/Ryujinx.Audio/Native/libsoundio/SoundIORingBuffer.cs @@ -0,0 +1,61 @@ +using System; +namespace SoundIOSharp +{ + public class SoundIORingBuffer : IDisposable + { + internal SoundIORingBuffer (IntPtr handle) + { + this.handle = handle; + } + + IntPtr handle; + + public int Capacity { + get { return Natives.soundio_ring_buffer_capacity (handle); } + } + + public void Clear () + { + Natives.soundio_ring_buffer_clear (handle); + } + + public void Dispose () + { + Natives.soundio_ring_buffer_destroy (handle); + } + + public int FillCount { + get { + return Natives.soundio_ring_buffer_fill_count (handle); + } + } + + public int FreeCount { + get { + return Natives.soundio_ring_buffer_free_count (handle); + } + } + + public IntPtr ReadPointer { + get { + return Natives.soundio_ring_buffer_read_ptr (handle); + } + } + + public IntPtr WritePointer { + get { + return Natives.soundio_ring_buffer_write_ptr (handle); + } + } + + public void AdvanceReadPointer (int count) + { + Natives.soundio_ring_buffer_advance_read_ptr (handle, count); + } + + public void AdvanceWritePointer (int count) + { + Natives.soundio_ring_buffer_advance_write_ptr (handle, count); + } + } +} diff --git a/Ryujinx.Audio/Native/libsoundio/SoundIOSampleRateRange.cs b/Ryujinx.Audio/Native/libsoundio/SoundIOSampleRateRange.cs new file mode 100644 index 0000000000..28fee4585d --- /dev/null +++ b/Ryujinx.Audio/Native/libsoundio/SoundIOSampleRateRange.cs @@ -0,0 +1,15 @@ +using System; +namespace SoundIOSharp +{ + public struct SoundIOSampleRateRange + { + internal SoundIOSampleRateRange (int min, int max) + { + Min = min; + Max = max; + } + + public readonly int Min; + public readonly int Max; + } +} diff --git a/Ryujinx.Audio/Native/libsoundio/libs/libsoundio.dll b/Ryujinx.Audio/Native/libsoundio/libs/libsoundio.dll new file mode 100644 index 0000000000..48804312e0 Binary files /dev/null and b/Ryujinx.Audio/Native/libsoundio/libs/libsoundio.dll differ diff --git a/Ryujinx.Audio/Native/libsoundio/libs/libsoundio.dylib b/Ryujinx.Audio/Native/libsoundio/libs/libsoundio.dylib new file mode 100644 index 0000000000..10171f4fb6 Binary files /dev/null and b/Ryujinx.Audio/Native/libsoundio/libs/libsoundio.dylib differ diff --git a/Ryujinx.Audio/Native/libsoundio/libs/libsoundio.so b/Ryujinx.Audio/Native/libsoundio/libs/libsoundio.so new file mode 100644 index 0000000000..87c8b50657 Binary files /dev/null and b/Ryujinx.Audio/Native/libsoundio/libs/libsoundio.so differ diff --git a/Ryujinx.Audio/Native/libsoundio/libsoundio-interop.cs b/Ryujinx.Audio/Native/libsoundio/libsoundio-interop.cs new file mode 100644 index 0000000000..5377582f31 --- /dev/null +++ b/Ryujinx.Audio/Native/libsoundio/libsoundio-interop.cs @@ -0,0 +1,643 @@ +// This source file is generated by nclang PInvokeGenerator. +using System; +using System.Runtime.InteropServices; +using delegate0 = SoundIOSharp.Delegates.delegate0; +using delegate1 = SoundIOSharp.Delegates.delegate1; +using delegate2 = SoundIOSharp.Delegates.delegate2; +using delegate3 = SoundIOSharp.Delegates.delegate3; +using delegate4 = SoundIOSharp.Delegates.delegate4; +using delegate5 = SoundIOSharp.Delegates.delegate5; +using delegate6 = SoundIOSharp.Delegates.delegate6; +using delegate7 = SoundIOSharp.Delegates.delegate7; +using delegate8 = SoundIOSharp.Delegates.delegate8; +using delegate9 = SoundIOSharp.Delegates.delegate9; + +namespace SoundIOSharp +{ + enum SoundIoError // soundio.h (72, 6) + { + SoundIoErrorNone = 0, + SoundIoErrorNoMem = 1, + SoundIoErrorInitAudioBackend = 2, + SoundIoErrorSystemResources = 3, + SoundIoErrorOpeningDevice = 4, + SoundIoErrorNoSuchDevice = 5, + SoundIoErrorInvalid = 6, + SoundIoErrorBackendUnavailable = 7, + SoundIoErrorStreaming = 8, + SoundIoErrorIncompatibleDevice = 9, + SoundIoErrorNoSuchClient = 10, + SoundIoErrorIncompatibleBackend = 11, + SoundIoErrorBackendDisconnected = 12, + SoundIoErrorInterrupted = 13, + SoundIoErrorUnderflow = 14, + SoundIoErrorEncodingString = 15, + } + + enum SoundIoChannelId // soundio.h (106, 6) + { + SoundIoChannelIdInvalid = 0, + SoundIoChannelIdFrontLeft = 1, + SoundIoChannelIdFrontRight = 2, + SoundIoChannelIdFrontCenter = 3, + SoundIoChannelIdLfe = 4, + SoundIoChannelIdBackLeft = 5, + SoundIoChannelIdBackRight = 6, + SoundIoChannelIdFrontLeftCenter = 7, + SoundIoChannelIdFrontRightCenter = 8, + SoundIoChannelIdBackCenter = 9, + SoundIoChannelIdSideLeft = 10, + SoundIoChannelIdSideRight = 11, + SoundIoChannelIdTopCenter = 12, + SoundIoChannelIdTopFrontLeft = 13, + SoundIoChannelIdTopFrontCenter = 14, + SoundIoChannelIdTopFrontRight = 15, + SoundIoChannelIdTopBackLeft = 16, + SoundIoChannelIdTopBackCenter = 17, + SoundIoChannelIdTopBackRight = 18, + SoundIoChannelIdBackLeftCenter = 19, + SoundIoChannelIdBackRightCenter = 20, + SoundIoChannelIdFrontLeftWide = 21, + SoundIoChannelIdFrontRightWide = 22, + SoundIoChannelIdFrontLeftHigh = 23, + SoundIoChannelIdFrontCenterHigh = 24, + SoundIoChannelIdFrontRightHigh = 25, + SoundIoChannelIdTopFrontLeftCenter = 26, + SoundIoChannelIdTopFrontRightCenter = 27, + SoundIoChannelIdTopSideLeft = 28, + SoundIoChannelIdTopSideRight = 29, + SoundIoChannelIdLeftLfe = 30, + SoundIoChannelIdRightLfe = 31, + SoundIoChannelIdLfe2 = 32, + SoundIoChannelIdBottomCenter = 33, + SoundIoChannelIdBottomLeftCenter = 34, + SoundIoChannelIdBottomRightCenter = 35, + SoundIoChannelIdMsMid = 36, + SoundIoChannelIdMsSide = 37, + SoundIoChannelIdAmbisonicW = 38, + SoundIoChannelIdAmbisonicX = 39, + SoundIoChannelIdAmbisonicY = 40, + SoundIoChannelIdAmbisonicZ = 41, + SoundIoChannelIdXyX = 42, + SoundIoChannelIdXyY = 43, + SoundIoChannelIdHeadphonesLeft = 44, + SoundIoChannelIdHeadphonesRight = 45, + SoundIoChannelIdClickTrack = 46, + SoundIoChannelIdForeignLanguage = 47, + SoundIoChannelIdHearingImpaired = 48, + SoundIoChannelIdNarration = 49, + SoundIoChannelIdHaptic = 50, + SoundIoChannelIdDialogCentricMix = 51, + SoundIoChannelIdAux = 52, + SoundIoChannelIdAux0 = 53, + SoundIoChannelIdAux1 = 54, + SoundIoChannelIdAux2 = 55, + SoundIoChannelIdAux3 = 56, + SoundIoChannelIdAux4 = 57, + SoundIoChannelIdAux5 = 58, + SoundIoChannelIdAux6 = 59, + SoundIoChannelIdAux7 = 60, + SoundIoChannelIdAux8 = 61, + SoundIoChannelIdAux9 = 62, + SoundIoChannelIdAux10 = 63, + SoundIoChannelIdAux11 = 64, + SoundIoChannelIdAux12 = 65, + SoundIoChannelIdAux13 = 66, + SoundIoChannelIdAux14 = 67, + SoundIoChannelIdAux15 = 68, + } + + enum SoundIoChannelLayoutId // soundio.h (189, 6) + { + SoundIoChannelLayoutIdMono = 0, + SoundIoChannelLayoutIdStereo = 1, + SoundIoChannelLayoutId2Point1 = 2, + SoundIoChannelLayoutId3Point0 = 3, + SoundIoChannelLayoutId3Point0Back = 4, + SoundIoChannelLayoutId3Point1 = 5, + SoundIoChannelLayoutId4Point0 = 6, + SoundIoChannelLayoutIdQuad = 7, + SoundIoChannelLayoutIdQuadSide = 8, + SoundIoChannelLayoutId4Point1 = 9, + SoundIoChannelLayoutId5Point0Back = 10, + SoundIoChannelLayoutId5Point0Side = 11, + SoundIoChannelLayoutId5Point1 = 12, + SoundIoChannelLayoutId5Point1Back = 13, + SoundIoChannelLayoutId6Point0Side = 14, + SoundIoChannelLayoutId6Point0Front = 15, + SoundIoChannelLayoutIdHexagonal = 16, + SoundIoChannelLayoutId6Point1 = 17, + SoundIoChannelLayoutId6Point1Back = 18, + SoundIoChannelLayoutId6Point1Front = 19, + SoundIoChannelLayoutId7Point0 = 20, + SoundIoChannelLayoutId7Point0Front = 21, + SoundIoChannelLayoutId7Point1 = 22, + SoundIoChannelLayoutId7Point1Wide = 23, + SoundIoChannelLayoutId7Point1WideBack = 24, + SoundIoChannelLayoutIdOctagonal = 25, + } + + enum SoundIoBackend // soundio.h (218, 6) + { + SoundIoBackendNone = 0, + SoundIoBackendJack = 1, + SoundIoBackendPulseAudio = 2, + SoundIoBackendAlsa = 3, + SoundIoBackendCoreAudio = 4, + SoundIoBackendWasapi = 5, + SoundIoBackendDummy = 6, + } + + enum SoundIoDeviceAim // soundio.h (228, 6) + { + SoundIoDeviceAimInput = 0, + SoundIoDeviceAimOutput = 1, + } + + enum SoundIoFormat // soundio.h (235, 6) + { + SoundIoFormatInvalid = 0, + SoundIoFormatS8 = 1, + SoundIoFormatU8 = 2, + SoundIoFormatS16LE = 3, + SoundIoFormatS16BE = 4, + SoundIoFormatU16LE = 5, + SoundIoFormatU16BE = 6, + SoundIoFormatS24LE = 7, + SoundIoFormatS24BE = 8, + SoundIoFormatU24LE = 9, + SoundIoFormatU24BE = 10, + SoundIoFormatS32LE = 11, + SoundIoFormatS32BE = 12, + SoundIoFormatU32LE = 13, + SoundIoFormatU32BE = 14, + SoundIoFormatFloat32LE = 15, + SoundIoFormatFloat32BE = 16, + SoundIoFormatFloat64LE = 17, + SoundIoFormatFloat64BE = 18, + } + + [StructLayout(LayoutKind.Sequential)] + struct SoundIoChannelLayout // soundio.h (306, 8) + { + [CTypeDetails("Pointer")] public System.IntPtr @name; + public int @channel_count; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 24)] + [CTypeDetails("ConstArrayOf")] public SoundIoChannelId[] @channels; + } + + [StructLayout(LayoutKind.Sequential)] + struct SoundIoSampleRateRange // soundio.h (313, 8) + { + public int @min; + public int @max; + } + + [StructLayout(LayoutKind.Sequential)] + struct SoundIoChannelArea // soundio.h (319, 8) + { + [CTypeDetails("Pointer")] public System.IntPtr @ptr; + public int @step; + } + + [StructLayout(LayoutKind.Sequential)] + struct SoundIo // soundio.h (328, 8) + { + [CTypeDetails("Pointer")] public System.IntPtr @userdata; + [CTypeDetails("Pointer")] public delegate0 @on_devices_change; + [CTypeDetails("Pointer")] public delegate1 @on_backend_disconnect; + [CTypeDetails("Pointer")] public Delegates.delegate0 @on_events_signal; + public SoundIoBackend @current_backend; + [CTypeDetails("Pointer")] public System.IntPtr @app_name; + [CTypeDetails("Pointer")] public delegate2 @emit_rtprio_warning; + [CTypeDetails("Pointer")] public delegate3 @jack_info_callback; + [CTypeDetails("Pointer")] public Delegates.delegate3 @jack_error_callback; + } + + [StructLayout(LayoutKind.Sequential)] + struct SoundIoDevice // soundio.h (387, 8) + { + [CTypeDetails("Pointer")] public System.IntPtr @soundio; + [CTypeDetails("Pointer")] public System.IntPtr @id; + [CTypeDetails("Pointer")] public System.IntPtr @name; + public SoundIoDeviceAim @aim; + [CTypeDetails("Pointer")] public System.IntPtr @layouts; + public int @layout_count; + public SoundIoChannelLayout @current_layout; + [CTypeDetails("Pointer")] public System.IntPtr @formats; + public int @format_count; + public SoundIoFormat @current_format; + [CTypeDetails("Pointer")] public System.IntPtr @sample_rates; + public int @sample_rate_count; + public int @sample_rate_current; + public double @software_latency_min; + public double @software_latency_max; + public double @software_latency_current; + public bool @is_raw; + public int @ref_count; + public int @probe_error; + } + + [StructLayout(LayoutKind.Sequential)] + struct SoundIoOutStream // soundio.h (497, 8) + { + [CTypeDetails("Pointer")] public System.IntPtr @device; + public SoundIoFormat @format; + public int @sample_rate; + public SoundIoChannelLayout @layout; + public double @software_latency; + public float @volume; + [CTypeDetails("Pointer")] public System.IntPtr @userdata; + [CTypeDetails("Pointer")] public delegate4 @write_callback; + [CTypeDetails("Pointer")] public delegate5 @underflow_callback; + [CTypeDetails("Pointer")] public delegate6 @error_callback; + [CTypeDetails("Pointer")] public System.IntPtr @name; + public bool @non_terminal_hint; + public int @bytes_per_frame; + public int @bytes_per_sample; + public int @layout_error; + } + + [StructLayout(LayoutKind.Sequential)] + struct SoundIoInStream // soundio.h (600, 8) + { + [CTypeDetails("Pointer")] public System.IntPtr @device; + public SoundIoFormat @format; + public int @sample_rate; + public SoundIoChannelLayout @layout; + public double @software_latency; + [CTypeDetails("Pointer")] public System.IntPtr @userdata; + [CTypeDetails("Pointer")] public delegate7 @read_callback; + [CTypeDetails("Pointer")] public delegate8 @overflow_callback; + [CTypeDetails("Pointer")] public delegate9 @error_callback; + [CTypeDetails("Pointer")] public System.IntPtr @name; + public bool @non_terminal_hint; + public int @bytes_per_frame; + public int @bytes_per_sample; + public int @layout_error; + } + + [StructLayout(LayoutKind.Sequential)] + struct SoundIoRingBuffer // soundio.h (1170, 8) + { + } + + partial class Natives + { + const string LibraryName = "libsoundio"; + // function soundio_version_string - soundio.h (682, 28) + [DllImport(LibraryName)] + internal static extern System.IntPtr soundio_version_string(); + + // function soundio_version_major - soundio.h (684, 20) + [DllImport(LibraryName)] + internal static extern int soundio_version_major(); + + // function soundio_version_minor - soundio.h (686, 20) + [DllImport(LibraryName)] + internal static extern int soundio_version_minor(); + + // function soundio_version_patch - soundio.h (688, 20) + [DllImport(LibraryName)] + internal static extern int soundio_version_patch(); + + // function soundio_create - soundio.h (694, 32) + [DllImport(LibraryName)] + internal static extern System.IntPtr soundio_create(); + + // function soundio_destroy - soundio.h (695, 21) + [DllImport(LibraryName)] + internal static extern void soundio_destroy([CTypeDetails("Pointer")]System.IntPtr @soundio); + + // function soundio_connect - soundio.h (705, 20) + [DllImport(LibraryName)] + internal static extern int soundio_connect([CTypeDetails("Pointer")]System.IntPtr @soundio); + + // function soundio_connect_backend - soundio.h (717, 20) + [DllImport(LibraryName)] + internal static extern int soundio_connect_backend([CTypeDetails("Pointer")]System.IntPtr @soundio, SoundIoBackend @backend); + + // function soundio_disconnect - soundio.h (718, 21) + [DllImport(LibraryName)] + internal static extern void soundio_disconnect([CTypeDetails("Pointer")]System.IntPtr @soundio); + + // function soundio_strerror - soundio.h (721, 28) + [DllImport(LibraryName)] + internal static extern System.IntPtr soundio_strerror(int @error); + + // function soundio_backend_name - soundio.h (723, 28) + [DllImport(LibraryName)] + internal static extern System.IntPtr soundio_backend_name(SoundIoBackend @backend); + + // function soundio_backend_count - soundio.h (726, 20) + [DllImport(LibraryName)] + internal static extern int soundio_backend_count([CTypeDetails("Pointer")]System.IntPtr @soundio); + + // function soundio_get_backend - soundio.h (729, 36) + [DllImport(LibraryName)] + internal static extern SoundIoBackend soundio_get_backend([CTypeDetails("Pointer")]System.IntPtr @soundio, int @index); + + // function soundio_have_backend - soundio.h (732, 21) + [DllImport(LibraryName)] + internal static extern bool soundio_have_backend(SoundIoBackend @backend); + + // function soundio_flush_events - soundio.h (756, 21) + [DllImport(LibraryName)] + internal static extern void soundio_flush_events([CTypeDetails("Pointer")]System.IntPtr @soundio); + + // function soundio_wait_events - soundio.h (760, 21) + [DllImport(LibraryName)] + internal static extern void soundio_wait_events([CTypeDetails("Pointer")]System.IntPtr @soundio); + + // function soundio_wakeup - soundio.h (763, 21) + [DllImport(LibraryName)] + internal static extern void soundio_wakeup([CTypeDetails("Pointer")]System.IntPtr @soundio); + + // function soundio_force_device_scan - soundio.h (780, 21) + [DllImport(LibraryName)] + internal static extern void soundio_force_device_scan([CTypeDetails("Pointer")]System.IntPtr @soundio); + + // function soundio_channel_layout_equal - soundio.h (787, 21) + [DllImport(LibraryName)] + internal static extern bool soundio_channel_layout_equal([CTypeDetails("Pointer")]System.IntPtr @a, [CTypeDetails("Pointer")]System.IntPtr @b); + + // function soundio_get_channel_name - soundio.h (791, 28) + [DllImport(LibraryName)] + internal static extern System.IntPtr soundio_get_channel_name(SoundIoChannelId @id); + + // function soundio_parse_channel_id - soundio.h (795, 38) + [DllImport(LibraryName)] + internal static extern SoundIoChannelId soundio_parse_channel_id([CTypeDetails("Pointer")]System.IntPtr @str, int @str_len); + + // function soundio_channel_layout_builtin_count - soundio.h (798, 20) + [DllImport(LibraryName)] + internal static extern int soundio_channel_layout_builtin_count(); + + // function soundio_channel_layout_get_builtin - soundio.h (803, 51) + [DllImport(LibraryName)] + internal static extern System.IntPtr soundio_channel_layout_get_builtin(int @index); + + // function soundio_channel_layout_get_default - soundio.h (806, 51) + [DllImport(LibraryName)] + internal static extern System.IntPtr soundio_channel_layout_get_default(int @channel_count); + + // function soundio_channel_layout_find_channel - soundio.h (809, 20) + [DllImport(LibraryName)] + internal static extern int soundio_channel_layout_find_channel([CTypeDetails("Pointer")]System.IntPtr @layout, SoundIoChannelId @channel); + + // function soundio_channel_layout_detect_builtin - soundio.h (814, 21) + [DllImport(LibraryName)] + internal static extern bool soundio_channel_layout_detect_builtin([CTypeDetails("Pointer")]System.IntPtr @layout); + + // function soundio_best_matching_channel_layout - soundio.h (819, 51) + [DllImport(LibraryName)] + internal static extern System.IntPtr soundio_best_matching_channel_layout([CTypeDetails("Pointer")]System.IntPtr @preferred_layouts, int @preferred_layout_count, [CTypeDetails("Pointer")]System.IntPtr @available_layouts, int @available_layout_count); + + // function soundio_sort_channel_layouts - soundio.h (824, 21) + [DllImport(LibraryName)] + internal static extern void soundio_sort_channel_layouts([CTypeDetails("Pointer")]System.IntPtr @layouts, int @layout_count); + + // function soundio_get_bytes_per_sample - soundio.h (830, 20) + [DllImport(LibraryName)] + internal static extern int soundio_get_bytes_per_sample(SoundIoFormat @format); + + // function soundio_get_bytes_per_frame - soundio.h (833, 19) + [DllImport(LibraryName)] + internal static extern int soundio_get_bytes_per_frame(SoundIoFormat @format, int @channel_count); + + // function soundio_get_bytes_per_second - soundio.h (838, 19) + [DllImport(LibraryName)] + internal static extern int soundio_get_bytes_per_second(SoundIoFormat @format, int @channel_count, int @sample_rate); + + // function soundio_format_string - soundio.h (845, 29) + [DllImport(LibraryName)] + internal static extern System.IntPtr soundio_format_string(SoundIoFormat @format); + + // function soundio_input_device_count - soundio.h (861, 20) + [DllImport(LibraryName)] + internal static extern int soundio_input_device_count([CTypeDetails("Pointer")]System.IntPtr @soundio); + + // function soundio_output_device_count - soundio.h (864, 20) + [DllImport(LibraryName)] + internal static extern int soundio_output_device_count([CTypeDetails("Pointer")]System.IntPtr @soundio); + + // function soundio_get_input_device - soundio.h (870, 38) + [DllImport(LibraryName)] + internal static extern System.IntPtr soundio_get_input_device([CTypeDetails("Pointer")]System.IntPtr @soundio, int @index); + + // function soundio_get_output_device - soundio.h (875, 38) + [DllImport(LibraryName)] + internal static extern System.IntPtr soundio_get_output_device([CTypeDetails("Pointer")]System.IntPtr @soundio, int @index); + + // function soundio_default_input_device_index - soundio.h (880, 20) + [DllImport(LibraryName)] + internal static extern int soundio_default_input_device_index([CTypeDetails("Pointer")]System.IntPtr @soundio); + + // function soundio_default_output_device_index - soundio.h (885, 20) + [DllImport(LibraryName)] + internal static extern int soundio_default_output_device_index([CTypeDetails("Pointer")]System.IntPtr @soundio); + + // function soundio_device_ref - soundio.h (888, 21) + [DllImport(LibraryName)] + internal static extern void soundio_device_ref([CTypeDetails("Pointer")]System.IntPtr @device); + + // function soundio_device_unref - soundio.h (891, 21) + [DllImport(LibraryName)] + internal static extern void soundio_device_unref([CTypeDetails("Pointer")]System.IntPtr @device); + + // function soundio_device_equal - soundio.h (895, 21) + [DllImport(LibraryName)] + internal static extern bool soundio_device_equal([CTypeDetails("Pointer")]System.IntPtr @a, [CTypeDetails("Pointer")]System.IntPtr @b); + + // function soundio_device_sort_channel_layouts - soundio.h (900, 21) + [DllImport(LibraryName)] + internal static extern void soundio_device_sort_channel_layouts([CTypeDetails("Pointer")]System.IntPtr @device); + + // function soundio_device_supports_format - soundio.h (904, 21) + [DllImport(LibraryName)] + internal static extern bool soundio_device_supports_format([CTypeDetails("Pointer")]System.IntPtr @device, SoundIoFormat @format); + + // function soundio_device_supports_layout - soundio.h (909, 21) + [DllImport(LibraryName)] + internal static extern bool soundio_device_supports_layout([CTypeDetails("Pointer")]System.IntPtr @device, [CTypeDetails("Pointer")]System.IntPtr @layout); + + // function soundio_device_supports_sample_rate - soundio.h (914, 21) + [DllImport(LibraryName)] + internal static extern bool soundio_device_supports_sample_rate([CTypeDetails("Pointer")]System.IntPtr @device, int @sample_rate); + + // function soundio_device_nearest_sample_rate - soundio.h (919, 20) + [DllImport(LibraryName)] + internal static extern int soundio_device_nearest_sample_rate([CTypeDetails("Pointer")]System.IntPtr @device, int @sample_rate); + + // function soundio_outstream_create - soundio.h (929, 41) + [DllImport(LibraryName)] + internal static extern System.IntPtr soundio_outstream_create([CTypeDetails("Pointer")]System.IntPtr @device); + + // function soundio_outstream_destroy - soundio.h (931, 21) + [DllImport(LibraryName)] + internal static extern void soundio_outstream_destroy([CTypeDetails("Pointer")]System.IntPtr @outstream); + + // function soundio_outstream_open - soundio.h (954, 20) + [DllImport(LibraryName)] + internal static extern int soundio_outstream_open([CTypeDetails("Pointer")]System.IntPtr @outstream); + + // function soundio_outstream_start - soundio.h (965, 20) + [DllImport(LibraryName)] + internal static extern int soundio_outstream_start([CTypeDetails("Pointer")]System.IntPtr @outstream); + + // function soundio_outstream_begin_write - soundio.h (997, 20) + [DllImport(LibraryName)] + internal static extern int soundio_outstream_begin_write([CTypeDetails("Pointer")]System.IntPtr @outstream, [CTypeDetails("Pointer")]System.IntPtr @areas, [CTypeDetails("Pointer")]System.IntPtr @frame_count); + + // function soundio_outstream_end_write - soundio.h (1009, 20) + [DllImport(LibraryName)] + internal static extern int soundio_outstream_end_write([CTypeDetails("Pointer")]System.IntPtr @outstream); + + // function soundio_outstream_clear_buffer - soundio.h (1024, 20) + [DllImport(LibraryName)] + internal static extern int soundio_outstream_clear_buffer([CTypeDetails("Pointer")]System.IntPtr @outstream); + + // function soundio_outstream_pause - soundio.h (1045, 20) + [DllImport(LibraryName)] + internal static extern int soundio_outstream_pause([CTypeDetails("Pointer")]System.IntPtr @outstream, bool @pause); + + // function soundio_outstream_get_latency - soundio.h (1058, 20) + [DllImport(LibraryName)] + internal static extern int soundio_outstream_get_latency([CTypeDetails("Pointer")]System.IntPtr @outstream, [CTypeDetails("Pointer")]System.IntPtr @out_latency); + + // function soundio_outstream_set_volume - soundio.h (1061, 20) + [DllImport(LibraryName)] + internal static extern int soundio_outstream_set_volume([CTypeDetails("Pointer")]System.IntPtr @outstream, double @volume); + + // function soundio_instream_create - soundio.h (1071, 40) + [DllImport(LibraryName)] + internal static extern System.IntPtr soundio_instream_create([CTypeDetails("Pointer")]System.IntPtr @device); + + // function soundio_instream_destroy - soundio.h (1073, 21) + [DllImport(LibraryName)] + internal static extern void soundio_instream_destroy([CTypeDetails("Pointer")]System.IntPtr @instream); + + // function soundio_instream_open - soundio.h (1093, 20) + [DllImport(LibraryName)] + internal static extern int soundio_instream_open([CTypeDetails("Pointer")]System.IntPtr @instream); + + // function soundio_instream_start - soundio.h (1102, 20) + [DllImport(LibraryName)] + internal static extern int soundio_instream_start([CTypeDetails("Pointer")]System.IntPtr @instream); + + // function soundio_instream_begin_read - soundio.h (1133, 20) + [DllImport(LibraryName)] + internal static extern int soundio_instream_begin_read([CTypeDetails("Pointer")]System.IntPtr @instream, [CTypeDetails("Pointer")]System.IntPtr @areas, [CTypeDetails("Pointer")]System.IntPtr @frame_count); + + // function soundio_instream_end_read - soundio.h (1143, 20) + [DllImport(LibraryName)] + internal static extern int soundio_instream_end_read([CTypeDetails("Pointer")]System.IntPtr @instream); + + // function soundio_instream_pause - soundio.h (1156, 20) + [DllImport(LibraryName)] + internal static extern int soundio_instream_pause([CTypeDetails("Pointer")]System.IntPtr @instream, bool @pause); + + // function soundio_instream_get_latency - soundio.h (1166, 20) + [DllImport(LibraryName)] + internal static extern int soundio_instream_get_latency([CTypeDetails("Pointer")]System.IntPtr @instream, [CTypeDetails("Pointer")]System.IntPtr @out_latency); + + // function soundio_ring_buffer_create - soundio.h (1181, 42) + [DllImport(LibraryName)] + internal static extern System.IntPtr soundio_ring_buffer_create([CTypeDetails("Pointer")]System.IntPtr @soundio, int @requested_capacity); + + // function soundio_ring_buffer_destroy - soundio.h (1182, 21) + [DllImport(LibraryName)] + internal static extern void soundio_ring_buffer_destroy([CTypeDetails("Pointer")]System.IntPtr @ring_buffer); + + // function soundio_ring_buffer_capacity - soundio.h (1186, 20) + [DllImport(LibraryName)] + internal static extern int soundio_ring_buffer_capacity([CTypeDetails("Pointer")]System.IntPtr @ring_buffer); + + // function soundio_ring_buffer_write_ptr - soundio.h (1189, 22) + [DllImport(LibraryName)] + internal static extern System.IntPtr soundio_ring_buffer_write_ptr([CTypeDetails("Pointer")]System.IntPtr @ring_buffer); + + // function soundio_ring_buffer_advance_write_ptr - soundio.h (1191, 21) + [DllImport(LibraryName)] + internal static extern void soundio_ring_buffer_advance_write_ptr([CTypeDetails("Pointer")]System.IntPtr @ring_buffer, int @count); + + // function soundio_ring_buffer_read_ptr - soundio.h (1194, 22) + [DllImport(LibraryName)] + internal static extern System.IntPtr soundio_ring_buffer_read_ptr([CTypeDetails("Pointer")]System.IntPtr @ring_buffer); + + // function soundio_ring_buffer_advance_read_ptr - soundio.h (1196, 21) + [DllImport(LibraryName)] + internal static extern void soundio_ring_buffer_advance_read_ptr([CTypeDetails("Pointer")]System.IntPtr @ring_buffer, int @count); + + // function soundio_ring_buffer_fill_count - soundio.h (1199, 20) + [DllImport(LibraryName)] + internal static extern int soundio_ring_buffer_fill_count([CTypeDetails("Pointer")]System.IntPtr @ring_buffer); + + // function soundio_ring_buffer_free_count - soundio.h (1202, 20) + [DllImport(LibraryName)] + internal static extern int soundio_ring_buffer_free_count([CTypeDetails("Pointer")]System.IntPtr @ring_buffer); + + // function soundio_ring_buffer_clear - soundio.h (1205, 21) + [DllImport(LibraryName)] + internal static extern void soundio_ring_buffer_clear([CTypeDetails("Pointer")]System.IntPtr @ring_buffer); + + } + + class Delegates + { + public delegate void delegate0(System.IntPtr p0); + public delegate void delegate1(System.IntPtr p0, int p1); + public delegate void delegate2(); + public delegate void delegate3(System.IntPtr p0); + public delegate void delegate4(System.IntPtr p0, int p1, int p2); + public delegate void delegate5(System.IntPtr p0); + public delegate void delegate6(System.IntPtr p0, int p1); + public delegate void delegate7(System.IntPtr p0, int p1, int p2); + public delegate void delegate8(System.IntPtr p0); + public delegate void delegate9(System.IntPtr p0, int p1); + } + + public struct Pointer + { + public IntPtr Handle; + public static implicit operator IntPtr(Pointer value) { return value.Handle; } + public static implicit operator Pointer(IntPtr value) { return new Pointer(value); } + + public Pointer(IntPtr handle) + { + Handle = handle; + } + + public override bool Equals(object obj) + { + return obj is Pointer && this == (Pointer)obj; + } + + public override int GetHashCode() + { + return (int)Handle; + } + + public static bool operator ==(Pointer p1, Pointer p2) + { + return p1.Handle == p2.Handle; + } + + public static bool operator !=(Pointer p1, Pointer p2) + { + return p1.Handle != p2.Handle; + } + } + public struct ArrayOf { } + public struct ConstArrayOf { } + public class CTypeDetailsAttribute : Attribute + { + public CTypeDetailsAttribute(string value) + { + Value = value; + } + + public string Value { get; set; } + } + +} diff --git a/Ryujinx.Audio/OpenAL/OpenALAudioOut.cs b/Ryujinx.Audio/OpenAL/OpenALAudioOut.cs deleted file mode 100644 index 2860dc2e2d..0000000000 --- a/Ryujinx.Audio/OpenAL/OpenALAudioOut.cs +++ /dev/null @@ -1,372 +0,0 @@ -using OpenTK.Audio; -using OpenTK.Audio.OpenAL; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Threading; - -namespace Ryujinx.Audio.OpenAL -{ - public class OpenALAudioOut : IAalOutput - { - private const int MaxTracks = 256; - - private const int MaxReleased = 32; - - private AudioContext Context; - - private class Track : IDisposable - { - public int SourceId { get; private set; } - - public int SampleRate { get; private set; } - - public ALFormat Format { get; private set; } - - private ReleaseCallback Callback; - - public PlaybackState State { get; set; } - - private bool ShouldCallReleaseCallback; - - private ConcurrentDictionary Buffers; - - private Queue QueuedTagsQueue; - - private Queue ReleasedTagsQueue; - - private int LastReleasedCount; - - private bool Disposed; - - public Track(int SampleRate, ALFormat Format, ReleaseCallback Callback) - { - this.SampleRate = SampleRate; - this.Format = Format; - this.Callback = Callback; - - State = PlaybackState.Stopped; - - SourceId = AL.GenSource(); - - Buffers = new ConcurrentDictionary(); - - QueuedTagsQueue = new Queue(); - - ReleasedTagsQueue = new Queue(); - } - - public bool ContainsBuffer(long Tag) - { - SyncQueuedTags(); - - foreach (long QueuedTag in QueuedTagsQueue) - { - if (QueuedTag == Tag) - { - return true; - } - } - - return false; - } - - public long[] GetReleasedBuffers(int MaxCount) - { - ClearReleased(); - - List Tags = new List(); - - HashSet Unique = new HashSet(); - - while (MaxCount-- > 0 && ReleasedTagsQueue.TryDequeue(out long Tag)) - { - if (Unique.Add(Tag)) - { - Tags.Add(Tag); - } - } - - return Tags.ToArray(); - } - - public int AppendBuffer(long Tag) - { - if (Disposed) - { - throw new ObjectDisposedException(nameof(Track)); - } - - int Id = AL.GenBuffer(); - - Buffers.AddOrUpdate(Tag, Id, (Key, OldId) => - { - AL.DeleteBuffer(OldId); - - return Id; - }); - - QueuedTagsQueue.Enqueue(Tag); - - return Id; - } - - public void ClearReleased() - { - SyncQueuedTags(); - - AL.GetSource(SourceId, ALGetSourcei.BuffersProcessed, out int ReleasedCount); - - CheckReleaseChanges(ReleasedCount); - - if (ReleasedCount > 0) - { - AL.SourceUnqueueBuffers(SourceId, ReleasedCount); - } - } - - public void CallReleaseCallbackIfNeeded() - { - CheckReleaseChanges(); - - if (ShouldCallReleaseCallback) - { - ShouldCallReleaseCallback = false; - - Callback(); - } - } - - private void CheckReleaseChanges() - { - AL.GetSource(SourceId, ALGetSourcei.BuffersProcessed, out int ReleasedCount); - - CheckReleaseChanges(ReleasedCount); - } - - private void CheckReleaseChanges(int NewReleasedCount) - { - if (LastReleasedCount != NewReleasedCount) - { - LastReleasedCount = NewReleasedCount; - - ShouldCallReleaseCallback = true; - } - } - - private void SyncQueuedTags() - { - AL.GetSource(SourceId, ALGetSourcei.BuffersQueued, out int QueuedCount); - AL.GetSource(SourceId, ALGetSourcei.BuffersProcessed, out int ReleasedCount); - - QueuedCount -= ReleasedCount; - - while (QueuedTagsQueue.Count > QueuedCount) - { - ReleasedTagsQueue.Enqueue(QueuedTagsQueue.Dequeue()); - } - - while (ReleasedTagsQueue.Count > MaxReleased) - { - ReleasedTagsQueue.Dequeue(); - } - } - - public void Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool Disposing) - { - if (Disposing && !Disposed) - { - Disposed = true; - - AL.DeleteSource(SourceId); - - foreach (int Id in Buffers.Values) - { - AL.DeleteBuffer(Id); - } - } - } - } - - private ConcurrentDictionary Tracks; - - private Thread AudioPollerThread; - - private bool KeepPolling; - - public OpenALAudioOut() - { - Context = new AudioContext(); - - Tracks = new ConcurrentDictionary(); - - KeepPolling = true; - - AudioPollerThread = new Thread(AudioPollerWork); - - AudioPollerThread.Start(); - } - - private void AudioPollerWork() - { - do - { - foreach (Track Td in Tracks.Values) - { - Td.CallReleaseCallbackIfNeeded(); - } - - Thread.Yield(); - } - while (KeepPolling); - } - - public int OpenTrack( - int SampleRate, - int Channels, - ReleaseCallback Callback, - out AudioFormat Format) - { - Format = AudioFormat.PcmInt16; - - Track Td = new Track(SampleRate, GetALFormat(Channels, Format), Callback); - - for (int Id = 0; Id < MaxTracks; Id++) - { - if (Tracks.TryAdd(Id, Td)) - { - return Id; - } - } - - return -1; - } - - private ALFormat GetALFormat(int Channels, AudioFormat Format) - { - if (Channels == 1) - { - switch (Format) - { - case AudioFormat.PcmInt8: return ALFormat.Mono8; - case AudioFormat.PcmInt16: return ALFormat.Mono16; - } - } - else if (Channels == 2) - { - switch (Format) - { - case AudioFormat.PcmInt8: return ALFormat.Stereo8; - case AudioFormat.PcmInt16: return ALFormat.Stereo16; - } - } - else if (Channels == 6) - { - switch (Format) - { - case AudioFormat.PcmInt8: return ALFormat.Multi51Chn8Ext; - case AudioFormat.PcmInt16: return ALFormat.Multi51Chn16Ext; - } - } - else - { - throw new ArgumentOutOfRangeException(nameof(Channels)); - } - - throw new ArgumentException(nameof(Format)); - } - - public void CloseTrack(int Track) - { - if (Tracks.TryRemove(Track, out Track Td)) - { - Td.Dispose(); - } - } - - public bool ContainsBuffer(int Track, long Tag) - { - if (Tracks.TryGetValue(Track, out Track Td)) - { - return Td.ContainsBuffer(Tag); - } - - return false; - } - - public long[] GetReleasedBuffers(int Track, int MaxCount) - { - if (Tracks.TryGetValue(Track, out Track Td)) - { - return Td.GetReleasedBuffers(MaxCount); - } - - return null; - } - - public void AppendBuffer(int Track, long Tag, byte[] Buffer) - { - if (Tracks.TryGetValue(Track, out Track Td)) - { - int BufferId = Td.AppendBuffer(Tag); - - AL.BufferData(BufferId, Td.Format, Buffer, Buffer.Length, Td.SampleRate); - - AL.SourceQueueBuffer(Td.SourceId, BufferId); - - StartPlaybackIfNeeded(Td); - } - } - - public void Start(int Track) - { - if (Tracks.TryGetValue(Track, out Track Td)) - { - Td.State = PlaybackState.Playing; - - StartPlaybackIfNeeded(Td); - } - } - - private void StartPlaybackIfNeeded(Track Td) - { - AL.GetSource(Td.SourceId, ALGetSourcei.SourceState, out int StateInt); - - ALSourceState State = (ALSourceState)StateInt; - - if (State != ALSourceState.Playing && Td.State == PlaybackState.Playing) - { - Td.ClearReleased(); - - AL.SourcePlay(Td.SourceId); - } - } - - public void Stop(int Track) - { - if (Tracks.TryGetValue(Track, out Track Td)) - { - Td.State = PlaybackState.Stopped; - - AL.SourceStop(Td.SourceId); - } - } - - public PlaybackState GetState(int Track) - { - if (Tracks.TryGetValue(Track, out Track Td)) - { - return Td.State; - } - - return PlaybackState.Stopped; - } - - - } -} \ No newline at end of file diff --git a/Ryujinx.Audio/PlaybackState.cs b/Ryujinx.Audio/PlaybackState.cs index 8b53128aaf..7d8620924b 100644 --- a/Ryujinx.Audio/PlaybackState.cs +++ b/Ryujinx.Audio/PlaybackState.cs @@ -1,8 +1,17 @@ namespace Ryujinx.Audio { + /// + /// The playback state of a track + /// public enum PlaybackState { + /// + /// The track is currently playing + /// Playing = 0, + /// + /// The track is currently stopped + /// Stopped = 1 } } \ No newline at end of file diff --git a/Ryujinx.Audio/Renderers/DummyAudioOut.cs b/Ryujinx.Audio/Renderers/DummyAudioOut.cs new file mode 100644 index 0000000000..10943ae62b --- /dev/null +++ b/Ryujinx.Audio/Renderers/DummyAudioOut.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; + +namespace Ryujinx.Audio +{ + /// + /// A Dummy audio renderer that does not output any audio + /// + public class DummyAudioOut : IAalOutput + { + private int _lastTrackId = 1; + private float _volume = 1.0f; + + private ConcurrentQueue _trackIds; + private ConcurrentQueue _buffers; + private ConcurrentDictionary _releaseCallbacks; + + public DummyAudioOut() + { + _buffers = new ConcurrentQueue(); + _trackIds = new ConcurrentQueue(); + _releaseCallbacks = new ConcurrentDictionary(); + } + + /// + /// Dummy audio output is always available, Baka! + /// + public static bool IsSupported => true; + + public PlaybackState GetState(int trackId) => PlaybackState.Stopped; + + public int OpenTrack(int sampleRate, int channels, ReleaseCallback callback) + { + if (!_trackIds.TryDequeue(out int trackId)) + { + trackId = ++_lastTrackId; + } + + _releaseCallbacks[trackId] = callback; + + return trackId; + } + + public void CloseTrack(int trackId) + { + _trackIds.Enqueue(trackId); + _releaseCallbacks.Remove(trackId, out _); + } + + public bool ContainsBuffer(int trackID, long bufferTag) => false; + + public long[] GetReleasedBuffers(int trackId, int maxCount) + { + List bufferTags = new List(); + + for (int i = 0; i < maxCount; i++) + { + if (!_buffers.TryDequeue(out long tag)) + { + break; + } + + bufferTags.Add(tag); + } + + return bufferTags.ToArray(); + } + + public void AppendBuffer(int trackID, long bufferTag, T[] buffer) where T : struct + { + _buffers.Enqueue(bufferTag); + + if (_releaseCallbacks.TryGetValue(trackID, out var callback)) + { + callback?.Invoke(); + } + } + + public void Start(int trackId) { } + + public void Stop(int trackId) { } + + public float GetVolume() => _volume; + + public void SetVolume(float volume) + { + _volume = volume; + } + + public void Dispose() + { + _buffers.Clear(); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Audio/Renderers/OpenAL/OpenALAudioOut.cs b/Ryujinx.Audio/Renderers/OpenAL/OpenALAudioOut.cs new file mode 100644 index 0000000000..30b325a51a --- /dev/null +++ b/Ryujinx.Audio/Renderers/OpenAL/OpenALAudioOut.cs @@ -0,0 +1,315 @@ +using OpenTK.Audio; +using OpenTK.Audio.OpenAL; +using System; +using System.Collections.Concurrent; +using System.Runtime.InteropServices; +using System.Threading; + +namespace Ryujinx.Audio +{ + /// + /// An audio renderer that uses OpenAL as the audio backend + /// + public class OpenALAudioOut : IAalOutput, IDisposable + { + /// + /// The maximum amount of tracks we can issue simultaneously + /// + private const int MaxTracks = 256; + + /// + /// The audio context + /// + private AudioContext _context; + + /// + /// An object pool containing objects + /// + private ConcurrentDictionary _tracks; + + /// + /// True if the thread need to keep polling + /// + private bool _keepPolling; + + /// + /// The poller thread audio context + /// + private Thread _audioPollerThread; + + /// + /// The volume of audio renderer + /// + private float _volume = 1.0f; + + /// + /// True if the volume of audio renderer have changed + /// + private bool _volumeChanged; + + /// + /// True if OpenAL is supported on the device + /// + public static bool IsSupported + { + get + { + try + { + return AudioContext.AvailableDevices.Count > 0; + } + catch + { + return false; + } + } + } + + public OpenALAudioOut() + { + _context = new AudioContext(); + _tracks = new ConcurrentDictionary(); + _keepPolling = true; + _audioPollerThread = new Thread(AudioPollerWork) + { + Name = "Audio.PollerThread" + }; + + _audioPollerThread.Start(); + } + + private void AudioPollerWork() + { + do + { + foreach (OpenALAudioTrack track in _tracks.Values) + { + lock (track) + { + track.CallReleaseCallbackIfNeeded(); + } + } + + // If it's not slept it will waste cycles. + Thread.Sleep(10); + } + while (_keepPolling); + + foreach (OpenALAudioTrack track in _tracks.Values) + { + track.Dispose(); + } + + _tracks.Clear(); + } + + /// + /// Creates a new audio track with the specified parameters + /// + /// The requested sample rate + /// The requested channels + /// A that represents the delegate to invoke when a buffer has been released by the audio track + public int OpenTrack(int sampleRate, int channels, ReleaseCallback callback) + { + OpenALAudioTrack track = new OpenALAudioTrack(sampleRate, GetALFormat(channels), callback); + + for (int id = 0; id < MaxTracks; id++) + { + if (_tracks.TryAdd(id, track)) + { + return id; + } + } + + return -1; + } + + private ALFormat GetALFormat(int channels) + { + switch (channels) + { + case 1: return ALFormat.Mono16; + case 2: return ALFormat.Stereo16; + case 6: return ALFormat.Multi51Chn16Ext; + } + + throw new ArgumentOutOfRangeException(nameof(channels)); + } + + /// + /// Stops playback and closes the track specified by + /// + /// The ID of the track to close + public void CloseTrack(int trackId) + { + if (_tracks.TryRemove(trackId, out OpenALAudioTrack track)) + { + lock (track) + { + track.Dispose(); + } + } + } + + /// + /// Returns a value indicating whether the specified buffer is currently reserved by the specified track + /// + /// The track to check + /// The buffer tag to check + public bool ContainsBuffer(int trackId, long bufferTag) + { + if (_tracks.TryGetValue(trackId, out OpenALAudioTrack track)) + { + lock (track) + { + return track.ContainsBuffer(bufferTag); + } + } + + return false; + } + + /// + /// Gets a list of buffer tags the specified track is no longer reserving + /// + /// The track to retrieve buffer tags from + /// The maximum amount of buffer tags to retrieve + /// Buffers released by the specified track + public long[] GetReleasedBuffers(int trackId, int maxCount) + { + if (_tracks.TryGetValue(trackId, out OpenALAudioTrack track)) + { + lock (track) + { + return track.GetReleasedBuffers(maxCount); + } + } + + return null; + } + + /// + /// Appends an audio buffer to the specified track + /// + /// The sample type of the buffer + /// The track to append the buffer to + /// The internal tag of the buffer + /// The buffer to append to the track + public void AppendBuffer(int trackId, long bufferTag, T[] buffer) where T : struct + { + if (_tracks.TryGetValue(trackId, out OpenALAudioTrack track)) + { + lock (track) + { + int bufferId = track.AppendBuffer(bufferTag); + + int size = buffer.Length * Marshal.SizeOf(); + + AL.BufferData(bufferId, track.Format, buffer, size, track.SampleRate); + + AL.SourceQueueBuffer(track.SourceId, bufferId); + + StartPlaybackIfNeeded(track); + } + } + } + + /// + /// Starts playback + /// + /// The ID of the track to start playback on + public void Start(int trackId) + { + if (_tracks.TryGetValue(trackId, out OpenALAudioTrack track)) + { + lock (track) + { + track.State = PlaybackState.Playing; + + StartPlaybackIfNeeded(track); + } + } + } + + private void StartPlaybackIfNeeded(OpenALAudioTrack track) + { + AL.GetSource(track.SourceId, ALGetSourcei.SourceState, out int stateInt); + + ALSourceState State = (ALSourceState)stateInt; + + if (State != ALSourceState.Playing && track.State == PlaybackState.Playing) + { + if (_volumeChanged) + { + AL.Source(track.SourceId, ALSourcef.Gain, _volume); + + _volumeChanged = false; + } + + AL.SourcePlay(track.SourceId); + } + } + + /// + /// Stops playback + /// + /// The ID of the track to stop playback on + public void Stop(int trackId) + { + if (_tracks.TryGetValue(trackId, out OpenALAudioTrack track)) + { + lock (track) + { + track.State = PlaybackState.Stopped; + + AL.SourceStop(track.SourceId); + } + } + } + + /// + /// Get playback volume + /// + public float GetVolume() => _volume; + + /// + /// Set playback volume + /// + /// The volume of the playback + public void SetVolume(float volume) + { + if (!_volumeChanged) + { + _volume = volume; + _volumeChanged = true; + } + } + + /// + /// Gets the current playback state of the specified track + /// + /// The track to retrieve the playback state for + public PlaybackState GetState(int trackId) + { + if (_tracks.TryGetValue(trackId, out OpenALAudioTrack track)) + { + return track.State; + } + + return PlaybackState.Stopped; + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _keepPolling = false; + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Audio/Renderers/OpenAL/OpenALAudioTrack.cs b/Ryujinx.Audio/Renderers/OpenAL/OpenALAudioTrack.cs new file mode 100644 index 0000000000..8629dc969c --- /dev/null +++ b/Ryujinx.Audio/Renderers/OpenAL/OpenALAudioTrack.cs @@ -0,0 +1,142 @@ +using OpenTK.Audio.OpenAL; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; + +namespace Ryujinx.Audio +{ + internal class OpenALAudioTrack : IDisposable + { + public int SourceId { get; private set; } + public int SampleRate { get; private set; } + public ALFormat Format { get; private set; } + public PlaybackState State { get; set; } + + private ReleaseCallback _callback; + + private ConcurrentDictionary _buffers; + + private Queue _queuedTagsQueue; + private Queue _releasedTagsQueue; + + private bool _disposed; + + public OpenALAudioTrack(int sampleRate, ALFormat format, ReleaseCallback callback) + { + SampleRate = sampleRate; + Format = format; + State = PlaybackState.Stopped; + SourceId = AL.GenSource(); + + _callback = callback; + + _buffers = new ConcurrentDictionary(); + + _queuedTagsQueue = new Queue(); + _releasedTagsQueue = new Queue(); + } + + public bool ContainsBuffer(long tag) + { + foreach (long queuedTag in _queuedTagsQueue) + { + if (queuedTag == tag) + { + return true; + } + } + + return false; + } + + public long[] GetReleasedBuffers(int count) + { + AL.GetSource(SourceId, ALGetSourcei.BuffersProcessed, out int releasedCount); + + releasedCount += _releasedTagsQueue.Count; + + if (count > releasedCount) + { + count = releasedCount; + } + + List tags = new List(); + + while (count-- > 0 && _releasedTagsQueue.TryDequeue(out long tag)) + { + tags.Add(tag); + } + + while (count-- > 0 && _queuedTagsQueue.TryDequeue(out long tag)) + { + AL.SourceUnqueueBuffers(SourceId, 1); + + tags.Add(tag); + } + + return tags.ToArray(); + } + + public int AppendBuffer(long tag) + { + if (_disposed) + { + throw new ObjectDisposedException(GetType().Name); + } + + int id = AL.GenBuffer(); + + _buffers.AddOrUpdate(tag, id, (key, oldId) => + { + AL.DeleteBuffer(oldId); + + return id; + }); + + _queuedTagsQueue.Enqueue(tag); + + return id; + } + + public void CallReleaseCallbackIfNeeded() + { + AL.GetSource(SourceId, ALGetSourcei.BuffersProcessed, out int releasedCount); + + if (releasedCount > 0) + { + // If we signal, then we also need to have released buffers available + // to return when GetReleasedBuffers is called. + // If playback needs to be re-started due to all buffers being processed, + // then OpenAL zeros the counts (ReleasedCount), so we keep it on the queue. + while (releasedCount-- > 0 && _queuedTagsQueue.TryDequeue(out long tag)) + { + AL.SourceUnqueueBuffers(SourceId, 1); + + _releasedTagsQueue.Enqueue(tag); + } + + _callback(); + } + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing && !_disposed) + { + _disposed = true; + + AL.DeleteSource(SourceId); + + foreach (int id in _buffers.Values) + { + AL.DeleteBuffer(id); + } + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Audio/Renderers/SoundIo/SoundIoAudioOut.cs b/Ryujinx.Audio/Renderers/SoundIo/SoundIoAudioOut.cs new file mode 100644 index 0000000000..1e487a6d93 --- /dev/null +++ b/Ryujinx.Audio/Renderers/SoundIo/SoundIoAudioOut.cs @@ -0,0 +1,324 @@ +using Ryujinx.Audio.SoundIo; +using SoundIOSharp; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Audio +{ + /// + /// An audio renderer that uses libsoundio as the audio backend + /// + public class SoundIoAudioOut : IAalOutput + { + /// + /// The maximum amount of tracks we can issue simultaneously + /// + private const int MaximumTracks = 256; + + /// + /// The volume of audio renderer + /// + private float _volume = 1.0f; + + /// + /// True if the volume of audio renderer have changed + /// + private bool _volumeChanged; + + /// + /// The audio context + /// + private SoundIO _audioContext; + + /// + /// The audio device + /// + private SoundIODevice _audioDevice; + + /// + /// An object pool containing objects + /// + private SoundIoAudioTrackPool _trackPool; + + /// + /// True if SoundIO is supported on the device + /// + public static bool IsSupported + { + get + { + return IsSupportedInternal(); + } + } + + /// + /// Constructs a new instance of a + /// + public SoundIoAudioOut() + { + _audioContext = new SoundIO(); + + _audioContext.Connect(); + _audioContext.FlushEvents(); + + _audioDevice = FindNonRawDefaultAudioDevice(_audioContext, true); + _trackPool = new SoundIoAudioTrackPool(_audioContext, _audioDevice, MaximumTracks); + } + + /// + /// Creates a new audio track with the specified parameters + /// + /// The requested sample rate + /// The requested channels + /// A that represents the delegate to invoke when a buffer has been released by the audio track + /// The created track's Track ID + public int OpenTrack(int sampleRate, int channels, ReleaseCallback callback) + { + if (!_trackPool.TryGet(out SoundIoAudioTrack track)) + { + return -1; + } + + // Open the output. We currently only support 16-bit signed LE + track.Open(sampleRate, channels, callback, SoundIOFormat.S16LE); + + return track.TrackID; + } + + /// + /// Stops playback and closes the track specified by + /// + /// The ID of the track to close + public void CloseTrack(int trackId) + { + if (_trackPool.TryGet(trackId, out SoundIoAudioTrack track)) + { + // Close and dispose of the track + track.Close(); + + // Recycle the track back into the pool + _trackPool.Put(track); + } + } + + /// + /// Returns a value indicating whether the specified buffer is currently reserved by the specified track + /// + /// The track to check + /// The buffer tag to check + public bool ContainsBuffer(int trackId, long bufferTag) + { + if (_trackPool.TryGet(trackId, out SoundIoAudioTrack track)) + { + return track.ContainsBuffer(bufferTag); + } + + return false; + } + + /// + /// Gets a list of buffer tags the specified track is no longer reserving + /// + /// The track to retrieve buffer tags from + /// The maximum amount of buffer tags to retrieve + /// Buffers released by the specified track + public long[] GetReleasedBuffers(int trackId, int maxCount) + { + if (_trackPool.TryGet(trackId, out SoundIoAudioTrack track)) + { + List bufferTags = new List(); + + while(maxCount-- > 0 && track.ReleasedBuffers.TryDequeue(out long tag)) + { + bufferTags.Add(tag); + } + + return bufferTags.ToArray(); + } + + return new long[0]; + } + + /// + /// Appends an audio buffer to the specified track + /// + /// The sample type of the buffer + /// The track to append the buffer to + /// The internal tag of the buffer + /// The buffer to append to the track + public void AppendBuffer(int trackId, long bufferTag, T[] buffer) where T : struct + { + if (_trackPool.TryGet(trackId, out SoundIoAudioTrack track)) + { + if (_volumeChanged) + { + track.AudioStream.SetVolume(_volume); + + _volumeChanged = false; + } + + track.AppendBuffer(bufferTag, buffer); + } + } + + /// + /// Starts playback + /// + /// The ID of the track to start playback on + public void Start(int trackId) + { + if (_trackPool.TryGet(trackId, out SoundIoAudioTrack track)) + { + track.Start(); + } + } + + /// + /// Stops playback + /// + /// The ID of the track to stop playback on + public void Stop(int trackId) + { + if (_trackPool.TryGet(trackId, out SoundIoAudioTrack track)) + { + track.Stop(); + } + } + + /// + /// Get playback volume + /// + public float GetVolume() => _volume; + + /// + /// Set playback volume + /// + /// The volume of the playback + public void SetVolume(float volume) + { + if (!_volumeChanged) + { + _volume = volume; + _volumeChanged = true; + } + } + + /// + /// Gets the current playback state of the specified track + /// + /// The track to retrieve the playback state for + public PlaybackState GetState(int trackId) + { + if (_trackPool.TryGet(trackId, out SoundIoAudioTrack track)) + { + return track.State; + } + + return PlaybackState.Stopped; + } + + /// + /// Releases the unmanaged resources used by the + /// + public void Dispose() + { + _trackPool.Dispose(); + _audioContext.Disconnect(); + _audioContext.Dispose(); + } + + /// + /// Searches for a shared version of the default audio device + /// + /// The audio context + /// Whether to fallback to the raw default audio device if a non-raw device cannot be found + private static SoundIODevice FindNonRawDefaultAudioDevice(SoundIO audioContext, bool fallback = false) + { + SoundIODevice defaultAudioDevice = audioContext.GetOutputDevice(audioContext.DefaultOutputDeviceIndex); + + if (!defaultAudioDevice.IsRaw) + { + return defaultAudioDevice; + } + + for (int i = 0; i < audioContext.BackendCount; i++) + { + SoundIODevice audioDevice = audioContext.GetOutputDevice(i); + + if (audioDevice.Id == defaultAudioDevice.Id && !audioDevice.IsRaw) + { + return audioDevice; + } + } + + return fallback ? defaultAudioDevice : null; + } + + /// + /// Determines if SoundIO can connect to a supported backend + /// + /// + private static bool IsSupportedInternal() + { + SoundIO context = null; + SoundIODevice device = null; + SoundIOOutStream stream = null; + + bool backendDisconnected = false; + + try + { + context = new SoundIO(); + + context.OnBackendDisconnect = (i) => { + backendDisconnected = true; + }; + + context.Connect(); + context.FlushEvents(); + + if (backendDisconnected) + { + return false; + } + + if (context.OutputDeviceCount == 0) + { + return false; + } + + device = FindNonRawDefaultAudioDevice(context); + + if (device == null || backendDisconnected) + { + return false; + } + + stream = device.CreateOutStream(); + + if (stream == null || backendDisconnected) + { + return false; + } + + return true; + } + catch + { + return false; + } + finally + { + if (stream != null) + { + stream.Dispose(); + } + + if (context != null) + { + context.Dispose(); + } + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Audio/Renderers/SoundIo/SoundIoAudioTrack.cs b/Ryujinx.Audio/Renderers/SoundIo/SoundIoAudioTrack.cs new file mode 100644 index 0000000000..97ba11d513 --- /dev/null +++ b/Ryujinx.Audio/Renderers/SoundIo/SoundIoAudioTrack.cs @@ -0,0 +1,560 @@ +using SoundIOSharp; +using System; +using System.Collections.Concurrent; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.SoundIo +{ + internal class SoundIoAudioTrack : IDisposable + { + /// + /// The audio track ring buffer + /// + private SoundIoRingBuffer m_Buffer; + + /// + /// A list of buffers currently pending writeback to the audio backend + /// + private ConcurrentQueue m_ReservedBuffers; + + /// + /// Occurs when a buffer has been released by the audio backend + /// + private event ReleaseCallback BufferReleased; + + /// + /// The track ID of this + /// + public int TrackID { get; private set; } + + /// + /// The current playback state + /// + public PlaybackState State { get; private set; } + + /// + /// The audio context this track belongs to + /// + public SoundIO AudioContext { get; private set; } + + /// + /// The this track belongs to + /// + public SoundIODevice AudioDevice { get; private set; } + + /// + /// The audio output stream of this track + /// + public SoundIOOutStream AudioStream { get; private set; } + + /// + /// Released buffers the track is no longer holding + /// + public ConcurrentQueue ReleasedBuffers { get; private set; } + + /// + /// Constructs a new instance of a + /// + /// The track ID + /// The SoundIO audio context + /// The SoundIO audio device + public SoundIoAudioTrack(int trackId, SoundIO audioContext, SoundIODevice audioDevice) + { + TrackID = trackId; + AudioContext = audioContext; + AudioDevice = audioDevice; + State = PlaybackState.Stopped; + ReleasedBuffers = new ConcurrentQueue(); + + m_Buffer = new SoundIoRingBuffer(); + m_ReservedBuffers = new ConcurrentQueue(); + } + + /// + /// Opens the audio track with the specified parameters + /// + /// The requested sample rate of the track + /// The requested channel count of the track + /// A that represents the delegate to invoke when a buffer has been released by the audio track + /// The requested sample format of the track + public void Open( + int sampleRate, + int channelCount, + ReleaseCallback callback, + SoundIOFormat format = SoundIOFormat.S16LE) + { + // Close any existing audio streams + if (AudioStream != null) + { + Close(); + } + + if (!AudioDevice.SupportsSampleRate(sampleRate)) + { + throw new InvalidOperationException($"This sound device does not support a sample rate of {sampleRate}Hz"); + } + + if (!AudioDevice.SupportsFormat(format)) + { + throw new InvalidOperationException($"This sound device does not support SoundIOFormat.{Enum.GetName(typeof(SoundIOFormat), format)}"); + } + + AudioStream = AudioDevice.CreateOutStream(); + + AudioStream.Name = $"SwitchAudioTrack_{TrackID}"; + AudioStream.Layout = SoundIOChannelLayout.GetDefault(channelCount); + AudioStream.Format = format; + AudioStream.SampleRate = sampleRate; + + AudioStream.WriteCallback = WriteCallback; + + BufferReleased += callback; + + AudioStream.Open(); + } + + /// + /// This callback occurs when the sound device is ready to buffer more frames + /// + /// The minimum amount of frames expected by the audio backend + /// The maximum amount of frames that can be written to the audio backend + private unsafe void WriteCallback(int minFrameCount, int maxFrameCount) + { + int bytesPerFrame = AudioStream.BytesPerFrame; + uint bytesPerSample = (uint)AudioStream.BytesPerSample; + + int bufferedFrames = m_Buffer.Length / bytesPerFrame; + long bufferedSamples = m_Buffer.Length / bytesPerSample; + + int frameCount = Math.Min(bufferedFrames, maxFrameCount); + + if (frameCount == 0) + { + return; + } + + SoundIOChannelAreas areas = AudioStream.BeginWrite(ref frameCount); + int channelCount = areas.ChannelCount; + + byte[] samples = new byte[frameCount * bytesPerFrame]; + + m_Buffer.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. + + // Mono + if (channelCount == 1) + { + SoundIOChannelArea area = areas.GetArea(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) + { + SoundIOChannelArea area1 = areas.GetArea(0); + SoundIOChannelArea area2 = areas.GetArea(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) + { + SoundIOChannelArea area1 = areas.GetArea(0); + SoundIOChannelArea area2 = areas.GetArea(1); + SoundIOChannelArea area3 = areas.GetArea(2); + SoundIOChannelArea area4 = areas.GetArea(3); + SoundIOChannelArea area5 = areas.GetArea(4); + SoundIOChannelArea area6 = areas.GetArea(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 + { + SoundIOChannelArea[] channels = new SoundIOChannelArea[channelCount]; + + // Obtain the channel area for each channel + for (int i = 0; i < channelCount; i++) + { + channels[i] = areas.GetArea(i); + } + + fixed (byte* srcptr = samples) + { + for (int frame = 0; frame < frameCount; frame++) + for (int channel = 0; channel < areas.ChannelCount; channel++) + { + // Copy channel by channel, frame by frame. This is slow! + Unsafe.CopyBlockUnaligned((byte*)channels[channel].Pointer, srcptr + (frame * bytesPerFrame) + (channel * bytesPerSample), bytesPerSample); + + channels[channel].Pointer += channels[channel].Step; + } + } + } + + AudioStream.EndWrite(); + + UpdateReleasedBuffers(samples.Length); + } + + /// + /// Releases any buffers that have been fully written to the output device + /// + /// The amount of bytes written in the last device write + private void UpdateReleasedBuffers(int bytesRead) + { + bool bufferReleased = false; + + while (bytesRead > 0) + { + if (m_ReservedBuffers.TryPeek(out SoundIoBuffer buffer)) + { + if (buffer.Length > bytesRead) + { + buffer.Length -= bytesRead; + bytesRead = 0; + } + else + { + bufferReleased = true; + bytesRead -= buffer.Length; + + m_ReservedBuffers.TryDequeue(out buffer); + ReleasedBuffers.Enqueue(buffer.Tag); + } + } + } + + if (bufferReleased) + { + OnBufferReleased(); + } + } + + /// + /// Starts audio playback + /// + public void Start() + { + if (AudioStream == null) + { + return; + } + + AudioStream.Start(); + AudioStream.Pause(false); + AudioContext.FlushEvents(); + State = PlaybackState.Playing; + } + + /// + /// Stops audio playback + /// + public void Stop() + { + if (AudioStream == null) + { + return; + } + + AudioStream.Pause(true); + AudioContext.FlushEvents(); + State = PlaybackState.Stopped; + } + + /// + /// Appends an audio buffer to the tracks internal ring buffer + /// + /// The audio sample type + /// The unqiue tag of the buffer being appended + /// The buffer to append + public void AppendBuffer(long bufferTag, T[] buffer) + { + if (AudioStream == null) + { + return; + } + + // Calculate the size of the audio samples + int size = Unsafe.SizeOf(); + + // Calculate the amount of bytes to copy from the buffer + int bytesToCopy = size * buffer.Length; + + // Copy the memory to our ring buffer + m_Buffer.Write(buffer, 0, bytesToCopy); + + // Keep track of "buffered" buffers + m_ReservedBuffers.Enqueue(new SoundIoBuffer(bufferTag, bytesToCopy)); + } + + /// + /// Returns a value indicating whether the specified buffer is currently reserved by the track + /// + /// The buffer tag to check + public bool ContainsBuffer(long bufferTag) + { + return m_ReservedBuffers.Any(x => x.Tag == bufferTag); + } + + /// + /// Closes the + /// + public void Close() + { + if (AudioStream != null) + { + AudioStream.Pause(true); + AudioStream.Dispose(); + } + + m_Buffer.Clear(); + OnBufferReleased(); + ReleasedBuffers.Clear(); + + State = PlaybackState.Stopped; + AudioStream = null; + BufferReleased = null; + } + + private void OnBufferReleased() + { + BufferReleased?.Invoke(); + } + + /// + /// Releases the unmanaged resources used by the + /// + public void Dispose() + { + Close(); + } + + ~SoundIoAudioTrack() + { + Dispose(); + } + } +} diff --git a/Ryujinx.Audio/Renderers/SoundIo/SoundIoAudioTrackPool.cs b/Ryujinx.Audio/Renderers/SoundIo/SoundIoAudioTrackPool.cs new file mode 100644 index 0000000000..95f181dc67 --- /dev/null +++ b/Ryujinx.Audio/Renderers/SoundIo/SoundIoAudioTrackPool.cs @@ -0,0 +1,193 @@ +using SoundIOSharp; +using System; +using System.Collections.Concurrent; +using System.Linq; + +namespace Ryujinx.Audio.SoundIo +{ + /// + /// An object pool containing a set of audio tracks + /// + internal class SoundIoAudioTrackPool : IDisposable + { + /// + /// The current size of the + /// + private int m_Size; + + /// + /// The maximum size of the + /// + private int m_MaxSize; + + /// + /// The audio context this track pool belongs to + /// + private SoundIO m_Context; + + /// + /// The audio device this track pool belongs to + /// + private SoundIODevice m_Device; + + /// + /// The queue that keeps track of the available in the pool. + /// + private ConcurrentQueue m_Queue; + + /// + /// The dictionary providing mapping between a TrackID and + /// + private ConcurrentDictionary m_TrackList; + + /// + /// Gets the current size of the + /// + public int Size { get => m_Size; } + + /// + /// Gets the maximum size of the + /// + public int MaxSize { get => m_MaxSize; } + + /// + /// Gets a value that indicates whether the is empty + /// + public bool IsEmpty { get => m_Queue.IsEmpty; } + + /// + /// Constructs a new instance of a that is empty + /// + /// The maximum amount of tracks that can be created + public SoundIoAudioTrackPool(SoundIO context, SoundIODevice device, int maxSize) + { + m_Size = 0; + m_Context = context; + m_Device = device; + m_MaxSize = maxSize; + + m_Queue = new ConcurrentQueue(); + m_TrackList = new ConcurrentDictionary(); + } + + /// + /// Constructs a new instance of a that contains + /// the specified amount of + /// + /// The maximum amount of tracks that can be created + /// The initial number of tracks that the pool contains + public SoundIoAudioTrackPool(SoundIO context, SoundIODevice device, int maxSize, int initialCapacity) + : this(context, device, maxSize) + { + var trackCollection = Enumerable.Range(0, initialCapacity) + .Select(TrackFactory); + + m_Size = initialCapacity; + m_Queue = new ConcurrentQueue(trackCollection); + } + + /// + /// Creates a new with the proper AudioContext and AudioDevice + /// and the specified + /// + /// The ID of the track to be created + /// A new AudioTrack with the specified ID + private SoundIoAudioTrack TrackFactory(int trackId) + { + // Create a new AudioTrack + SoundIoAudioTrack track = new SoundIoAudioTrack(trackId, m_Context, m_Device); + + // Keep track of issued tracks + m_TrackList[trackId] = track; + + return track; + } + + /// + /// Retrieves a from the pool + /// + /// An AudioTrack from the pool + public SoundIoAudioTrack Get() + { + // If we have a track available, reuse it + if (m_Queue.TryDequeue(out SoundIoAudioTrack track)) + { + return track; + } + + // Have we reached the maximum size of our pool? + if (m_Size >= m_MaxSize) + { + return null; + } + + // We don't have any pooled tracks, so create a new one + return TrackFactory(m_Size++); + } + + /// + /// Retrieves the associated with the specified from the pool + /// + /// The ID of the track to retrieve + public SoundIoAudioTrack Get(int trackId) + { + if (m_TrackList.TryGetValue(trackId, out SoundIoAudioTrack track)) + { + return track; + } + + return null; + } + + /// + /// Attempts to get a from the pool + /// + /// The track retrieved from the pool + /// True if retrieve was successful + public bool TryGet(out SoundIoAudioTrack track) + { + track = Get(); + + return track != null; + } + + /// + /// Attempts to get the associated with the specified from the pool + /// + /// The ID of the track to retrieve + /// The track retrieved from the pool + public bool TryGet(int trackId, out SoundIoAudioTrack track) + { + return m_TrackList.TryGetValue(trackId, out track); + } + + /// + /// Returns an back to the pool for reuse + /// + /// The track to be returned to the pool + public void Put(SoundIoAudioTrack track) + { + // Ensure the track is disposed and not playing audio + track.Close(); + + // Requeue the track for reuse later + m_Queue.Enqueue(track); + } + + /// + /// Releases the unmanaged resources used by the + /// + public void Dispose() + { + foreach (var track in m_TrackList) + { + track.Value.Close(); + track.Value.Dispose(); + } + + m_Size = 0; + m_Queue.Clear(); + m_TrackList.Clear(); + } + } +} diff --git a/Ryujinx.Audio/Renderers/SoundIo/SoundIoBuffer.cs b/Ryujinx.Audio/Renderers/SoundIo/SoundIoBuffer.cs new file mode 100644 index 0000000000..2a6190b53b --- /dev/null +++ b/Ryujinx.Audio/Renderers/SoundIo/SoundIoBuffer.cs @@ -0,0 +1,29 @@ +namespace Ryujinx.Audio.SoundIo +{ + /// + /// Represents the remaining bytes left buffered for a specific buffer tag + /// + internal class SoundIoBuffer + { + /// + /// The buffer tag this represents + /// + public long Tag { get; private set; } + + /// + /// The remaining bytes still to be released + /// + public int Length { get; set; } + + /// + /// Constructs a new instance of a + /// + /// The buffer tag + /// The size of the buffer + public SoundIoBuffer(long tag, int length) + { + Tag = tag; + Length = length; + } + } +} diff --git a/Ryujinx.Audio/Renderers/SoundIo/SoundIoRingBuffer.cs b/Ryujinx.Audio/Renderers/SoundIo/SoundIoRingBuffer.cs new file mode 100644 index 0000000000..b288502132 --- /dev/null +++ b/Ryujinx.Audio/Renderers/SoundIo/SoundIoRingBuffer.cs @@ -0,0 +1,204 @@ +using System; + +namespace Ryujinx.Audio.SoundIo +{ + /// + /// A thread-safe variable-size circular buffer + /// + internal class SoundIoRingBuffer + { + private byte[] m_Buffer; + private int m_Size; + private int m_HeadOffset; + private int m_TailOffset; + + /// + /// Gets the available bytes in the ring buffer + /// + public int Length + { + get { return m_Size; } + } + + /// + /// Constructs a new instance of a + /// + public SoundIoRingBuffer() + { + m_Buffer = new byte[2048]; + } + + /// + /// Constructs a new instance of a with the specified capacity + /// + /// The number of entries that the can initially contain + public SoundIoRingBuffer(int capacity) + { + m_Buffer = new byte[capacity]; + } + + /// + /// Clears the ring buffer + /// + public void Clear() + { + m_Size = 0; + m_HeadOffset = 0; + m_TailOffset = 0; + } + + /// + /// Clears the specified amount of bytes from the ring buffer + /// + /// The amount of bytes to clear from the ring buffer + public void Clear(int size) + { + lock (this) + { + if (size > m_Size) + { + size = m_Size; + } + + if (size == 0) + { + return; + } + + m_HeadOffset = (m_HeadOffset + size) % m_Buffer.Length; + m_Size -= size; + + if (m_Size == 0) + { + m_HeadOffset = 0; + m_TailOffset = 0; + } + + return; + } + } + + /// + /// Extends the capacity of the ring buffer + /// + private void SetCapacity(int capacity) + { + byte[] buffer = new byte[capacity]; + + if (m_Size > 0) + { + if (m_HeadOffset < m_TailOffset) + { + Buffer.BlockCopy(m_Buffer, m_HeadOffset, buffer, 0, m_Size); + } + else + { + Buffer.BlockCopy(m_Buffer, m_HeadOffset, buffer, 0, m_Buffer.Length - m_HeadOffset); + Buffer.BlockCopy(m_Buffer, 0, buffer, m_Buffer.Length - m_HeadOffset, m_TailOffset); + } + } + + m_Buffer = buffer; + m_HeadOffset = 0; + m_TailOffset = m_Size; + } + + + /// + /// Writes a sequence of bytes to the ring buffer + /// + /// A byte array containing the data to write + /// The zero-based byte offset in from which to begin copying bytes to the ring buffer + /// The number of bytes to write + public void Write(T[] buffer, int index, int count) + { + if (count == 0) + { + return; + } + + lock (this) + { + if ((m_Size + count) > m_Buffer.Length) + { + SetCapacity((m_Size + count + 2047) & ~2047); + } + + if (m_HeadOffset < m_TailOffset) + { + int tailLength = m_Buffer.Length - m_TailOffset; + + if (tailLength >= count) + { + Buffer.BlockCopy(buffer, index, m_Buffer, m_TailOffset, count); + } + else + { + Buffer.BlockCopy(buffer, index, m_Buffer, m_TailOffset, tailLength); + Buffer.BlockCopy(buffer, index + tailLength, m_Buffer, 0, count - tailLength); + } + } + else + { + Buffer.BlockCopy(buffer, index, m_Buffer, m_TailOffset, count); + } + + m_Size += count; + m_TailOffset = (m_TailOffset + count) % m_Buffer.Length; + } + } + + /// + /// Reads a sequence of bytes from the ring buffer and advances the position within the ring buffer by the number of bytes read + /// + /// The buffer to write the data into + /// The zero-based byte offset in at which the read bytes will be placed + /// The maximum number of bytes to read + /// The total number of bytes read into the buffer. This might be less than the number of bytes requested if that number of bytes are not currently available, or zero if the ring buffer is empty + public int Read(T[] buffer, int index, int count) + { + lock (this) + { + if (count > m_Size) + { + count = m_Size; + } + + if (count == 0) + { + return 0; + } + + if (m_HeadOffset < m_TailOffset) + { + Buffer.BlockCopy(m_Buffer, m_HeadOffset, buffer, index, count); + } + else + { + int tailLength = m_Buffer.Length - m_HeadOffset; + + if (tailLength >= count) + { + Buffer.BlockCopy(m_Buffer, m_HeadOffset, buffer, index, count); + } + else + { + Buffer.BlockCopy(m_Buffer, m_HeadOffset, buffer, index, tailLength); + Buffer.BlockCopy(m_Buffer, 0, buffer, index + tailLength, count - tailLength); + } + } + + m_Size -= count; + m_HeadOffset = (m_HeadOffset + count) % m_Buffer.Length; + + if (m_Size == 0) + { + m_HeadOffset = 0; + m_TailOffset = 0; + } + + return count; + } + } + } +} diff --git a/Ryujinx.Audio/Ryujinx.Audio.csproj b/Ryujinx.Audio/Ryujinx.Audio.csproj index 2cd38add9b..588b691814 100644 --- a/Ryujinx.Audio/Ryujinx.Audio.csproj +++ b/Ryujinx.Audio/Ryujinx.Audio.csproj @@ -1,12 +1,53 @@  - netcoreapp2.1 - win10-x64;osx-x64;linux-x64 + netcoreapp3.0 + win-x64;osx-x64;linux-x64 + Debug;Release;Profile Debug;Profile Release + + + + true + + + + true + TRACE;USE_PROFILING + false + + + + true + + + + true + TRACE;USE_PROFILING + true + + + + + + + + + + PreserveNewest + libsoundio.dll + + + PreserveNewest + libsoundio.dylib + + + PreserveNewest + libsoundio.so + diff --git a/Ryujinx.Common/Configuration/ConfigurationFileFormat.cs b/Ryujinx.Common/Configuration/ConfigurationFileFormat.cs new file mode 100644 index 0000000000..1a9407cb2f --- /dev/null +++ b/Ryujinx.Common/Configuration/ConfigurationFileFormat.cs @@ -0,0 +1,211 @@ +using JsonPrettyPrinterPlus; +using Ryujinx.Common.Logging; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using Utf8Json; +using Utf8Json.Resolvers; +using Ryujinx.Configuration.System; +using Ryujinx.Configuration.Hid; +using Ryujinx.Common.Configuration.Hid; +using Ryujinx.UI.Input; +using Ryujinx.Configuration.Ui; + +namespace Ryujinx.Configuration +{ + public class ConfigurationFileFormat + { + public int Version { get; set; } + + /// + /// Dumps shaders in this local directory + /// + public string GraphicsShadersDumpPath { get; set; } + + /// + /// Enables printing debug log messages + /// + public bool LoggingEnableDebug { get; set; } + + /// + /// Enables printing stub log messages + /// + public bool LoggingEnableStub { get; set; } + + /// + /// Enables printing info log messages + /// + public bool LoggingEnableInfo { get; set; } + + /// + /// Enables printing warning log messages + /// + public bool LoggingEnableWarn { get; set; } + + /// + /// Enables printing error log messages + /// + public bool LoggingEnableError { get; set; } + + /// + /// Enables printing guest log messages + /// + public bool LoggingEnableGuest { get; set; } + + /// + /// Enables printing FS access log messages + /// + public bool LoggingEnableFsAccessLog { get; set; } + + /// + /// Controls which log messages are written to the log targets + /// + public LogClass[] LoggingFilteredClasses { get; set; } + + /// + /// Enables or disables logging to a file on disk + /// + public bool EnableFileLog { get; set; } + + /// + /// Change System Language + /// + public Language SystemLanguage { get; set; } + + /// + /// Enables or disables Docked Mode + /// + public bool DockedMode { get; set; } + + /// + /// Enables or disables Discord Rich Presence + /// + public bool EnableDiscordIntegration { get; set; } + + /// + /// Enables or disables Vertical Sync + /// + public bool EnableVsync { get; set; } + + /// + /// Enables or disables multi-core scheduling of threads + /// + public bool EnableMulticoreScheduling { get; set; } + + /// + /// Enables integrity checks on Game content files + /// + public bool EnableFsIntegrityChecks { get; set; } + + /// + /// Enables FS access log output to the console. Possible modes are 0-3 + /// + public int FsGlobalAccessLogMode { get; set; } + + /// + /// Enable or disable ignoring missing services + /// + public bool IgnoreMissingServices { get; set; } + + /// + /// The primary controller's type + /// + public ControllerType ControllerType { get; set; } + + /// + /// Used to toggle columns in the GUI + /// + public GuiColumns GuiColumns { get; set; } + + /// + /// A list of directories containing games to be used to load games into the games list + /// + public List GameDirs { get; set; } + + /// + /// Enable or disable custom themes in the GUI + /// + public bool EnableCustomTheme { get; set; } + + /// + /// Path to custom GUI theme + /// + public string CustomThemePath { get; set; } + + /// + /// Enable or disable keyboard support (Independent from controllers binding) + /// + public bool EnableKeyboard { get; set; } + + /// + /// Keyboard control bindings + /// + public NpadKeyboard KeyboardControls { get; set; } + + /// + /// Controller control bindings + /// + public NpadController JoystickControls { get; set; } + + /// + /// Loads a configuration file from disk + /// + /// The path to the JSON configuration file + public static ConfigurationFileFormat Load(string path) + { + var resolver = CompositeResolver.Create( + new[] { new ConfigurationEnumFormatter() }, + new[] { StandardResolver.AllowPrivateSnakeCase } + ); + + using (Stream stream = File.OpenRead(path)) + { + return JsonSerializer.Deserialize(stream, resolver); + } + } + + /// + /// Save a configuration file to disk + /// + /// The path to the JSON configuration file + public void SaveConfig(string path) + { + IJsonFormatterResolver resolver = CompositeResolver.Create( + new[] { new ConfigurationEnumFormatter() }, + new[] { StandardResolver.AllowPrivateSnakeCase } + ); + + byte[] data = JsonSerializer.Serialize(this, resolver); + File.WriteAllText(path, Encoding.UTF8.GetString(data, 0, data.Length).PrettyPrintJson()); + } + + private class ConfigurationEnumFormatter : IJsonFormatter + where T : struct + { + public void Serialize(ref JsonWriter writer, T value, IJsonFormatterResolver formatterResolver) + { + formatterResolver.GetFormatterWithVerify() + .Serialize(ref writer, value.ToString(), formatterResolver); + } + + public T Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver) + { + if (reader.ReadIsNull()) + { + return default(T); + } + + string enumName = formatterResolver.GetFormatterWithVerify() + .Deserialize(ref reader, formatterResolver); + + if (Enum.TryParse(enumName, out T result)) + { + return result; + } + + return default(T); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Common/Configuration/ConfigurationState.cs b/Ryujinx.Common/Configuration/ConfigurationState.cs new file mode 100644 index 0000000000..050b497385 --- /dev/null +++ b/Ryujinx.Common/Configuration/ConfigurationState.cs @@ -0,0 +1,502 @@ +using Ryujinx.Common; +using Ryujinx.Common.Configuration.Hid; +using Ryujinx.Common.Logging; +using Ryujinx.Configuration.Hid; +using Ryujinx.Configuration.System; +using Ryujinx.Configuration.Ui; +using Ryujinx.UI.Input; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Configuration +{ + public class ConfigurationState + { + /// + /// UI configuration section + /// + public class UiSection + { + public class Columns + { + public ReactiveObject FavColumn { get; private set; } + public ReactiveObject IconColumn { get; private set; } + public ReactiveObject AppColumn { get; private set; } + public ReactiveObject DevColumn { get; private set; } + public ReactiveObject VersionColumn { get; private set; } + public ReactiveObject TimePlayedColumn { get; private set; } + public ReactiveObject LastPlayedColumn { get; private set; } + public ReactiveObject FileExtColumn { get; private set; } + public ReactiveObject FileSizeColumn { get; private set; } + public ReactiveObject PathColumn { get; private set; } + + public Columns() + { + FavColumn = new ReactiveObject(); + IconColumn = new ReactiveObject(); + AppColumn = new ReactiveObject(); + DevColumn = new ReactiveObject(); + VersionColumn = new ReactiveObject(); + TimePlayedColumn = new ReactiveObject(); + LastPlayedColumn = new ReactiveObject(); + FileExtColumn = new ReactiveObject(); + FileSizeColumn = new ReactiveObject(); + PathColumn = new ReactiveObject(); + } + } + + /// + /// Used to toggle columns in the GUI + /// + public Columns GuiColumns { get; private set; } + + /// + /// A list of directories containing games to be used to load games into the games list + /// + public ReactiveObject> GameDirs { get; private set; } + + /// + /// Enable or disable custom themes in the GUI + /// + public ReactiveObject EnableCustomTheme { get; private set; } + + /// + /// Path to custom GUI theme + /// + public ReactiveObject CustomThemePath { get; private set; } + + public UiSection() + { + GuiColumns = new Columns(); + GameDirs = new ReactiveObject>(); + EnableCustomTheme = new ReactiveObject(); + CustomThemePath = new ReactiveObject(); + } + } + + /// + /// Logger configuration section + /// + public class LoggerSection + { + /// + /// Enables printing debug log messages + /// + public ReactiveObject EnableDebug { get; private set; } + + /// + /// Enables printing stub log messages + /// + public ReactiveObject EnableStub { get; private set; } + + /// + /// Enables printing info log messages + /// + public ReactiveObject EnableInfo { get; private set; } + + /// + /// Enables printing warning log messages + /// + public ReactiveObject EnableWarn { get; private set; } + + /// + /// Enables printing error log messages + /// + public ReactiveObject EnableError { get; private set; } + + /// + /// Enables printing guest log messages + /// + public ReactiveObject EnableGuest { get; private set; } + + /// + /// Enables printing FS access log messages + /// + public ReactiveObject EnableFsAccessLog { get; private set; } + + /// + /// Controls which log messages are written to the log targets + /// + public ReactiveObject FilteredClasses { get; private set; } + + /// + /// Enables or disables logging to a file on disk + /// + public ReactiveObject EnableFileLog { get; private set; } + + public LoggerSection() + { + EnableDebug = new ReactiveObject(); + EnableStub = new ReactiveObject(); + EnableInfo = new ReactiveObject(); + EnableWarn = new ReactiveObject(); + EnableError = new ReactiveObject(); + EnableGuest = new ReactiveObject(); + EnableFsAccessLog = new ReactiveObject(); + FilteredClasses = new ReactiveObject(); + EnableFileLog = new ReactiveObject(); + } + } + + /// + /// System configuration section + /// + public class SystemSection + { + /// + /// Change System Language + /// + public ReactiveObject Language { get; private set; } + + /// + /// Enables or disables Docked Mode + /// + public ReactiveObject EnableDockedMode { get; private set; } + + /// + /// Enables or disables multi-core scheduling of threads + /// + public ReactiveObject EnableMulticoreScheduling { get; private set; } + + /// + /// Enables integrity checks on Game content files + /// + public ReactiveObject EnableFsIntegrityChecks { get; private set; } + + /// + /// Enables FS access log output to the console. Possible modes are 0-3 + /// + public ReactiveObject FsGlobalAccessLogMode { get; private set; } + + /// + /// Enable or disable ignoring missing services + /// + public ReactiveObject IgnoreMissingServices { get; private set; } + + public SystemSection() + { + Language = new ReactiveObject(); + EnableDockedMode = new ReactiveObject(); + EnableMulticoreScheduling = new ReactiveObject(); + EnableFsIntegrityChecks = new ReactiveObject(); + FsGlobalAccessLogMode = new ReactiveObject(); + IgnoreMissingServices = new ReactiveObject(); + } + } + + /// + /// Hid configuration section + /// + public class HidSection + { + /// + /// The primary controller's type + /// + public ReactiveObject ControllerType { get; private set; } + + /// + /// Enable or disable keyboard support (Independent from controllers binding) + /// + public ReactiveObject EnableKeyboard { get; private set; } + + /// + /// Keyboard control bindings + /// + public ReactiveObject KeyboardControls { get; private set; } + + /// + /// Controller control bindings + /// + public ReactiveObject JoystickControls { get; private set; } + + public HidSection() + { + ControllerType = new ReactiveObject(); + EnableKeyboard = new ReactiveObject(); + KeyboardControls = new ReactiveObject(); + JoystickControls = new ReactiveObject(); + } + } + + /// + /// Graphics configuration section + /// + public class GraphicsSection + { + /// + /// Dumps shaders in this local directory + /// + public ReactiveObject ShadersDumpPath { get; private set; } + + /// + /// Enables or disables Vertical Sync + /// + public ReactiveObject EnableVsync { get; private set; } + + public GraphicsSection() + { + ShadersDumpPath = new ReactiveObject(); + EnableVsync = new ReactiveObject(); + } + } + + /// + /// The default configuration instance + /// + public static ConfigurationState Instance { get; private set; } + + /// + /// The Ui section + /// + public UiSection Ui { get; private set; } + + /// + /// The Logger section + /// + public LoggerSection Logger { get; private set; } + + /// + /// The System section + /// + public SystemSection System { get; private set; } + + /// + /// The Graphics section + /// + public GraphicsSection Graphics { get; private set; } + + /// + /// The Hid section + /// + public HidSection Hid { get; private set; } + + /// + /// Enables or disables Discord Rich Presence + /// + public ReactiveObject EnableDiscordIntegration { get; private set; } + + private ConfigurationState() + { + Ui = new UiSection(); + Logger = new LoggerSection(); + System = new SystemSection(); + Graphics = new GraphicsSection(); + Hid = new HidSection(); + EnableDiscordIntegration = new ReactiveObject(); + } + + public ConfigurationFileFormat ToFileFormat() + { + ConfigurationFileFormat configurationFile = new ConfigurationFileFormat + { + Version = 1, + GraphicsShadersDumpPath = Graphics.ShadersDumpPath, + LoggingEnableDebug = Logger.EnableDebug, + LoggingEnableStub = Logger.EnableStub, + LoggingEnableInfo = Logger.EnableInfo, + LoggingEnableWarn = Logger.EnableWarn, + LoggingEnableError = Logger.EnableError, + LoggingEnableGuest = Logger.EnableGuest, + LoggingEnableFsAccessLog = Logger.EnableFsAccessLog, + LoggingFilteredClasses = Logger.FilteredClasses, + EnableFileLog = Logger.EnableFileLog, + SystemLanguage = System.Language, + DockedMode = System.EnableDockedMode, + EnableDiscordIntegration = EnableDiscordIntegration, + EnableVsync = Graphics.EnableVsync, + EnableMulticoreScheduling = System.EnableMulticoreScheduling, + EnableFsIntegrityChecks = System.EnableFsIntegrityChecks, + FsGlobalAccessLogMode = System.FsGlobalAccessLogMode, + IgnoreMissingServices = System.IgnoreMissingServices, + ControllerType = Hid.ControllerType, + GuiColumns = new GuiColumns() + { + FavColumn = Ui.GuiColumns.FavColumn, + IconColumn = Ui.GuiColumns.IconColumn, + AppColumn = Ui.GuiColumns.AppColumn, + DevColumn = Ui.GuiColumns.DevColumn, + VersionColumn = Ui.GuiColumns.VersionColumn, + TimePlayedColumn = Ui.GuiColumns.TimePlayedColumn, + LastPlayedColumn = Ui.GuiColumns.LastPlayedColumn, + FileExtColumn = Ui.GuiColumns.FileExtColumn, + FileSizeColumn = Ui.GuiColumns.FileSizeColumn, + PathColumn = Ui.GuiColumns.PathColumn, + }, + GameDirs = Ui.GameDirs, + EnableCustomTheme = Ui.EnableCustomTheme, + CustomThemePath = Ui.CustomThemePath, + EnableKeyboard = Hid.EnableKeyboard, + KeyboardControls = Hid.KeyboardControls, + JoystickControls = Hid.JoystickControls + }; + + return configurationFile; + } + + public void LoadDefault() + { + Graphics.ShadersDumpPath.Value = ""; + Logger.EnableDebug.Value = false; + Logger.EnableStub.Value = true; + Logger.EnableInfo.Value = true; + Logger.EnableWarn.Value = true; + Logger.EnableError.Value = true; + Logger.EnableGuest.Value = true; + Logger.EnableFsAccessLog.Value = false; + Logger.FilteredClasses.Value = new LogClass[] { }; + Logger.EnableFileLog.Value = true; + System.Language.Value = Language.AmericanEnglish; + System.EnableDockedMode.Value = false; + EnableDiscordIntegration.Value = true; + Graphics.EnableVsync.Value = true; + System.EnableMulticoreScheduling.Value = true; + System.EnableFsIntegrityChecks.Value = true; + System.FsGlobalAccessLogMode.Value = 0; + System.IgnoreMissingServices.Value = false; + Hid.ControllerType.Value = ControllerType.Handheld; + Ui.GuiColumns.FavColumn.Value = true; + Ui.GuiColumns.IconColumn.Value = true; + Ui.GuiColumns.AppColumn.Value = true; + Ui.GuiColumns.DevColumn.Value = true; + Ui.GuiColumns.VersionColumn.Value = true; + Ui.GuiColumns.TimePlayedColumn.Value = true; + Ui.GuiColumns.LastPlayedColumn.Value = true; + Ui.GuiColumns.FileExtColumn.Value = true; + Ui.GuiColumns.FileSizeColumn.Value = true; + Ui.GuiColumns.PathColumn.Value = true; + Ui.GameDirs.Value = new List(); + Ui.EnableCustomTheme.Value = false; + Ui.CustomThemePath.Value = ""; + Hid.EnableKeyboard.Value = false; + + Hid.KeyboardControls.Value = new NpadKeyboard + { + LeftJoycon = new NpadKeyboardLeft + { + StickUp = Key.W, + StickDown = Key.S, + StickLeft = Key.A, + StickRight = Key.D, + StickButton = Key.F, + DPadUp = Key.Up, + DPadDown = Key.Down, + DPadLeft = Key.Left, + DPadRight = Key.Right, + ButtonMinus = Key.Minus, + ButtonL = Key.E, + ButtonZl = Key.Q, + }, + RightJoycon = new NpadKeyboardRight + { + StickUp = Key.I, + StickDown = Key.K, + StickLeft = Key.J, + StickRight = Key.L, + StickButton = Key.H, + ButtonA = Key.Z, + ButtonB = Key.X, + ButtonX = Key.C, + ButtonY = Key.V, + ButtonPlus = Key.Plus, + ButtonR = Key.U, + ButtonZr = Key.O, + }, + Hotkeys = new KeyboardHotkeys + { + ToggleVsync = Key.Tab + } + }; + + Hid.JoystickControls.Value = new NpadController + { + Enabled = true, + Index = 0, + Deadzone = 0.05f, + TriggerThreshold = 0.5f, + LeftJoycon = new NpadControllerLeft + { + Stick = ControllerInputId.Axis0, + StickButton = ControllerInputId.Button8, + DPadUp = ControllerInputId.Hat0Up, + DPadDown = ControllerInputId.Hat0Down, + DPadLeft = ControllerInputId.Hat0Left, + DPadRight = ControllerInputId.Hat0Right, + ButtonMinus = ControllerInputId.Button6, + ButtonL = ControllerInputId.Button4, + ButtonZl = ControllerInputId.Axis2, + }, + RightJoycon = new NpadControllerRight + { + Stick = ControllerInputId.Axis3, + StickButton = ControllerInputId.Button9, + ButtonA = ControllerInputId.Button1, + ButtonB = ControllerInputId.Button0, + ButtonX = ControllerInputId.Button3, + ButtonY = ControllerInputId.Button2, + ButtonPlus = ControllerInputId.Button7, + ButtonR = ControllerInputId.Button5, + ButtonZr = ControllerInputId.Axis5, + } + }; + } + + public void Load(ConfigurationFileFormat configurationFileFormat) + { + if (configurationFileFormat.Version != 1 && configurationFileFormat.Version != 0) + { + Common.Logging.Logger.PrintWarning(LogClass.Application, $"Unsupported configuration version {configurationFileFormat.Version}, loading default."); + + LoadDefault(); + + return; + } + + Graphics.ShadersDumpPath.Value = configurationFileFormat.GraphicsShadersDumpPath; + Logger.EnableDebug.Value = configurationFileFormat.LoggingEnableDebug; + Logger.EnableStub.Value = configurationFileFormat.LoggingEnableStub; + Logger.EnableInfo.Value = configurationFileFormat.LoggingEnableInfo; + Logger.EnableWarn.Value = configurationFileFormat.LoggingEnableWarn; + Logger.EnableError.Value = configurationFileFormat.LoggingEnableError; + Logger.EnableGuest.Value = configurationFileFormat.LoggingEnableGuest; + Logger.EnableFsAccessLog.Value = configurationFileFormat.LoggingEnableFsAccessLog; + Logger.FilteredClasses.Value = configurationFileFormat.LoggingFilteredClasses; + Logger.EnableFileLog.Value = configurationFileFormat.EnableFileLog; + System.Language.Value = configurationFileFormat.SystemLanguage; + System.EnableDockedMode.Value = configurationFileFormat.DockedMode; + System.EnableDockedMode.Value = configurationFileFormat.DockedMode; + EnableDiscordIntegration.Value = configurationFileFormat.EnableDiscordIntegration; + Graphics.EnableVsync.Value = configurationFileFormat.EnableVsync; + System.EnableMulticoreScheduling.Value = configurationFileFormat.EnableMulticoreScheduling; + System.EnableFsIntegrityChecks.Value = configurationFileFormat.EnableFsIntegrityChecks; + System.FsGlobalAccessLogMode.Value = configurationFileFormat.FsGlobalAccessLogMode; + System.IgnoreMissingServices.Value = configurationFileFormat.IgnoreMissingServices; + Hid.ControllerType.Value = configurationFileFormat.ControllerType; + Ui.GuiColumns.FavColumn.Value = configurationFileFormat.GuiColumns.FavColumn; + Ui.GuiColumns.IconColumn.Value = configurationFileFormat.GuiColumns.IconColumn; + Ui.GuiColumns.AppColumn.Value = configurationFileFormat.GuiColumns.AppColumn; + Ui.GuiColumns.DevColumn.Value = configurationFileFormat.GuiColumns.DevColumn; + Ui.GuiColumns.VersionColumn.Value = configurationFileFormat.GuiColumns.VersionColumn; + Ui.GuiColumns.TimePlayedColumn.Value = configurationFileFormat.GuiColumns.TimePlayedColumn; + Ui.GuiColumns.LastPlayedColumn.Value = configurationFileFormat.GuiColumns.LastPlayedColumn; + Ui.GuiColumns.FileExtColumn.Value = configurationFileFormat.GuiColumns.FileExtColumn; + Ui.GuiColumns.FileSizeColumn.Value = configurationFileFormat.GuiColumns.FileSizeColumn; + Ui.GuiColumns.PathColumn.Value = configurationFileFormat.GuiColumns.PathColumn; + Ui.GameDirs.Value = configurationFileFormat.GameDirs; + Ui.EnableCustomTheme.Value = configurationFileFormat.EnableCustomTheme; + Ui.CustomThemePath.Value = configurationFileFormat.CustomThemePath; + Hid.EnableKeyboard.Value = configurationFileFormat.EnableKeyboard; + Hid.KeyboardControls.Value = configurationFileFormat.KeyboardControls; + Hid.JoystickControls.Value = configurationFileFormat.JoystickControls; + } + + public static void Initialize() + { + if (Instance != null) + { + throw new InvalidOperationException("Configuration is already initialized"); + } + + Instance = new ConfigurationState(); + } + } +} diff --git a/Ryujinx.Common/Configuration/Hid/ControllerInputId.cs b/Ryujinx.Common/Configuration/Hid/ControllerInputId.cs new file mode 100644 index 0000000000..8969b6a4b8 --- /dev/null +++ b/Ryujinx.Common/Configuration/Hid/ControllerInputId.cs @@ -0,0 +1,45 @@ +namespace Ryujinx.Common.Configuration.Hid +{ + public enum ControllerInputId + { + Button0, + Button1, + Button2, + Button3, + Button4, + Button5, + Button6, + Button7, + Button8, + Button9, + Button10, + Button11, + Button12, + Button13, + Button14, + Button15, + Button16, + Button17, + Button18, + Button19, + Button20, + Axis0, + Axis1, + Axis2, + Axis3, + Axis4, + Axis5, + Hat0Up, + Hat0Down, + Hat0Left, + Hat0Right, + Hat1Up, + Hat1Down, + Hat1Left, + Hat1Right, + Hat2Up, + Hat2Down, + Hat2Left, + Hat2Right + } +} diff --git a/Ryujinx.Common/Configuration/Hid/ControllerType.cs b/Ryujinx.Common/Configuration/Hid/ControllerType.cs new file mode 100644 index 0000000000..b0613b2d66 --- /dev/null +++ b/Ryujinx.Common/Configuration/Hid/ControllerType.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.Configuration.Hid +{ + public enum ControllerType + { + ProController, + Handheld, + NpadPair, + NpadLeft, + NpadRight + } +} diff --git a/Ryujinx.Common/Configuration/Hid/Key.cs b/Ryujinx.Common/Configuration/Hid/Key.cs new file mode 100644 index 0000000000..b658396b9b --- /dev/null +++ b/Ryujinx.Common/Configuration/Hid/Key.cs @@ -0,0 +1,153 @@ +namespace Ryujinx.Configuration.Hid +{ + public enum Key + { + Unknown = 0, + ShiftLeft = 1, + LShift = 1, + ShiftRight = 2, + RShift = 2, + ControlLeft = 3, + LControl = 3, + ControlRight = 4, + RControl = 4, + AltLeft = 5, + LAlt = 5, + AltRight = 6, + RAlt = 6, + WinLeft = 7, + LWin = 7, + WinRight = 8, + RWin = 8, + Menu = 9, + F1 = 10, + F2 = 11, + F3 = 12, + F4 = 13, + F5 = 14, + F6 = 15, + F7 = 16, + F8 = 17, + F9 = 18, + F10 = 19, + F11 = 20, + F12 = 21, + F13 = 22, + F14 = 23, + F15 = 24, + F16 = 25, + F17 = 26, + F18 = 27, + F19 = 28, + F20 = 29, + F21 = 30, + F22 = 31, + F23 = 32, + F24 = 33, + F25 = 34, + F26 = 35, + F27 = 36, + F28 = 37, + F29 = 38, + F30 = 39, + F31 = 40, + F32 = 41, + F33 = 42, + F34 = 43, + F35 = 44, + Up = 45, + Down = 46, + Left = 47, + Right = 48, + Enter = 49, + Escape = 50, + Space = 51, + Tab = 52, + BackSpace = 53, + Back = 53, + Insert = 54, + Delete = 55, + PageUp = 56, + PageDown = 57, + Home = 58, + End = 59, + CapsLock = 60, + ScrollLock = 61, + PrintScreen = 62, + Pause = 63, + NumLock = 64, + Clear = 65, + Sleep = 66, + Keypad0 = 67, + Keypad1 = 68, + Keypad2 = 69, + Keypad3 = 70, + Keypad4 = 71, + Keypad5 = 72, + Keypad6 = 73, + Keypad7 = 74, + Keypad8 = 75, + Keypad9 = 76, + KeypadDivide = 77, + KeypadMultiply = 78, + KeypadSubtract = 79, + KeypadMinus = 79, + KeypadAdd = 80, + KeypadPlus = 80, + KeypadDecimal = 81, + KeypadPeriod = 81, + KeypadEnter = 82, + A = 83, + B = 84, + C = 85, + D = 86, + E = 87, + F = 88, + G = 89, + H = 90, + I = 91, + J = 92, + K = 93, + L = 94, + M = 95, + N = 96, + O = 97, + P = 98, + Q = 99, + R = 100, + S = 101, + T = 102, + U = 103, + V = 104, + W = 105, + X = 106, + Y = 107, + Z = 108, + Number0 = 109, + Number1 = 110, + Number2 = 111, + Number3 = 112, + Number4 = 113, + Number5 = 114, + Number6 = 115, + Number7 = 116, + Number8 = 117, + Number9 = 118, + Tilde = 119, + Grave = 119, + Minus = 120, + Plus = 121, + BracketLeft = 122, + LBracket = 122, + BracketRight = 123, + RBracket = 123, + Semicolon = 124, + Quote = 125, + Comma = 126, + Period = 127, + Slash = 128, + BackSlash = 129, + NonUSBackSlash = 130, + LastKey = 131 + } +} diff --git a/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs b/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs new file mode 100644 index 0000000000..1d0b050492 --- /dev/null +++ b/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.Configuration.Hid +{ + public struct KeyboardHotkeys + { + public Key ToggleVsync; + } +} diff --git a/Ryujinx.Common/Configuration/Hid/NpadController.cs b/Ryujinx.Common/Configuration/Hid/NpadController.cs new file mode 100644 index 0000000000..f00865d556 --- /dev/null +++ b/Ryujinx.Common/Configuration/Hid/NpadController.cs @@ -0,0 +1,35 @@ +namespace Ryujinx.Common.Configuration.Hid +{ + public class NpadController + { + /// + /// Enables or disables controller support + /// + public bool Enabled; + + /// + /// Controller Device Index + /// + public int Index; + + /// + /// Controller Analog Stick Deadzone + /// + public float Deadzone; + + /// + /// Controller Trigger Threshold + /// + public float TriggerThreshold; + + /// + /// Left JoyCon Controller Bindings + /// + public NpadControllerLeft LeftJoycon; + + /// + /// Right JoyCon Controller Bindings + /// + public NpadControllerRight RightJoycon; + } +} diff --git a/Ryujinx.Common/Configuration/Hid/NpadControllerLeft.cs b/Ryujinx.Common/Configuration/Hid/NpadControllerLeft.cs new file mode 100644 index 0000000000..54ac0f03ae --- /dev/null +++ b/Ryujinx.Common/Configuration/Hid/NpadControllerLeft.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.Common.Configuration.Hid +{ + public struct NpadControllerLeft + { + public ControllerInputId Stick; + public ControllerInputId StickButton; + public ControllerInputId ButtonMinus; + public ControllerInputId ButtonL; + public ControllerInputId ButtonZl; + public ControllerInputId DPadUp; + public ControllerInputId DPadDown; + public ControllerInputId DPadLeft; + public ControllerInputId DPadRight; + } +} diff --git a/Ryujinx.Common/Configuration/Hid/NpadControllerRight.cs b/Ryujinx.Common/Configuration/Hid/NpadControllerRight.cs new file mode 100644 index 0000000000..315136d9f6 --- /dev/null +++ b/Ryujinx.Common/Configuration/Hid/NpadControllerRight.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.Common.Configuration.Hid +{ + public struct NpadControllerRight + { + public ControllerInputId Stick; + public ControllerInputId StickButton; + public ControllerInputId ButtonA; + public ControllerInputId ButtonB; + public ControllerInputId ButtonX; + public ControllerInputId ButtonY; + public ControllerInputId ButtonPlus; + public ControllerInputId ButtonR; + public ControllerInputId ButtonZr; + } +} diff --git a/Ryujinx.Common/Configuration/Hid/NpadKeyboard.cs b/Ryujinx.Common/Configuration/Hid/NpadKeyboard.cs new file mode 100644 index 0000000000..911f5119ea --- /dev/null +++ b/Ryujinx.Common/Configuration/Hid/NpadKeyboard.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.UI.Input +{ + public class NpadKeyboard + { + /// + /// Left JoyCon Keyboard Bindings + /// + public Configuration.Hid.NpadKeyboardLeft LeftJoycon; + + /// + /// Right JoyCon Keyboard Bindings + /// + public Configuration.Hid.NpadKeyboardRight RightJoycon; + + /// + /// Hotkey Keyboard Bindings + /// + public Configuration.Hid.KeyboardHotkeys Hotkeys; + } +} diff --git a/Ryujinx.Common/Configuration/Hid/NpadKeyboardLeft.cs b/Ryujinx.Common/Configuration/Hid/NpadKeyboardLeft.cs new file mode 100644 index 0000000000..799cdfdb8a --- /dev/null +++ b/Ryujinx.Common/Configuration/Hid/NpadKeyboardLeft.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Configuration.Hid +{ + public struct NpadKeyboardLeft + { + public Key StickUp; + public Key StickDown; + public Key StickLeft; + public Key StickRight; + public Key StickButton; + public Key DPadUp; + public Key DPadDown; + public Key DPadLeft; + public Key DPadRight; + public Key ButtonMinus; + public Key ButtonL; + public Key ButtonZl; + } +} diff --git a/Ryujinx.Common/Configuration/Hid/NpadKeyboardRight.cs b/Ryujinx.Common/Configuration/Hid/NpadKeyboardRight.cs new file mode 100644 index 0000000000..311504bb7e --- /dev/null +++ b/Ryujinx.Common/Configuration/Hid/NpadKeyboardRight.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Configuration.Hid +{ + public struct NpadKeyboardRight + { + public Key StickUp; + public Key StickDown; + public Key StickLeft; + public Key StickRight; + public Key StickButton; + public Key ButtonA; + public Key ButtonB; + public Key ButtonX; + public Key ButtonY; + public Key ButtonPlus; + public Key ButtonR; + public Key ButtonZr; + } +} diff --git a/Ryujinx.Common/Configuration/LoggerModule.cs b/Ryujinx.Common/Configuration/LoggerModule.cs new file mode 100644 index 0000000000..504a81418f --- /dev/null +++ b/Ryujinx.Common/Configuration/LoggerModule.cs @@ -0,0 +1,109 @@ +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using System; +using System.IO; + +namespace Ryujinx.Configuration +{ + public static class LoggerModule + { + public static void Initialize() + { + AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; + AppDomain.CurrentDomain.ProcessExit += CurrentDomain_ProcessExit; + + ConfigurationState.Instance.Logger.EnableDebug.Event += ReloadEnableDebug; + ConfigurationState.Instance.Logger.EnableStub.Event += ReloadEnableStub; + ConfigurationState.Instance.Logger.EnableInfo.Event += ReloadEnableInfo; + ConfigurationState.Instance.Logger.EnableWarn.Event += ReloadEnableWarning; + ConfigurationState.Instance.Logger.EnableError.Event += ReloadEnableError; + ConfigurationState.Instance.Logger.EnableGuest.Event += ReloadEnableGuest; + ConfigurationState.Instance.Logger.EnableFsAccessLog.Event += ReloadEnableFsAccessLog; + ConfigurationState.Instance.Logger.FilteredClasses.Event += ReloadFilteredClasses; + ConfigurationState.Instance.Logger.EnableFileLog.Event += ReloadFileLogger; + } + + private static void ReloadEnableDebug(object sender, ReactiveEventArgs e) + { + Logger.SetEnable(LogLevel.Debug, e.NewValue); + } + + private static void ReloadEnableStub(object sender, ReactiveEventArgs e) + { + Logger.SetEnable(LogLevel.Stub, e.NewValue); + } + + private static void ReloadEnableInfo(object sender, ReactiveEventArgs e) + { + Logger.SetEnable(LogLevel.Info, e.NewValue); + } + + private static void ReloadEnableWarning(object sender, ReactiveEventArgs e) + { + Logger.SetEnable(LogLevel.Warning, e.NewValue); + } + + private static void ReloadEnableError(object sender, ReactiveEventArgs e) + { + Logger.SetEnable(LogLevel.Error, e.NewValue); + } + + private static void ReloadEnableGuest(object sender, ReactiveEventArgs e) + { + Logger.SetEnable(LogLevel.Guest, e.NewValue); + } + + private static void ReloadEnableFsAccessLog(object sender, ReactiveEventArgs e) + { + Logger.SetEnable(LogLevel.AccessLog, e.NewValue); + } + + private static void ReloadFilteredClasses(object sender, ReactiveEventArgs e) + { + bool noFilter = e.NewValue.Length == 0; + + foreach (var logClass in EnumExtensions.GetValues()) + { + Logger.SetEnable(logClass, noFilter); + } + + foreach (var logClass in e.NewValue) + { + Logger.SetEnable(logClass, true); + } + } + + private static void ReloadFileLogger(object sender, ReactiveEventArgs e) + { + if (e.NewValue) + { + Logger.AddTarget(new AsyncLogTargetWrapper( + new FileLogTarget(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Ryujinx.log"), "file"), + 1000, + AsyncLogTargetOverflowAction.Block + )); + } + else + { + Logger.RemoveTarget("file"); + } + } + + private static void CurrentDomain_ProcessExit(object sender, EventArgs e) + { + Logger.Shutdown(); + } + + private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) + { + var exception = e.ExceptionObject as Exception; + + Logger.PrintError(LogClass.Emulation, $"Unhandled exception caught: {exception}"); + + if (e.IsTerminating) + { + Logger.Shutdown(); + } + } + } +} diff --git a/Ryujinx.Common/Configuration/System/Language.cs b/Ryujinx.Common/Configuration/System/Language.cs new file mode 100644 index 0000000000..d3af296ba9 --- /dev/null +++ b/Ryujinx.Common/Configuration/System/Language.cs @@ -0,0 +1,23 @@ +namespace Ryujinx.Configuration.System +{ + public enum Language + { + Japanese, + AmericanEnglish, + French, + German, + Italian, + Spanish, + Chinese, + Korean, + Dutch, + Portuguese, + Russian, + Taiwanese, + BritishEnglish, + CanadianFrench, + LatinAmericanSpanish, + SimplifiedChinese, + TraditionalChinese + } +} diff --git a/Ryujinx.Common/Configuration/Ui/GuiColumns.cs b/Ryujinx.Common/Configuration/Ui/GuiColumns.cs new file mode 100644 index 0000000000..2b3524aa82 --- /dev/null +++ b/Ryujinx.Common/Configuration/Ui/GuiColumns.cs @@ -0,0 +1,16 @@ +namespace Ryujinx.Configuration.Ui +{ + public struct GuiColumns + { + public bool FavColumn; + public bool IconColumn; + public bool AppColumn; + public bool DevColumn; + public bool VersionColumn; + public bool TimePlayedColumn; + public bool LastPlayedColumn; + public bool FileExtColumn; + public bool FileSizeColumn; + public bool PathColumn; + } +} diff --git a/Ryujinx.Common/Extensions/BinaryReaderExtensions.cs b/Ryujinx.Common/Extensions/BinaryReaderExtensions.cs new file mode 100644 index 0000000000..05c77fe9a2 --- /dev/null +++ b/Ryujinx.Common/Extensions/BinaryReaderExtensions.cs @@ -0,0 +1,57 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; + +namespace Ryujinx.Common +{ + public static class BinaryReaderExtensions + { + public unsafe static T ReadStruct(this BinaryReader reader) + where T : struct + { + int size = Marshal.SizeOf(); + + byte[] data = reader.ReadBytes(size); + + fixed (byte* ptr = data) + { + return Marshal.PtrToStructure((IntPtr)ptr); + } + } + + public unsafe static T[] ReadStructArray(this BinaryReader reader, int count) + where T : struct + { + int size = Marshal.SizeOf(); + + T[] result = new T[count]; + + for (int i = 0; i < count; i++) + { + byte[] data = reader.ReadBytes(size); + + fixed (byte* ptr = data) + { + result[i] = Marshal.PtrToStructure((IntPtr)ptr); + } + } + + return result; + } + + public unsafe static void WriteStruct(this BinaryWriter writer, T value) + where T : struct + { + long size = Marshal.SizeOf(); + + byte[] data = new byte[size]; + + fixed (byte* ptr = data) + { + Marshal.StructureToPtr(value, (IntPtr)ptr, false); + } + + writer.Write(data); + } + } +} diff --git a/Ryujinx.Common/Extensions/EnumExtensions.cs b/Ryujinx.Common/Extensions/EnumExtensions.cs new file mode 100644 index 0000000000..560af88296 --- /dev/null +++ b/Ryujinx.Common/Extensions/EnumExtensions.cs @@ -0,0 +1,12 @@ +using System; + +namespace Ryujinx.Common +{ + public static class EnumExtensions + { + public static T[] GetValues() + { + return (T[])Enum.GetValues(typeof(T)); + } + } +} diff --git a/Ryujinx.Common/Logging/Formatters/DefaultLogFormatter.cs b/Ryujinx.Common/Logging/Formatters/DefaultLogFormatter.cs new file mode 100644 index 0000000000..73b0e2b6ac --- /dev/null +++ b/Ryujinx.Common/Logging/Formatters/DefaultLogFormatter.cs @@ -0,0 +1,75 @@ +using System; +using System.Reflection; +using System.Text; + +namespace Ryujinx.Common.Logging +{ + internal class DefaultLogFormatter : ILogFormatter + { + private static readonly ObjectPool _stringBuilderPool = SharedPools.Default(); + + public string Format(LogEventArgs args) + { + StringBuilder sb = _stringBuilderPool.Allocate(); + + try + { + sb.Clear(); + + sb.AppendFormat(@"{0:hh\:mm\:ss\.fff}", args.Time); + sb.Append(" | "); + + if (args.ThreadName != null) + { + sb.Append(args.ThreadName); + sb.Append(' '); + } + + sb.Append(args.Message); + + if (args.Data != null) + { + PropertyInfo[] props = args.Data.GetType().GetProperties(); + + sb.Append(' '); + + foreach (var prop in props) + { + sb.Append(prop.Name); + sb.Append(": "); + + if (typeof(Array).IsAssignableFrom(prop.PropertyType)) + { + Array enumerable = (Array)prop.GetValue(args.Data); + foreach (var item in enumerable) + { + sb.Append(item.ToString()); + sb.Append(", "); + } + + sb.Remove(sb.Length - 2, 2); + } + else + { + sb.Append(prop.GetValue(args.Data)); + } + + sb.Append(" - "); + } + + // We remove the final '-' from the string + if (props.Length > 0) + { + sb.Remove(sb.Length - 3, 3); + } + } + + return sb.ToString(); + } + finally + { + _stringBuilderPool.Release(sb); + } + } + } +} diff --git a/Ryujinx.Common/Logging/Formatters/ILogFormatter.cs b/Ryujinx.Common/Logging/Formatters/ILogFormatter.cs new file mode 100644 index 0000000000..9a55bc6b0d --- /dev/null +++ b/Ryujinx.Common/Logging/Formatters/ILogFormatter.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.Common.Logging +{ + interface ILogFormatter + { + string Format(LogEventArgs args); + } +} diff --git a/Ryujinx.HLE/Logging/LogClass.cs b/Ryujinx.Common/Logging/LogClass.cs similarity index 77% rename from Ryujinx.HLE/Logging/LogClass.cs rename to Ryujinx.Common/Logging/LogClass.cs index c377ace662..43efd8d58b 100644 --- a/Ryujinx.HLE/Logging/LogClass.cs +++ b/Ryujinx.Common/Logging/LogClass.cs @@ -1,9 +1,12 @@ -namespace Ryujinx.HLE.Logging +namespace Ryujinx.Common.Logging { public enum LogClass { + Application, Audio, Cpu, + Font, + Emulation, Gpu, Hid, Kernel, @@ -17,19 +20,25 @@ namespace Ryujinx.HLE.Logging ServiceApm, ServiceAudio, ServiceBsd, + ServiceBtm, ServiceCaps, ServiceFriend, ServiceFs, ServiceHid, + ServiceIrs, + ServiceLdn, + ServiceLdr, ServiceLm, ServiceMm, ServiceNfp, ServiceNifm, ServiceNs, + ServiceNsd, ServiceNv, ServicePctl, ServicePl, ServicePrepo, + ServicePsm, ServiceSet, ServiceSfdnsres, ServiceSm, @@ -38,4 +47,4 @@ namespace Ryujinx.HLE.Logging ServiceTime, ServiceVi } -} +} \ No newline at end of file diff --git a/Ryujinx.Common/Logging/LogEventArgs.cs b/Ryujinx.Common/Logging/LogEventArgs.cs new file mode 100644 index 0000000000..af33463240 --- /dev/null +++ b/Ryujinx.Common/Logging/LogEventArgs.cs @@ -0,0 +1,31 @@ +using System; + +namespace Ryujinx.Common.Logging +{ + public class LogEventArgs : EventArgs + { + public LogLevel Level { get; private set; } + public TimeSpan Time { get; private set; } + public string ThreadName { get; private set; } + + public string Message { get; private set; } + public object Data { get; private set; } + + public LogEventArgs(LogLevel level, TimeSpan time, string threadName, string message) + { + Level = level; + Time = time; + ThreadName = threadName; + Message = message; + } + + public LogEventArgs(LogLevel level, TimeSpan time, string threadName, string message, object data) + { + Level = level; + Time = time; + ThreadName = threadName; + Message = message; + Data = data; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/Logging/LogLevel.cs b/Ryujinx.Common/Logging/LogLevel.cs similarity index 55% rename from Ryujinx.HLE/Logging/LogLevel.cs rename to Ryujinx.Common/Logging/LogLevel.cs index 971333e60a..5f80714f1a 100644 --- a/Ryujinx.HLE/Logging/LogLevel.cs +++ b/Ryujinx.Common/Logging/LogLevel.cs @@ -1,4 +1,4 @@ -namespace Ryujinx.HLE.Logging +namespace Ryujinx.Common.Logging { public enum LogLevel { @@ -6,6 +6,8 @@ namespace Ryujinx.HLE.Logging Stub, Info, Warning, - Error + Error, + Guest, + AccessLog } } diff --git a/Ryujinx.Common/Logging/Logger.cs b/Ryujinx.Common/Logging/Logger.cs new file mode 100644 index 0000000000..e3d82201d4 --- /dev/null +++ b/Ryujinx.Common/Logging/Logger.cs @@ -0,0 +1,175 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Threading; + +namespace Ryujinx.Common.Logging +{ + public static class Logger + { + private static Stopwatch m_Time; + + private static readonly bool[] m_EnabledLevels; + private static readonly bool[] m_EnabledClasses; + + private static readonly List m_LogTargets; + + public static event EventHandler Updated; + + static Logger() + { + m_EnabledLevels = new bool[Enum.GetNames(typeof(LogLevel)).Length]; + m_EnabledClasses = new bool[Enum.GetNames(typeof(LogClass)).Length]; + + m_EnabledLevels[(int)LogLevel.Stub] = true; + m_EnabledLevels[(int)LogLevel.Info] = true; + m_EnabledLevels[(int)LogLevel.Warning] = true; + m_EnabledLevels[(int)LogLevel.Error] = true; + m_EnabledLevels[(int)LogLevel.Guest] = true; + m_EnabledLevels[(int)LogLevel.AccessLog] = true; + + for (int index = 0; index < m_EnabledClasses.Length; index++) + { + m_EnabledClasses[index] = true; + } + + m_LogTargets = new List(); + + m_Time = Stopwatch.StartNew(); + + // Logger should log to console by default + AddTarget(new AsyncLogTargetWrapper( + new ConsoleLogTarget("console"), + 1000, + AsyncLogTargetOverflowAction.Block)); + } + + public static void RestartTime() + { + m_Time.Restart(); + } + + private static ILogTarget GetTarget(string targetName) + { + foreach (var target in m_LogTargets) + { + if (target.Name.Equals(targetName)) + { + return target; + } + } + + return null; + } + + public static void AddTarget(ILogTarget target) + { + m_LogTargets.Add(target); + + Updated += target.Log; + } + + public static void RemoveTarget(string target) + { + ILogTarget logTarget = GetTarget(target); + + if (logTarget != null) + { + Updated -= logTarget.Log; + + m_LogTargets.Remove(logTarget); + + logTarget.Dispose(); + } + } + + public static void Shutdown() + { + Updated = null; + + foreach(var target in m_LogTargets) + { + target.Dispose(); + } + + m_LogTargets.Clear(); + } + + public static void SetEnable(LogLevel logLevel, bool enabled) + { + m_EnabledLevels[(int)logLevel] = enabled; + } + + public static void SetEnable(LogClass logClass, bool enabled) + { + m_EnabledClasses[(int)logClass] = enabled; + } + + public static void PrintDebug(LogClass logClass, string message, [CallerMemberName] string caller = "") + { + Print(LogLevel.Debug, logClass, GetFormattedMessage(logClass, message, caller)); + } + + public static void PrintInfo(LogClass logClass, string message, [CallerMemberName] string Caller = "") + { + Print(LogLevel.Info, logClass, GetFormattedMessage(logClass, message, Caller)); + } + + public static void PrintWarning(LogClass logClass, string message, [CallerMemberName] string Caller = "") + { + Print(LogLevel.Warning, logClass, GetFormattedMessage(logClass, message, Caller)); + } + + public static void PrintError(LogClass logClass, string message, [CallerMemberName] string Caller = "") + { + Print(LogLevel.Error, logClass, GetFormattedMessage(logClass, message, Caller)); + } + + public static void PrintStub(LogClass logClass, string message = "", [CallerMemberName] string caller = "") + { + Print(LogLevel.Stub, logClass, GetFormattedMessage(logClass, "Stubbed. " + message, caller)); + } + + public static void PrintStub(LogClass logClass, T obj, [CallerMemberName] string caller = "") + { + Print(LogLevel.Stub, logClass, GetFormattedMessage(logClass, "Stubbed.", caller), obj); + } + + public static void PrintStub(LogClass logClass, string message, T obj, [CallerMemberName] string caller = "") + { + Print(LogLevel.Stub, logClass, GetFormattedMessage(logClass, "Stubbed. " + message, caller), obj); + } + + public static void PrintGuest(LogClass logClass, string message, [CallerMemberName] string caller = "") + { + Print(LogLevel.Guest, logClass, GetFormattedMessage(logClass, message, caller)); + } + + public static void PrintAccessLog(LogClass logClass, string message) + { + Print(LogLevel.AccessLog, logClass, message); + } + + private static void Print(LogLevel logLevel, LogClass logClass, string message) + { + if (m_EnabledLevels[(int)logLevel] && m_EnabledClasses[(int)logClass]) + { + Updated?.Invoke(null, new LogEventArgs(logLevel, m_Time.Elapsed, Thread.CurrentThread.Name, message)); + } + } + + private static void Print(LogLevel logLevel, LogClass logClass, string message, object data) + { + if (m_EnabledLevels[(int)logLevel] && m_EnabledClasses[(int)logClass]) + { + Updated?.Invoke(null, new LogEventArgs(logLevel, m_Time.Elapsed, Thread.CurrentThread.Name, message, data)); + } + } + + private static string GetFormattedMessage(LogClass Class, string Message, string Caller) + { + return $"{Class} {Caller}: {Message}"; + } + } +} diff --git a/Ryujinx.Common/Logging/Targets/AsyncLogTargetWrapper.cs b/Ryujinx.Common/Logging/Targets/AsyncLogTargetWrapper.cs new file mode 100644 index 0000000000..43c62d319c --- /dev/null +++ b/Ryujinx.Common/Logging/Targets/AsyncLogTargetWrapper.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Concurrent; +using System.Threading; + +namespace Ryujinx.Common.Logging +{ + public enum AsyncLogTargetOverflowAction + { + /// + /// Block until there's more room in the queue + /// + Block = 0, + + /// + /// Discard the overflowing item + /// + Discard = 1 + } + + public class AsyncLogTargetWrapper : ILogTarget + { + private ILogTarget _target; + + private Thread _messageThread; + + private BlockingCollection _messageQueue; + + private readonly int _overflowTimeout; + + string ILogTarget.Name { get => _target.Name; } + + public AsyncLogTargetWrapper(ILogTarget target) + : this(target, -1, AsyncLogTargetOverflowAction.Block) + { } + + public AsyncLogTargetWrapper(ILogTarget target, int queueLimit, AsyncLogTargetOverflowAction overflowAction) + { + _target = target; + _messageQueue = new BlockingCollection(queueLimit); + _overflowTimeout = overflowAction == AsyncLogTargetOverflowAction.Block ? -1 : 0; + + _messageThread = new Thread(() => { + while (!_messageQueue.IsCompleted) + { + try + { + _target.Log(this, _messageQueue.Take()); + } + catch (InvalidOperationException) + { + // IOE means that Take() was called on a completed collection. + // Some other thread can call CompleteAdding after we pass the + // IsCompleted check but before we call Take. + // We can simply catch the exception since the loop will break + // on the next iteration. + } + } + }); + + _messageThread.Name = "Logger.MessageThread"; + _messageThread.IsBackground = true; + _messageThread.Start(); + } + + public void Log(object sender, LogEventArgs e) + { + if (!_messageQueue.IsAddingCompleted) + { + _messageQueue.TryAdd(e, _overflowTimeout); + } + } + + public void Dispose() + { + _messageQueue.CompleteAdding(); + _messageThread.Join(); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Common/Logging/Targets/ConsoleLogTarget.cs b/Ryujinx.Common/Logging/Targets/ConsoleLogTarget.cs new file mode 100644 index 0000000000..ff5c6f5acb --- /dev/null +++ b/Ryujinx.Common/Logging/Targets/ConsoleLogTarget.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Concurrent; + +namespace Ryujinx.Common.Logging +{ + public class ConsoleLogTarget : ILogTarget + { + private static readonly ConcurrentDictionary _logColors; + + private readonly ILogFormatter _formatter; + + private readonly string _name; + + string ILogTarget.Name { get => _name; } + + static ConsoleLogTarget() + { + _logColors = new ConcurrentDictionary { + [ LogLevel.Stub ] = ConsoleColor.DarkGray, + [ LogLevel.Info ] = ConsoleColor.White, + [ LogLevel.Warning ] = ConsoleColor.Yellow, + [ LogLevel.Error ] = ConsoleColor.Red + }; + } + + public ConsoleLogTarget(string name) + { + _formatter = new DefaultLogFormatter(); + _name = name; + } + + public void Log(object sender, LogEventArgs args) + { + if (_logColors.TryGetValue(args.Level, out ConsoleColor color)) + { + Console.ForegroundColor = color; + + Console.WriteLine(_formatter.Format(args)); + + Console.ResetColor(); + } + else + { + Console.WriteLine(_formatter.Format(args)); + } + } + + public void Dispose() + { + Console.ResetColor(); + } + } +} diff --git a/Ryujinx.Common/Logging/Targets/FileLogTarget.cs b/Ryujinx.Common/Logging/Targets/FileLogTarget.cs new file mode 100644 index 0000000000..4db5f7bce5 --- /dev/null +++ b/Ryujinx.Common/Logging/Targets/FileLogTarget.cs @@ -0,0 +1,41 @@ +using System; +using System.IO; +using System.Text; + +namespace Ryujinx.Common.Logging +{ + public class FileLogTarget : ILogTarget + { + private static readonly ObjectPool _stringBuilderPool = SharedPools.Default(); + + private readonly StreamWriter _logWriter; + private readonly ILogFormatter _formatter; + private readonly string _name; + + string ILogTarget.Name { get => _name; } + + public FileLogTarget(string path, string name) + : this(path, name, FileShare.Read, FileMode.Append) + { } + + public FileLogTarget(string path, string name, FileShare fileShare, FileMode fileMode) + { + _name = name; + _logWriter = new StreamWriter(File.Open(path, fileMode, FileAccess.Write, fileShare)); + _formatter = new DefaultLogFormatter(); + } + + public void Log(object sender, LogEventArgs args) + { + _logWriter.WriteLine(_formatter.Format(args)); + _logWriter.Flush(); + } + + public void Dispose() + { + _logWriter.WriteLine("---- End of Log ----"); + _logWriter.Flush(); + _logWriter.Dispose(); + } + } +} diff --git a/Ryujinx.Common/Logging/Targets/ILogTarget.cs b/Ryujinx.Common/Logging/Targets/ILogTarget.cs new file mode 100644 index 0000000000..d4d26a936d --- /dev/null +++ b/Ryujinx.Common/Logging/Targets/ILogTarget.cs @@ -0,0 +1,11 @@ +using System; + +namespace Ryujinx.Common.Logging +{ + public interface ILogTarget : IDisposable + { + void Log(object sender, LogEventArgs args); + + string Name { get; } + } +} diff --git a/Ryujinx.Common/Logging/Targets/JsonLogTarget.cs b/Ryujinx.Common/Logging/Targets/JsonLogTarget.cs new file mode 100644 index 0000000000..3729b18d13 --- /dev/null +++ b/Ryujinx.Common/Logging/Targets/JsonLogTarget.cs @@ -0,0 +1,40 @@ +using System; +using System.IO; +using Utf8Json; + +namespace Ryujinx.Common.Logging +{ + public class JsonLogTarget : ILogTarget + { + private Stream _stream; + private bool _leaveOpen; + private string _name; + + string ILogTarget.Name { get => _name; } + + public JsonLogTarget(Stream stream, string name) + { + _stream = stream; + _name = name; + } + + public JsonLogTarget(Stream stream, bool leaveOpen) + { + _stream = stream; + _leaveOpen = leaveOpen; + } + + public void Log(object sender, LogEventArgs e) + { + JsonSerializer.Serialize(_stream, e); + } + + public void Dispose() + { + if (!_leaveOpen) + { + _stream.Dispose(); + } + } + } +} diff --git a/Ryujinx.Common/PerformanceCounter.cs b/Ryujinx.Common/PerformanceCounter.cs new file mode 100644 index 0000000000..1c2782e34b --- /dev/null +++ b/Ryujinx.Common/PerformanceCounter.cs @@ -0,0 +1,82 @@ +using System.Diagnostics; + +namespace Ryujinx.Common +{ + public static class PerformanceCounter + { + private static double _ticksToNs; + + /// + /// Represents the number of ticks in 1 day. + /// + public static long TicksPerDay { get; } + + /// + /// Represents the number of ticks in 1 hour. + /// + public static long TicksPerHour { get; } + + /// + /// Represents the number of ticks in 1 minute. + /// + public static long TicksPerMinute { get; } + + /// + /// Represents the number of ticks in 1 second. + /// + public static long TicksPerSecond { get; } + + /// + /// Represents the number of ticks in 1 millisecond. + /// + public static long TicksPerMillisecond { get; } + + /// + /// Gets the number of milliseconds elapsed since the system started. + /// + public static long ElapsedTicks + { + get + { + return Stopwatch.GetTimestamp(); + } + } + + /// + /// Gets the number of milliseconds elapsed since the system started. + /// + public static long ElapsedMilliseconds + { + get + { + long timestamp = Stopwatch.GetTimestamp(); + + return timestamp / TicksPerMillisecond; + } + } + + /// + /// Gets the number of nanoseconds elapsed since the system started. + /// + public static long ElapsedNanoseconds + { + get + { + long timestamp = Stopwatch.GetTimestamp(); + + return (long)(timestamp * _ticksToNs); + } + } + + static PerformanceCounter() + { + TicksPerMillisecond = Stopwatch.Frequency / 1000; + TicksPerSecond = Stopwatch.Frequency; + TicksPerMinute = TicksPerSecond * 60; + TicksPerHour = TicksPerMinute * 60; + TicksPerDay = TicksPerHour * 24; + + _ticksToNs = 1000000000.0 / (double)Stopwatch.Frequency; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Common/Pools/ObjectPool.cs b/Ryujinx.Common/Pools/ObjectPool.cs new file mode 100644 index 0000000000..e0bf5df60b --- /dev/null +++ b/Ryujinx.Common/Pools/ObjectPool.cs @@ -0,0 +1,75 @@ +using System; +using System.Threading; + +namespace Ryujinx.Common +{ + public class ObjectPool + where T : class + { + private T _firstItem; + private readonly T[] _items; + + private readonly Func _factory; + + public ObjectPool(Func factory, int size) + { + _items = new T[size - 1]; + _factory = factory; + } + + public T Allocate() + { + T instance = _firstItem; + + if (instance == null || instance != Interlocked.CompareExchange(ref _firstItem, null, instance)) + { + instance = AllocateInternal(); + } + + return instance; + } + + private T AllocateInternal() + { + T[] items = _items; + + for (int i = 0; i < items.Length; i++) + { + T instance = items[i]; + + if (instance != null && instance == Interlocked.CompareExchange(ref items[i], null, instance)) + { + return instance; + } + } + + return _factory(); + } + + public void Release(T obj) + { + if (_firstItem == null) + { + _firstItem = obj; + } + else + { + ReleaseInternal(obj); + } + } + + private void ReleaseInternal(T obj) + { + T[] items = _items; + + for (int i = 0; i < items.Length; i++) + { + if (items[i] == null) + { + items[i] = obj; + break; + } + } + } + } +} diff --git a/Ryujinx.Common/Pools/SharedPools.cs b/Ryujinx.Common/Pools/SharedPools.cs new file mode 100644 index 0000000000..b4860b85b6 --- /dev/null +++ b/Ryujinx.Common/Pools/SharedPools.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.Common +{ + public static class SharedPools + { + private static class DefaultPool + where T : class, new() + { + public static readonly ObjectPool Instance = new ObjectPool(() => new T(), 20); + } + + public static ObjectPool Default() + where T : class, new() + { + return DefaultPool.Instance; + } + } +} diff --git a/Ryujinx.Common/ReactiveObject.cs b/Ryujinx.Common/ReactiveObject.cs new file mode 100644 index 0000000000..8495c78f56 --- /dev/null +++ b/Ryujinx.Common/ReactiveObject.cs @@ -0,0 +1,61 @@ +using System; +using System.Threading; + +namespace Ryujinx.Common +{ + public class ReactiveObject + { + private ReaderWriterLock _readerWriterLock = new ReaderWriterLock(); + private bool _isInitialized = false; + private T _value; + + public event EventHandler> Event; + + public T Value + { + get + { + _readerWriterLock.AcquireReaderLock(Timeout.Infinite); + T value = _value; + _readerWriterLock.ReleaseReaderLock(); + + return value; + } + set + { + _readerWriterLock.AcquireWriterLock(Timeout.Infinite); + + T oldValue = _value; + + bool oldIsInitialized = _isInitialized; + + _isInitialized = true; + _value = value; + + _readerWriterLock.ReleaseWriterLock(); + + if (!oldIsInitialized || !oldValue.Equals(_value)) + { + Event?.Invoke(this, new ReactiveEventArgs(oldValue, value)); + } + } + } + + public static implicit operator T(ReactiveObject obj) + { + return obj.Value; + } + } + + public class ReactiveEventArgs + { + public T OldValue { get; } + public T NewValue { get; } + + public ReactiveEventArgs(T oldValue, T newValue) + { + OldValue = oldValue; + NewValue = newValue; + } + } +} diff --git a/Ryujinx.Common/Ryujinx.Common.csproj b/Ryujinx.Common/Ryujinx.Common.csproj new file mode 100644 index 0000000000..7f6fa32323 --- /dev/null +++ b/Ryujinx.Common/Ryujinx.Common.csproj @@ -0,0 +1,34 @@ + + + + netcoreapp3.0 + win-x64;osx-x64;linux-x64 + Debug;Release;Profile Debug;Profile Release + + + + true + + + + true + TRACE;USE_PROFILING + false + + + + true + + + + true + TRACE;USE_PROFILING + true + + + + + + + + diff --git a/Ryujinx.Common/Utilities/BitUtils.cs b/Ryujinx.Common/Utilities/BitUtils.cs new file mode 100644 index 0000000000..5f70f742a0 --- /dev/null +++ b/Ryujinx.Common/Utilities/BitUtils.cs @@ -0,0 +1,139 @@ +namespace Ryujinx.Common +{ + public static class BitUtils + { + private static readonly byte[] ClzNibbleTbl = { 4, 3, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0 }; + + public static int AlignUp(int value, int size) + { + return (value + (size - 1)) & -size; + } + + public static ulong AlignUp(ulong value, int size) + { + return (ulong)AlignUp((long)value, size); + } + + public static long AlignUp(long value, int size) + { + return (value + (size - 1)) & -(long)size; + } + + public static int AlignDown(int value, int size) + { + return value & -size; + } + + public static ulong AlignDown(ulong value, int size) + { + return (ulong)AlignDown((long)value, size); + } + + public static long AlignDown(long value, int size) + { + return value & -(long)size; + } + + public static int DivRoundUp(int value, int dividend) + { + return (value + dividend - 1) / dividend; + } + + public static ulong DivRoundUp(ulong value, uint dividend) + { + return (value + dividend - 1) / dividend; + } + + public static long DivRoundUp(long value, int dividend) + { + return (value + dividend - 1) / dividend; + } + + public static int Pow2RoundUp(int value) + { + value--; + + value |= (value >> 1); + value |= (value >> 2); + value |= (value >> 4); + value |= (value >> 8); + value |= (value >> 16); + + return ++value; + } + + public static int Pow2RoundDown(int value) + { + return IsPowerOfTwo32(value) ? value : Pow2RoundUp(value) >> 1; + } + + public static bool IsPowerOfTwo32(int value) + { + return value != 0 && (value & (value - 1)) == 0; + } + + public static bool IsPowerOfTwo64(long value) + { + return value != 0 && (value & (value - 1)) == 0; + } + + public static int CountLeadingZeros32(int value) + { + return (int)CountLeadingZeros((ulong)value, 32); + } + + public static int CountLeadingZeros64(long value) + { + return (int)CountLeadingZeros((ulong)value, 64); + } + + private static ulong CountLeadingZeros(ulong value, int size) + { + 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; + } + + public static int CountTrailingZeros32(int value) + { + int count = 0; + + while (((value >> count) & 1) == 0) + { + count++; + } + + return count; + } + + public static long ReverseBits64(long value) + { + return (long)ReverseBits64((ulong)value); + } + + private static ulong ReverseBits64(ulong value) + { + value = ((value & 0xaaaaaaaaaaaaaaaa) >> 1 ) | ((value & 0x5555555555555555) << 1 ); + value = ((value & 0xcccccccccccccccc) >> 2 ) | ((value & 0x3333333333333333) << 2 ); + value = ((value & 0xf0f0f0f0f0f0f0f0) >> 4 ) | ((value & 0x0f0f0f0f0f0f0f0f) << 4 ); + value = ((value & 0xff00ff00ff00ff00) >> 8 ) | ((value & 0x00ff00ff00ff00ff) << 8 ); + value = ((value & 0xffff0000ffff0000) >> 16) | ((value & 0x0000ffff0000ffff) << 16); + + return (value >> 32) | (value << 32); + } + } +} diff --git a/Ryujinx.Common/Utilities/Buffers.cs b/Ryujinx.Common/Utilities/Buffers.cs new file mode 100644 index 0000000000..d614bfc43b --- /dev/null +++ b/Ryujinx.Common/Utilities/Buffers.cs @@ -0,0 +1,59 @@ +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Common.Utilities +{ + [DebuggerDisplay("{ToString()}")] + [StructLayout(LayoutKind.Sequential, Size = 16)] + public struct Buffer16 + { + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy0; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy1; + + public byte this[int i] + { + get => Bytes[i]; + set => Bytes[i] = value; + } + + public Span Bytes => SpanHelpers.AsByteSpan(ref this); + + // Prevent a defensive copy by changing the read-only in reference to a reference with Unsafe.AsRef() + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Span(in Buffer16 value) + { + return SpanHelpers.AsByteSpan(ref Unsafe.AsRef(in value)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ReadOnlySpan(in Buffer16 value) + { + return SpanHelpers.AsReadOnlyByteSpan(ref Unsafe.AsRef(in value)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref T As() where T : unmanaged + { + if (Unsafe.SizeOf() > (uint)Unsafe.SizeOf()) + { + throw new ArgumentException(); + } + + return ref MemoryMarshal.GetReference(AsSpan()); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span AsSpan() where T : unmanaged + { + return SpanHelpers.AsSpan(ref this); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly ReadOnlySpan AsReadOnlySpan() where T : unmanaged + { + return SpanHelpers.AsReadOnlySpan(ref Unsafe.AsRef(in this)); + } + } +} diff --git a/Ryujinx.Common/Utilities/EmbeddedResources.cs b/Ryujinx.Common/Utilities/EmbeddedResources.cs new file mode 100644 index 0000000000..97f349c192 --- /dev/null +++ b/Ryujinx.Common/Utilities/EmbeddedResources.cs @@ -0,0 +1,148 @@ +using System; +using System.IO; +using System.Reflection; +using System.Threading.Tasks; + +namespace Ryujinx.Common +{ + public static class EmbeddedResources + { + private readonly static Assembly ResourceAssembly; + + static EmbeddedResources() + { + ResourceAssembly = Assembly.GetAssembly(typeof(EmbeddedResources)); + } + + public static byte[] Read(string filename) + { + var (assembly, path) = ResolveManifestPath(filename); + + return Read(assembly, path); + } + + public static Task ReadAsync(string filename) + { + var (assembly, path) = ResolveManifestPath(filename); + + return ReadAsync(assembly, path); + } + + public static byte[] Read(Assembly assembly, string filename) + { + using (var stream = GetStream(assembly, filename)) + { + if (stream == null) + { + return null; + } + + using (var mem = new MemoryStream()) + { + stream.CopyTo(mem); + + return mem.ToArray(); + } + } + } + + public async static Task ReadAsync(Assembly assembly, string filename) + { + using (var stream = GetStream(assembly, filename)) + { + if (stream == null) + { + return null; + } + + using (var mem = new MemoryStream()) + { + await stream.CopyToAsync(mem); + + return mem.ToArray(); + } + } + } + + public static string ReadAllText(string filename) + { + var (assembly, path) = ResolveManifestPath(filename); + + return ReadAllText(assembly, path); + } + + public static Task ReadAllTextAsync(string filename) + { + var (assembly, path) = ResolveManifestPath(filename); + + return ReadAllTextAsync(assembly, path); + } + + public static string ReadAllText(Assembly assembly, string filename) + { + using (var stream = GetStream(assembly, filename)) + { + if (stream == null) + { + return null; + } + + using (var reader = new StreamReader(stream)) + { + return reader.ReadToEnd(); + } + } + } + + public async static Task ReadAllTextAsync(Assembly assembly, string filename) + { + using (var stream = GetStream(assembly, filename)) + { + if (stream == null) + { + return null; + } + + using (var reader = new StreamReader(stream)) + { + return await reader.ReadToEndAsync(); + } + } + } + + public static Stream GetStream(string filename) + { + var (assembly, _) = ResolveManifestPath(filename); + + return GetStream(assembly, filename); + } + + public static Stream GetStream(Assembly assembly, string filename) + { + var namespace_ = assembly.GetName().Name; + var manifestUri = namespace_ + "." + filename.Replace('/', '.'); + + var stream = assembly.GetManifestResourceStream(manifestUri); + + return stream; + } + + private static (Assembly, string) ResolveManifestPath(string filename) + { + var segments = filename.Split(new[] { '/' }, 2, StringSplitOptions.RemoveEmptyEntries); + + if (segments.Length >= 2) + { + foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) + { + if (assembly.GetName().Name == segments[0]) + { + return (assembly, segments[1]); + } + } + } + + return (ResourceAssembly, filename); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Common/Utilities/HexUtils.cs b/Ryujinx.Common/Utilities/HexUtils.cs new file mode 100644 index 0000000000..63587ceaa8 --- /dev/null +++ b/Ryujinx.Common/Utilities/HexUtils.cs @@ -0,0 +1,90 @@ +using System; +using System.Text; + +namespace Ryujinx.Common +{ + public static class HexUtils + { + private static readonly char[] HexChars = "0123456789ABCDEF".ToCharArray(); + + private const int HexTableColumnWidth = 8; + private const int HexTableColumnSpace = 3; + + // Modified for Ryujinx + // Original by Pascal Ganaye - CPOL License + // https://www.codeproject.com/Articles/36747/Quick-and-Dirty-HexDump-of-a-Byte-Array + public static string HexTable(byte[] bytes, int bytesPerLine = 16) + { + if (bytes == null) + { + return ""; + } + + int bytesLength = bytes.Length; + + int firstHexColumn = + HexTableColumnWidth + + HexTableColumnSpace; + + int firstCharColumn = firstHexColumn + + bytesPerLine * HexTableColumnSpace + + (bytesPerLine - 1) / HexTableColumnWidth + + 2; + + int lineLength = firstCharColumn + + bytesPerLine + + Environment.NewLine.Length; + + char[] line = (new String(' ', lineLength - Environment.NewLine.Length) + Environment.NewLine).ToCharArray(); + + int expectedLines = (bytesLength + bytesPerLine - 1) / bytesPerLine; + + StringBuilder result = new StringBuilder(expectedLines * lineLength); + + for (int i = 0; i < bytesLength; i += bytesPerLine) + { + line[0] = HexChars[(i >> 28) & 0xF]; + line[1] = HexChars[(i >> 24) & 0xF]; + line[2] = HexChars[(i >> 20) & 0xF]; + line[3] = HexChars[(i >> 16) & 0xF]; + line[4] = HexChars[(i >> 12) & 0xF]; + line[5] = HexChars[(i >> 8) & 0xF]; + line[6] = HexChars[(i >> 4) & 0xF]; + line[7] = HexChars[(i >> 0) & 0xF]; + + int hexColumn = firstHexColumn; + int charColumn = firstCharColumn; + + for (int j = 0; j < bytesPerLine; j++) + { + if (j > 0 && (j & 7) == 0) + { + hexColumn++; + } + + if (i + j >= bytesLength) + { + line[hexColumn] = ' '; + line[hexColumn + 1] = ' '; + line[charColumn] = ' '; + } + else + { + byte b = bytes[i + j]; + + line[hexColumn] = HexChars[(b >> 4) & 0xF]; + line[hexColumn + 1] = HexChars[b & 0xF]; + line[charColumn] = (b < 32 ? '·' : (char)b); + } + + hexColumn += 3; + charColumn++; + } + + result.Append(line); + } + + return result.ToString(); + } + } +} diff --git a/Ryujinx.Common/Utilities/SpanHelpers.cs b/Ryujinx.Common/Utilities/SpanHelpers.cs new file mode 100644 index 0000000000..84c130233f --- /dev/null +++ b/Ryujinx.Common/Utilities/SpanHelpers.cs @@ -0,0 +1,61 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Common.Utilities +{ + public static class SpanHelpers + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span CreateSpan(ref T reference, int length) + { + return MemoryMarshal.CreateSpan(ref reference, length); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span AsSpan(ref T reference) where T : unmanaged + { + return CreateSpan(ref reference, 1); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span AsSpan(ref TStruct reference) + where TStruct : unmanaged where TSpan : unmanaged + { + return CreateSpan(ref Unsafe.As(ref reference), + Unsafe.SizeOf() / Unsafe.SizeOf()); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span AsByteSpan(ref T reference) where T : unmanaged + { + return CreateSpan(ref Unsafe.As(ref reference), Unsafe.SizeOf()); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan CreateReadOnlySpan(ref T reference, int length) + { + return MemoryMarshal.CreateReadOnlySpan(ref reference, length); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan AsReadOnlySpan(ref T reference) where T : unmanaged + { + return CreateReadOnlySpan(ref reference, 1); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan AsReadOnlySpan(ref TStruct reference) + where TStruct : unmanaged where TSpan : unmanaged + { + return CreateReadOnlySpan(ref Unsafe.As(ref reference), + Unsafe.SizeOf() / Unsafe.SizeOf()); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan AsReadOnlyByteSpan(ref T reference) where T : unmanaged + { + return CreateReadOnlySpan(ref Unsafe.As(ref reference), Unsafe.SizeOf()); + } + } +} diff --git a/Ryujinx.Graphics.GAL/AddressMode.cs b/Ryujinx.Graphics.GAL/AddressMode.cs new file mode 100644 index 0000000000..153925b108 --- /dev/null +++ b/Ryujinx.Graphics.GAL/AddressMode.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.Graphics.GAL +{ + public enum AddressMode + { + Repeat, + MirroredRepeat, + ClampToEdge, + ClampToBorder, + Clamp, + MirrorClampToEdge, + MirrorClampToBorder, + MirrorClamp + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.GAL/BlendDescriptor.cs b/Ryujinx.Graphics.GAL/BlendDescriptor.cs new file mode 100644 index 0000000000..b35a0169dd --- /dev/null +++ b/Ryujinx.Graphics.GAL/BlendDescriptor.cs @@ -0,0 +1,32 @@ +namespace Ryujinx.Graphics.GAL +{ + public struct BlendDescriptor + { + public bool Enable { get; } + + public BlendOp ColorOp { get; } + public BlendFactor ColorSrcFactor { get; } + public BlendFactor ColorDstFactor { get; } + public BlendOp AlphaOp { get; } + public BlendFactor AlphaSrcFactor { get; } + public BlendFactor AlphaDstFactor { get; } + + public BlendDescriptor( + bool enable, + BlendOp colorOp, + BlendFactor colorSrcFactor, + BlendFactor colorDstFactor, + BlendOp alphaOp, + BlendFactor alphaSrcFactor, + BlendFactor alphaDstFactor) + { + Enable = enable; + ColorOp = colorOp; + ColorSrcFactor = colorSrcFactor; + ColorDstFactor = colorDstFactor; + AlphaOp = alphaOp; + AlphaSrcFactor = alphaSrcFactor; + AlphaDstFactor = alphaDstFactor; + } + } +} diff --git a/Ryujinx.Graphics.GAL/BlendFactor.cs b/Ryujinx.Graphics.GAL/BlendFactor.cs new file mode 100644 index 0000000000..135873e992 --- /dev/null +++ b/Ryujinx.Graphics.GAL/BlendFactor.cs @@ -0,0 +1,41 @@ +namespace Ryujinx.Graphics.GAL +{ + public enum BlendFactor + { + Zero = 1, + One, + SrcColor, + OneMinusSrcColor, + SrcAlpha, + OneMinusSrcAlpha, + DstAlpha, + OneMinusDstAlpha, + DstColor, + OneMinusDstColor, + SrcAlphaSaturate, + Src1Color = 0x10, + OneMinusSrc1Color, + Src1Alpha, + OneMinusSrc1Alpha, + ConstantColor = 0xc001, + OneMinusConstantColor, + ConstantAlpha, + OneMinusConstantAlpha, + + ZeroGl = 0x4000, + OneGl = 0x4001, + SrcColorGl = 0x4300, + OneMinusSrcColorGl = 0x4301, + SrcAlphaGl = 0x4302, + OneMinusSrcAlphaGl = 0x4303, + DstAlphaGl = 0x4304, + OneMinusDstAlphaGl = 0x4305, + DstColorGl = 0x4306, + OneMinusDstColorGl = 0x4307, + SrcAlphaSaturateGl = 0x4308, + Src1ColorGl = 0xc900, + OneMinusSrc1ColorGl = 0xc901, + Src1AlphaGl = 0xc902, + OneMinusSrc1AlphaGl = 0xc903 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.GAL/BlendOp.cs b/Ryujinx.Graphics.GAL/BlendOp.cs new file mode 100644 index 0000000000..de1ab67d01 --- /dev/null +++ b/Ryujinx.Graphics.GAL/BlendOp.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.Graphics.GAL +{ + public enum BlendOp + { + Add = 1, + Subtract, + ReverseSubtract, + Minimum, + Maximum, + + AddGl = 0x8006, + SubtractGl = 0x8007, + ReverseSubtractGl = 0x8008, + MinimumGl = 0x800a, + MaximumGl = 0x800b + } +} diff --git a/Ryujinx.Graphics.GAL/BufferRange.cs b/Ryujinx.Graphics.GAL/BufferRange.cs new file mode 100644 index 0000000000..a35636aa73 --- /dev/null +++ b/Ryujinx.Graphics.GAL/BufferRange.cs @@ -0,0 +1,21 @@ +namespace Ryujinx.Graphics.GAL +{ + public struct BufferRange + { + private static BufferRange _empty = new BufferRange(null, 0, 0); + + public BufferRange Empty => _empty; + + public IBuffer Buffer { get; } + + public int Offset { get; } + public int Size { get; } + + public BufferRange(IBuffer buffer, int offset, int size) + { + Buffer = buffer; + Offset = offset; + Size = size; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.GAL/Capabilities.cs b/Ryujinx.Graphics.GAL/Capabilities.cs new file mode 100644 index 0000000000..b75ceb94f3 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Capabilities.cs @@ -0,0 +1,23 @@ +namespace Ryujinx.Graphics.GAL +{ + public struct Capabilities + { + public bool SupportsAstcCompression { get; } + public bool SupportsNonConstantTextureOffset { get; } + + public int MaximumComputeSharedMemorySize { get; } + public int StorageBufferOffsetAlignment { get; } + + public Capabilities( + bool supportsAstcCompression, + bool supportsNonConstantTextureOffset, + int maximumComputeSharedMemorySize, + int storageBufferOffsetAlignment) + { + SupportsAstcCompression = supportsAstcCompression; + SupportsNonConstantTextureOffset = supportsNonConstantTextureOffset; + MaximumComputeSharedMemorySize = maximumComputeSharedMemorySize; + StorageBufferOffsetAlignment = storageBufferOffsetAlignment; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.GAL/ColorF.cs b/Ryujinx.Graphics.GAL/ColorF.cs new file mode 100644 index 0000000000..2e971a62c8 --- /dev/null +++ b/Ryujinx.Graphics.GAL/ColorF.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Graphics.GAL +{ + public struct ColorF + { + public float Red { get; } + public float Green { get; } + public float Blue { get; } + public float Alpha { get; } + + public ColorF(float red, float green, float blue, float alpha) + { + Red = red; + Green = green; + Blue = blue; + Alpha = alpha; + } + } +} diff --git a/Ryujinx.Graphics.GAL/CompareMode.cs b/Ryujinx.Graphics.GAL/CompareMode.cs new file mode 100644 index 0000000000..7a64d9bb9a --- /dev/null +++ b/Ryujinx.Graphics.GAL/CompareMode.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Graphics.GAL +{ + public enum CompareMode + { + None, + CompareRToTexture + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.GAL/CompareOp.cs b/Ryujinx.Graphics.GAL/CompareOp.cs new file mode 100644 index 0000000000..358ed2b46c --- /dev/null +++ b/Ryujinx.Graphics.GAL/CompareOp.cs @@ -0,0 +1,23 @@ +namespace Ryujinx.Graphics.GAL +{ + public enum CompareOp + { + Never = 1, + Less, + Equal, + LessOrEqual, + Greater, + NotEqual, + GreaterOrEqual, + Always, + + NeverGl = 0x200, + LessGl = 0x201, + EqualGl = 0x202, + LessOrEqualGl = 0x203, + GreaterGl = 0x204, + NotEqualGl = 0x205, + GreaterOrEqualGl = 0x206, + AlwaysGl = 0x207, + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.GAL/CounterType.cs b/Ryujinx.Graphics.GAL/CounterType.cs new file mode 100644 index 0000000000..9d7b3b9816 --- /dev/null +++ b/Ryujinx.Graphics.GAL/CounterType.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Graphics.GAL +{ + public enum CounterType + { + SamplesPassed, + PrimitivesGenerated, + TransformFeedbackPrimitivesWritten + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.GAL/DepthMode.cs b/Ryujinx.Graphics.GAL/DepthMode.cs new file mode 100644 index 0000000000..aafbb65a61 --- /dev/null +++ b/Ryujinx.Graphics.GAL/DepthMode.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Graphics.GAL +{ + public enum DepthMode + { + MinusOneToOne, + ZeroToOne + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.GAL/DepthStencilMode.cs b/Ryujinx.Graphics.GAL/DepthStencilMode.cs new file mode 100644 index 0000000000..e80d0d4b36 --- /dev/null +++ b/Ryujinx.Graphics.GAL/DepthStencilMode.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Graphics.GAL +{ + public enum DepthStencilMode + { + Depth, + Stencil + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.GAL/DepthStencilState.cs b/Ryujinx.Graphics.GAL/DepthStencilState.cs new file mode 100644 index 0000000000..d81e84360d --- /dev/null +++ b/Ryujinx.Graphics.GAL/DepthStencilState.cs @@ -0,0 +1,47 @@ +namespace Ryujinx.Graphics.GAL +{ + public struct DepthStencilState + { + public bool DepthTestEnable { get; } + public bool DepthWriteEnable { get; } + public bool StencilTestEnable { get; } + + public CompareOp DepthFunc { get; } + public CompareOp StencilFrontFunc { get; } + public StencilOp StencilFrontSFail { get; } + public StencilOp StencilFrontDpPass { get; } + public StencilOp StencilFrontDpFail { get; } + public CompareOp StencilBackFunc { get; } + public StencilOp StencilBackSFail { get; } + public StencilOp StencilBackDpPass { get; } + public StencilOp StencilBackDpFail { get; } + + public DepthStencilState( + bool depthTestEnable, + bool depthWriteEnable, + bool stencilTestEnable, + CompareOp depthFunc, + CompareOp stencilFrontFunc, + StencilOp stencilFrontSFail, + StencilOp stencilFrontDpPass, + StencilOp stencilFrontDpFail, + CompareOp stencilBackFunc, + StencilOp stencilBackSFail, + StencilOp stencilBackDpPass, + StencilOp stencilBackDpFail) + { + DepthTestEnable = depthTestEnable; + DepthWriteEnable = depthWriteEnable; + StencilTestEnable = stencilTestEnable; + DepthFunc = depthFunc; + StencilFrontFunc = stencilFrontFunc; + StencilFrontSFail = stencilFrontSFail; + StencilFrontDpPass = stencilFrontDpPass; + StencilFrontDpFail = stencilFrontDpFail; + StencilBackFunc = stencilBackFunc; + StencilBackSFail = stencilBackSFail; + StencilBackDpPass = stencilBackDpPass; + StencilBackDpFail = stencilBackDpFail; + } + } +} diff --git a/Ryujinx.Graphics.GAL/DepthTestDescriptor.cs b/Ryujinx.Graphics.GAL/DepthTestDescriptor.cs new file mode 100644 index 0000000000..c835e94141 --- /dev/null +++ b/Ryujinx.Graphics.GAL/DepthTestDescriptor.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.Graphics.GAL +{ + public struct DepthTestDescriptor + { + public bool TestEnable { get; } + public bool WriteEnable { get; } + + public CompareOp Func { get; } + + public DepthTestDescriptor( + bool testEnable, + bool writeEnable, + CompareOp func) + { + TestEnable = testEnable; + WriteEnable = writeEnable; + Func = func; + } + } +} diff --git a/Ryujinx.Graphics.GAL/Extents2D.cs b/Ryujinx.Graphics.GAL/Extents2D.cs new file mode 100644 index 0000000000..e9e26af411 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Extents2D.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Graphics.GAL +{ + public struct Extents2D + { + public int X1 { get; } + public int Y1 { get; } + public int X2 { get; } + public int Y2 { get; } + + public Extents2D(int x1, int y1, int x2, int y2) + { + X1 = x1; + Y1 = y1; + X2 = x2; + Y2 = y2; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalCullFace.cs b/Ryujinx.Graphics.GAL/Face.cs similarity index 61% rename from Ryujinx.Graphics/Gal/GalCullFace.cs rename to Ryujinx.Graphics.GAL/Face.cs index 4ab3e1742c..0587641fab 100644 --- a/Ryujinx.Graphics/Gal/GalCullFace.cs +++ b/Ryujinx.Graphics.GAL/Face.cs @@ -1,9 +1,9 @@ -namespace Ryujinx.Graphics.Gal +namespace Ryujinx.Graphics.GAL { - public enum GalCullFace + public enum Face { Front = 0x404, Back = 0x405, FrontAndBack = 0x408 } -} +} \ No newline at end of file diff --git a/Ryujinx.Graphics.GAL/Format.cs b/Ryujinx.Graphics.GAL/Format.cs new file mode 100644 index 0000000000..0221746b32 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Format.cs @@ -0,0 +1,218 @@ +namespace Ryujinx.Graphics.GAL +{ + public enum Format + { + R8Unorm, + R8Snorm, + R8Uint, + R8Sint, + R16Float, + R16Unorm, + R16Snorm, + R16Uint, + R16Sint, + R32Float, + R32Uint, + R32Sint, + R8G8Unorm, + R8G8Snorm, + R8G8Uint, + R8G8Sint, + R16G16Float, + R16G16Unorm, + R16G16Snorm, + R16G16Uint, + R16G16Sint, + R32G32Float, + R32G32Uint, + R32G32Sint, + R8G8B8Unorm, + R8G8B8Snorm, + R8G8B8Uint, + R8G8B8Sint, + R16G16B16Float, + R16G16B16Unorm, + R16G16B16Snorm, + R16G16B16Uint, + R16G16B16Sint, + R32G32B32Float, + R32G32B32Uint, + R32G32B32Sint, + R8G8B8A8Unorm, + R8G8B8A8Snorm, + R8G8B8A8Uint, + R8G8B8A8Sint, + R16G16B16A16Float, + R16G16B16A16Unorm, + R16G16B16A16Snorm, + R16G16B16A16Uint, + R16G16B16A16Sint, + R32G32B32A32Float, + R32G32B32A32Uint, + R32G32B32A32Sint, + S8Uint, + D16Unorm, + D24X8Unorm, + D32Float, + D24UnormS8Uint, + D32FloatS8Uint, + R8G8B8X8Srgb, + R8G8B8A8Srgb, + R4G4B4A4Unorm, + R5G5B5X1Unorm, + R5G5B5A1Unorm, + R5G6B5Unorm, + R10G10B10A2Unorm, + R10G10B10A2Uint, + R11G11B10Float, + R9G9B9E5Float, + Bc1RgbUnorm, + Bc1RgbaUnorm, + Bc2Unorm, + Bc3Unorm, + Bc1RgbSrgb, + Bc1RgbaSrgb, + Bc2Srgb, + Bc3Srgb, + Bc4Unorm, + Bc4Snorm, + Bc5Unorm, + Bc5Snorm, + Bc7Unorm, + Bc7Srgb, + Bc6HSfloat, + Bc6HUfloat, + R8Uscaled, + R8Sscaled, + R16Uscaled, + R16Sscaled, + R32Uscaled, + R32Sscaled, + R8G8Uscaled, + R8G8Sscaled, + R16G16Uscaled, + R16G16Sscaled, + R32G32Uscaled, + R32G32Sscaled, + R8G8B8Uscaled, + R8G8B8Sscaled, + R16G16B16Uscaled, + R16G16B16Sscaled, + R32G32B32Uscaled, + R32G32B32Sscaled, + R8G8B8A8Uscaled, + R8G8B8A8Sscaled, + R16G16B16A16Uscaled, + R16G16B16A16Sscaled, + R32G32B32A32Uscaled, + R32G32B32A32Sscaled, + R10G10B10A2Snorm, + R10G10B10A2Sint, + R10G10B10A2Uscaled, + R10G10B10A2Sscaled, + R8G8B8X8Unorm, + R8G8B8X8Snorm, + R8G8B8X8Uint, + R8G8B8X8Sint, + R16G16B16X16Float, + R16G16B16X16Unorm, + R16G16B16X16Snorm, + R16G16B16X16Uint, + R16G16B16X16Sint, + R32G32B32X32Float, + R32G32B32X32Uint, + R32G32B32X32Sint, + Astc4x4Unorm, + Astc5x4Unorm, + Astc5x5Unorm, + Astc6x5Unorm, + Astc6x6Unorm, + Astc8x5Unorm, + Astc8x6Unorm, + Astc8x8Unorm, + Astc10x5Unorm, + Astc10x6Unorm, + Astc10x8Unorm, + Astc10x10Unorm, + Astc12x10Unorm, + Astc12x12Unorm, + Astc4x4Srgb, + Astc5x4Srgb, + Astc5x5Srgb, + Astc6x5Srgb, + Astc6x6Srgb, + Astc8x5Srgb, + Astc8x6Srgb, + Astc8x8Srgb, + Astc10x5Srgb, + Astc10x6Srgb, + Astc10x8Srgb, + Astc10x10Srgb, + Astc12x10Srgb, + Astc12x12Srgb, + B5G6R5Unorm, + B5G5R5X1Unorm, + B5G5R5A1Unorm, + A1B5G5R5Unorm, + B8G8R8X8Unorm, + B8G8R8A8Unorm, + B8G8R8X8Srgb, + B8G8R8A8Srgb + } + + public static class FormatExtensions + { + public static bool IsAstc(this Format format) + { + return format.IsAstcUnorm() || format.IsAstcSrgb(); + } + + public static bool IsAstcUnorm(this Format format) + { + switch (format) + { + case Format.Astc4x4Unorm: + case Format.Astc5x4Unorm: + case Format.Astc5x5Unorm: + case Format.Astc6x5Unorm: + case Format.Astc6x6Unorm: + case Format.Astc8x5Unorm: + case Format.Astc8x6Unorm: + case Format.Astc8x8Unorm: + case Format.Astc10x5Unorm: + case Format.Astc10x6Unorm: + case Format.Astc10x8Unorm: + case Format.Astc10x10Unorm: + case Format.Astc12x10Unorm: + case Format.Astc12x12Unorm: + return true; + } + + return false; + } + + public static bool IsAstcSrgb(this Format format) + { + switch (format) + { + case Format.Astc4x4Srgb: + case Format.Astc5x4Srgb: + case Format.Astc5x5Srgb: + case Format.Astc6x5Srgb: + case Format.Astc6x6Srgb: + case Format.Astc8x5Srgb: + case Format.Astc8x6Srgb: + case Format.Astc8x8Srgb: + case Format.Astc10x5Srgb: + case Format.Astc10x6Srgb: + case Format.Astc10x8Srgb: + case Format.Astc10x10Srgb: + case Format.Astc12x10Srgb: + case Format.Astc12x12Srgb: + return true; + } + + return false; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.GAL/FrontFace.cs b/Ryujinx.Graphics.GAL/FrontFace.cs new file mode 100644 index 0000000000..aa6bfdc5b7 --- /dev/null +++ b/Ryujinx.Graphics.GAL/FrontFace.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Graphics.GAL +{ + public enum FrontFace + { + Clockwise = 0x900, + CounterClockwise = 0x901 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.GAL/IBuffer.cs b/Ryujinx.Graphics.GAL/IBuffer.cs new file mode 100644 index 0000000000..43e3769186 --- /dev/null +++ b/Ryujinx.Graphics.GAL/IBuffer.cs @@ -0,0 +1,15 @@ +using System; + +namespace Ryujinx.Graphics.GAL +{ + public interface IBuffer : IDisposable + { + void CopyTo(IBuffer destination, int srcOffset, int dstOffset, int size); + + byte[] GetData(int offset, int size); + + void SetData(ReadOnlySpan data); + + void SetData(int offset, ReadOnlySpan data); + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.GAL/IPipeline.cs b/Ryujinx.Graphics.GAL/IPipeline.cs new file mode 100644 index 0000000000..41e35dd40a --- /dev/null +++ b/Ryujinx.Graphics.GAL/IPipeline.cs @@ -0,0 +1,73 @@ +using Ryujinx.Graphics.Shader; + +namespace Ryujinx.Graphics.GAL +{ + public interface IPipeline + { + void Barrier(); + + void ClearRenderTargetColor(int index, uint componentMask, ColorF color); + + void ClearRenderTargetDepthStencil( + float depthValue, + bool depthMask, + int stencilValue, + int stencilMask); + + void DispatchCompute(int groupsX, int groupsY, int groupsZ); + + void Draw(int vertexCount, int instanceCount, int firstVertex, int firstInstance); + void DrawIndexed( + int indexCount, + int instanceCount, + int firstIndex, + int firstVertex, + int firstInstance); + + void SetBlendState(int index, BlendDescriptor blend); + + void SetBlendColor(ColorF color); + + void SetDepthBias(PolygonModeMask enables, float factor, float units, float clamp); + + void SetDepthMode(DepthMode mode); + + void SetDepthTest(DepthTestDescriptor depthTest); + + void SetFaceCulling(bool enable, Face face); + + void SetFrontFace(FrontFace frontFace); + + void SetIndexBuffer(BufferRange buffer, IndexType type); + + void SetImage(int index, ShaderStage stage, ITexture texture); + + void SetPrimitiveRestart(bool enable, int index); + + void SetPrimitiveTopology(PrimitiveTopology topology); + + void SetProgram(IProgram program); + + void SetRenderTargetColorMasks(uint[] componentMask); + + void SetRenderTargets(ITexture[] colors, ITexture depthStencil); + + void SetSampler(int index, ShaderStage stage, ISampler sampler); + + void SetStencilTest(StencilTestDescriptor stencilTest); + + void SetStorageBuffer(int index, ShaderStage stage, BufferRange buffer); + + void SetTexture(int index, ShaderStage stage, ITexture texture); + + void SetUniformBuffer(int index, ShaderStage stage, BufferRange buffer); + + void SetVertexAttribs(VertexAttribDescriptor[] vertexAttribs); + void SetVertexBuffers(VertexBufferDescriptor[] vertexBuffers); + + void SetViewports(int first, Viewport[] viewports); + + void TextureBarrier(); + void TextureBarrierTiled(); + } +} diff --git a/Ryujinx.Graphics.GAL/IProgram.cs b/Ryujinx.Graphics.GAL/IProgram.cs new file mode 100644 index 0000000000..ef44fc47eb --- /dev/null +++ b/Ryujinx.Graphics.GAL/IProgram.cs @@ -0,0 +1,6 @@ +using System; + +namespace Ryujinx.Graphics.GAL +{ + public interface IProgram : IDisposable { } +} diff --git a/Ryujinx.Graphics.GAL/IRenderer.cs b/Ryujinx.Graphics.GAL/IRenderer.cs new file mode 100644 index 0000000000..56856b2368 --- /dev/null +++ b/Ryujinx.Graphics.GAL/IRenderer.cs @@ -0,0 +1,29 @@ +using Ryujinx.Graphics.Shader; +using System; + +namespace Ryujinx.Graphics.GAL +{ + public interface IRenderer : IDisposable + { + IPipeline Pipeline { get; } + + IWindow Window { get; } + + IShader CompileShader(ShaderProgram shader); + + IBuffer CreateBuffer(int size); + + IProgram CreateProgram(IShader[] shaders); + + ISampler CreateSampler(SamplerCreateInfo info); + ITexture CreateTexture(TextureCreateInfo info); + + Capabilities GetCapabilities(); + + ulong GetCounter(CounterType type); + + void Initialize(); + + void ResetCounter(CounterType type); + } +} diff --git a/Ryujinx.Graphics.GAL/ISampler.cs b/Ryujinx.Graphics.GAL/ISampler.cs new file mode 100644 index 0000000000..3aefc6a780 --- /dev/null +++ b/Ryujinx.Graphics.GAL/ISampler.cs @@ -0,0 +1,6 @@ +using System; + +namespace Ryujinx.Graphics.GAL +{ + public interface ISampler : IDisposable { } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.GAL/IShader.cs b/Ryujinx.Graphics.GAL/IShader.cs new file mode 100644 index 0000000000..be24adcdaa --- /dev/null +++ b/Ryujinx.Graphics.GAL/IShader.cs @@ -0,0 +1,6 @@ +using System; + +namespace Ryujinx.Graphics.GAL +{ + public interface IShader : IDisposable { } +} diff --git a/Ryujinx.Graphics.GAL/ITexture.cs b/Ryujinx.Graphics.GAL/ITexture.cs new file mode 100644 index 0000000000..5278e3b74d --- /dev/null +++ b/Ryujinx.Graphics.GAL/ITexture.cs @@ -0,0 +1,16 @@ +using System; + +namespace Ryujinx.Graphics.GAL +{ + public interface ITexture : IDisposable + { + void CopyTo(ITexture destination, int firstLayer, int firstLevel); + void CopyTo(ITexture destination, Extents2D srcRegion, Extents2D dstRegion, bool linearFilter); + + ITexture CreateView(TextureCreateInfo info, int firstLayer, int firstLevel); + + byte[] GetData(); + + void SetData(ReadOnlySpan data); + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.GAL/IWindow.cs b/Ryujinx.Graphics.GAL/IWindow.cs new file mode 100644 index 0000000000..369f7b9a67 --- /dev/null +++ b/Ryujinx.Graphics.GAL/IWindow.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Graphics.GAL +{ + public interface IWindow + { + void Present(ITexture texture, ImageCrop crop); + + void SetSize(int width, int height); + } +} diff --git a/Ryujinx.Graphics.GAL/ImageCrop.cs b/Ryujinx.Graphics.GAL/ImageCrop.cs new file mode 100644 index 0000000000..4428eee972 --- /dev/null +++ b/Ryujinx.Graphics.GAL/ImageCrop.cs @@ -0,0 +1,28 @@ +namespace Ryujinx.Graphics.GAL +{ + public struct ImageCrop + { + public int Left { get; } + public int Right { get; } + public int Top { get; } + public int Bottom { get; } + public bool FlipX { get; } + public bool FlipY { get; } + + public ImageCrop( + int left, + int right, + int top, + int bottom, + bool flipX, + bool flipY) + { + Left = left; + Right = right; + Top = top; + Bottom = bottom; + FlipX = flipX; + FlipY = flipY; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.GAL/IndexType.cs b/Ryujinx.Graphics.GAL/IndexType.cs new file mode 100644 index 0000000000..4abf28d9c4 --- /dev/null +++ b/Ryujinx.Graphics.GAL/IndexType.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Graphics.GAL +{ + public enum IndexType + { + UByte, + UShort, + UInt + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.GAL/MagFilter.cs b/Ryujinx.Graphics.GAL/MagFilter.cs new file mode 100644 index 0000000000..f20d095e7e --- /dev/null +++ b/Ryujinx.Graphics.GAL/MagFilter.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Graphics.GAL +{ + public enum MagFilter + { + Nearest = 1, + Linear + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.GAL/MinFilter.cs b/Ryujinx.Graphics.GAL/MinFilter.cs new file mode 100644 index 0000000000..b7a0740be9 --- /dev/null +++ b/Ryujinx.Graphics.GAL/MinFilter.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.GAL +{ + public enum MinFilter + { + Nearest = 1, + Linear, + NearestMipmapNearest, + LinearMipmapNearest, + NearestMipmapLinear, + LinearMipmapLinear + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.GAL/PolygonModeMask.cs b/Ryujinx.Graphics.GAL/PolygonModeMask.cs new file mode 100644 index 0000000000..514b423172 --- /dev/null +++ b/Ryujinx.Graphics.GAL/PolygonModeMask.cs @@ -0,0 +1,12 @@ +using System; + +namespace Ryujinx.Graphics.GAL +{ + [Flags] + public enum PolygonModeMask + { + Point = 1 << 0, + Line = 1 << 1, + Fill = 1 << 2 + } +} diff --git a/Ryujinx.Graphics.GAL/PrimitiveTopology.cs b/Ryujinx.Graphics.GAL/PrimitiveTopology.cs new file mode 100644 index 0000000000..ed567a68a8 --- /dev/null +++ b/Ryujinx.Graphics.GAL/PrimitiveTopology.cs @@ -0,0 +1,21 @@ +namespace Ryujinx.Graphics.GAL +{ + public enum PrimitiveTopology + { + Points, + Lines, + LineLoop, + LineStrip, + Triangles, + TriangleStrip, + TriangleFan, + Quads, + QuadStrip, + Polygon, + LinesAdjacency, + LineStripAdjacency, + TrianglesAdjacency, + TriangleStripAdjacency, + Patches + } +} diff --git a/Ryujinx.Graphics.GAL/RectangleF.cs b/Ryujinx.Graphics.GAL/RectangleF.cs new file mode 100644 index 0000000000..c58aabf0e8 --- /dev/null +++ b/Ryujinx.Graphics.GAL/RectangleF.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Graphics.GAL +{ + public struct RectangleF + { + public float X { get; } + public float Y { get; } + public float Width { get; } + public float Height { get; } + + public RectangleF(float x, float y, float width, float height) + { + X = x; + Y = y; + Width = width; + Height = height; + } + } +} diff --git a/Ryujinx.Graphics.GAL/Ryujinx.Graphics.GAL.csproj b/Ryujinx.Graphics.GAL/Ryujinx.Graphics.GAL.csproj new file mode 100644 index 0000000000..3f2df4092e --- /dev/null +++ b/Ryujinx.Graphics.GAL/Ryujinx.Graphics.GAL.csproj @@ -0,0 +1,13 @@ + + + + + + + + + netcoreapp3.0 + win-x64;osx-x64;linux-x64 + + + diff --git a/Ryujinx.Graphics.GAL/SamplerCreateInfo.cs b/Ryujinx.Graphics.GAL/SamplerCreateInfo.cs new file mode 100644 index 0000000000..33d80af5c6 --- /dev/null +++ b/Ryujinx.Graphics.GAL/SamplerCreateInfo.cs @@ -0,0 +1,50 @@ +namespace Ryujinx.Graphics.GAL +{ + public struct SamplerCreateInfo + { + public MinFilter MinFilter { get; } + public MagFilter MagFilter { get; } + + public AddressMode AddressU { get; } + public AddressMode AddressV { get; } + public AddressMode AddressP { get; } + + public CompareMode CompareMode { get; } + public CompareOp CompareOp { get; } + + public ColorF BorderColor { get; } + + public float MinLod { get; } + public float MaxLod { get; } + public float MipLodBias { get; } + public float MaxAnisotropy { get; } + + public SamplerCreateInfo( + MinFilter minFilter, + MagFilter magFilter, + AddressMode addressU, + AddressMode addressV, + AddressMode addressP, + CompareMode compareMode, + CompareOp compareOp, + ColorF borderColor, + float minLod, + float maxLod, + float mipLodBias, + float maxAnisotropy) + { + MinFilter = minFilter; + MagFilter = magFilter; + AddressU = addressU; + AddressV = addressV; + AddressP = addressP; + CompareMode = compareMode; + CompareOp = compareOp; + BorderColor = borderColor; + MinLod = minLod; + MaxLod = maxLod; + MipLodBias = mipLodBias; + MaxAnisotropy = maxAnisotropy; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.GAL/StencilOp.cs b/Ryujinx.Graphics.GAL/StencilOp.cs new file mode 100644 index 0000000000..f0ac829e67 --- /dev/null +++ b/Ryujinx.Graphics.GAL/StencilOp.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.Graphics.GAL +{ + public enum StencilOp + { + Keep = 1, + Zero, + Replace, + IncrementAndClamp, + DecrementAndClamp, + Invert, + IncrementAndWrap, + DecrementAndWrap + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.GAL/StencilTestDescriptor.cs b/Ryujinx.Graphics.GAL/StencilTestDescriptor.cs new file mode 100644 index 0000000000..8c9d1644aa --- /dev/null +++ b/Ryujinx.Graphics.GAL/StencilTestDescriptor.cs @@ -0,0 +1,56 @@ +namespace Ryujinx.Graphics.GAL +{ + public struct StencilTestDescriptor + { + public bool TestEnable { get; } + + public CompareOp FrontFunc { get; } + public StencilOp FrontSFail { get; } + public StencilOp FrontDpPass { get; } + public StencilOp FrontDpFail { get; } + public int FrontFuncRef { get; } + public int FrontFuncMask { get; } + public int FrontMask { get; } + public CompareOp BackFunc { get; } + public StencilOp BackSFail { get; } + public StencilOp BackDpPass { get; } + public StencilOp BackDpFail { get; } + public int BackFuncRef { get; } + public int BackFuncMask { get; } + public int BackMask { get; } + + public StencilTestDescriptor( + bool testEnable, + CompareOp frontFunc, + StencilOp frontSFail, + StencilOp frontDpPass, + StencilOp frontDpFail, + int frontFuncRef, + int frontFuncMask, + int frontMask, + CompareOp backFunc, + StencilOp backSFail, + StencilOp backDpPass, + StencilOp backDpFail, + int backFuncRef, + int backFuncMask, + int backMask) + { + TestEnable = testEnable; + FrontFunc = frontFunc; + FrontSFail = frontSFail; + FrontDpPass = frontDpPass; + FrontDpFail = frontDpFail; + FrontFuncRef = frontFuncRef; + FrontFuncMask = frontFuncMask; + FrontMask = frontMask; + BackFunc = backFunc; + BackSFail = backSFail; + BackDpPass = backDpPass; + BackDpFail = backDpFail; + BackFuncRef = backFuncRef; + BackFuncMask = backFuncMask; + BackMask = backMask; + } + } +} diff --git a/Ryujinx.Graphics.GAL/SwizzleComponent.cs b/Ryujinx.Graphics.GAL/SwizzleComponent.cs new file mode 100644 index 0000000000..a405bd139b --- /dev/null +++ b/Ryujinx.Graphics.GAL/SwizzleComponent.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.GAL +{ + public enum SwizzleComponent + { + Zero, + One, + Red, + Green, + Blue, + Alpha + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.GAL/Target.cs b/Ryujinx.Graphics.GAL/Target.cs new file mode 100644 index 0000000000..73f77f4370 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Target.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.Graphics.GAL +{ + public enum Target + { + Texture1D, + Texture2D, + Texture3D, + Texture1DArray, + Texture2DArray, + Texture2DMultisample, + Texture2DMultisampleArray, + Rectangle, + Cubemap, + CubemapArray, + TextureBuffer + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.GAL/TextureCreateInfo.cs b/Ryujinx.Graphics.GAL/TextureCreateInfo.cs new file mode 100644 index 0000000000..d74ac62dd2 --- /dev/null +++ b/Ryujinx.Graphics.GAL/TextureCreateInfo.cs @@ -0,0 +1,120 @@ +using Ryujinx.Common; +using System; + +namespace Ryujinx.Graphics.GAL +{ + public struct TextureCreateInfo + { + public int Width { get; } + public int Height { get; } + public int Depth { get; } + public int Levels { get; } + public int Samples { get; } + public int BlockWidth { get; } + public int BlockHeight { get; } + public int BytesPerPixel { get; } + + public bool IsCompressed => (BlockWidth | BlockHeight) != 1; + + public Format Format { get; } + + public DepthStencilMode DepthStencilMode { get; } + + public Target Target { get; } + + public SwizzleComponent SwizzleR { get; } + public SwizzleComponent SwizzleG { get; } + public SwizzleComponent SwizzleB { get; } + public SwizzleComponent SwizzleA { get; } + + public TextureCreateInfo( + int width, + int height, + int depth, + int levels, + int samples, + int blockWidth, + int blockHeight, + int bytesPerPixel, + Format format, + DepthStencilMode depthStencilMode, + Target target, + SwizzleComponent swizzleR, + SwizzleComponent swizzleG, + SwizzleComponent swizzleB, + SwizzleComponent swizzleA) + { + Width = width; + Height = height; + Depth = depth; + Levels = levels; + Samples = samples; + BlockWidth = blockWidth; + BlockHeight = blockHeight; + BytesPerPixel = bytesPerPixel; + Format = format; + DepthStencilMode = depthStencilMode; + Target = target; + SwizzleR = swizzleR; + SwizzleG = swizzleG; + SwizzleB = swizzleB; + SwizzleA = swizzleA; + } + + public readonly int GetMipSize(int level) + { + return GetMipStride(level) * GetLevelHeight(level) * GetLevelDepth(level); + } + + public readonly int GetMipSize2D(int level) + { + return GetMipStride(level) * GetLevelHeight(level); + } + + public readonly int GetMipStride(int level) + { + return BitUtils.AlignUp(GetLevelWidth(level) * BytesPerPixel, 4); + } + + private readonly int GetLevelWidth(int level) + { + return BitUtils.DivRoundUp(GetLevelSize(Width, level), BlockWidth); + } + + private readonly int GetLevelHeight(int level) + { + return BitUtils.DivRoundUp(GetLevelSize(Height, level), BlockHeight); + } + + private readonly int GetLevelDepth(int level) + { + return Target == Target.Texture3D ? GetLevelSize(Depth, level) : GetLayers(); + } + + public readonly int GetDepthOrLayers() + { + return Target == Target.Texture3D ? Depth : GetLayers(); + } + + public readonly int GetLayers() + { + if (Target == Target.Texture2DArray || + Target == Target.Texture2DMultisampleArray || + Target == Target.CubemapArray) + { + return Depth; + } + else if (Target == Target.Cubemap) + { + return 6; + } + + return 1; + } + + private static int GetLevelSize(int size, int level) + { + return Math.Max(1, size >> level); + } + } +} diff --git a/Ryujinx.Graphics.GAL/TextureReleaseCallback.cs b/Ryujinx.Graphics.GAL/TextureReleaseCallback.cs new file mode 100644 index 0000000000..c058df2bb2 --- /dev/null +++ b/Ryujinx.Graphics.GAL/TextureReleaseCallback.cs @@ -0,0 +1,4 @@ +namespace Ryujinx.Graphics.GAL +{ + public delegate void TextureReleaseCallback(object context); +} diff --git a/Ryujinx.Graphics.GAL/VertexAttribDescriptor.cs b/Ryujinx.Graphics.GAL/VertexAttribDescriptor.cs new file mode 100644 index 0000000000..34daff57b6 --- /dev/null +++ b/Ryujinx.Graphics.GAL/VertexAttribDescriptor.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.Graphics.GAL +{ + public struct VertexAttribDescriptor + { + public int BufferIndex { get; } + public int Offset { get; } + + public Format Format { get; } + + public VertexAttribDescriptor(int bufferIndex, int offset, Format format) + { + BufferIndex = bufferIndex; + Offset = offset; + Format = format; + } + } +} diff --git a/Ryujinx.Graphics.GAL/VertexBufferDescriptor.cs b/Ryujinx.Graphics.GAL/VertexBufferDescriptor.cs new file mode 100644 index 0000000000..bcd3b28f5d --- /dev/null +++ b/Ryujinx.Graphics.GAL/VertexBufferDescriptor.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.Graphics.GAL +{ + public struct VertexBufferDescriptor + { + public BufferRange Buffer { get; } + + public int Stride { get; } + public int Divisor { get; } + + public VertexBufferDescriptor(BufferRange buffer, int stride, int divisor) + { + Buffer = buffer; + Stride = stride; + Divisor = divisor; + } + } +} diff --git a/Ryujinx.Graphics.GAL/Viewport.cs b/Ryujinx.Graphics.GAL/Viewport.cs new file mode 100644 index 0000000000..d9d6e20a42 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Viewport.cs @@ -0,0 +1,33 @@ +namespace Ryujinx.Graphics.GAL +{ + public struct Viewport + { + public RectangleF Region { get; } + + public ViewportSwizzle SwizzleX { get; } + public ViewportSwizzle SwizzleY { get; } + public ViewportSwizzle SwizzleZ { get; } + public ViewportSwizzle SwizzleW { get; } + + public float DepthNear { get; } + public float DepthFar { get; } + + public Viewport( + RectangleF region, + ViewportSwizzle swizzleX, + ViewportSwizzle swizzleY, + ViewportSwizzle swizzleZ, + ViewportSwizzle swizzleW, + float depthNear, + float depthFar) + { + Region = region; + SwizzleX = swizzleX; + SwizzleY = swizzleY; + SwizzleZ = swizzleZ; + SwizzleW = swizzleW; + DepthNear = depthNear; + DepthFar = depthFar; + } + } +} diff --git a/Ryujinx.Graphics.GAL/ViewportSwizzle.cs b/Ryujinx.Graphics.GAL/ViewportSwizzle.cs new file mode 100644 index 0000000000..5f04bf87d3 --- /dev/null +++ b/Ryujinx.Graphics.GAL/ViewportSwizzle.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.Graphics.GAL +{ + public enum ViewportSwizzle + { + PositiveX, + NegativeX, + PositiveY, + NegativeY, + PositiveZ, + NegativeZ, + PositiveW, + NegativeW + } +} diff --git a/Ryujinx.Graphics.Gpu/ClassId.cs b/Ryujinx.Graphics.Gpu/ClassId.cs new file mode 100644 index 0000000000..be4ebe4b68 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/ClassId.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.Graphics.Gpu +{ + /// + /// GPU engine class ID. + /// + enum ClassId + { + Engine2D = 0x902d, + Engine3D = 0xb197, + EngineCompute = 0xb1c0, + EngineInline2Memory = 0xa140, + EngineDma = 0xb0b5 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Constants.cs b/Ryujinx.Graphics.Gpu/Constants.cs new file mode 100644 index 0000000000..65cd8846d4 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Constants.cs @@ -0,0 +1,48 @@ +namespace Ryujinx.Graphics.Gpu +{ + /// + /// Common Maxwell GPU constants. + /// + static class Constants + { + /// + /// Maximum number of compute uniform buffers. + /// + public const int TotalCpUniformBuffers = 8; + + /// + /// Maximum number of compute storage buffers (this is an API limitation). + /// + public const int TotalCpStorageBuffers = 16; + + /// + /// Maximum number of graphics uniform buffers. + /// + public const int TotalGpUniformBuffers = 18; + + /// + /// Maximum number of graphics storage buffers (this is an API limitation). + /// + public const int TotalGpStorageBuffers = 16; + + /// + /// Maximum number of render target color buffers. + /// + public const int TotalRenderTargets = 8; + + /// + /// Number of shader stages. + /// + public const int ShaderStages = 5; + + /// + /// Maximum number of vertex buffers. + /// + public const int TotalVertexBuffers = 16; + + /// + /// Maximum number of viewports. + /// + public const int TotalViewports = 8; + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/DmaPusher.cs b/Ryujinx.Graphics.Gpu/DmaPusher.cs new file mode 100644 index 0000000000..1c85686a52 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/DmaPusher.cs @@ -0,0 +1,214 @@ +using System.Collections.Concurrent; +using System.Threading; + +namespace Ryujinx.Graphics.Gpu +{ + /// + /// GPU DMA pusher, used to push commands to the GPU. + /// + public class DmaPusher + { + private ConcurrentQueue _ibBuffer; + + private ulong _dmaPut; + private ulong _dmaGet; + + /// + /// Internal GPFIFO state. + /// + private struct DmaState + { + public int Method; + public int SubChannel; + public int MethodCount; + public bool NonIncrementing; + public bool IncrementOnce; + public int LengthPending; + } + + private DmaState _state; + + private bool _sliEnable; + private bool _sliActive; + + private bool _ibEnable; + private bool _nonMain; + + private ulong _dmaMGet; + + private GpuContext _context; + + private AutoResetEvent _event; + + /// + /// Creates a new instance of the GPU DMA pusher. + /// + /// GPU context that the pusher belongs to + internal DmaPusher(GpuContext context) + { + _context = context; + + _ibBuffer = new ConcurrentQueue(); + + _ibEnable = true; + + _event = new AutoResetEvent(false); + } + + /// + /// Pushes a GPFIFO entry. + /// + /// GPFIFO entry + public void Push(ulong entry) + { + _ibBuffer.Enqueue(entry); + + _event.Set(); + } + + /// + /// Waits until commands are pushed to the FIFO. + /// + /// True if commands were received, false if wait timed out + public bool WaitForCommands() + { + return _event.WaitOne(8); + } + + /// + /// Processes commands pushed to the FIFO. + /// + public void DispatchCalls() + { + while (Step()); + } + + /// + /// Processes a single command on the FIFO. + /// + /// True if the FIFO still has commands to be processed, false otherwise + private bool Step() + { + if (_dmaGet != _dmaPut) + { + int word = _context.MemoryAccessor.ReadInt32(_dmaGet); + + _dmaGet += 4; + + if (!_nonMain) + { + _dmaMGet = _dmaGet; + } + + if (_state.LengthPending != 0) + { + _state.LengthPending = 0; + _state.MethodCount = word & 0xffffff; + } + else if (_state.MethodCount != 0) + { + if (!_sliEnable || _sliActive) + { + CallMethod(word); + } + + if (!_state.NonIncrementing) + { + _state.Method++; + } + + if (_state.IncrementOnce) + { + _state.NonIncrementing = true; + } + + _state.MethodCount--; + } + else + { + int submissionMode = (word >> 29) & 7; + + switch (submissionMode) + { + case 1: + // Incrementing. + SetNonImmediateState(word); + + _state.NonIncrementing = false; + _state.IncrementOnce = false; + + break; + + case 3: + // Non-incrementing. + SetNonImmediateState(word); + + _state.NonIncrementing = true; + _state.IncrementOnce = false; + + break; + + case 4: + // Immediate. + _state.Method = (word >> 0) & 0x1fff; + _state.SubChannel = (word >> 13) & 7; + _state.NonIncrementing = true; + _state.IncrementOnce = false; + + CallMethod((word >> 16) & 0x1fff); + + break; + + case 5: + // Increment-once. + SetNonImmediateState(word); + + _state.NonIncrementing = false; + _state.IncrementOnce = true; + + break; + } + } + } + else if (_ibEnable && _ibBuffer.TryDequeue(out ulong entry)) + { + ulong length = (entry >> 42) & 0x1fffff; + + _dmaGet = entry & 0xfffffffffc; + _dmaPut = _dmaGet + length * 4; + + _nonMain = (entry & (1UL << 41)) != 0; + } + else + { + return false; + } + + return true; + } + + /// + /// Sets current non-immediate method call state. + /// + /// Compressed method word + private void SetNonImmediateState(int word) + { + _state.Method = (word >> 0) & 0x1fff; + _state.SubChannel = (word >> 13) & 7; + _state.MethodCount = (word >> 16) & 0x1fff; + } + + /// + /// Forwards the method call to GPU engines. + /// + /// Call argument + private void CallMethod(int argument) + { + _context.Fifo.CallMethod(new MethodParams( + _state.Method, + argument, + _state.SubChannel, + _state.MethodCount)); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Engine/Compute.cs b/Ryujinx.Graphics.Gpu/Engine/Compute.cs new file mode 100644 index 0000000000..d24d2d8d72 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Engine/Compute.cs @@ -0,0 +1,141 @@ +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Image; +using Ryujinx.Graphics.Gpu.Shader; +using Ryujinx.Graphics.Gpu.State; +using Ryujinx.Graphics.Shader; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Gpu.Engine +{ + partial class Methods + { + /// + /// Dispatches compute work. + /// + /// Current GPU state + /// Method call argument + public void Dispatch(GpuState state, int argument) + { + uint dispatchParamsAddress = (uint)state.Get(MethodOffset.DispatchParamsAddress); + + var dispatchParams = _context.MemoryAccessor.Read((ulong)dispatchParamsAddress << 8); + + GpuVa shaderBaseAddress = state.Get(MethodOffset.ShaderBaseAddress); + + ulong shaderGpuVa = shaderBaseAddress.Pack() + (uint)dispatchParams.ShaderOffset; + + // Note: A size of 0 is also invalid, the size must be at least 1. + int sharedMemorySize = Math.Clamp(dispatchParams.SharedMemorySize & 0xffff, 1, _context.Capabilities.MaximumComputeSharedMemorySize); + + ComputeShader cs = ShaderCache.GetComputeShader( + shaderGpuVa, + sharedMemorySize, + dispatchParams.UnpackBlockSizeX(), + dispatchParams.UnpackBlockSizeY(), + dispatchParams.UnpackBlockSizeZ()); + + _context.Renderer.Pipeline.SetProgram(cs.HostProgram); + + var samplerPool = state.Get(MethodOffset.SamplerPoolState); + + TextureManager.SetComputeSamplerPool(samplerPool.Address.Pack(), samplerPool.MaximumId, dispatchParams.SamplerIndex); + + var texturePool = state.Get(MethodOffset.TexturePoolState); + + TextureManager.SetComputeTexturePool(texturePool.Address.Pack(), texturePool.MaximumId); + + TextureManager.SetComputeTextureBufferIndex(state.Get(MethodOffset.TextureBufferIndex)); + + ShaderProgramInfo info = cs.Shader.Program.Info; + + uint sbEnableMask = 0; + uint ubEnableMask = dispatchParams.UnpackUniformBuffersEnableMask(); + + for (int index = 0; index < dispatchParams.UniformBuffers.Length; index++) + { + if ((ubEnableMask & (1 << index)) == 0) + { + continue; + } + + ulong gpuVa = dispatchParams.UniformBuffers[index].PackAddress(); + ulong size = dispatchParams.UniformBuffers[index].UnpackSize(); + + BufferManager.SetComputeUniformBuffer(index, gpuVa, size); + } + + for (int index = 0; index < info.SBuffers.Count; index++) + { + BufferDescriptor sb = info.SBuffers[index]; + + sbEnableMask |= 1u << sb.Slot; + + ulong sbDescAddress = BufferManager.GetComputeUniformBufferAddress(0); + + int sbDescOffset = 0x310 + sb.Slot * 0x10; + + sbDescAddress += (ulong)sbDescOffset; + + ReadOnlySpan sbDescriptorData = _context.PhysicalMemory.GetSpan(sbDescAddress, 0x10); + + SbDescriptor sbDescriptor = MemoryMarshal.Cast(sbDescriptorData)[0]; + + BufferManager.SetComputeStorageBuffer(sb.Slot, sbDescriptor.PackAddress(), (uint)sbDescriptor.Size); + } + + ubEnableMask = 0; + + for (int index = 0; index < info.CBuffers.Count; index++) + { + ubEnableMask |= 1u << info.CBuffers[index].Slot; + } + + BufferManager.SetComputeStorageBufferEnableMask(sbEnableMask); + BufferManager.SetComputeUniformBufferEnableMask(ubEnableMask); + + var textureBindings = new TextureBindingInfo[info.Textures.Count]; + + for (int index = 0; index < info.Textures.Count; index++) + { + var descriptor = info.Textures[index]; + + Target target = GetTarget(descriptor.Type); + + if (descriptor.IsBindless) + { + textureBindings[index] = new TextureBindingInfo(target, descriptor.CbufOffset, descriptor.CbufSlot); + } + else + { + textureBindings[index] = new TextureBindingInfo(target, descriptor.HandleIndex); + } + } + + TextureManager.SetComputeTextures(textureBindings); + + var imageBindings = new TextureBindingInfo[info.Images.Count]; + + for (int index = 0; index < info.Images.Count; index++) + { + var descriptor = info.Images[index]; + + Target target = GetTarget(descriptor.Type); + + imageBindings[index] = new TextureBindingInfo(target, descriptor.HandleIndex); + } + + TextureManager.SetComputeImages(imageBindings); + + BufferManager.CommitComputeBindings(); + TextureManager.CommitComputeBindings(); + + _context.Renderer.Pipeline.DispatchCompute( + dispatchParams.UnpackGridSizeX(), + dispatchParams.UnpackGridSizeY(), + dispatchParams.UnpackGridSizeZ()); + + UpdateShaderState(state); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Engine/ComputeParams.cs b/Ryujinx.Graphics.Gpu/Engine/ComputeParams.cs new file mode 100644 index 0000000000..c19b43d81e --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Engine/ComputeParams.cs @@ -0,0 +1,173 @@ +using Ryujinx.Graphics.Gpu.State; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Gpu.Engine +{ + /// + /// Compute uniform buffer parameters. + /// + struct UniformBufferParams + { + public int AddressLow; + public int AddressHighAndSize; + + /// + /// Packs the split address to a 64-bits integer. + /// + /// Uniform buffer GPU virtual address + public ulong PackAddress() + { + return (uint)AddressLow | ((ulong)(AddressHighAndSize & 0xff) << 32); + } + + /// + /// Unpacks the uniform buffer size in bytes. + /// + /// Uniform buffer size in bytes + public ulong UnpackSize() + { + return (ulong)((AddressHighAndSize >> 15) & 0x1ffff); + } + } + + /// + /// Compute dispatch parameters. + /// + struct ComputeParams + { + public int Unknown0; + public int Unknown1; + public int Unknown2; + public int Unknown3; + public int Unknown4; + public int Unknown5; + public int Unknown6; + public int Unknown7; + public int ShaderOffset; + public int Unknown9; + public int Unknown10; + public SamplerIndex SamplerIndex; + public int GridSizeX; + public int GridSizeYZ; + public int Unknown14; + public int Unknown15; + public int Unknown16; + public int SharedMemorySize; + public int BlockSizeX; + public int BlockSizeYZ; + public int UniformBuffersConfig; + public int Unknown21; + public int Unknown22; + public int Unknown23; + public int Unknown24; + public int Unknown25; + public int Unknown26; + public int Unknown27; + public int Unknown28; + + private UniformBufferParams _uniformBuffer0; + private UniformBufferParams _uniformBuffer1; + private UniformBufferParams _uniformBuffer2; + private UniformBufferParams _uniformBuffer3; + private UniformBufferParams _uniformBuffer4; + private UniformBufferParams _uniformBuffer5; + private UniformBufferParams _uniformBuffer6; + private UniformBufferParams _uniformBuffer7; + + /// + /// Uniform buffer parameters. + /// + public Span UniformBuffers + { + get + { + return MemoryMarshal.CreateSpan(ref _uniformBuffer0, 8); + } + } + + public int Unknown45; + public int Unknown46; + public int Unknown47; + public int Unknown48; + public int Unknown49; + public int Unknown50; + public int Unknown51; + public int Unknown52; + public int Unknown53; + public int Unknown54; + public int Unknown55; + public int Unknown56; + public int Unknown57; + public int Unknown58; + public int Unknown59; + public int Unknown60; + public int Unknown61; + public int Unknown62; + public int Unknown63; + + /// + /// Unpacks the work group X size. + /// + /// Work group X size + public int UnpackGridSizeX() + { + return GridSizeX & 0x7fffffff; + } + + /// + /// Unpacks the work group Y size. + /// + /// Work group Y size + public int UnpackGridSizeY() + { + return GridSizeYZ & 0xffff; + } + + /// + /// Unpacks the work group Z size. + /// + /// Work group Z size + public int UnpackGridSizeZ() + { + return (GridSizeYZ >> 16) & 0xffff; + } + + /// + /// Unpacks the local group X size. + /// + /// Local group X size + public int UnpackBlockSizeX() + { + return (BlockSizeX >> 16) & 0xffff; + } + + /// + /// Unpacks the local group Y size. + /// + /// Local group Y size + public int UnpackBlockSizeY() + { + return BlockSizeYZ & 0xffff; + } + + /// + /// Unpacks the local group Z size. + /// + /// Local group Z size + public int UnpackBlockSizeZ() + { + return (BlockSizeYZ >> 16) & 0xffff; + } + + /// + /// Unpacks the uniform buffers enable mask. + /// Each bit set on the mask indicates that the respective buffer index is enabled. + /// + /// Uniform buffers enable mask + public uint UnpackUniformBuffersEnableMask() + { + return (uint)UniformBuffersConfig & 0xff; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Engine/Inline2Memory.cs b/Ryujinx.Graphics.Gpu/Engine/Inline2Memory.cs new file mode 100644 index 0000000000..7943239568 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Engine/Inline2Memory.cs @@ -0,0 +1,130 @@ +using Ryujinx.Common; +using Ryujinx.Graphics.Gpu.State; +using Ryujinx.Graphics.Texture; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Gpu.Engine +{ + partial class Methods + { + private Inline2MemoryParams _params; + + private bool _isLinear; + + private int _offset; + private int _size; + + private bool _finished; + + private int[] _buffer; + + /// + /// Launches Inline-to-Memory engine DMA copy. + /// + /// Current GPU state + /// Method call argument + public void LaunchDma(GpuState state, int argument) + { + _params = state.Get(MethodOffset.I2mParams); + + _isLinear = (argument & 1) != 0; + + _offset = 0; + _size = _params.LineLengthIn * _params.LineCount; + + int count = BitUtils.DivRoundUp(_size, 4); + + if (_buffer == null || _buffer.Length < count) + { + _buffer = new int[count]; + } + + ulong dstBaseAddress = _context.MemoryManager.Translate(_params.DstAddress.Pack()); + + _context.Methods.TextureManager.Flush(dstBaseAddress, (ulong)_size); + + _finished = false; + } + + /// + /// Pushes a word of data to the Inline-to-Memory engine. + /// + /// Current GPU state + /// Method call argument + public void LoadInlineData(GpuState state, int argument) + { + if (!_finished) + { + _buffer[_offset++] = argument; + + if (_offset * 4 >= _size) + { + FinishTransfer(); + } + } + } + + /// + /// Performs actual copy of the inline data after the transfer is finished. + /// + private void FinishTransfer() + { + Span data = MemoryMarshal.Cast(_buffer).Slice(0, _size); + + if (_isLinear && _params.LineCount == 1) + { + ulong address = _context.MemoryManager.Translate( _params.DstAddress.Pack()); + + _context.PhysicalMemory.Write(address, data); + } + else + { + var dstCalculator = new OffsetCalculator( + _params.DstWidth, + _params.DstHeight, + _params.DstStride, + _isLinear, + _params.DstMemoryLayout.UnpackGobBlocksInY(), + 1); + + int srcOffset = 0; + + ulong dstBaseAddress = _context.MemoryManager.Translate(_params.DstAddress.Pack()); + + for (int y = _params.DstY; y < _params.DstY + _params.LineCount; y++) + { + int x1 = _params.DstX; + int x2 = _params.DstX + _params.LineLengthIn; + int x2Trunc = _params.DstX + BitUtils.AlignDown(_params.LineLengthIn, 16); + + int x; + + for (x = x1; x < x2Trunc; x += 16, srcOffset += 16) + { + int dstOffset = dstCalculator.GetOffset(x, y); + + ulong dstAddress = dstBaseAddress + (ulong)dstOffset; + + Span pixel = data.Slice(srcOffset, 16); + + _context.PhysicalMemory.Write(dstAddress, pixel); + } + + for (; x < x2; x++, srcOffset++) + { + int dstOffset = dstCalculator.GetOffset(x, y); + + ulong dstAddress = dstBaseAddress + (ulong)dstOffset; + + Span pixel = data.Slice(srcOffset, 1); + + _context.PhysicalMemory.Write(dstAddress, pixel); + } + } + } + + _finished = true; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Engine/MethodClear.cs b/Ryujinx.Graphics.Gpu/Engine/MethodClear.cs new file mode 100644 index 0000000000..dfa7b12ed3 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Engine/MethodClear.cs @@ -0,0 +1,62 @@ +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.State; + +namespace Ryujinx.Graphics.Gpu.Engine +{ + partial class Methods + { + /// + /// Clears the current color and depth-stencil buffers. + /// Which buffers should be cleared is also specified on the argument. + /// + /// Current GPU state + /// Method call argument + private void Clear(GpuState state, int argument) + { + UpdateRenderTargetState(state, useControl: false); + + TextureManager.CommitGraphicsBindings(); + + bool clearDepth = (argument & 1) != 0; + bool clearStencil = (argument & 2) != 0; + + uint componentMask = (uint)((argument >> 2) & 0xf); + + int index = (argument >> 6) & 0xf; + + if (componentMask != 0) + { + var clearColor = state.Get(MethodOffset.ClearColors); + + ColorF color = new ColorF( + clearColor.Red, + clearColor.Green, + clearColor.Blue, + clearColor.Alpha); + + _context.Renderer.Pipeline.ClearRenderTargetColor(index, componentMask, color); + } + + if (clearDepth || clearStencil) + { + float depthValue = state.Get(MethodOffset.ClearDepthValue); + int stencilValue = state.Get (MethodOffset.ClearStencilValue); + + int stencilMask = 0; + + if (clearStencil) + { + stencilMask = state.Get(MethodOffset.StencilTestState).FrontMask; + } + + _context.Renderer.Pipeline.ClearRenderTargetDepthStencil( + depthValue, + clearDepth, + stencilValue, + stencilMask); + } + + UpdateRenderTargetState(state, useControl: true); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Engine/MethodCopyBuffer.cs b/Ryujinx.Graphics.Gpu/Engine/MethodCopyBuffer.cs new file mode 100644 index 0000000000..9f638f504c --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Engine/MethodCopyBuffer.cs @@ -0,0 +1,80 @@ +using Ryujinx.Graphics.Gpu.State; +using Ryujinx.Graphics.Texture; +using System; + +namespace Ryujinx.Graphics.Gpu.Engine +{ + partial class Methods + { + /// + /// Performs a buffer to buffer, or buffer to texture copy. + /// + /// Current GPU state + /// Method call argument + private void CopyBuffer(GpuState state, int argument) + { + var cbp = state.Get(MethodOffset.CopyBufferParams); + + var swizzle = state.Get(MethodOffset.CopyBufferSwizzle); + + bool srcLinear = (argument & (1 << 7)) != 0; + bool dstLinear = (argument & (1 << 8)) != 0; + bool copy2D = (argument & (1 << 9)) != 0; + + int size = cbp.XCount; + + if (size == 0) + { + return; + } + + if (copy2D) + { + // Buffer to texture copy. + int srcBpp = swizzle.UnpackSrcComponentsCount() * swizzle.UnpackComponentSize(); + int dstBpp = swizzle.UnpackDstComponentsCount() * swizzle.UnpackComponentSize(); + + var dst = state.Get(MethodOffset.CopyBufferDstTexture); + var src = state.Get(MethodOffset.CopyBufferSrcTexture); + + var srcCalculator = new OffsetCalculator( + src.Width, + src.Height, + cbp.SrcStride, + srcLinear, + src.MemoryLayout.UnpackGobBlocksInY(), + srcBpp); + + var dstCalculator = new OffsetCalculator( + dst.Width, + dst.Height, + cbp.DstStride, + dstLinear, + dst.MemoryLayout.UnpackGobBlocksInY(), + dstBpp); + + ulong srcBaseAddress = _context.MemoryManager.Translate(cbp.SrcAddress.Pack()); + ulong dstBaseAddress = _context.MemoryManager.Translate(cbp.DstAddress.Pack()); + + for (int y = 0; y < cbp.YCount; y++) + for (int x = 0; x < cbp.XCount; x++) + { + int srcOffset = srcCalculator.GetOffset(src.RegionX + x, src.RegionY + y); + int dstOffset = dstCalculator.GetOffset(dst.RegionX + x, dst.RegionY + y); + + ulong srcAddress = srcBaseAddress + (ulong)srcOffset; + ulong dstAddress = dstBaseAddress + (ulong)dstOffset; + + ReadOnlySpan pixel = _context.PhysicalMemory.GetSpan(srcAddress, (ulong)srcBpp); + + _context.PhysicalMemory.Write(dstAddress, pixel); + } + } + else + { + // Buffer to buffer copy. + BufferManager.CopyBuffer(cbp.SrcAddress, cbp.DstAddress, (uint)size); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Engine/MethodCopyTexture.cs b/Ryujinx.Graphics.Gpu/Engine/MethodCopyTexture.cs new file mode 100644 index 0000000000..8d1b2b714f --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Engine/MethodCopyTexture.cs @@ -0,0 +1,100 @@ +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.State; + +namespace Ryujinx.Graphics.Gpu.Engine +{ + using Texture = Image.Texture; + + partial class Methods + { + /// + /// Performs a texture to texture copy. + /// + /// Current GPU state + /// Method call argument + private void CopyTexture(GpuState state, int argument) + { + var dstCopyTexture = state.Get(MethodOffset.CopyDstTexture); + var srcCopyTexture = state.Get(MethodOffset.CopySrcTexture); + + Texture srcTexture = TextureManager.FindOrCreateTexture(srcCopyTexture); + + if (srcTexture == null) + { + return; + } + + // When the source texture that was found has a depth format, + // we must enforce the target texture also has a depth format, + // as copies between depth and color formats are not allowed. + if (srcTexture.Format == Format.D32Float) + { + dstCopyTexture.Format = RtFormat.D32Float; + } + + Texture dstTexture = TextureManager.FindOrCreateTexture(dstCopyTexture); + + if (dstTexture == null) + { + return; + } + + var control = state.Get(MethodOffset.CopyTextureControl); + + var region = state.Get(MethodOffset.CopyRegion); + + int srcX1 = (int)(region.SrcXF >> 32); + int srcY1 = (int)(region.SrcYF >> 32); + + int srcX2 = (int)((region.SrcXF + region.SrcWidthRF * region.DstWidth) >> 32); + int srcY2 = (int)((region.SrcYF + region.SrcHeightRF * region.DstHeight) >> 32); + + int dstX1 = region.DstX; + int dstY1 = region.DstY; + + int dstX2 = region.DstX + region.DstWidth; + int dstY2 = region.DstY + region.DstHeight; + + Extents2D srcRegion = new Extents2D( + srcX1 / srcTexture.Info.SamplesInX, + srcY1 / srcTexture.Info.SamplesInY, + srcX2 / srcTexture.Info.SamplesInX, + srcY2 / srcTexture.Info.SamplesInY); + + Extents2D dstRegion = new Extents2D( + dstX1 / dstTexture.Info.SamplesInX, + dstY1 / dstTexture.Info.SamplesInY, + dstX2 / dstTexture.Info.SamplesInX, + dstY2 / dstTexture.Info.SamplesInY); + + bool linearFilter = control.UnpackLinearFilter(); + + srcTexture.HostTexture.CopyTo(dstTexture.HostTexture, srcRegion, dstRegion, linearFilter); + + // For an out of bounds copy, we must ensure that the copy wraps to the next line, + // so for a copy from a 64x64 texture, in the region [32, 96[, there are 32 pixels that are + // outside the bounds of the texture. We fill the destination with the first 32 pixels + // of the next line on the source texture. + // This can be emulated with 2 copies (the first copy handles the region inside the bounds, + // the second handles the region outside of the bounds). + // We must also extend the source texture by one line to ensure we can wrap on the last line. + // This is required by the (guest) OpenGL driver. + if (srcRegion.X2 > srcTexture.Info.Width) + { + srcCopyTexture.Height++; + + srcTexture = TextureManager.FindOrCreateTexture(srcCopyTexture); + + srcRegion = new Extents2D( + srcRegion.X1 - srcTexture.Info.Width, + srcRegion.Y1 + 1, + srcRegion.X2 - srcTexture.Info.Width, + srcRegion.Y2 + 1); + + srcTexture.HostTexture.CopyTo(dstTexture.HostTexture, srcRegion, dstRegion, linearFilter); + } + + dstTexture.Modified = true; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Engine/MethodDraw.cs b/Ryujinx.Graphics.Gpu/Engine/MethodDraw.cs new file mode 100644 index 0000000000..b13cc9cab3 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Engine/MethodDraw.cs @@ -0,0 +1,169 @@ +using Ryujinx.Graphics.Gpu.State; +using Ryujinx.Graphics.Gpu.Image; + +namespace Ryujinx.Graphics.Gpu.Engine +{ + partial class Methods + { + private bool _drawIndexed; + + private int _firstIndex; + private int _indexCount; + + private bool _instancedDrawPending; + private bool _instancedIndexed; + + private int _instancedFirstIndex; + private int _instancedFirstVertex; + private int _instancedFirstInstance; + private int _instancedIndexCount; + private int _instancedDrawStateFirst; + private int _instancedDrawStateCount; + + private int _instanceIndex; + + /// + /// Primitive type of the current draw. + /// + public PrimitiveType PrimitiveType { get; private set; } + + /// + /// Finishes draw call. + /// This draws geometry on the bound buffers based on the current GPU state. + /// + /// Current GPU state + /// Method call argument + private void DrawEnd(GpuState state, int argument) + { + if (_instancedDrawPending) + { + _drawIndexed = false; + + return; + } + + UpdateState(state); + + bool instanced = _vsUsesInstanceId || _isAnyVbInstanced; + + if (instanced) + { + _instancedDrawPending = true; + + _instancedIndexed = _drawIndexed; + + _instancedFirstIndex = _firstIndex; + _instancedFirstVertex = state.Get(MethodOffset.FirstVertex); + _instancedFirstInstance = state.Get(MethodOffset.FirstInstance); + + _instancedIndexCount = _indexCount; + + var drawState = state.Get(MethodOffset.VertexBufferDrawState); + + _instancedDrawStateFirst = drawState.First; + _instancedDrawStateCount = drawState.Count; + + _drawIndexed = false; + + return; + } + + int firstInstance = state.Get(MethodOffset.FirstInstance); + + if (_drawIndexed) + { + _drawIndexed = false; + + int firstVertex = state.Get(MethodOffset.FirstVertex); + + _context.Renderer.Pipeline.DrawIndexed( + _indexCount, + 1, + _firstIndex, + firstVertex, + firstInstance); + } + else + { + var drawState = state.Get(MethodOffset.VertexBufferDrawState); + + _context.Renderer.Pipeline.Draw( + drawState.Count, + 1, + drawState.First, + firstInstance); + } + } + + /// + /// Starts draw. + /// This sets primitive type and instanced draw parameters. + /// + /// Current GPU state + /// Method call argument + private void DrawBegin(GpuState state, int argument) + { + if ((argument & (1 << 26)) != 0) + { + _instanceIndex++; + } + else if ((argument & (1 << 27)) == 0) + { + PerformDeferredDraws(); + + _instanceIndex = 0; + } + + PrimitiveType type = (PrimitiveType)(argument & 0xffff); + + _context.Renderer.Pipeline.SetPrimitiveTopology(type.Convert()); + + PrimitiveType = type; + } + + /// + /// Sets the index buffer count. + /// This also sets internal state that indicates that the next draw is an indexed draw. + /// + /// Current GPU state + /// Method call argument + private void SetIndexBufferCount(GpuState state, int argument) + { + _drawIndexed = true; + } + + /// + /// Perform any deferred draws. + /// This is used for instanced draws. + /// Since each instance is a separate draw, we defer the draw and accumulate the instance count. + /// Once we detect the last instanced draw, then we perform the host instanced draw, + /// with the accumulated instance count. + /// + public void PerformDeferredDraws() + { + // Perform any pending instanced draw. + if (_instancedDrawPending) + { + _instancedDrawPending = false; + + if (_instancedIndexed) + { + _context.Renderer.Pipeline.DrawIndexed( + _instancedIndexCount, + _instanceIndex + 1, + _instancedFirstIndex, + _instancedFirstVertex, + _instancedFirstInstance); + } + else + { + _context.Renderer.Pipeline.Draw( + _instancedDrawStateCount, + _instanceIndex + 1, + _instancedDrawStateFirst, + _instancedFirstInstance); + } + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Engine/MethodReport.cs b/Ryujinx.Graphics.Gpu/Engine/MethodReport.cs new file mode 100644 index 0000000000..173989c388 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Engine/MethodReport.cs @@ -0,0 +1,129 @@ +using Ryujinx.Common; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.State; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Gpu.Engine +{ + partial class Methods + { + private const int NsToTicksFractionNumerator = 384; + private const int NsToTicksFractionDenominator = 625; + + private ulong _runningCounter; + + /// + /// Writes a GPU counter to guest memory. + /// + /// Current GPU state + /// Method call argument + private void Report(GpuState state, int argument) + { + ReportMode mode = (ReportMode)(argument & 3); + + ReportCounterType type = (ReportCounterType)((argument >> 23) & 0x1f); + + switch (mode) + { + case ReportMode.Semaphore: ReportSemaphore(state); break; + case ReportMode.Counter: ReportCounter(state, type); break; + } + } + + /// + /// Writes a GPU semaphore value to guest memory. + /// + /// Current GPU state + private void ReportSemaphore(GpuState state) + { + var rs = state.Get(MethodOffset.ReportState); + + _context.MemoryAccessor.Write(rs.Address.Pack(), rs.Payload); + + _context.AdvanceSequence(); + } + + /// + /// Packed GPU counter data (including GPU timestamp) in memory. + /// + private struct CounterData + { + public ulong Counter; + public ulong Timestamp; + } + + /// + /// Writes a GPU counter to guest memory. + /// This also writes the current timestamp value. + /// + /// Current GPU state + /// Counter to be written to memory + private void ReportCounter(GpuState state, ReportCounterType type) + { + CounterData counterData = new CounterData(); + + ulong counter = 0; + + switch (type) + { + case ReportCounterType.Zero: + counter = 0; + break; + case ReportCounterType.SamplesPassed: + counter = _context.Renderer.GetCounter(CounterType.SamplesPassed); + break; + case ReportCounterType.PrimitivesGenerated: + counter = _context.Renderer.GetCounter(CounterType.PrimitivesGenerated); + break; + case ReportCounterType.TransformFeedbackPrimitivesWritten: + counter = _context.Renderer.GetCounter(CounterType.TransformFeedbackPrimitivesWritten); + break; + } + + ulong ticks; + + if (GraphicsConfig.FastGpuTime) + { + ticks = _runningCounter++; + } + else + { + ticks = ConvertNanosecondsToTicks((ulong)PerformanceCounter.ElapsedNanoseconds); + } + + counterData.Counter = counter; + counterData.Timestamp = ticks; + + Span counterDataSpan = MemoryMarshal.CreateSpan(ref counterData, 1); + + Span data = MemoryMarshal.Cast(counterDataSpan); + + var rs = state.Get(MethodOffset.ReportState); + + _context.MemoryAccessor.Write(rs.Address.Pack(), data); + } + + /// + /// Converts a nanoseconds timestamp value to Maxwell time ticks. + /// + /// + /// The frequency is 614400000 Hz. + /// + /// Timestamp in nanoseconds + /// Maxwell ticks + private static ulong ConvertNanosecondsToTicks(ulong nanoseconds) + { + // We need to divide first to avoid overflows. + // We fix up the result later by calculating the difference and adding + // that to the result. + ulong divided = nanoseconds / NsToTicksFractionDenominator; + + ulong rounded = divided * NsToTicksFractionDenominator; + + ulong errorBias = (nanoseconds - rounded) * NsToTicksFractionNumerator / NsToTicksFractionDenominator; + + return divided * NsToTicksFractionNumerator + errorBias; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Engine/MethodResetCounter.cs b/Ryujinx.Graphics.Gpu/Engine/MethodResetCounter.cs new file mode 100644 index 0000000000..79f8e1e8ae --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Engine/MethodResetCounter.cs @@ -0,0 +1,31 @@ +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.State; + +namespace Ryujinx.Graphics.Gpu.Engine +{ + partial class Methods + { + /// + /// Resets the value of an internal GPU counter back to zero. + /// + /// Current GPU state + /// Method call argument + private void ResetCounter(GpuState state, int argument) + { + ResetCounterType type = (ResetCounterType)argument; + + switch (type) + { + case ResetCounterType.SamplesPassed: + _context.Renderer.ResetCounter(CounterType.SamplesPassed); + break; + case ResetCounterType.PrimitivesGenerated: + _context.Renderer.ResetCounter(CounterType.PrimitivesGenerated); + break; + case ResetCounterType.TransformFeedbackPrimitivesWritten: + _context.Renderer.ResetCounter(CounterType.TransformFeedbackPrimitivesWritten); + break; + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Engine/MethodUniformBufferBind.cs b/Ryujinx.Graphics.Gpu/Engine/MethodUniformBufferBind.cs new file mode 100644 index 0000000000..3fee1fcf8b --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Engine/MethodUniformBufferBind.cs @@ -0,0 +1,83 @@ +using Ryujinx.Graphics.Gpu.State; + +namespace Ryujinx.Graphics.Gpu.Engine +{ + partial class Methods + { + /// + /// Binds a uniform buffer for the vertex shader stage. + /// + /// Current GPU state + /// Method call argument + private void UniformBufferBindVertex(GpuState state, int argument) + { + UniformBufferBind(state, argument, ShaderType.Vertex); + } + + /// + /// Binds a uniform buffer for the tessellation control shader stage. + /// + /// Current GPU state + /// Method call argument + private void UniformBufferBindTessControl(GpuState state, int argument) + { + UniformBufferBind(state, argument, ShaderType.TessellationControl); + } + + /// + /// Binds a uniform buffer for the tessellation evaluation shader stage. + /// + /// Current GPU state + /// Method call argument + private void UniformBufferBindTessEvaluation(GpuState state, int argument) + { + UniformBufferBind(state, argument, ShaderType.TessellationEvaluation); + } + + /// + /// Binds a uniform buffer for the geometry shader stage. + /// + /// Current GPU state + /// Method call argument + private void UniformBufferBindGeometry(GpuState state, int argument) + { + UniformBufferBind(state, argument, ShaderType.Geometry); + } + + /// + /// Binds a uniform buffer for the fragment shader stage. + /// + /// Current GPU state + /// Method call argument + private void UniformBufferBindFragment(GpuState state, int argument) + { + UniformBufferBind(state, argument, ShaderType.Fragment); + } + + /// + ///Binds a uniform buffer for the specified shader stage. + /// + /// Current GPU state + /// Method call argument + /// Shader stage that will access the uniform buffer + private void UniformBufferBind(GpuState state, int argument, ShaderType type) + { + bool enable = (argument & 1) != 0; + + int index = (argument >> 4) & 0x1f; + + if (enable) + { + var uniformBuffer = state.Get(MethodOffset.UniformBufferState); + + ulong address = uniformBuffer.Address.Pack(); + + BufferManager.SetGraphicsUniformBuffer((int)type, index, address, (uint)uniformBuffer.Size); + } + else + { + BufferManager.SetGraphicsUniformBuffer((int)type, index, 0, 0); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Engine/MethodUniformBufferUpdate.cs b/Ryujinx.Graphics.Gpu/Engine/MethodUniformBufferUpdate.cs new file mode 100644 index 0000000000..524f5e0399 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Engine/MethodUniformBufferUpdate.cs @@ -0,0 +1,23 @@ +using Ryujinx.Graphics.Gpu.State; + +namespace Ryujinx.Graphics.Gpu.Engine +{ + partial class Methods + { + /// + /// Updates the uniform buffer data with inline data. + /// + /// Current GPU state + /// New uniform buffer data word + private void UniformBufferUpdate(GpuState state, int argument) + { + var uniformBuffer = state.Get(MethodOffset.UniformBufferState); + + _context.MemoryAccessor.Write(uniformBuffer.Address.Pack() + (uint)uniformBuffer.Offset, argument); + + state.SetUniformBufferOffset(uniformBuffer.Offset + 4); + + _context.AdvanceSequence(); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Engine/Methods.cs b/Ryujinx.Graphics.Gpu/Engine/Methods.cs new file mode 100644 index 0000000000..823ac878a6 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Engine/Methods.cs @@ -0,0 +1,890 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Image; +using Ryujinx.Graphics.Gpu.Memory; +using Ryujinx.Graphics.Gpu.Shader; +using Ryujinx.Graphics.Gpu.State; +using Ryujinx.Graphics.Shader; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Gpu.Engine +{ + using Texture = Image.Texture; + + /// + /// GPU method implementations. + /// + partial class Methods + { + private readonly GpuContext _context; + private readonly ShaderProgramInfo[] _currentProgramInfo; + + /// + /// In-memory shader cache. + /// + public ShaderCache ShaderCache { get; } + + /// + /// GPU buffer manager. + /// + public BufferManager BufferManager { get; } + + /// + /// GPU texture manager. + /// + public TextureManager TextureManager { get; } + + private bool _isAnyVbInstanced; + private bool _vsUsesInstanceId; + + /// + /// Creates a new instance of the GPU methods class. + /// + /// GPU context + public Methods(GpuContext context) + { + _context = context; + + ShaderCache = new ShaderCache(_context); + + _currentProgramInfo = new ShaderProgramInfo[Constants.ShaderStages]; + + BufferManager = new BufferManager(context); + TextureManager = new TextureManager(context); + } + + /// + /// Register callback for GPU method calls that triggers an action on the GPU. + /// + /// GPU state where the triggers will be registered + public void RegisterCallbacks(GpuState state) + { + state.RegisterCallback(MethodOffset.LaunchDma, LaunchDma); + state.RegisterCallback(MethodOffset.LoadInlineData, LoadInlineData); + + state.RegisterCallback(MethodOffset.Dispatch, Dispatch); + + state.RegisterCallback(MethodOffset.CopyBuffer, CopyBuffer); + state.RegisterCallback(MethodOffset.CopyTexture, CopyTexture); + + state.RegisterCallback(MethodOffset.TextureBarrier, TextureBarrier); + state.RegisterCallback(MethodOffset.InvalidateTextures, InvalidateTextures); + state.RegisterCallback(MethodOffset.TextureBarrierTiled, TextureBarrierTiled); + + state.RegisterCallback(MethodOffset.ResetCounter, ResetCounter); + + state.RegisterCallback(MethodOffset.DrawEnd, DrawEnd); + state.RegisterCallback(MethodOffset.DrawBegin, DrawBegin); + + state.RegisterCallback(MethodOffset.IndexBufferCount, SetIndexBufferCount); + + state.RegisterCallback(MethodOffset.Clear, Clear); + + state.RegisterCallback(MethodOffset.Report, Report); + + state.RegisterCallback(MethodOffset.UniformBufferUpdateData, 16, UniformBufferUpdate); + + state.RegisterCallback(MethodOffset.UniformBufferBindVertex, UniformBufferBindVertex); + state.RegisterCallback(MethodOffset.UniformBufferBindTessControl, UniformBufferBindTessControl); + state.RegisterCallback(MethodOffset.UniformBufferBindTessEvaluation, UniformBufferBindTessEvaluation); + state.RegisterCallback(MethodOffset.UniformBufferBindGeometry, UniformBufferBindGeometry); + state.RegisterCallback(MethodOffset.UniformBufferBindFragment, UniformBufferBindFragment); + } + + /// + /// Updates host state based on the current guest GPU state. + /// + /// Guest GPU state + private void UpdateState(GpuState state) + { + // Shaders must be the first one to be updated if modified, because + // some of the other state depends on information from the currently + // bound shaders. + if (state.QueryModified(MethodOffset.ShaderBaseAddress, MethodOffset.ShaderState)) + { + UpdateShaderState(state); + } + + if (state.QueryModified(MethodOffset.RtColorState, + MethodOffset.RtDepthStencilState, + MethodOffset.RtControl, + MethodOffset.RtDepthStencilSize, + MethodOffset.RtDepthStencilEnable)) + { + UpdateRenderTargetState(state, useControl: true); + } + + if (state.QueryModified(MethodOffset.DepthTestEnable, + MethodOffset.DepthWriteEnable, + MethodOffset.DepthTestFunc)) + { + UpdateDepthTestState(state); + } + + if (state.QueryModified(MethodOffset.DepthMode, MethodOffset.ViewportTransform, MethodOffset.ViewportExtents)) + { + UpdateViewportTransform(state); + } + + if (state.QueryModified(MethodOffset.DepthBiasState, + MethodOffset.DepthBiasFactor, + MethodOffset.DepthBiasUnits, + MethodOffset.DepthBiasClamp)) + { + UpdateDepthBiasState(state); + } + + if (state.QueryModified(MethodOffset.StencilBackMasks, + MethodOffset.StencilTestState, + MethodOffset.StencilBackTestState)) + { + UpdateStencilTestState(state); + } + + // Pools. + if (state.QueryModified(MethodOffset.SamplerPoolState, MethodOffset.SamplerIndex)) + { + UpdateSamplerPoolState(state); + } + + if (state.QueryModified(MethodOffset.TexturePoolState)) + { + UpdateTexturePoolState(state); + } + + // Input assembler state. + if (state.QueryModified(MethodOffset.VertexAttribState)) + { + UpdateVertexAttribState(state); + } + + if (state.QueryModified(MethodOffset.PrimitiveRestartState)) + { + UpdatePrimitiveRestartState(state); + } + + if (state.QueryModified(MethodOffset.IndexBufferState)) + { + UpdateIndexBufferState(state); + } + + if (state.QueryModified(MethodOffset.VertexBufferDrawState, + MethodOffset.VertexBufferInstanced, + MethodOffset.VertexBufferState, + MethodOffset.VertexBufferEndAddress)) + { + UpdateVertexBufferState(state); + } + + if (state.QueryModified(MethodOffset.FaceState)) + { + UpdateFaceState(state); + } + + if (state.QueryModified(MethodOffset.RtColorMaskShared, MethodOffset.RtColorMask)) + { + UpdateRtColorMask(state); + } + + if (state.QueryModified(MethodOffset.BlendIndependent, + MethodOffset.BlendStateCommon, + MethodOffset.BlendEnableCommon, + MethodOffset.BlendEnable, + MethodOffset.BlendState)) + { + UpdateBlendState(state); + } + + CommitBindings(); + } + + /// + /// Ensures that the bindings are visible to the host GPU. + /// Note: this actually performs the binding using the host graphics API. + /// + private void CommitBindings() + { + UpdateStorageBuffers(); + + BufferManager.CommitBindings(); + TextureManager.CommitGraphicsBindings(); + } + + /// + /// Updates storage buffer bindings. + /// + private void UpdateStorageBuffers() + { + for (int stage = 0; stage < _currentProgramInfo.Length; stage++) + { + ShaderProgramInfo info = _currentProgramInfo[stage]; + + if (info == null) + { + continue; + } + + for (int index = 0; index < info.SBuffers.Count; index++) + { + BufferDescriptor sb = info.SBuffers[index]; + + ulong sbDescAddress = BufferManager.GetGraphicsUniformBufferAddress(stage, 0); + + int sbDescOffset = 0x110 + stage * 0x100 + sb.Slot * 0x10; + + sbDescAddress += (ulong)sbDescOffset; + + ReadOnlySpan sbDescriptorData = _context.PhysicalMemory.GetSpan(sbDescAddress, 0x10); + + SbDescriptor sbDescriptor = MemoryMarshal.Cast(sbDescriptorData)[0]; + + BufferManager.SetGraphicsStorageBuffer(stage, sb.Slot, sbDescriptor.PackAddress(), (uint)sbDescriptor.Size); + } + } + } + + /// + /// Updates render targets (color and depth-stencil buffers) based on current render target state. + /// + /// Current GPU state + /// Use draw buffers information from render target control register + private void UpdateRenderTargetState(GpuState state, bool useControl) + { + var rtControl = state.Get(MethodOffset.RtControl); + + int count = useControl ? rtControl.UnpackCount() : Constants.TotalRenderTargets; + + var msaaMode = state.Get(MethodOffset.RtMsaaMode); + + int samplesInX = msaaMode.SamplesInX(); + int samplesInY = msaaMode.SamplesInY(); + + for (int index = 0; index < Constants.TotalRenderTargets; index++) + { + int rtIndex = useControl ? rtControl.UnpackPermutationIndex(index) : index; + + var colorState = state.Get(MethodOffset.RtColorState, rtIndex); + + if (index >= count || !IsRtEnabled(colorState)) + { + TextureManager.SetRenderTargetColor(index, null); + + continue; + } + + Texture color = TextureManager.FindOrCreateTexture(colorState, samplesInX, samplesInY); + + TextureManager.SetRenderTargetColor(index, color); + + if (color != null) + { + color.Modified = true; + } + } + + bool dsEnable = state.Get(MethodOffset.RtDepthStencilEnable); + + Texture depthStencil = null; + + if (dsEnable) + { + var dsState = state.Get(MethodOffset.RtDepthStencilState); + var dsSize = state.Get (MethodOffset.RtDepthStencilSize); + + depthStencil = TextureManager.FindOrCreateTexture(dsState, dsSize, samplesInX, samplesInY); + } + + TextureManager.SetRenderTargetDepthStencil(depthStencil); + + if (depthStencil != null) + { + depthStencil.Modified = true; + } + } + + /// + /// Checks if a render target color buffer is used. + /// + /// Color buffer information + /// True if the specified buffer is enabled/used, false otherwise + private static bool IsRtEnabled(RtColorState colorState) + { + // Colors are disabled by writing 0 to the format. + return colorState.Format != 0 && colorState.WidthOrStride != 0; + } + + /// + /// Updates host depth test state based on current GPU state. + /// + /// Current GPU state + private void UpdateDepthTestState(GpuState state) + { + _context.Renderer.Pipeline.SetDepthTest(new DepthTestDescriptor( + state.Get(MethodOffset.DepthTestEnable), + state.Get(MethodOffset.DepthWriteEnable), + state.Get(MethodOffset.DepthTestFunc))); + } + + /// + /// Updates host viewport transform and clipping state based on current GPU state. + /// + /// Current GPU state + private void UpdateViewportTransform(GpuState state) + { + DepthMode depthMode = state.Get(MethodOffset.DepthMode); + + _context.Renderer.Pipeline.SetDepthMode(depthMode); + + bool flipY = (state.Get(MethodOffset.YControl) & 1) != 0; + + float yFlip = flipY ? -1 : 1; + + Viewport[] viewports = new Viewport[Constants.TotalViewports]; + + for (int index = 0; index < Constants.TotalViewports; index++) + { + var transform = state.Get(MethodOffset.ViewportTransform, index); + var extents = state.Get (MethodOffset.ViewportExtents, index); + + float x = transform.TranslateX - MathF.Abs(transform.ScaleX); + float y = transform.TranslateY - MathF.Abs(transform.ScaleY); + + float width = transform.ScaleX * 2; + float height = transform.ScaleY * 2 * yFlip; + + RectangleF region = new RectangleF(x, y, width, height); + + viewports[index] = new Viewport( + region, + transform.UnpackSwizzleX(), + transform.UnpackSwizzleY(), + transform.UnpackSwizzleZ(), + transform.UnpackSwizzleW(), + extents.DepthNear, + extents.DepthFar); + } + + _context.Renderer.Pipeline.SetViewports(0, viewports); + } + + /// + /// Updates host depth bias (also called polygon offset) state based on current GPU state. + /// + /// Current GPU state + private void UpdateDepthBiasState(GpuState state) + { + var depthBias = state.Get(MethodOffset.DepthBiasState); + + float factor = state.Get(MethodOffset.DepthBiasFactor); + float units = state.Get(MethodOffset.DepthBiasUnits); + float clamp = state.Get(MethodOffset.DepthBiasClamp); + + PolygonModeMask enables; + + enables = (depthBias.PointEnable ? PolygonModeMask.Point : 0); + enables |= (depthBias.LineEnable ? PolygonModeMask.Line : 0); + enables |= (depthBias.FillEnable ? PolygonModeMask.Fill : 0); + + _context.Renderer.Pipeline.SetDepthBias(enables, factor, units, clamp); + } + + /// + /// Updates host stencil test state based on current GPU state. + /// + /// Current GPU state + private void UpdateStencilTestState(GpuState state) + { + var backMasks = state.Get (MethodOffset.StencilBackMasks); + var test = state.Get (MethodOffset.StencilTestState); + var backTest = state.Get(MethodOffset.StencilBackTestState); + + CompareOp backFunc; + StencilOp backSFail; + StencilOp backDpPass; + StencilOp backDpFail; + int backFuncRef; + int backFuncMask; + int backMask; + + if (backTest.TwoSided) + { + backFunc = backTest.BackFunc; + backSFail = backTest.BackSFail; + backDpPass = backTest.BackDpPass; + backDpFail = backTest.BackDpFail; + backFuncRef = backMasks.FuncRef; + backFuncMask = backMasks.FuncMask; + backMask = backMasks.Mask; + } + else + { + backFunc = test.FrontFunc; + backSFail = test.FrontSFail; + backDpPass = test.FrontDpPass; + backDpFail = test.FrontDpFail; + backFuncRef = test.FrontFuncRef; + backFuncMask = test.FrontFuncMask; + backMask = test.FrontMask; + } + + _context.Renderer.Pipeline.SetStencilTest(new StencilTestDescriptor( + test.Enable, + test.FrontFunc, + test.FrontSFail, + test.FrontDpPass, + test.FrontDpFail, + test.FrontFuncRef, + test.FrontFuncMask, + test.FrontMask, + backFunc, + backSFail, + backDpPass, + backDpFail, + backFuncRef, + backFuncMask, + backMask)); + } + + /// + /// Updates current sampler pool address and size based on guest GPU state. + /// + /// Current GPU state + private void UpdateSamplerPoolState(GpuState state) + { + var texturePool = state.Get(MethodOffset.TexturePoolState); + var samplerPool = state.Get(MethodOffset.SamplerPoolState); + + var samplerIndex = state.Get(MethodOffset.SamplerIndex); + + int maximumId = samplerIndex == SamplerIndex.ViaHeaderIndex + ? texturePool.MaximumId + : samplerPool.MaximumId; + + TextureManager.SetGraphicsSamplerPool(samplerPool.Address.Pack(), maximumId, samplerIndex); + } + + /// + /// Updates current texture pool address and size based on guest GPU state. + /// + /// Current GPU state + private void UpdateTexturePoolState(GpuState state) + { + var texturePool = state.Get(MethodOffset.TexturePoolState); + + TextureManager.SetGraphicsTexturePool(texturePool.Address.Pack(), texturePool.MaximumId); + + TextureManager.SetGraphicsTextureBufferIndex(state.Get(MethodOffset.TextureBufferIndex)); + } + + /// + /// Updates host vertex attributes based on guest GPU state. + /// + /// Current GPU state + private void UpdateVertexAttribState(GpuState state) + { + VertexAttribDescriptor[] vertexAttribs = new VertexAttribDescriptor[16]; + + for (int index = 0; index < 16; index++) + { + var vertexAttrib = state.Get(MethodOffset.VertexAttribState, index); + + if (!FormatTable.TryGetAttribFormat(vertexAttrib.UnpackFormat(), out Format format)) + { + Logger.PrintDebug(LogClass.Gpu, $"Invalid attribute format 0x{vertexAttrib.UnpackFormat():X}."); + + format = Format.R32G32B32A32Float; + } + + vertexAttribs[index] = new VertexAttribDescriptor( + vertexAttrib.UnpackBufferIndex(), + vertexAttrib.UnpackOffset(), + format); + } + + _context.Renderer.Pipeline.SetVertexAttribs(vertexAttribs); + } + + /// + /// Updates host primitive restart based on guest GPU state. + /// + /// Current GPU state + private void UpdatePrimitiveRestartState(GpuState state) + { + PrimitiveRestartState primitiveRestart = state.Get(MethodOffset.PrimitiveRestartState); + + _context.Renderer.Pipeline.SetPrimitiveRestart( + primitiveRestart.Enable, + primitiveRestart.Index); + } + + /// + /// Updates host index buffer binding based on guest GPU state. + /// + /// Current GPU state + private void UpdateIndexBufferState(GpuState state) + { + var indexBuffer = state.Get(MethodOffset.IndexBufferState); + + _firstIndex = indexBuffer.First; + _indexCount = indexBuffer.Count; + + if (_indexCount == 0) + { + return; + } + + ulong gpuVa = indexBuffer.Address.Pack(); + + // Do not use the end address to calculate the size, because + // the result may be much larger than the real size of the index buffer. + ulong size = (ulong)(_firstIndex + _indexCount); + + switch (indexBuffer.Type) + { + case IndexType.UShort: size *= 2; break; + case IndexType.UInt: size *= 4; break; + } + + BufferManager.SetIndexBuffer(gpuVa, size, indexBuffer.Type); + + // The index buffer affects the vertex buffer size calculation, we + // need to ensure that they are updated. + UpdateVertexBufferState(state); + } + + /// + /// Updates host vertex buffer bindings based on guest GPU state. + /// + /// Current GPU state + private void UpdateVertexBufferState(GpuState state) + { + _isAnyVbInstanced = false; + + for (int index = 0; index < 16; index++) + { + var vertexBuffer = state.Get(MethodOffset.VertexBufferState, index); + + if (!vertexBuffer.UnpackEnable()) + { + BufferManager.SetVertexBuffer(index, 0, 0, 0, 0); + + continue; + } + + GpuVa endAddress = state.Get(MethodOffset.VertexBufferEndAddress, index); + + ulong address = vertexBuffer.Address.Pack(); + + int stride = vertexBuffer.UnpackStride(); + + bool instanced = state.Get(MethodOffset.VertexBufferInstanced + index); + + int divisor = instanced ? vertexBuffer.Divisor : 0; + + _isAnyVbInstanced |= divisor != 0; + + ulong size; + + if (_drawIndexed || stride == 0 || instanced) + { + // This size may be (much) larger than the real vertex buffer size. + // Avoid calculating it this way, unless we don't have any other option. + size = endAddress.Pack() - address + 1; + } + else + { + // For non-indexed draws, we can guess the size from the vertex count + // and stride. + int firstInstance = state.Get(MethodOffset.FirstInstance); + + var drawState = state.Get(MethodOffset.VertexBufferDrawState); + + size = (ulong)((firstInstance + drawState.First + drawState.Count) * stride); + } + + BufferManager.SetVertexBuffer(index, address, size, stride, divisor); + } + } + + /// + /// Updates host face culling and orientation based on guest GPU state. + /// + /// Current GPU state + private void UpdateFaceState(GpuState state) + { + var face = state.Get(MethodOffset.FaceState); + + _context.Renderer.Pipeline.SetFaceCulling(face.CullEnable, face.CullFace); + + _context.Renderer.Pipeline.SetFrontFace(face.FrontFace); + } + + /// + /// Updates host render target color masks, based on guest GPU state. + /// This defines which color channels are written to each color buffer. + /// + /// Current GPU state + private void UpdateRtColorMask(GpuState state) + { + bool rtColorMaskShared = state.Get(MethodOffset.RtColorMaskShared); + + uint[] componentMasks = new uint[Constants.TotalRenderTargets]; + + for (int index = 0; index < Constants.TotalRenderTargets; index++) + { + var colorMask = state.Get(MethodOffset.RtColorMask, rtColorMaskShared ? 0 : index); + + uint componentMask; + + componentMask = (colorMask.UnpackRed() ? 1u : 0u); + componentMask |= (colorMask.UnpackGreen() ? 2u : 0u); + componentMask |= (colorMask.UnpackBlue() ? 4u : 0u); + componentMask |= (colorMask.UnpackAlpha() ? 8u : 0u); + + componentMasks[index] = componentMask; + } + + _context.Renderer.Pipeline.SetRenderTargetColorMasks(componentMasks); + } + + /// + /// Updates host render target color buffer blending state, based on guest state. + /// + /// Current GPU state + private void UpdateBlendState(GpuState state) + { + bool blendIndependent = state.Get(MethodOffset.BlendIndependent); + + for (int index = 0; index < 8; index++) + { + BlendDescriptor descriptor; + + if (blendIndependent) + { + bool enable = state.Get (MethodOffset.BlendEnable, index); + var blend = state.Get(MethodOffset.BlendState, index); + + descriptor = new BlendDescriptor( + enable, + blend.ColorOp, + blend.ColorSrcFactor, + blend.ColorDstFactor, + blend.AlphaOp, + blend.AlphaSrcFactor, + blend.AlphaDstFactor); + } + else + { + bool enable = state.Get (MethodOffset.BlendEnable, 0); + var blend = state.Get(MethodOffset.BlendStateCommon); + + descriptor = new BlendDescriptor( + enable, + blend.ColorOp, + blend.ColorSrcFactor, + blend.ColorDstFactor, + blend.AlphaOp, + blend.AlphaSrcFactor, + blend.AlphaDstFactor); + } + + _context.Renderer.Pipeline.SetBlendState(index, descriptor); + } + } + + /// + /// Storage buffer address and size information. + /// + private struct SbDescriptor + { + public uint AddressLow; + public uint AddressHigh; + public int Size; + public int Padding; + + public ulong PackAddress() + { + return AddressLow | ((ulong)AddressHigh << 32); + } + } + + /// + /// Updates host shaders based on the guest GPU state. + /// + /// Current GPU state + private void UpdateShaderState(GpuState state) + { + ShaderAddresses addresses = new ShaderAddresses(); + + Span addressesSpan = MemoryMarshal.CreateSpan(ref addresses, 1); + + Span addressesArray = MemoryMarshal.Cast(addressesSpan); + + ulong baseAddress = state.Get(MethodOffset.ShaderBaseAddress).Pack(); + + for (int index = 0; index < 6; index++) + { + var shader = state.Get(MethodOffset.ShaderState, index); + + if (!shader.UnpackEnable() && index != 1) + { + continue; + } + + addressesArray[index] = baseAddress + shader.Offset; + } + + GraphicsShader gs = ShaderCache.GetGraphicsShader(state, addresses); + + _vsUsesInstanceId = gs.Shaders[0]?.Program.Info.UsesInstanceId ?? false; + + for (int stage = 0; stage < Constants.ShaderStages; stage++) + { + ShaderProgramInfo info = gs.Shaders[stage]?.Program.Info; + + _currentProgramInfo[stage] = info; + + if (info == null) + { + continue; + } + + var textureBindings = new TextureBindingInfo[info.Textures.Count]; + + for (int index = 0; index < info.Textures.Count; index++) + { + var descriptor = info.Textures[index]; + + Target target = GetTarget(descriptor.Type); + + if (descriptor.IsBindless) + { + textureBindings[index] = new TextureBindingInfo(target, descriptor.CbufSlot, descriptor.CbufOffset); + } + else + { + textureBindings[index] = new TextureBindingInfo(target, descriptor.HandleIndex); + } + } + + TextureManager.SetGraphicsTextures(stage, textureBindings); + + var imageBindings = new TextureBindingInfo[info.Images.Count]; + + for (int index = 0; index < info.Images.Count; index++) + { + var descriptor = info.Images[index]; + + Target target = GetTarget(descriptor.Type); + + imageBindings[index] = new TextureBindingInfo(target, descriptor.HandleIndex); + } + + TextureManager.SetGraphicsImages(stage, imageBindings); + + uint sbEnableMask = 0; + uint ubEnableMask = 0; + + for (int index = 0; index < info.SBuffers.Count; index++) + { + sbEnableMask |= 1u << info.SBuffers[index].Slot; + } + + for (int index = 0; index < info.CBuffers.Count; index++) + { + ubEnableMask |= 1u << info.CBuffers[index].Slot; + } + + BufferManager.SetGraphicsStorageBufferEnableMask(stage, sbEnableMask); + BufferManager.SetGraphicsUniformBufferEnableMask(stage, ubEnableMask); + } + + _context.Renderer.Pipeline.SetProgram(gs.HostProgram); + } + + /// + /// Gets texture target from a sampler type. + /// + /// Sampler type + /// Texture target value + private static Target GetTarget(SamplerType type) + { + type &= ~(SamplerType.Indexed | SamplerType.Shadow); + + switch (type) + { + case SamplerType.Texture1D: + return Target.Texture1D; + + case SamplerType.TextureBuffer: + return Target.TextureBuffer; + + case SamplerType.Texture1D | SamplerType.Array: + return Target.Texture1DArray; + + case SamplerType.Texture2D: + return Target.Texture2D; + + case SamplerType.Texture2D | SamplerType.Array: + return Target.Texture2DArray; + + case SamplerType.Texture2D | SamplerType.Multisample: + return Target.Texture2DMultisample; + + case SamplerType.Texture2D | SamplerType.Multisample | SamplerType.Array: + return Target.Texture2DMultisampleArray; + + case SamplerType.Texture3D: + return Target.Texture3D; + + case SamplerType.TextureCube: + return Target.Cubemap; + + case SamplerType.TextureCube | SamplerType.Array: + return Target.CubemapArray; + } + + Logger.PrintWarning(LogClass.Gpu, $"Invalid sampler type \"{type}\"."); + + return Target.Texture2D; + } + + /// + /// Issues a texture barrier. + /// This waits until previous texture writes from the GPU to finish, before + /// performing new operations with said textures. + /// + /// Current GPU state (unused) + /// Method call argument (unused) + private void TextureBarrier(GpuState state, int argument) + { + _context.Renderer.Pipeline.TextureBarrier(); + } + + /// + /// Invalidates all modified textures on the cache. + /// + /// Current GPU state (unused) + /// Method call argument (unused) + private void InvalidateTextures(GpuState state, int argument) + { + TextureManager.Flush(); + } + + /// + /// Issues a texture barrier. + /// This waits until previous texture writes from the GPU to finish, before + /// performing new operations with said textures. + /// This performs a per-tile wait, it is only valid if both the previous write + /// and current access has the same access patterns. + /// This may be faster than the regular barrier on tile-based rasterizers. + /// + /// Current GPU state (unused) + /// Method call argument (unused) + private void TextureBarrierTiled(GpuState state, int argument) + { + _context.Renderer.Pipeline.TextureBarrierTiled(); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/GpuContext.cs b/Ryujinx.Graphics.Gpu/GpuContext.cs new file mode 100644 index 0000000000..b644c54ddb --- /dev/null +++ b/Ryujinx.Graphics.Gpu/GpuContext.cs @@ -0,0 +1,121 @@ +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Engine; +using Ryujinx.Graphics.Gpu.Memory; +using System; + +namespace Ryujinx.Graphics.Gpu +{ + /// + /// GPU emulation context. + /// + public sealed class GpuContext : IDisposable + { + /// + /// Host renderer. + /// + public IRenderer Renderer { get; } + + /// + /// Physical memory access (it actually accesses the process memory, not actual physical memory). + /// + internal PhysicalMemory PhysicalMemory { get; private set; } + + /// + /// GPU memory manager. + /// + public MemoryManager MemoryManager { get; } + + /// + /// GPU memory accessor. + /// + public MemoryAccessor MemoryAccessor { get; } + + /// + /// GPU engine methods processing. + /// + internal Methods Methods { get; } + + /// + /// GPU commands FIFO. + /// + internal NvGpuFifo Fifo { get; } + + /// + /// DMA pusher. + /// + public DmaPusher DmaPusher { get; } + + /// + /// Presentation window. + /// + public Window Window { get; } + + /// + /// Internal sequence number, used to avoid needless resource data updates + /// in the middle of a command buffer before synchronizations. + /// + internal int SequenceNumber { get; private set; } + + private readonly Lazy _caps; + + /// + /// Host hardware capabilities. + /// + internal Capabilities Capabilities => _caps.Value; + + /// + /// Creates a new instance of the GPU emulation context. + /// + /// Host renderer + public GpuContext(IRenderer renderer) + { + Renderer = renderer; + + MemoryManager = new MemoryManager(); + + MemoryAccessor = new MemoryAccessor(this); + + Methods = new Methods(this); + + Fifo = new NvGpuFifo(this); + + DmaPusher = new DmaPusher(this); + + Window = new Window(this); + + _caps = new Lazy(Renderer.GetCapabilities); + } + + /// + /// Advances internal sequence number. + /// This forces the update of any modified GPU resource. + /// + internal void AdvanceSequence() + { + SequenceNumber++; + } + + /// + /// Sets the process memory manager, after the application process is initialized. + /// This is required for any GPU memory access. + /// + /// CPU memory manager + public void SetVmm(ARMeilleure.Memory.MemoryManager cpuMemory) + { + PhysicalMemory = new PhysicalMemory(cpuMemory); + } + + /// + /// Disposes all GPU resources currently cached. + /// It's an error to push any GPU commands after disposal. + /// Additionally, the GPU commands FIFO must be empty for disposal, + /// and processing of all commands must have finished. + /// + public void Dispose() + { + Methods.ShaderCache.Dispose(); + Methods.BufferManager.Dispose(); + Methods.TextureManager.Dispose(); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/GraphicsConfig.cs b/Ryujinx.Graphics.Gpu/GraphicsConfig.cs new file mode 100644 index 0000000000..468d3a3426 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/GraphicsConfig.cs @@ -0,0 +1,21 @@ +namespace Ryujinx.Graphics.Gpu +{ + /// + /// General GPU and graphics configuration. + /// + public static class GraphicsConfig + { + /// + /// Base directory used to write shader code dumps. + /// Set to null to disable code dumping. + /// + public static string ShadersDumpPath; + + /// + /// Fast GPU time calculates the internal GPU time ticks as if the GPU was capable of + /// processing commands almost instantly, instead of using the host timer. + /// This can avoid lower resolution on some games when GPU performance is poor. + /// + public static bool FastGpuTime = true; + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs b/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs new file mode 100644 index 0000000000..d66eab93f0 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs @@ -0,0 +1,87 @@ +using System.Collections; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// A texture cache that automatically removes older textures that are not used for some time. + /// The cache works with a rotated list with a fixed size. When new textures are added, the + /// old ones at the bottom of the list are deleted. + /// + class AutoDeleteCache : IEnumerable + { + private const int MaxCapacity = 2048; + + private readonly LinkedList _textures; + + /// + /// Creates a new instance of the automatic deletion cache. + /// + public AutoDeleteCache() + { + _textures = new LinkedList(); + } + + /// + /// Adds a new texture to the cache, even if the texture added is already on the cache. + /// + /// + /// Using this method is only recommended if you know that the texture is not yet on the cache, + /// otherwise it would store the same texture more than once. + /// + /// The texture to be added to the cache + public void Add(Texture texture) + { + texture.IncrementReferenceCount(); + + texture.CacheNode = _textures.AddLast(texture); + + if (_textures.Count > MaxCapacity) + { + Texture oldestTexture = _textures.First.Value; + + _textures.RemoveFirst(); + + oldestTexture.DecrementReferenceCount(); + + oldestTexture.CacheNode = null; + } + } + + /// + /// Adds a new texture to the cache, or just moves it to the top of the list if the + /// texture is already on the cache. + /// + /// + /// Moving the texture to the top of the list prevents it from being deleted, + /// as the textures on the bottom of the list are deleted when new ones are added. + /// + /// The texture to be added, or moved to the top + public void Lift(Texture texture) + { + if (texture.CacheNode != null) + { + if (texture.CacheNode != _textures.Last) + { + _textures.Remove(texture.CacheNode); + + texture.CacheNode = _textures.AddLast(texture); + } + } + else + { + Add(texture); + } + } + + public IEnumerator GetEnumerator() + { + return _textures.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return _textures.GetEnumerator(); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Image/FormatInfo.cs b/Ryujinx.Graphics.Gpu/Image/FormatInfo.cs new file mode 100644 index 0000000000..12f3aecbbd --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Image/FormatInfo.cs @@ -0,0 +1,65 @@ +using Ryujinx.Graphics.GAL; + +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// Represents texture format information. + /// + struct FormatInfo + { + /// + /// A default, generic RGBA8 texture format. + /// + public static FormatInfo Default { get; } = new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4); + + /// + /// The format of the texture data. + /// + public Format Format { get; } + + /// + /// The block width for compressed formats. + /// + /// + /// Must be 1 for non-compressed formats. + /// + public int BlockWidth { get; } + + /// + /// The block height for compressed formats. + /// + /// + /// Must be 1 for non-compressed formats. + /// + public int BlockHeight { get; } + + /// + /// The number of bytes occupied by a single pixel in memory of the texture data. + /// + public int BytesPerPixel { get; } + + /// + /// Whenever or not the texture format is a compressed format. Determined from block size. + /// + public bool IsCompressed => (BlockWidth | BlockHeight) != 1; + + /// + /// Constructs the texture format info structure. + /// + /// The format of the texture data + /// The block width for compressed formats. Must be 1 for non-compressed formats + /// The block height for compressed formats. Must be 1 for non-compressed formats + /// The number of bytes occupied by a single pixel in memory of the texture data + public FormatInfo( + Format format, + int blockWidth, + int blockHeight, + int bytesPerPixel) + { + Format = format; + BlockWidth = blockWidth; + BlockHeight = blockHeight; + BytesPerPixel = bytesPerPixel; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Image/FormatTable.cs b/Ryujinx.Graphics.Gpu/Image/FormatTable.cs new file mode 100644 index 0000000000..517caecaf8 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Image/FormatTable.cs @@ -0,0 +1,217 @@ +using Ryujinx.Graphics.GAL; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// Contains format tables, for texture and vertex attribute formats. + /// + static class FormatTable + { + private static Dictionary _textureFormats = new Dictionary() + { + { 0x2491d, new FormatInfo(Format.R8Unorm, 1, 1, 1) }, + { 0x1249d, new FormatInfo(Format.R8Snorm, 1, 1, 1) }, + { 0x4921d, new FormatInfo(Format.R8Uint, 1, 1, 1) }, + { 0x36d9d, new FormatInfo(Format.R8Sint, 1, 1, 1) }, + { 0x7ff9b, new FormatInfo(Format.R16Float, 1, 1, 2) }, + { 0x2491b, new FormatInfo(Format.R16Unorm, 1, 1, 2) }, + { 0x1249b, new FormatInfo(Format.R16Snorm, 1, 1, 2) }, + { 0x4921b, new FormatInfo(Format.R16Uint, 1, 1, 2) }, + { 0x36d9b, new FormatInfo(Format.R16Sint, 1, 1, 2) }, + { 0x7ff8f, new FormatInfo(Format.R32Float, 1, 1, 4) }, + { 0x4920f, new FormatInfo(Format.R32Uint, 1, 1, 4) }, + { 0x36d8f, new FormatInfo(Format.R32Sint, 1, 1, 4) }, + { 0x24918, new FormatInfo(Format.R8G8Unorm, 1, 1, 2) }, + { 0x12498, new FormatInfo(Format.R8G8Snorm, 1, 1, 2) }, + { 0x49218, new FormatInfo(Format.R8G8Uint, 1, 1, 2) }, + { 0x36d98, new FormatInfo(Format.R8G8Sint, 1, 1, 2) }, + { 0x7ff8c, new FormatInfo(Format.R16G16Float, 1, 1, 4) }, + { 0x2490c, new FormatInfo(Format.R16G16Unorm, 1, 1, 4) }, + { 0x1248c, new FormatInfo(Format.R16G16Snorm, 1, 1, 4) }, + { 0x4920c, new FormatInfo(Format.R16G16Uint, 1, 1, 4) }, + { 0x36d8c, new FormatInfo(Format.R16G16Sint, 1, 1, 4) }, + { 0x7ff84, new FormatInfo(Format.R32G32Float, 1, 1, 8) }, + { 0x49204, new FormatInfo(Format.R32G32Uint, 1, 1, 8) }, + { 0x36d84, new FormatInfo(Format.R32G32Sint, 1, 1, 8) }, + { 0x7ff82, new FormatInfo(Format.R32G32B32Float, 1, 1, 12) }, + { 0x49202, new FormatInfo(Format.R32G32B32Uint, 1, 1, 12) }, + { 0x36d82, new FormatInfo(Format.R32G32B32Sint, 1, 1, 12) }, + { 0x24908, new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4) }, + { 0x12488, new FormatInfo(Format.R8G8B8A8Snorm, 1, 1, 4) }, + { 0x49208, new FormatInfo(Format.R8G8B8A8Uint, 1, 1, 4) }, + { 0x36d88, new FormatInfo(Format.R8G8B8A8Sint, 1, 1, 4) }, + { 0x7ff83, new FormatInfo(Format.R16G16B16A16Float, 1, 1, 8) }, + { 0x24903, new FormatInfo(Format.R16G16B16A16Unorm, 1, 1, 8) }, + { 0x12483, new FormatInfo(Format.R16G16B16A16Snorm, 1, 1, 8) }, + { 0x49203, new FormatInfo(Format.R16G16B16A16Uint, 1, 1, 8) }, + { 0x36d83, new FormatInfo(Format.R16G16B16A16Sint, 1, 1, 8) }, + { 0x7ff81, new FormatInfo(Format.R32G32B32A32Float, 1, 1, 16) }, + { 0x49201, new FormatInfo(Format.R32G32B32A32Uint, 1, 1, 16) }, + { 0x36d81, new FormatInfo(Format.R32G32B32A32Sint, 1, 1, 16) }, + { 0x2493a, new FormatInfo(Format.D16Unorm, 1, 1, 2) }, + { 0x7ffaf, new FormatInfo(Format.D32Float, 1, 1, 4) }, + { 0x24a29, new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4) }, + { 0x253b0, new FormatInfo(Format.D32FloatS8Uint, 1, 1, 8) }, + { 0xa4908, new FormatInfo(Format.R8G8B8A8Srgb, 1, 1, 4) }, + { 0x24912, new FormatInfo(Format.R4G4B4A4Unorm, 1, 1, 2) }, + { 0x24914, new FormatInfo(Format.R5G5B5A1Unorm, 1, 1, 2) }, + { 0x24915, new FormatInfo(Format.R5G6B5Unorm, 1, 1, 2) }, + { 0x24909, new FormatInfo(Format.R10G10B10A2Unorm, 1, 1, 4) }, + { 0x49209, new FormatInfo(Format.R10G10B10A2Uint, 1, 1, 4) }, + { 0x7ffa1, new FormatInfo(Format.R11G11B10Float, 1, 1, 4) }, + { 0x7ffa0, new FormatInfo(Format.R9G9B9E5Float, 1, 1, 4) }, + { 0x24924, new FormatInfo(Format.Bc1RgbaUnorm, 4, 4, 8) }, + { 0x24925, new FormatInfo(Format.Bc2Unorm, 4, 4, 16) }, + { 0x24926, new FormatInfo(Format.Bc3Unorm, 4, 4, 16) }, + { 0xa4924, new FormatInfo(Format.Bc1RgbaSrgb, 4, 4, 8) }, + { 0xa4925, new FormatInfo(Format.Bc2Srgb, 4, 4, 16) }, + { 0xa4926, new FormatInfo(Format.Bc3Srgb, 4, 4, 16) }, + { 0x24927, new FormatInfo(Format.Bc4Unorm, 4, 4, 8) }, + { 0x124a7, new FormatInfo(Format.Bc4Snorm, 4, 4, 8) }, + { 0x24928, new FormatInfo(Format.Bc5Unorm, 4, 4, 16) }, + { 0x124a8, new FormatInfo(Format.Bc5Snorm, 4, 4, 16) }, + { 0x24917, new FormatInfo(Format.Bc7Unorm, 4, 4, 16) }, + { 0xa4917, new FormatInfo(Format.Bc7Srgb, 4, 4, 16) }, + { 0x7ff90, new FormatInfo(Format.Bc6HSfloat, 4, 4, 16) }, + { 0x7ff91, new FormatInfo(Format.Bc6HUfloat, 4, 4, 16) }, + { 0x24940, new FormatInfo(Format.Astc4x4Unorm, 4, 4, 16) }, + { 0x24950, new FormatInfo(Format.Astc5x4Unorm, 5, 4, 16) }, + { 0x24941, new FormatInfo(Format.Astc5x5Unorm, 5, 5, 16) }, + { 0x24951, new FormatInfo(Format.Astc6x5Unorm, 6, 5, 16) }, + { 0x24942, new FormatInfo(Format.Astc6x6Unorm, 6, 6, 16) }, + { 0x24955, new FormatInfo(Format.Astc8x5Unorm, 8, 5, 16) }, + { 0x24952, new FormatInfo(Format.Astc8x6Unorm, 8, 6, 16) }, + { 0x24944, new FormatInfo(Format.Astc8x8Unorm, 8, 8, 16) }, + { 0x24956, new FormatInfo(Format.Astc10x5Unorm, 10, 5, 16) }, + { 0x24957, new FormatInfo(Format.Astc10x6Unorm, 10, 6, 16) }, + { 0x24953, new FormatInfo(Format.Astc10x8Unorm, 10, 8, 16) }, + { 0x24945, new FormatInfo(Format.Astc10x10Unorm, 10, 10, 16) }, + { 0x24954, new FormatInfo(Format.Astc12x10Unorm, 12, 10, 16) }, + { 0x24946, new FormatInfo(Format.Astc12x12Unorm, 12, 12, 16) }, + { 0xa4940, new FormatInfo(Format.Astc4x4Srgb, 4, 4, 16) }, + { 0xa4950, new FormatInfo(Format.Astc5x4Srgb, 5, 4, 16) }, + { 0xa4941, new FormatInfo(Format.Astc5x5Srgb, 5, 5, 16) }, + { 0xa4951, new FormatInfo(Format.Astc6x5Srgb, 6, 5, 16) }, + { 0xa4942, new FormatInfo(Format.Astc6x6Srgb, 6, 6, 16) }, + { 0xa4955, new FormatInfo(Format.Astc8x5Srgb, 8, 5, 16) }, + { 0xa4952, new FormatInfo(Format.Astc8x6Srgb, 8, 6, 16) }, + { 0xa4944, new FormatInfo(Format.Astc8x8Srgb, 8, 8, 16) }, + { 0xa4956, new FormatInfo(Format.Astc10x5Srgb, 10, 5, 16) }, + { 0xa4957, new FormatInfo(Format.Astc10x6Srgb, 10, 6, 16) }, + { 0xa4953, new FormatInfo(Format.Astc10x8Srgb, 10, 8, 16) }, + { 0xa4945, new FormatInfo(Format.Astc10x10Srgb, 10, 10, 16) }, + { 0xa4954, new FormatInfo(Format.Astc12x10Srgb, 12, 10, 16) }, + { 0xa4946, new FormatInfo(Format.Astc12x12Srgb, 12, 12, 16) }, + { 0x24913, new FormatInfo(Format.A1B5G5R5Unorm, 1, 1, 2) } + }; + + private static Dictionary _attribFormats = new Dictionary() + { + { 0x13a00000, Format.R8Unorm }, + { 0x0ba00000, Format.R8Snorm }, + { 0x23a00000, Format.R8Uint }, + { 0x1ba00000, Format.R8Sint }, + { 0x3b600000, Format.R16Float }, + { 0x13600000, Format.R16Unorm }, + { 0x0b600000, Format.R16Snorm }, + { 0x23600000, Format.R16Uint }, + { 0x1b600000, Format.R16Sint }, + { 0x3a400000, Format.R32Float }, + { 0x22400000, Format.R32Uint }, + { 0x1a400000, Format.R32Sint }, + { 0x13000000, Format.R8G8Unorm }, + { 0x0b000000, Format.R8G8Snorm }, + { 0x23000000, Format.R8G8Uint }, + { 0x1b000000, Format.R8G8Sint }, + { 0x39e00000, Format.R16G16Float }, + { 0x11e00000, Format.R16G16Unorm }, + { 0x09e00000, Format.R16G16Snorm }, + { 0x21e00000, Format.R16G16Uint }, + { 0x19e00000, Format.R16G16Sint }, + { 0x38800000, Format.R32G32Float }, + { 0x20800000, Format.R32G32Uint }, + { 0x18800000, Format.R32G32Sint }, + { 0x12600000, Format.R8G8B8Unorm }, + { 0x0a600000, Format.R8G8B8Snorm }, + { 0x22600000, Format.R8G8B8Uint }, + { 0x1a600000, Format.R8G8B8Sint }, + { 0x38a00000, Format.R16G16B16Float }, + { 0x10a00000, Format.R16G16B16Unorm }, + { 0x08a00000, Format.R16G16B16Snorm }, + { 0x20a00000, Format.R16G16B16Uint }, + { 0x18a00000, Format.R16G16B16Sint }, + { 0x38400000, Format.R32G32B32Float }, + { 0x20400000, Format.R32G32B32Uint }, + { 0x18400000, Format.R32G32B32Sint }, + { 0x11400000, Format.R8G8B8A8Unorm }, + { 0x09400000, Format.R8G8B8A8Snorm }, + { 0x21400000, Format.R8G8B8A8Uint }, + { 0x19400000, Format.R8G8B8A8Sint }, + { 0x38600000, Format.R16G16B16A16Float }, + { 0x10600000, Format.R16G16B16A16Unorm }, + { 0x08600000, Format.R16G16B16A16Snorm }, + { 0x20600000, Format.R16G16B16A16Uint }, + { 0x18600000, Format.R16G16B16A16Sint }, + { 0x38200000, Format.R32G32B32A32Float }, + { 0x20200000, Format.R32G32B32A32Uint }, + { 0x18200000, Format.R32G32B32A32Sint }, + { 0x16000000, Format.R10G10B10A2Unorm }, + { 0x26000000, Format.R10G10B10A2Uint }, + { 0x3e200000, Format.R11G11B10Float }, + { 0x2ba00000, Format.R8Uscaled }, + { 0x33a00000, Format.R8Sscaled }, + { 0x2b600000, Format.R16Uscaled }, + { 0x33600000, Format.R16Sscaled }, + { 0x2a400000, Format.R32Uscaled }, + { 0x32400000, Format.R32Sscaled }, + { 0x2b000000, Format.R8G8Uscaled }, + { 0x33000000, Format.R8G8Sscaled }, + { 0x29e00000, Format.R16G16Uscaled }, + { 0x31e00000, Format.R16G16Sscaled }, + { 0x28800000, Format.R32G32Uscaled }, + { 0x30800000, Format.R32G32Sscaled }, + { 0x2a600000, Format.R8G8B8Uscaled }, + { 0x32600000, Format.R8G8B8Sscaled }, + { 0x28a00000, Format.R16G16B16Uscaled }, + { 0x30a00000, Format.R16G16B16Sscaled }, + { 0x28400000, Format.R32G32B32Uscaled }, + { 0x30400000, Format.R32G32B32Sscaled }, + { 0x29400000, Format.R8G8B8A8Uscaled }, + { 0x31400000, Format.R8G8B8A8Sscaled }, + { 0x28600000, Format.R16G16B16A16Uscaled }, + { 0x30600000, Format.R16G16B16A16Sscaled }, + { 0x28200000, Format.R32G32B32A32Uscaled }, + { 0x30200000, Format.R32G32B32A32Sscaled }, + { 0x0e000000, Format.R10G10B10A2Snorm }, + { 0x1e000000, Format.R10G10B10A2Sint }, + { 0x2e000000, Format.R10G10B10A2Uscaled }, + { 0x36000000, Format.R10G10B10A2Sscaled } + }; + + /// + /// Try getting the texture format from an encoded format integer from the Maxwell texture descriptor. + /// + /// The encoded format integer from the texture descriptor + /// Indicates if the format is a sRGB format + /// The output texture format + /// True if the format is valid, false otherwise + public static bool TryGetTextureFormat(uint encoded, bool isSrgb, out FormatInfo format) + { + encoded |= (isSrgb ? 1u << 19 : 0u); + + return _textureFormats.TryGetValue(encoded, out format); + } + + /// + /// Try getting the vertex attribute format from an encoded format integer from Maxwell attribute registers. + /// + /// The encoded format integer from the attribute registers + /// The output vertex attribute format + /// True if the format is valid, false otherwise + public static bool TryGetAttribFormat(uint encoded, out Format format) + { + return _attribFormats.TryGetValue(encoded, out format); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Image/Pool.cs b/Ryujinx.Graphics.Gpu/Image/Pool.cs new file mode 100644 index 0000000000..7cf06d0b01 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Image/Pool.cs @@ -0,0 +1,143 @@ +using Ryujinx.Graphics.Gpu.Memory; +using System; + +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// Represents a pool of GPU resources, such as samplers or textures. + /// + /// Type of the GPU resource + abstract class Pool : IDisposable + { + protected const int DescriptorSize = 0x20; + + protected GpuContext Context; + + protected T[] Items; + + /// + /// The maximum ID value of resources on the pool (inclusive). + /// + /// + /// The maximum amount of resources on the pool is equal to this value plus one. + /// + public int MaximumId { get; } + + /// + /// The address of the pool in guest memory. + /// + public ulong Address { get; } + + /// + /// The size of the pool in bytes. + /// + public ulong Size { get; } + + public Pool(GpuContext context, ulong address, int maximumId) + { + Context = context; + MaximumId = maximumId; + + int count = maximumId + 1; + + ulong size = (ulong)(uint)count * DescriptorSize;; + + Items = new T[count]; + + Address = address; + Size = size; + } + + /// + /// Gets the GPU resource with the given ID. + /// + /// ID of the resource. This is effectively a zero-based index + /// The GPU resource with the given ID + public abstract T Get(int id); + + /// + /// Synchronizes host memory with guest memory. + /// This causes invalidation of pool entries, + /// if a modification of entries by the CPU is detected. + /// + public void SynchronizeMemory() + { + (ulong, ulong)[] modifiedRanges = Context.PhysicalMemory.GetModifiedRanges(Address, Size, ResourceName.TexturePool); + + for (int index = 0; index < modifiedRanges.Length; index++) + { + (ulong mAddress, ulong mSize) = modifiedRanges[index]; + + if (mAddress < Address) + { + mAddress = Address; + } + + ulong maxSize = Address + Size - mAddress; + + if (mSize > maxSize) + { + mSize = maxSize; + } + + InvalidateRangeImpl(mAddress, mSize); + } + } + + /// + /// Invalidates a range of memory of the GPU resource pool. + /// Entries that falls inside the speicified range will be invalidated, + /// causing all the data to be reloaded from guest memory. + /// + /// The start address of the range to invalidate + /// The size of the range to invalidate + public void InvalidateRange(ulong address, ulong size) + { + ulong endAddress = address + size; + + ulong texturePoolEndAddress = Address + Size; + + // If the range being invalidated is not overlapping the texture pool range, + // then we don't have anything to do, exit early. + if (address >= texturePoolEndAddress || endAddress <= Address) + { + return; + } + + if (address < Address) + { + address = Address; + } + + if (endAddress > texturePoolEndAddress) + { + endAddress = texturePoolEndAddress; + } + + size = endAddress - address; + + InvalidateRangeImpl(address, size); + } + + protected abstract void InvalidateRangeImpl(ulong address, ulong size); + + protected abstract void Delete(T item); + + /// + /// Performs the disposal of all resources stored on the pool. + /// It's an error to try using the pool after disposal. + /// + public void Dispose() + { + if (Items != null) + { + for (int index = 0; index < Items.Length; index++) + { + Delete(Items[index]); + } + + Items = null; + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Image/ReductionFilter.cs b/Ryujinx.Graphics.Gpu/Image/ReductionFilter.cs new file mode 100644 index 0000000000..1f7d9b0703 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Image/ReductionFilter.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// Represents a filter used with texture minification linear filtering. + /// + /// + /// This feature is only supported on NVIDIA GPUs. + /// + enum ReductionFilter + { + Average, + Minimum, + Maximum + } +} diff --git a/Ryujinx.Graphics.Gpu/Image/Sampler.cs b/Ryujinx.Graphics.Gpu/Image/Sampler.cs new file mode 100644 index 0000000000..23c6160e39 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Image/Sampler.cs @@ -0,0 +1,64 @@ +using Ryujinx.Graphics.GAL; +using System; + +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// Cached sampler entry for sampler pools. + /// + class Sampler : IDisposable + { + /// + /// Host sampler object. + /// + public ISampler HostSampler { get; } + + /// + /// Creates a new instance of the cached sampler. + /// + /// The GPU context the sampler belongs to + /// The Maxwell sampler descriptor + public Sampler(GpuContext context, SamplerDescriptor descriptor) + { + MinFilter minFilter = descriptor.UnpackMinFilter(); + MagFilter magFilter = descriptor.UnpackMagFilter(); + + AddressMode addressU = descriptor.UnpackAddressU(); + AddressMode addressV = descriptor.UnpackAddressV(); + AddressMode addressP = descriptor.UnpackAddressP(); + + CompareMode compareMode = descriptor.UnpackCompareMode(); + CompareOp compareOp = descriptor.UnpackCompareOp(); + + ColorF color = new ColorF(0, 0, 0, 0); + + float minLod = descriptor.UnpackMinLod(); + float maxLod = descriptor.UnpackMaxLod(); + float mipLodBias = descriptor.UnpackMipLodBias(); + + float maxAnisotropy = descriptor.UnpackMaxAnisotropy(); + + HostSampler = context.Renderer.CreateSampler(new SamplerCreateInfo( + minFilter, + magFilter, + addressU, + addressV, + addressP, + compareMode, + compareOp, + color, + minLod, + maxLod, + mipLodBias, + maxAnisotropy)); + } + + /// + /// Disposes the host sampler object. + /// + public void Dispose() + { + HostSampler.Dispose(); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Image/SamplerDescriptor.cs b/Ryujinx.Graphics.Gpu/Image/SamplerDescriptor.cs new file mode 100644 index 0000000000..80b1b70fd5 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Image/SamplerDescriptor.cs @@ -0,0 +1,237 @@ +using Ryujinx.Graphics.GAL; + +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// Maxwell sampler descriptor structure. + /// This structure defines the sampler descriptor as it is packed on the GPU sampler pool region. + /// + struct SamplerDescriptor + { + private static readonly float[] _f5ToF32ConversionLut = new float[] + { + 0.0f, + 0.055555556f, + 0.1f, + 0.13636364f, + 0.16666667f, + 0.1923077f, + 0.21428572f, + 0.23333333f, + 0.25f, + 0.2777778f, + 0.3f, + 0.3181818f, + 0.33333334f, + 0.34615386f, + 0.35714287f, + 0.36666667f, + 0.375f, + 0.3888889f, + 0.4f, + 0.4090909f, + 0.41666666f, + 0.42307693f, + 0.42857143f, + 0.43333334f, + 0.4375f, + 0.44444445f, + 0.45f, + 0.45454547f, + 0.45833334f, + 0.46153846f, + 0.4642857f, + 0.46666667f + }; + + private static readonly float[] _maxAnisotropyLut = new float[] + { + 1, 2, 4, 6, 8, 10, 12, 16 + }; + + private const float Frac8ToF32 = 1.0f / 256.0f; + + public uint Word0; + public uint Word1; + public uint Word2; + public uint Word3; + public uint BorderColorR; + public uint BorderColorG; + public uint BorderColorB; + public uint BorderColorA; + + /// + /// Unpacks the texture wrap mode along the X axis. + /// + /// The texture wrap mode enum + public AddressMode UnpackAddressU() + { + return (AddressMode)(Word0 & 7); + } + + // + /// Unpacks the texture wrap mode along the Y axis. + /// + /// The texture wrap mode enum + public AddressMode UnpackAddressV() + { + return (AddressMode)((Word0 >> 3) & 7); + } + + // + /// Unpacks the texture wrap mode along the Z axis. + /// + /// The texture wrap mode enum + public AddressMode UnpackAddressP() + { + return (AddressMode)((Word0 >> 6) & 7); + } + + /// + /// Unpacks the compare mode used for depth comparison on the shader, for + /// depth buffer texture. + /// This is only relevant for shaders with shadow samplers. + /// + /// The depth comparison mode enum + public CompareMode UnpackCompareMode() + { + return (CompareMode)((Word0 >> 9) & 1); + } + + /// + /// Unpacks the compare operation used for depth comparison on the shader, for + /// depth buffer texture. + /// This is only relevant for shaders with shadow samplers. + /// + /// The depth comparison operation enum + public CompareOp UnpackCompareOp() + { + return (CompareOp)(((Word0 >> 10) & 7) + 1); + } + + /// + /// Unpacks and converts the maximum anisotropy value used for texture anisotropic filtering. + /// + /// The maximum anisotropy + public float UnpackMaxAnisotropy() + { + return _maxAnisotropyLut[(Word0 >> 20) & 7]; + } + + /// + /// Unpacks the texture magnification filter. + /// This defines the filtering used when the texture covers an area on the screen + /// that is larger than the texture size. + /// + /// The magnification filter + public MagFilter UnpackMagFilter() + { + return (MagFilter)(Word1 & 3); + } + + /// + /// Unpacks the texture minification filter. + /// This defines the filtering used when the texture covers an area on the screen + /// that is smaller than the texture size. + /// + /// The minification filter + public MinFilter UnpackMinFilter() + { + SamplerMinFilter minFilter = (SamplerMinFilter)((Word1 >> 4) & 3); + SamplerMipFilter mipFilter = (SamplerMipFilter)((Word1 >> 6) & 3); + + return ConvertFilter(minFilter, mipFilter); + } + + /// + /// Converts two minification and filter enum, to a single minification enum, + /// including mipmap filtering information, as expected from the host API. + /// + /// The minification filter + /// The mipmap level filter + /// The combined, host API compatible filter enum + private static MinFilter ConvertFilter(SamplerMinFilter minFilter, SamplerMipFilter mipFilter) + { + switch (mipFilter) + { + case SamplerMipFilter.None: + switch (minFilter) + { + case SamplerMinFilter.Nearest: return MinFilter.Nearest; + case SamplerMinFilter.Linear: return MinFilter.Linear; + } + break; + + case SamplerMipFilter.Nearest: + switch (minFilter) + { + case SamplerMinFilter.Nearest: return MinFilter.NearestMipmapNearest; + case SamplerMinFilter.Linear: return MinFilter.LinearMipmapNearest; + } + break; + + case SamplerMipFilter.Linear: + switch (minFilter) + { + case SamplerMinFilter.Nearest: return MinFilter.NearestMipmapLinear; + case SamplerMinFilter.Linear: return MinFilter.LinearMipmapLinear; + } + break; + } + + return MinFilter.Nearest; + } + + /// + /// Unpacks the reduction filter, used with texture minification linear filtering. + /// This describes how the final value will be computed from neighbouring pixels. + /// + /// The reduction filter + public ReductionFilter UnpackReductionFilter() + { + return (ReductionFilter)((Word1 >> 10) & 3); + } + + /// + /// Unpacks the level-of-detail bias value. + /// This is a bias added to the level-of-detail value as computed by the GPU, used to select + /// which mipmap level to use from a given texture. + /// + /// The level-of-detail bias value + public float UnpackMipLodBias() + { + int fixedValue = (int)(Word1 >> 12) & 0x1fff; + + fixedValue = (fixedValue << 19) >> 19; + + return fixedValue * Frac8ToF32; + } + + /// + /// Unpacks the level-of-detail snap value. + /// + /// The level-of-detail snap value + public float UnpackLodSnap() + { + return _f5ToF32ConversionLut[(Word1 >> 26) & 0x1f]; + } + + /// + /// Unpacks the minimum level-of-detail value. + /// + /// The minimum level-of-detail value + public float UnpackMinLod() + { + return (Word2 & 0xfff) * Frac8ToF32; + } + + /// + /// Unpacks the maximum level-of-detail value. + /// + /// The maximum level-of-detail value + public float UnpackMaxLod() + { + return ((Word2 >> 12) & 0xfff) * Frac8ToF32; + } + } +} diff --git a/Ryujinx.Graphics.Gpu/Image/SamplerMinFilter.cs b/Ryujinx.Graphics.Gpu/Image/SamplerMinFilter.cs new file mode 100644 index 0000000000..17beb12933 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Image/SamplerMinFilter.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// Sampler texture minification filter. + /// + enum SamplerMinFilter + { + Nearest = 1, + Linear + } +} diff --git a/Ryujinx.Graphics.Gpu/Image/SamplerMipFilter.cs b/Ryujinx.Graphics.Gpu/Image/SamplerMipFilter.cs new file mode 100644 index 0000000000..319d419609 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Image/SamplerMipFilter.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// Sampler texture mipmap level filter. + /// + enum SamplerMipFilter + { + None = 1, + Nearest, + Linear + } +} diff --git a/Ryujinx.Graphics.Gpu/Image/SamplerPool.cs b/Ryujinx.Graphics.Gpu/Image/SamplerPool.cs new file mode 100644 index 0000000000..2abf96de90 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Image/SamplerPool.cs @@ -0,0 +1,92 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// Sampler pool. + /// + class SamplerPool : Pool + { + private int _sequenceNumber; + + /// + /// Constructs a new instance of the sampler pool. + /// + /// GPU context that the sampler pool belongs to + /// Address of the sampler pool in guest memory + /// Maximum sampler ID of the sampler pool (equal to maximum samplers minus one) + public SamplerPool(GpuContext context, ulong address, int maximumId) : base(context, address, maximumId) { } + + /// + /// Gets the sampler with the given ID. + /// + /// ID of the sampler. This is effectively a zero-based index + /// The sampler with the given ID + public override Sampler Get(int id) + { + if ((uint)id >= Items.Length) + { + return null; + } + + if (_sequenceNumber != Context.SequenceNumber) + { + _sequenceNumber = Context.SequenceNumber; + + SynchronizeMemory(); + } + + Sampler sampler = Items[id]; + + if (sampler == null) + { + ulong address = Address + (ulong)(uint)id * DescriptorSize; + + ReadOnlySpan data = Context.PhysicalMemory.GetSpan(address, DescriptorSize); + + SamplerDescriptor descriptor = MemoryMarshal.Cast(data)[0]; + + sampler = new Sampler(Context, descriptor); + + Items[id] = sampler; + } + + return sampler; + } + + /// + /// Implementation of the sampler pool range invalidation. + /// + /// Start address of the range of the sampler pool + /// Size of the range being invalidated + protected override void InvalidateRangeImpl(ulong address, ulong size) + { + ulong endAddress = address + size; + + for (; address < endAddress; address += DescriptorSize) + { + int id = (int)((address - Address) / DescriptorSize); + + Sampler sampler = Items[id]; + + if (sampler != null) + { + sampler.Dispose(); + + Items[id] = null; + } + } + } + + /// + /// Deletes a given sampler pool entry. + /// The host memory used by the sampler is released by the driver. + /// + /// The entry to be deleted + protected override void Delete(Sampler item) + { + item?.Dispose(); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Image/Texture.cs b/Ryujinx.Graphics.Gpu/Image/Texture.cs new file mode 100644 index 0000000000..7d5e9079da --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Image/Texture.cs @@ -0,0 +1,1025 @@ +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Memory; +using Ryujinx.Graphics.Texture; +using Ryujinx.Graphics.Texture.Astc; +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// Represents a cached GPU texture. + /// + class Texture : IRange, IDisposable + { + private GpuContext _context; + + private SizeInfo _sizeInfo; + + /// + /// Texture format. + /// + public Format Format => Info.FormatInfo.Format; + + /// + /// Texture information. + /// + public TextureInfo Info { get; private set; } + + private int _depth; + private int _layers; + private readonly int _firstLayer; + private readonly int _firstLevel; + + private bool _hasData; + + private ITexture _arrayViewTexture; + private Target _arrayViewTarget; + + private Texture _viewStorage; + + private List _views; + + /// + /// Host texture. + /// + public ITexture HostTexture { get; private set; } + + /// + /// Intrusive linked list node used on the auto deletion texture cache. + /// + public LinkedListNode CacheNode { get; set; } + + /// + /// Texture data modified by the GPU. + /// + public bool Modified { get; set; } + + /// + /// Start address of the texture in guest memory. + /// + public ulong Address => Info.Address; + + /// + /// End address of the texture in guest memory. + /// + public ulong EndAddress => Info.Address + Size; + + /// + /// Texture size in bytes. + /// + public ulong Size => (ulong)_sizeInfo.TotalSize; + + private int _referenceCount; + + private int _sequenceNumber; + + /// + /// Constructs a new instance of the cached GPU texture. + /// + /// GPU context that the texture belongs to + /// Texture information + /// Size information of the texture + /// The first layer of the texture, or 0 if the texture has no parent + /// The first mipmap level of the texture, or 0 if the texture has no parent + private Texture( + GpuContext context, + TextureInfo info, + SizeInfo sizeInfo, + int firstLayer, + int firstLevel) + { + InitializeTexture(context, info, sizeInfo); + + _firstLayer = firstLayer; + _firstLevel = firstLevel; + + _hasData = true; + } + + /// + /// Constructs a new instance of the cached GPU texture. + /// + /// GPU context that the texture belongs to + /// Texture information + /// Size information of the texture + public Texture(GpuContext context, TextureInfo info, SizeInfo sizeInfo) + { + InitializeTexture(context, info, sizeInfo); + + TextureCreateInfo createInfo = TextureManager.GetCreateInfo(info, context.Capabilities); + + HostTexture = _context.Renderer.CreateTexture(createInfo); + } + + /// + /// Common texture initialization method. + /// This sets the context, info and sizeInfo fields. + /// Other fields are initialized with their default values. + /// + /// GPU context that the texture belongs to + /// Texture information + /// Size information of the texture + private void InitializeTexture(GpuContext context, TextureInfo info, SizeInfo sizeInfo) + { + _context = context; + _sizeInfo = sizeInfo; + + SetInfo(info); + + _viewStorage = this; + + _views = new List(); + } + + /// + /// Create a texture view from this texture. + /// A texture view is defined as a child texture, from a sub-range of their parent texture. + /// For example, the initial layer and mipmap level of the view can be defined, so the texture + /// will start at the given layer/level of the parent texture. + /// + /// Child texture information + /// Child texture size information + /// Start layer of the child texture on the parent texture + /// Start mipmap level of the child texture on the parent texture + /// The child texture + public Texture CreateView(TextureInfo info, SizeInfo sizeInfo, int firstLayer, int firstLevel) + { + Texture texture = new Texture( + _context, + info, + sizeInfo, + _firstLayer + firstLayer, + _firstLevel + firstLevel); + + TextureCreateInfo createInfo = TextureManager.GetCreateInfo(info, _context.Capabilities); + + texture.HostTexture = HostTexture.CreateView(createInfo, firstLayer, firstLevel); + + _viewStorage.AddView(texture); + + return texture; + } + + /// + /// Adds a child texture to this texture. + /// + /// The child texture + private void AddView(Texture texture) + { + _views.Add(texture); + + texture._viewStorage = this; + } + + /// + /// Removes a child texture from this texture. + /// + /// The child texture + private void RemoveView(Texture texture) + { + _views.Remove(texture); + + texture._viewStorage = null; + + DeleteIfNotUsed(); + } + + /// + /// Changes the texture size. + /// + /// + /// This operation may also change the size of all mipmap levels, including from the parent + /// and other possible child textures, to ensure that all sizes are consistent. + /// + /// The new texture width + /// The new texture height + /// The new texture depth (for 3D textures) or layers (for layered textures) + public void ChangeSize(int width, int height, int depthOrLayers) + { + width <<= _firstLevel; + height <<= _firstLevel; + + if (Info.Target == Target.Texture3D) + { + depthOrLayers <<= _firstLevel; + } + else + { + depthOrLayers = _viewStorage.Info.DepthOrLayers; + } + + _viewStorage.RecreateStorageOrView(width, height, depthOrLayers); + + foreach (Texture view in _viewStorage._views) + { + int viewWidth = Math.Max(1, width >> view._firstLevel); + int viewHeight = Math.Max(1, height >> view._firstLevel); + + int viewDepthOrLayers; + + if (view.Info.Target == Target.Texture3D) + { + viewDepthOrLayers = Math.Max(1, depthOrLayers >> view._firstLevel); + } + else + { + viewDepthOrLayers = view.Info.DepthOrLayers; + } + + view.RecreateStorageOrView(viewWidth, viewHeight, viewDepthOrLayers); + } + } + + /// + /// Recreates the texture storage (or view, in the case of child textures) of this texture. + /// This allows recreating the texture with a new size. + /// A copy is automatically performed from the old to the new texture. + /// + /// The new texture width + /// The new texture height + /// The new texture depth (for 3D textures) or layers (for layered textures) + private void RecreateStorageOrView(int width, int height, int depthOrLayers) + { + SetInfo(new TextureInfo( + Info.Address, + width, + height, + depthOrLayers, + Info.Levels, + Info.SamplesInX, + Info.SamplesInY, + Info.Stride, + Info.IsLinear, + Info.GobBlocksInY, + Info.GobBlocksInZ, + Info.GobBlocksInTileX, + Info.Target, + Info.FormatInfo, + Info.DepthStencilMode, + Info.SwizzleR, + Info.SwizzleG, + Info.SwizzleB, + Info.SwizzleA)); + + TextureCreateInfo createInfo = TextureManager.GetCreateInfo(Info, _context.Capabilities); + + if (_viewStorage != this) + { + ReplaceStorage(_viewStorage.HostTexture.CreateView(createInfo, _firstLayer, _firstLevel)); + } + else + { + ITexture newStorage = _context.Renderer.CreateTexture(createInfo); + + HostTexture.CopyTo(newStorage, 0, 0); + + ReplaceStorage(newStorage); + } + } + + /// + /// Synchronizes guest and host memory. + /// This will overwrite the texture data with the texture data on the guest memory, if a CPU + /// modification is detected. + /// Be aware that this can cause texture data written by the GPU to be lost, this is just a + /// one way copy (from CPU owned to GPU owned memory). + /// + public void SynchronizeMemory() + { + if (_sequenceNumber == _context.SequenceNumber && _hasData) + { + return; + } + + _sequenceNumber = _context.SequenceNumber; + + bool modified = _context.PhysicalMemory.GetModifiedRanges(Address, Size, ResourceName.Texture).Length != 0; + + if (!modified && _hasData) + { + return; + } + + ReadOnlySpan data = _context.PhysicalMemory.GetSpan(Address, Size); + + if (Info.IsLinear) + { + data = LayoutConverter.ConvertLinearStridedToLinear( + Info.Width, + Info.Height, + Info.FormatInfo.BlockWidth, + Info.FormatInfo.BlockHeight, + Info.Stride, + Info.FormatInfo.BytesPerPixel, + data); + } + else + { + data = LayoutConverter.ConvertBlockLinearToLinear( + Info.Width, + Info.Height, + _depth, + Info.Levels, + _layers, + Info.FormatInfo.BlockWidth, + Info.FormatInfo.BlockHeight, + Info.FormatInfo.BytesPerPixel, + Info.GobBlocksInY, + Info.GobBlocksInZ, + Info.GobBlocksInTileX, + _sizeInfo, + data); + } + + if (!_context.Capabilities.SupportsAstcCompression && Info.FormatInfo.Format.IsAstc()) + { + if (!AstcDecoder.TryDecodeToRgba8( + data.ToArray(), + Info.FormatInfo.BlockWidth, + Info.FormatInfo.BlockHeight, + Info.Width, + Info.Height, + _depth, + Info.Levels, + out Span decoded)) + { + string texInfo = $"{Info.Target} {Info.FormatInfo.Format} {Info.Width}x{Info.Height}x{Info.DepthOrLayers} levels {Info.Levels}"; + + Logger.PrintDebug(LogClass.Gpu, $"Invalid ASTC texture at 0x{Info.Address:X} ({texInfo})."); + } + + data = decoded; + } + + HostTexture.SetData(data); + + _hasData = true; + } + + /// + /// Flushes the texture data. + /// This causes the texture data to be written back to guest memory. + /// If the texture was written by the GPU, this includes all modification made by the GPU + /// up to this point. + /// Be aware that this is an expensive operation, avoid calling it unless strictly needed. + /// This may cause data corruption if the memory is already being used for something else on the CPU side. + /// + public void Flush() + { + Span data = HostTexture.GetData(); + + if (Info.IsLinear) + { + data = LayoutConverter.ConvertLinearToLinearStrided( + Info.Width, + Info.Height, + Info.FormatInfo.BlockWidth, + Info.FormatInfo.BlockHeight, + Info.Stride, + Info.FormatInfo.BytesPerPixel, + data); + } + else + { + data = LayoutConverter.ConvertLinearToBlockLinear( + Info.Width, + Info.Height, + _depth, + Info.Levels, + _layers, + Info.FormatInfo.BlockWidth, + Info.FormatInfo.BlockHeight, + Info.FormatInfo.BytesPerPixel, + Info.GobBlocksInY, + Info.GobBlocksInZ, + Info.GobBlocksInTileX, + _sizeInfo, + data); + } + + _context.PhysicalMemory.Write(Address, data); + } + + /// + /// Performs a comparison of this texture information, with the specified texture information. + /// This performs a strict comparison, used to check if two textures are equal. + /// + /// Texture information to compare with + /// Comparison flags + /// True if the textures are strictly equal or similar, false otherwise + public bool IsPerfectMatch(TextureInfo info, TextureSearchFlags flags) + { + if (!FormatMatches(info, (flags & TextureSearchFlags.Strict) != 0)) + { + return false; + } + + if (!LayoutMatches(info)) + { + return false; + } + + if (!SizeMatches(info, (flags & TextureSearchFlags.Strict) == 0)) + { + return false; + } + + if ((flags & TextureSearchFlags.Sampler) != 0) + { + if (!SamplerParamsMatches(info)) + { + return false; + } + } + + if ((flags & TextureSearchFlags.IgnoreMs) != 0) + { + bool msTargetCompatible = Info.Target == Target.Texture2DMultisample && info.Target == Target.Texture2D; + + if (!msTargetCompatible && !TargetAndSamplesCompatible(info)) + { + return false; + } + } + else if (!TargetAndSamplesCompatible(info)) + { + return false; + } + + return Info.Address == info.Address && Info.Levels == info.Levels; + } + + /// + /// Checks if the texture format matches with the specified texture information. + /// + /// Texture information to compare with + /// True to perform a strict comparison (formats must be exactly equal) + /// True if the format matches, with the given comparison rules + private bool FormatMatches(TextureInfo info, bool strict) + { + // D32F and R32F texture have the same representation internally, + // however the R32F format is used to sample from depth textures. + if (Info.FormatInfo.Format == Format.D32Float && info.FormatInfo.Format == Format.R32Float && !strict) + { + return true; + } + + return Info.FormatInfo.Format == info.FormatInfo.Format; + } + + /// + /// Checks if the texture layout specified matches with this texture layout. + /// The layout information is composed of the Stride for linear textures, or GOB block size + /// for block linear textures. + /// + /// Texture information to compare with + /// True if the layout matches, false otherwise + private bool LayoutMatches(TextureInfo info) + { + if (Info.IsLinear != info.IsLinear) + { + return false; + } + + // For linear textures, gob block sizes are ignored. + // For block linear textures, the stride is ignored. + if (info.IsLinear) + { + return Info.Stride == info.Stride; + } + else + { + return Info.GobBlocksInY == info.GobBlocksInY && + Info.GobBlocksInZ == info.GobBlocksInZ; + } + } + + /// + /// Checks if the texture sizes of the supplied texture information matches this texture. + /// + /// Texture information to compare with + /// True if the size matches, false otherwise + public bool SizeMatches(TextureInfo info) + { + return SizeMatches(info, alignSizes: false); + } + + /// + /// Checks if the texture sizes of the supplied texture information matches the given level of + /// this texture. + /// + /// Texture information to compare with + /// Mipmap level of this texture to compare with + /// True if the size matches with the level, false otherwise + public bool SizeMatches(TextureInfo info, int level) + { + return Math.Max(1, Info.Width >> level) == info.Width && + Math.Max(1, Info.Height >> level) == info.Height && + Math.Max(1, Info.GetDepth() >> level) == info.GetDepth(); + } + + /// + /// Checks if the texture sizes of the supplied texture information matches this texture. + /// + /// Texture information to compare with + /// True to align the sizes according to the texture layout for comparison + /// True if the sizes matches, false otherwise + private bool SizeMatches(TextureInfo info, bool alignSizes) + { + if (Info.GetLayers() != info.GetLayers()) + { + return false; + } + + if (alignSizes) + { + Size size0 = GetAlignedSize(Info); + Size size1 = GetAlignedSize(info); + + return size0.Width == size1.Width && + size0.Height == size1.Height && + size0.Depth == size1.Depth; + } + else + { + return Info.Width == info.Width && + Info.Height == info.Height && + Info.GetDepth() == info.GetDepth(); + } + } + + /// + /// Checks if the texture shader sampling parameters matches. + /// + /// Texture information to compare with + /// True if the texture shader sampling parameters matches, false otherwise + private bool SamplerParamsMatches(TextureInfo info) + { + return Info.DepthStencilMode == info.DepthStencilMode && + Info.SwizzleR == info.SwizzleR && + Info.SwizzleG == info.SwizzleG && + Info.SwizzleB == info.SwizzleB && + Info.SwizzleA == info.SwizzleA; + } + + /// + /// Check if the texture target and samples count (for multisampled textures) matches. + /// + /// Texture information to compare with + /// True if the texture target and samples count matches, false otherwise + private bool TargetAndSamplesCompatible(TextureInfo info) + { + return Info.Target == info.Target && + Info.SamplesInX == info.SamplesInX && + Info.SamplesInY == info.SamplesInY; + } + + /// + /// Check if it's possible to create a view, with the given parameters, from this texture. + /// + /// Texture view information + /// Texture view size + /// Texture view initial layer on this texture + /// Texture view first mipmap level on this texture + /// True if a view with the given parameters can be created from this texture, false otherwise + public bool IsViewCompatible( + TextureInfo info, + ulong size, + out int firstLayer, + out int firstLevel) + { + return IsViewCompatible(info, size, isCopy: false, out firstLayer, out firstLevel); + } + + /// + /// Check if it's possible to create a view, with the given parameters, from this texture. + /// + /// Texture view information + /// Texture view size + /// True to check for copy compability, instead of view compatibility + /// Texture view initial layer on this texture + /// Texture view first mipmap level on this texture + /// True if a view with the given parameters can be created from this texture, false otherwise + public bool IsViewCompatible( + TextureInfo info, + ulong size, + bool isCopy, + out int firstLayer, + out int firstLevel) + { + // Out of range. + if (info.Address < Address || info.Address + size > EndAddress) + { + firstLayer = 0; + firstLevel = 0; + + return false; + } + + int offset = (int)(info.Address - Address); + + if (!_sizeInfo.FindView(offset, (int)size, out firstLayer, out firstLevel)) + { + return false; + } + + if (!ViewLayoutCompatible(info, firstLevel)) + { + return false; + } + + if (!ViewFormatCompatible(info)) + { + return false; + } + + if (!ViewSizeMatches(info, firstLevel, isCopy)) + { + return false; + } + + if (!ViewTargetCompatible(info, isCopy)) + { + return false; + } + + return Info.SamplesInX == info.SamplesInX && + Info.SamplesInY == info.SamplesInY; + } + + /// + /// Check if it's possible to create a view with the specified layout. + /// The layout information is composed of the Stride for linear textures, or GOB block size + /// for block linear textures. + /// + /// Texture information of the texture view + /// Start level of the texture view, in relation with this texture + /// True if the layout is compatible, false otherwise + private bool ViewLayoutCompatible(TextureInfo info, int level) + { + if (Info.IsLinear != info.IsLinear) + { + return false; + } + + // For linear textures, gob block sizes are ignored. + // For block linear textures, the stride is ignored. + if (info.IsLinear) + { + int width = Math.Max(1, Info.Width >> level); + + int stride = width * Info.FormatInfo.BytesPerPixel; + + stride = BitUtils.AlignUp(stride, 32); + + return stride == info.Stride; + } + else + { + int height = Math.Max(1, Info.Height >> level); + int depth = Math.Max(1, Info.GetDepth() >> level); + + (int gobBlocksInY, int gobBlocksInZ) = SizeCalculator.GetMipGobBlockSizes( + height, + depth, + Info.FormatInfo.BlockHeight, + Info.GobBlocksInY, + Info.GobBlocksInZ); + + return gobBlocksInY == info.GobBlocksInY && + gobBlocksInZ == info.GobBlocksInZ; + } + } + + /// + /// Checks if the view format is compatible with this texture format. + /// In general, the formats are considered compatible if the bytes per pixel values are equal, + /// but there are more complex rules for some formats, like compressed or depth-stencil formats. + /// This follows the host API copy compatibility rules. + /// + /// Texture information of the texture view + /// True if the formats are compatible, false otherwise + private bool ViewFormatCompatible(TextureInfo info) + { + return TextureCompatibility.FormatCompatible(Info.FormatInfo, info.FormatInfo); + } + + /// + /// Checks if the size of a given texture view is compatible with this texture. + /// + /// Texture information of the texture view + /// Mipmap level of the texture view in relation to this texture + /// True to check for copy compatibility rather than view compatibility + /// True if the sizes are compatible, false otherwise + private bool ViewSizeMatches(TextureInfo info, int level, bool isCopy) + { + Size size = GetAlignedSize(Info, level); + + Size otherSize = GetAlignedSize(info); + + // For copies, we can copy a subset of the 3D texture slices, + // so the depth may be different in this case. + if (!isCopy && info.Target == Target.Texture3D && size.Depth != otherSize.Depth) + { + return false; + } + + return size.Width == otherSize.Width && + size.Height == otherSize.Height; + } + + /// + /// Check if the target of the specified texture view information is compatible with this + /// texture. + /// This follows the host API target compatibility rules. + /// + /// Texture information of the texture view + /// True to check for copy rather than view compatibility + /// True if the targets are compatible, false otherwise + private bool ViewTargetCompatible(TextureInfo info, bool isCopy) + { + switch (Info.Target) + { + case Target.Texture1D: + case Target.Texture1DArray: + return info.Target == Target.Texture1D || + info.Target == Target.Texture1DArray; + + case Target.Texture2D: + return info.Target == Target.Texture2D || + info.Target == Target.Texture2DArray; + + case Target.Texture2DArray: + case Target.Cubemap: + case Target.CubemapArray: + return info.Target == Target.Texture2D || + info.Target == Target.Texture2DArray || + info.Target == Target.Cubemap || + info.Target == Target.CubemapArray; + + case Target.Texture2DMultisample: + case Target.Texture2DMultisampleArray: + return info.Target == Target.Texture2DMultisample || + info.Target == Target.Texture2DMultisampleArray; + + case Target.Texture3D: + return info.Target == Target.Texture3D || + (info.Target == Target.Texture2D && isCopy); + } + + return false; + } + + /// + /// Gets the aligned sizes of the specified texture information. + /// The alignment depends on the texture layout and format bytes per pixel. + /// + /// Texture information to calculate the aligned size from + /// Mipmap level for texture views + /// The aligned texture size + private static Size GetAlignedSize(TextureInfo info, int level = 0) + { + int width = Math.Max(1, info.Width >> level); + int height = Math.Max(1, info.Height >> level); + + if (info.IsLinear) + { + return SizeCalculator.GetLinearAlignedSize( + width, + height, + info.FormatInfo.BlockWidth, + info.FormatInfo.BlockHeight, + info.FormatInfo.BytesPerPixel); + } + else + { + int depth = Math.Max(1, info.GetDepth() >> level); + + (int gobBlocksInY, int gobBlocksInZ) = SizeCalculator.GetMipGobBlockSizes( + height, + depth, + info.FormatInfo.BlockHeight, + info.GobBlocksInY, + info.GobBlocksInZ); + + return SizeCalculator.GetBlockLinearAlignedSize( + width, + height, + depth, + info.FormatInfo.BlockWidth, + info.FormatInfo.BlockHeight, + info.FormatInfo.BytesPerPixel, + gobBlocksInY, + gobBlocksInZ, + info.GobBlocksInTileX); + } + } + + /// + /// Gets a texture of the specified target type from this texture. + /// This can be used to get an array texture from a non-array texture and vice-versa. + /// If this texture and the requested targets are equal, then this texture Host texture is returned directly. + /// + /// The desired target type + /// A view of this texture with the requested target, or null if the target is invalid for this texture + public ITexture GetTargetTexture(Target target) + { + if (target == Info.Target) + { + return HostTexture; + } + + if (_arrayViewTexture == null && IsSameDimensionsTarget(target)) + { + TextureCreateInfo createInfo = new TextureCreateInfo( + Info.Width, + Info.Height, + target == Target.CubemapArray ? 6 : 1, + Info.Levels, + Info.Samples, + Info.FormatInfo.BlockWidth, + Info.FormatInfo.BlockHeight, + Info.FormatInfo.BytesPerPixel, + Info.FormatInfo.Format, + Info.DepthStencilMode, + target, + Info.SwizzleR, + Info.SwizzleG, + Info.SwizzleB, + Info.SwizzleA); + + ITexture viewTexture = HostTexture.CreateView(createInfo, 0, 0); + + _arrayViewTexture = viewTexture; + _arrayViewTarget = target; + + return viewTexture; + } + else if (_arrayViewTarget == target) + { + return _arrayViewTexture; + } + + return null; + } + + /// + /// Check if this texture and the specified target have the same number of dimensions. + /// For the purposes of this comparison, 2D and 2D Multisample textures are not considered to have + /// the same number of dimensions. Same for Cubemap and 3D textures. + /// + /// The target to compare with + /// True if both targets have the same number of dimensions, false otherwise + private bool IsSameDimensionsTarget(Target target) + { + switch (Info.Target) + { + case Target.Texture1D: + case Target.Texture1DArray: + return target == Target.Texture1D || + target == Target.Texture1DArray; + + case Target.Texture2D: + case Target.Texture2DArray: + return target == Target.Texture2D || + target == Target.Texture2DArray; + + case Target.Cubemap: + case Target.CubemapArray: + return target == Target.Cubemap || + target == Target.CubemapArray; + + case Target.Texture2DMultisample: + case Target.Texture2DMultisampleArray: + return target == Target.Texture2DMultisample || + target == Target.Texture2DMultisampleArray; + + case Target.Texture3D: + return target == Target.Texture3D; + } + + return false; + } + + /// + /// Replaces view texture information. + /// This should only be used for child textures with a parent. + /// + /// The parent texture + /// The new view texture information + /// The new host texture + public void ReplaceView(Texture parent, TextureInfo info, ITexture hostTexture) + { + ReplaceStorage(hostTexture); + + parent._viewStorage.AddView(this); + + SetInfo(info); + } + + /// + /// Sets the internal texture information structure. + /// + /// The new texture information + private void SetInfo(TextureInfo info) + { + Info = info; + + _depth = info.GetDepth(); + _layers = info.GetLayers(); + } + + /// + /// Replaces the host texture, while disposing of the old one if needed. + /// + /// The new host texture + private void ReplaceStorage(ITexture hostTexture) + { + DisposeTextures(); + + HostTexture = hostTexture; + } + + /// + /// Checks if the texture overlaps with a memory range. + /// + /// Start address of the range + /// Size of the range + /// True if the texture overlaps with the range, false otherwise + public bool OverlapsWith(ulong address, ulong size) + { + return Address < address + size && address < EndAddress; + } + + /// + /// Increments the texture reference count. + /// + public void IncrementReferenceCount() + { + _referenceCount++; + } + + /// + /// Decrements the texture reference count. + /// When the reference count hits zero, the texture may be deleted and can't be used anymore. + /// + public void DecrementReferenceCount() + { + int newRefCount = --_referenceCount; + + if (newRefCount == 0) + { + if (_viewStorage != this) + { + _viewStorage.RemoveView(this); + } + + _context.Methods.TextureManager.RemoveTextureFromCache(this); + } + + Debug.Assert(newRefCount >= 0); + + DeleteIfNotUsed(); + } + + /// + /// Delete the texture if it is not used anymore. + /// The texture is considered unused when the reference count is zero, + /// and it has no child views. + /// + private void DeleteIfNotUsed() + { + // We can delete the texture as long it is not being used + // in any cache (the reference count is 0 in this case), and + // also all views that may be created from this texture were + // already deleted (views count is 0). + if (_referenceCount == 0 && _views.Count == 0) + { + DisposeTextures(); + } + } + + /// + /// Performs texture disposal, deleting the texture. + /// + private void DisposeTextures() + { + HostTexture.Dispose(); + + _arrayViewTexture?.Dispose(); + _arrayViewTexture = null; + } + + /// + /// Performs texture disposal, deleting the texture. + /// + public void Dispose() + { + DisposeTextures(); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs b/Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs new file mode 100644 index 0000000000..b4793f58ff --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs @@ -0,0 +1,73 @@ +using Ryujinx.Graphics.GAL; + +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// Texture binding information. + /// This is used for textures that needs to be accessed from shaders. + /// + struct TextureBindingInfo + { + /// + /// Shader sampler target type. + /// + public Target Target { get; } + + /// + /// Shader texture handle. + /// This is an index into the texture constant buffer. + /// + public int Handle { get; } + + /// + /// Indicates if the texture is a bindless texture. + /// + /// + /// For those textures, Handle is ignored. + /// + public bool IsBindless { get; } + + /// + /// Constant buffer slot with the bindless texture handle, for bindless texture. + /// + public int CbufSlot { get; } + + /// + /// Constant buffer offset of the bindless texture handle, for bindless texture. + /// + public int CbufOffset { get; } + + /// + /// Constructs the texture binding information structure. + /// + /// The shader sampler target type + /// The shader texture handle (read index into the texture constant buffer) + public TextureBindingInfo(Target target, int handle) + { + Target = target; + Handle = handle; + + IsBindless = false; + + CbufSlot = 0; + CbufOffset = 0; + } + + /// + /// Constructs the bindless texture binding information structure. + /// + /// The shader sampler target type + /// Constant buffer slot where the bindless texture handle is located + /// Constant buffer offset of the bindless texture handle + public TextureBindingInfo(Target target, int cbufSlot, int cbufOffset) + { + Target = target; + Handle = 0; + + IsBindless = true; + + CbufSlot = cbufSlot; + CbufOffset = cbufOffset; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs b/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs new file mode 100644 index 0000000000..7cc7f04688 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs @@ -0,0 +1,367 @@ +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.State; +using Ryujinx.Graphics.Shader; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// Texture bindings manager. + /// + class TextureBindingsManager + { + private GpuContext _context; + + private bool _isCompute; + + private SamplerPool _samplerPool; + + private SamplerIndex _samplerIndex; + + private ulong _texturePoolAddress; + private int _texturePoolMaximumId; + + private TexturePoolCache _texturePoolCache; + + private TextureBindingInfo[][] _textureBindings; + private TextureBindingInfo[][] _imageBindings; + + private struct TextureStatePerStage + { + public ITexture Texture; + public ISampler Sampler; + } + + private TextureStatePerStage[][] _textureState; + private TextureStatePerStage[][] _imageState; + + private int _textureBufferIndex; + + private bool _rebind; + + /// + /// Constructs a new instance of the texture bindings manager. + /// + /// The GPU context that the texture bindings manager belongs to + /// Texture pools cache used to get texture pools from + /// True if the bindings manager is used for the compute engine + public TextureBindingsManager(GpuContext context, TexturePoolCache texturePoolCache, bool isCompute) + { + _context = context; + _texturePoolCache = texturePoolCache; + _isCompute = isCompute; + + int stages = isCompute ? 1 : Constants.ShaderStages; + + _textureBindings = new TextureBindingInfo[stages][]; + _imageBindings = new TextureBindingInfo[stages][]; + + _textureState = new TextureStatePerStage[stages][]; + _imageState = new TextureStatePerStage[stages][]; + } + + /// + /// Binds textures for a given shader stage. + /// + /// Shader stage number, or 0 for compute shaders + /// Texture bindings + public void SetTextures(int stage, TextureBindingInfo[] bindings) + { + _textureBindings[stage] = bindings; + + _textureState[stage] = new TextureStatePerStage[bindings.Length]; + } + + /// + /// Binds images for a given shader stage. + /// + /// Shader stage number, or 0 for compute shaders + /// Image bindings + public void SetImages(int stage, TextureBindingInfo[] bindings) + { + _imageBindings[stage] = bindings; + + _imageState[stage] = new TextureStatePerStage[bindings.Length]; + } + + /// + /// Sets the textures constant buffer index. + /// The constant buffer specified holds the texture handles. + /// + /// Constant buffer index + public void SetTextureBufferIndex(int index) + { + _textureBufferIndex = index; + } + + /// + /// Sets the current texture sampler pool to be used. + /// + /// Start GPU virtual address of the pool + /// Maximum ID of the pool (total count minus one) + /// Type of the sampler pool indexing used for bound samplers + public void SetSamplerPool(ulong gpuVa, int maximumId, SamplerIndex samplerIndex) + { + ulong address = _context.MemoryManager.Translate(gpuVa); + + if (_samplerPool != null) + { + if (_samplerPool.Address == address && _samplerPool.MaximumId >= maximumId) + { + return; + } + + _samplerPool.Dispose(); + } + + _samplerPool = new SamplerPool(_context, address, maximumId); + + _samplerIndex = samplerIndex; + } + + /// + /// Sets the current texture pool to be used. + /// + /// Start GPU virtual address of the pool + /// Maximum ID of the pool (total count minus one) + public void SetTexturePool(ulong gpuVa, int maximumId) + { + ulong address = _context.MemoryManager.Translate(gpuVa); + + _texturePoolAddress = address; + _texturePoolMaximumId = maximumId; + } + + /// + /// Ensures that the bindings are visible to the host GPU. + /// Note: this actually performs the binding using the host graphics API. + /// + public void CommitBindings() + { + TexturePool texturePool = _texturePoolCache.FindOrCreate( + _texturePoolAddress, + _texturePoolMaximumId); + + if (_isCompute) + { + CommitTextureBindings(texturePool, ShaderStage.Compute, 0); + CommitImageBindings (texturePool, ShaderStage.Compute, 0); + } + else + { + for (ShaderStage stage = ShaderStage.Vertex; stage <= ShaderStage.Fragment; stage++) + { + int stageIndex = (int)stage - 1; + + CommitTextureBindings(texturePool, stage, stageIndex); + CommitImageBindings (texturePool, stage, stageIndex); + } + } + + _rebind = false; + } + + /// + /// Ensures that the texture bindings are visible to the host GPU. + /// Note: this actually performs the binding using the host graphics API. + /// + /// The current texture pool + /// The shader stage using the textures to be bound + /// The stage number of the specified shader stage + private void CommitTextureBindings(TexturePool pool, ShaderStage stage, int stageIndex) + { + if (_textureBindings[stageIndex] == null) + { + return; + } + + for (int index = 0; index < _textureBindings[stageIndex].Length; index++) + { + TextureBindingInfo binding = _textureBindings[stageIndex][index]; + + int packedId; + + if (binding.IsBindless) + { + ulong address; + + var bufferManager = _context.Methods.BufferManager; + + if (_isCompute) + { + address = bufferManager.GetComputeUniformBufferAddress(binding.CbufSlot); + } + else + { + address = bufferManager.GetGraphicsUniformBufferAddress(stageIndex, binding.CbufSlot); + } + + packedId = MemoryMarshal.Cast(_context.PhysicalMemory.GetSpan(address + (ulong)binding.CbufOffset * 4, 4))[0]; + } + else + { + packedId = ReadPackedId(stageIndex, binding.Handle); + } + + int textureId = UnpackTextureId(packedId); + int samplerId; + + if (_samplerIndex == SamplerIndex.ViaHeaderIndex) + { + samplerId = textureId; + } + else + { + samplerId = UnpackSamplerId(packedId); + } + + Texture texture = pool.Get(textureId); + + ITexture hostTexture = texture?.GetTargetTexture(binding.Target); + + if (_textureState[stageIndex][index].Texture != hostTexture || _rebind) + { + _textureState[stageIndex][index].Texture = hostTexture; + + _context.Renderer.Pipeline.SetTexture(index, stage, hostTexture); + } + + Sampler sampler = _samplerPool.Get(samplerId); + + ISampler hostSampler = sampler?.HostSampler; + + if (_textureState[stageIndex][index].Sampler != hostSampler || _rebind) + { + _textureState[stageIndex][index].Sampler = hostSampler; + + _context.Renderer.Pipeline.SetSampler(index, stage, hostSampler); + } + } + } + + /// + /// Ensures that the image bindings are visible to the host GPU. + /// Note: this actually performs the binding using the host graphics API. + /// + /// The current texture pool + /// The shader stage using the textures to be bound + /// The stage number of the specified shader stage + private void CommitImageBindings(TexturePool pool, ShaderStage stage, int stageIndex) + { + if (_imageBindings[stageIndex] == null) + { + return; + } + + for (int index = 0; index < _imageBindings[stageIndex].Length; index++) + { + TextureBindingInfo binding = _imageBindings[stageIndex][index]; + + int packedId = ReadPackedId(stageIndex, binding.Handle); + + int textureId = UnpackTextureId(packedId); + + Texture texture = pool.Get(textureId); + + ITexture hostTexture = texture?.GetTargetTexture(binding.Target); + + if (_imageState[stageIndex][index].Texture != hostTexture || _rebind) + { + _imageState[stageIndex][index].Texture = hostTexture; + + _context.Renderer.Pipeline.SetImage(index, stage, hostTexture); + } + } + } + + /// + /// Gets the texture descriptor for a given texture handle. + /// + /// The current GPU state + /// The stage number where the texture is bound + /// The texture handle + /// The texture descriptor for the specified texture + public TextureDescriptor GetTextureDescriptor(GpuState state, int stageIndex, int handle) + { + int packedId = ReadPackedId(stageIndex, handle); + + int textureId = UnpackTextureId(packedId); + + var poolState = state.Get(MethodOffset.TexturePoolState); + + ulong poolAddress = _context.MemoryManager.Translate(poolState.Address.Pack()); + + TexturePool texturePool = _texturePoolCache.FindOrCreate(poolAddress, poolState.MaximumId); + + return texturePool.GetDescriptor(textureId); + } + + /// + /// Reads a packed texture and sampler ID (basically, the real texture handle) + /// from the texture constant buffer. + /// + /// The number of the shader stage where the texture is bound + /// A word offset of the handle on the buffer (the "fake" shader handle) + /// The packed texture and sampler ID (the real texture handle) + private int ReadPackedId(int stageIndex, int wordOffset) + { + ulong address; + + var bufferManager = _context.Methods.BufferManager; + + if (_isCompute) + { + address = bufferManager.GetComputeUniformBufferAddress(_textureBufferIndex); + } + else + { + address = bufferManager.GetGraphicsUniformBufferAddress(stageIndex, _textureBufferIndex); + } + + address += (uint)wordOffset * 4; + + return BitConverter.ToInt32(_context.PhysicalMemory.GetSpan(address, 4)); + } + + /// + /// Unpacks the texture ID from the real texture handle. + /// + /// The real texture handle + /// The texture ID + private static int UnpackTextureId(int packedId) + { + return (packedId >> 0) & 0xfffff; + } + + /// + /// Unpacks the sampler ID from the real texture handle. + /// + /// The real texture handle + /// The sampler ID + private static int UnpackSamplerId(int packedId) + { + return (packedId >> 20) & 0xfff; + } + + /// + /// Invalidates a range of memory on all GPU resource pools (both texture and sampler pools). + /// + /// Start address of the range to invalidate + /// Size of the range to invalidate + public void InvalidatePoolRange(ulong address, ulong size) + { + _samplerPool?.InvalidateRange(address, size); + + _texturePoolCache.InvalidateRange(address, size); + } + + /// + /// Force all bound textures and images to be rebound the next time CommitBindings is called. + /// + public void Rebind() + { + _rebind = true; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs b/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs new file mode 100644 index 0000000000..408f6e2ac2 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs @@ -0,0 +1,115 @@ +using Ryujinx.Graphics.GAL; + +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// Texture format compatibility checks. + /// + static class TextureCompatibility + { + private enum FormatClass + { + Unclassified, + BCn64, + BCn128, + Bc1Rgb, + Bc1Rgba, + Bc2, + Bc3, + Bc4, + Bc5, + Bc6, + Bc7 + } + + /// + /// Checks if two formats are compatible, according to the host API copy format compatibility rules. + /// + /// First comparand + /// Second comparand + /// True if the formats are compatible, false otherwise + public static bool FormatCompatible(FormatInfo lhs, FormatInfo rhs) + { + if (IsDsFormat(lhs.Format) || IsDsFormat(rhs.Format)) + { + return lhs.Format == rhs.Format; + } + + if (lhs.Format.IsAstc() || rhs.Format.IsAstc()) + { + return lhs.Format == rhs.Format; + } + + if (lhs.IsCompressed && rhs.IsCompressed) + { + FormatClass lhsClass = GetFormatClass(lhs.Format); + FormatClass rhsClass = GetFormatClass(rhs.Format); + + return lhsClass == rhsClass; + } + else + { + return lhs.BytesPerPixel == rhs.BytesPerPixel; + } + } + + /// + /// Gets the texture format class, for compressed textures, or Unclassified otherwise. + /// + /// The format + /// Format class + private static FormatClass GetFormatClass(Format format) + { + switch (format) + { + case Format.Bc1RgbSrgb: + case Format.Bc1RgbUnorm: + return FormatClass.Bc1Rgb; + case Format.Bc1RgbaSrgb: + case Format.Bc1RgbaUnorm: + return FormatClass.Bc1Rgba; + case Format.Bc2Srgb: + case Format.Bc2Unorm: + return FormatClass.Bc2; + case Format.Bc3Srgb: + case Format.Bc3Unorm: + return FormatClass.Bc3; + case Format.Bc4Snorm: + case Format.Bc4Unorm: + return FormatClass.Bc4; + case Format.Bc5Snorm: + case Format.Bc5Unorm: + return FormatClass.Bc5; + case Format.Bc6HSfloat: + case Format.Bc6HUfloat: + return FormatClass.Bc6; + case Format.Bc7Srgb: + case Format.Bc7Unorm: + return FormatClass.Bc7; + } + + return FormatClass.Unclassified; + } + + /// + /// Checks if the format is a depth-stencil texture format. + /// + /// Format to check + /// True if the format is a depth-stencil format (including depth only), false otherwise + private static bool IsDsFormat(Format format) + { + switch (format) + { + case Format.D16Unorm: + case Format.D24X8Unorm: + case Format.D24UnormS8Uint: + case Format.D32Float: + case Format.D32FloatS8Uint: + case Format.S8Uint: + return true; + } + + return false; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Image/TextureComponent.cs b/Ryujinx.Graphics.Gpu/Image/TextureComponent.cs new file mode 100644 index 0000000000..359069bcfa --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Image/TextureComponent.cs @@ -0,0 +1,43 @@ +using Ryujinx.Graphics.GAL; + +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// Texture swizzle color component. + /// + enum TextureComponent + { + Zero = 0, + Red = 2, + Green = 3, + Blue = 4, + Alpha = 5, + OneSI = 6, + OneF = 7 + } + + static class TextureComponentConverter + { + /// + /// Converts the texture swizzle color component enum to the respective Graphics Abstraction Layer enum. + /// + /// Texture swizzle color component + /// Converted enum + public static SwizzleComponent Convert(this TextureComponent component) + { + switch (component) + { + case TextureComponent.Zero: return SwizzleComponent.Zero; + case TextureComponent.Red: return SwizzleComponent.Red; + case TextureComponent.Green: return SwizzleComponent.Green; + case TextureComponent.Blue: return SwizzleComponent.Blue; + case TextureComponent.Alpha: return SwizzleComponent.Alpha; + case TextureComponent.OneSI: + case TextureComponent.OneF: + return SwizzleComponent.One; + } + + return SwizzleComponent.Zero; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Image/TextureDescriptor.cs b/Ryujinx.Graphics.Gpu/Image/TextureDescriptor.cs new file mode 100644 index 0000000000..aebf4abf7a --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Image/TextureDescriptor.cs @@ -0,0 +1,229 @@ +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// Maxwell texture descriptor, as stored on the GPU texture pool memory region. + /// + struct TextureDescriptor + { + public uint Word0; + public uint Word1; + public uint Word2; + public uint Word3; + public uint Word4; + public uint Word5; + public uint Word6; + public uint Word7; + + /// + /// Unpacks Maxwell texture format integer. + /// + /// The texture format integer + public uint UnpackFormat() + { + return Word0 & 0x8007ffff; + } + + /// + /// Unpacks the swizzle component for the texture red color channel. + /// + /// The swizzle component + public TextureComponent UnpackSwizzleR() + { + return(TextureComponent)((Word0 >> 19) & 7); + } + + /// + /// Unpacks the swizzle component for the texture green color channel. + /// + /// The swizzle component + public TextureComponent UnpackSwizzleG() + { + return(TextureComponent)((Word0 >> 22) & 7); + } + + /// + /// Unpacks the swizzle component for the texture blue color channel. + /// + /// The swizzle component + public TextureComponent UnpackSwizzleB() + { + return(TextureComponent)((Word0 >> 25) & 7); + } + + /// + /// Unpacks the swizzle component for the texture alpha color channel. + /// + /// The swizzle component + public TextureComponent UnpackSwizzleA() + { + return(TextureComponent)((Word0 >> 28) & 7); + } + + /// + /// Unpacks the 40-bits texture GPU virtual address. + /// + /// The GPU virtual address + public ulong UnpackAddress() + { + return Word1 | ((ulong)(Word2 & 0xffff) << 32); + } + + /// + /// Unpacks texture descriptor type for this texture descriptor. + /// This defines the texture layout, among other things. + /// + /// The texture descriptor type + public TextureDescriptorType UnpackTextureDescriptorType() + { + return (TextureDescriptorType)((Word2 >> 21) & 7); + } + + /// + /// Unpacks the texture stride (bytes per line) for linear textures only. + /// Always 32-bytes aligned. + /// + /// The linear texture stride + public int UnpackStride() + { + return (int)(Word3 & 0xffff) << 5; + } + + /// + /// Unpacks the GOB block size in X (width) for block linear textures. + /// Must be always 1, ignored by the GPU. + /// + /// THe GOB block X size + public int UnpackGobBlocksInX() + { + return 1 << (int)(Word3 & 7); + } + + /// + /// Unpacks the GOB block size in Y (height) for block linear textures. + /// Must be always a power of 2, with a maximum value of 32. + /// + /// THe GOB block Y size + public int UnpackGobBlocksInY() + { + return 1 << (int)((Word3 >> 3) & 7); + } + + /// + /// Unpacks the GOB block size in Z (depth) for block linear textures. + /// Must be always a power of 2, with a maximum value of 32. + /// Must be 1 for any texture target other than 3D textures. + /// + /// The GOB block Z size + public int UnpackGobBlocksInZ() + { + return 1 << (int)((Word3 >> 6) & 7); + } + + /// + /// Number of GOB blocks per tile in the X direction. + /// This is only used for sparse textures, should be 1 otherwise. + /// + /// The number of GOB blocks per tile + public int UnpackGobBlocksInTileX() + { + return 1 << (int)((Word3 >> 10) & 7); + } + + /// + /// Unpacks the number of mipmap levels of the texture. + /// + /// The number of mipmap levels + public int UnpackLevels() + { + return (int)(Word3 >> 28) + 1; + } + + /// + /// Unpack the base level texture width size. + /// + /// The texture width + public int UnpackWidth() + { + return (int)(Word4 & 0xffff) + 1; + } + + /// + /// Unpacks the texture sRGB format flag. + /// + /// True if the texture is sRGB, false otherwise + public bool UnpackSrgb() + { + return (Word4 & (1 << 22)) != 0; + } + + /// + /// Unpacks the texture target. + /// + /// The texture target + public TextureTarget UnpackTextureTarget() + { + return (TextureTarget)((Word4 >> 23) & 0xf); + } + + /// + /// Unpack the base level texture height size, or array layers for 1D array textures. + /// Should be ignored for 1D or buffer textures. + /// + /// The texture height or layers count + public int UnpackHeight() + { + return (int)(Word5 & 0xffff) + 1; + } + + /// + /// Unpack the base level texture depth size, number of array layers or cubemap faces. + /// The meaning of this value depends on the texture target. + /// + /// The texture depth, layer or faces count + public int UnpackDepth() + { + return (int)((Word5 >> 16) & 0x3fff) + 1; + } + + /// + /// Unpacks the texture coordinates normalized flag. + /// When this is true, texture coordinates are expected to be in the [0, 1] range on the shader. + /// When this is false, texture coordinates are expected to be in the [0, W], [0, H] and [0, D] range. + /// It must be set to false (by the guest driver) for rectangle textures. + /// + /// The texture coordinates normalized flag + public bool UnpackTextureCoordNormalized() + { + return (Word5 & (1 << 31)) != 0; + } + + /// + /// Unpacks the base mipmap level of the texture. + /// + /// The base mipmap level of the texture + public int UnpackBaseLevel() + { + return (int)(Word7 & 0xf); + } + + /// + /// Unpacks the maximum mipmap level (inclusive) of the texture. + /// Usually equal to Levels minus 1. + /// + /// The maximum mipmap level (inclusive) of the texture + public int UnpackMaxLevelInclusive() + { + return (int)((Word7 >> 4) & 0xf); + } + + /// + /// Unpacks the multisampled texture samples count in each direction. + /// Must be ignored for non-multisample textures. + /// + /// The multisample counts enum + public TextureMsaaMode UnpackTextureMsaaMode() + { + return (TextureMsaaMode)((Word7 >> 8) & 0xf); + } + } +} diff --git a/Ryujinx.Graphics.Gpu/Image/TextureDescriptorType.cs b/Ryujinx.Graphics.Gpu/Image/TextureDescriptorType.cs new file mode 100644 index 0000000000..8e7d40bbe1 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Image/TextureDescriptorType.cs @@ -0,0 +1,16 @@ +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// The texture descriptor type. + /// This specifies the texture memory layout. + /// The texture descriptor structure depends on the type. + /// + enum TextureDescriptorType + { + Buffer, + LinearColorKey, + Linear, + BlockLinear, + BlockLinearColorKey + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Image/TextureInfo.cs b/Ryujinx.Graphics.Gpu/Image/TextureInfo.cs new file mode 100644 index 0000000000..2bcafd727f --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Image/TextureInfo.cs @@ -0,0 +1,207 @@ +using Ryujinx.Graphics.GAL; + +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// Texture information. + /// + struct TextureInfo + { + /// + /// Address of the texture in guest memory. + /// + public ulong Address { get; } + + /// + /// The width of the texture. + /// + public int Width { get; } + + /// + /// The height of the texture, or layers count for 1D array textures. + /// + public int Height { get; } + + /// + /// The depth of the texture (for 3D textures), or layers count for array textures. + /// + public int DepthOrLayers { get; } + + /// + /// The number of mipmap levels of the texture. + /// + public int Levels { get; } + + /// + /// The number of samples in the X direction for multisampled textures. + /// + public int SamplesInX { get; } + + /// + /// The number of samples in the Y direction for multisampled textures. + /// + public int SamplesInY { get; } + + /// + /// The number of bytes per line for linear textures. + /// + public int Stride { get; } + + /// + /// Indicates whenever or not the texture is a linear texture. + /// + public bool IsLinear { get; } + + /// + /// GOB blocks in the Y direction, for block linear textures. + /// + public int GobBlocksInY { get; } + + /// + /// GOB blocks in the Z direction, for block linear textures. + /// + public int GobBlocksInZ { get; } + + /// + /// Number of GOB blocks per tile in the X direction, for block linear textures. + /// + public int GobBlocksInTileX { get; } + + /// + /// Total number of samples for multisampled textures. + /// + public int Samples => SamplesInX * SamplesInY; + + /// + /// Texture target type. + /// + public Target Target { get; } + + /// + /// Texture format information. + /// + public FormatInfo FormatInfo { get; } + + /// + /// Depth-stencil mode of the texture. This defines whenever the depth or stencil value is read from shaders, + /// for depth-stencil texture formats. + /// + public DepthStencilMode DepthStencilMode { get; } + + /// + /// Texture swizzle for the red color channel. + /// + public SwizzleComponent SwizzleR { get; } + /// + /// Texture swizzle for the green color channel. + /// + public SwizzleComponent SwizzleG { get; } + /// + /// Texture swizzle for the blue color channel. + /// + public SwizzleComponent SwizzleB { get; } + /// + /// Texture swizzle for the alpha color channel. + /// + public SwizzleComponent SwizzleA { get; } + + /// + /// Constructs the texture information structure. + /// + /// The address of the texture + /// The width of the texture + /// The height or the texture + /// The depth or layers count of the texture + /// The amount of mipmap levels of the texture + /// The number of samples in the X direction for multisample textures, should be 1 otherwise + /// The number of samples in the Y direction for multisample textures, should be 1 otherwise + /// The stride for linear textures + /// Whenever the texture is linear or block linear + /// Number of GOB blocks in the Y direction + /// Number of GOB blocks in the Z direction + /// Number of GOB blocks per tile in the X direction + /// Texture target type + /// Texture format information + /// Depth-stencil mode + /// Swizzle for the red color channel + /// Swizzle for the green color channel + /// Swizzle for the blue color channel + /// Swizzle for the alpha color channel + public TextureInfo( + ulong address, + int width, + int height, + int depthOrLayers, + int levels, + int samplesInX, + int samplesInY, + int stride, + bool isLinear, + int gobBlocksInY, + int gobBlocksInZ, + int gobBlocksInTileX, + Target target, + FormatInfo formatInfo, + DepthStencilMode depthStencilMode = DepthStencilMode.Depth, + SwizzleComponent swizzleR = SwizzleComponent.Red, + SwizzleComponent swizzleG = SwizzleComponent.Green, + SwizzleComponent swizzleB = SwizzleComponent.Blue, + SwizzleComponent swizzleA = SwizzleComponent.Alpha) + { + Address = address; + Width = width; + Height = height; + DepthOrLayers = depthOrLayers; + Levels = levels; + SamplesInX = samplesInX; + SamplesInY = samplesInY; + Stride = stride; + IsLinear = isLinear; + GobBlocksInY = gobBlocksInY; + GobBlocksInZ = gobBlocksInZ; + GobBlocksInTileX = gobBlocksInTileX; + Target = target; + FormatInfo = formatInfo; + DepthStencilMode = depthStencilMode; + SwizzleR = swizzleR; + SwizzleG = swizzleG; + SwizzleB = swizzleB; + SwizzleA = swizzleA; + } + + /// + /// Gets the real texture depth. + /// Returns 1 for any target other than 3D textures. + /// + /// Texture depth + public int GetDepth() + { + return Target == Target.Texture3D ? DepthOrLayers : 1; + } + + /// + /// Gets the number of layers of the texture. + /// Returns 1 for non-array textures, 6 for cubemap textures, and layer faces for cubemap array textures. + /// + /// The number of texture layers + public int GetLayers() + { + if (Target == Target.Texture2DArray || Target == Target.Texture2DMultisampleArray) + { + return DepthOrLayers; + } + else if (Target == Target.CubemapArray) + { + return DepthOrLayers * 6; + } + else if (Target == Target.Cubemap) + { + return 6; + } + else + { + return 1; + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Image/TextureManager.cs b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs new file mode 100644 index 0000000000..1572719118 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs @@ -0,0 +1,779 @@ +using Ryujinx.Common; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Image; +using Ryujinx.Graphics.Gpu.Memory; +using Ryujinx.Graphics.Gpu.State; +using Ryujinx.Graphics.Texture; +using System; + +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// Texture manager. + /// + class TextureManager : IDisposable + { + private const int OverlapsBufferInitialCapacity = 10; + private const int OverlapsBufferMaxCapacity = 10000; + + private readonly GpuContext _context; + + private readonly TextureBindingsManager _cpBindingsManager; + private readonly TextureBindingsManager _gpBindingsManager; + + private readonly Texture[] _rtColors; + + private Texture _rtDepthStencil; + + private readonly ITexture[] _rtHostColors; + + private ITexture _rtHostDs; + + private readonly RangeList _textures; + + private Texture[] _textureOverlaps; + + private readonly AutoDeleteCache _cache; + + /// + /// Constructs a new instance of the texture manager. + /// + /// The GPU context that the texture manager belongs to + public TextureManager(GpuContext context) + { + _context = context; + + TexturePoolCache texturePoolCache = new TexturePoolCache(context); + + _cpBindingsManager = new TextureBindingsManager(context, texturePoolCache, isCompute: true); + _gpBindingsManager = new TextureBindingsManager(context, texturePoolCache, isCompute: false); + + _rtColors = new Texture[Constants.TotalRenderTargets]; + + _rtHostColors = new ITexture[Constants.TotalRenderTargets]; + + _textures = new RangeList(); + + _textureOverlaps = new Texture[OverlapsBufferInitialCapacity]; + + _cache = new AutoDeleteCache(); + } + + /// + /// Sets texture bindings on the compute pipeline. + /// + /// The texture bindings + public void SetComputeTextures(TextureBindingInfo[] bindings) + { + _cpBindingsManager.SetTextures(0, bindings); + } + + /// + /// Sets texture bindings on the graphics pipeline. + /// + /// The index of the shader stage to bind the textures + /// The texture bindings + public void SetGraphicsTextures(int stage, TextureBindingInfo[] bindings) + { + _gpBindingsManager.SetTextures(stage, bindings); + } + + /// + /// Sets image bindings on the compute pipeline. + /// + /// The image bindings + public void SetComputeImages(TextureBindingInfo[] bindings) + { + _cpBindingsManager.SetImages(0, bindings); + } + + /// + /// Sets image bindings on the graphics pipeline. + /// + /// The index of the shader stage to bind the images + /// The image bindings + public void SetGraphicsImages(int stage, TextureBindingInfo[] bindings) + { + _gpBindingsManager.SetImages(stage, bindings); + } + + /// + /// Sets the texture constant buffer index on the compute pipeline. + /// + /// The texture constant buffer index + public void SetComputeTextureBufferIndex(int index) + { + _cpBindingsManager.SetTextureBufferIndex(index); + } + + /// + /// Sets the texture constant buffer index on the graphics pipeline. + /// + /// The texture constant buffer index + public void SetGraphicsTextureBufferIndex(int index) + { + _gpBindingsManager.SetTextureBufferIndex(index); + } + + /// + /// Sets the current sampler pool on the compute pipeline. + /// + /// The start GPU virtual address of the sampler pool + /// The maximum ID of the sampler pool + /// The indexing type of the sampler pool + public void SetComputeSamplerPool(ulong gpuVa, int maximumId, SamplerIndex samplerIndex) + { + _cpBindingsManager.SetSamplerPool(gpuVa, maximumId, samplerIndex); + } + + /// + /// Sets the current sampler pool on the graphics pipeline. + /// + /// The start GPU virtual address of the sampler pool + /// The maximum ID of the sampler pool + /// The indexing type of the sampler pool + public void SetGraphicsSamplerPool(ulong gpuVa, int maximumId, SamplerIndex samplerIndex) + { + _gpBindingsManager.SetSamplerPool(gpuVa, maximumId, samplerIndex); + } + + /// + /// Sets the current texture pool on the compute pipeline. + /// + /// The start GPU virtual address of the texture pool + /// The maximum ID of the texture pool + public void SetComputeTexturePool(ulong gpuVa, int maximumId) + { + _cpBindingsManager.SetTexturePool(gpuVa, maximumId); + } + + /// + /// Sets the current texture pool on the graphics pipeline. + /// + /// The start GPU virtual address of the texture pool + /// The maximum ID of the texture pool + public void SetGraphicsTexturePool(ulong gpuVa, int maximumId) + { + _gpBindingsManager.SetTexturePool(gpuVa, maximumId); + } + + /// + /// Sets the render target color buffer. + /// + /// The index of the color buffer to set (up to 8) + /// The color buffer texture + public void SetRenderTargetColor(int index, Texture color) + { + _rtColors[index] = color; + } + + /// + /// Sets the render target depth-stencil buffer. + /// + /// The depth-stencil buffer texture + public void SetRenderTargetDepthStencil(Texture depthStencil) + { + _rtDepthStencil = depthStencil; + } + + /// + /// Commits bindings on the compute pipeline. + /// + public void CommitComputeBindings() + { + // Every time we switch between graphics and compute work, + // we must rebind everything. + // Since compute work happens less often, we always do that + // before and after the compute dispatch. + _cpBindingsManager.Rebind(); + _cpBindingsManager.CommitBindings(); + _gpBindingsManager.Rebind(); + } + + /// + /// Commits bindings on the graphics pipeline. + /// + public void CommitGraphicsBindings() + { + _gpBindingsManager.CommitBindings(); + + UpdateRenderTargets(); + } + + /// + /// Gets a texture descriptor used on the graphics pipeline. + /// + /// Current GPU state + /// Index of the shader stage where the texture is bound + /// Shader "fake" handle of the texture + /// The texture descriptor + public TextureDescriptor GetGraphicsTextureDescriptor(GpuState state, int stageIndex, int handle) + { + return _gpBindingsManager.GetTextureDescriptor(state, stageIndex, handle); + } + + /// + /// Update host framebuffer attachments based on currently bound render target buffers. + /// + private void UpdateRenderTargets() + { + bool anyChanged = false; + + if (_rtHostDs != _rtDepthStencil?.HostTexture) + { + _rtHostDs = _rtDepthStencil?.HostTexture; + + anyChanged = true; + } + + for (int index = 0; index < _rtColors.Length; index++) + { + ITexture hostTexture = _rtColors[index]?.HostTexture; + + if (_rtHostColors[index] != hostTexture) + { + _rtHostColors[index] = hostTexture; + + anyChanged = true; + } + } + + if (anyChanged) + { + _context.Renderer.Pipeline.SetRenderTargets(_rtHostColors, _rtHostDs); + } + } + + /// + /// Tries to find an existing texture, or create a new one if not found. + /// + /// Copy texture to find or create + /// The texture + public Texture FindOrCreateTexture(CopyTexture copyTexture) + { + ulong address = _context.MemoryManager.Translate(copyTexture.Address.Pack()); + + if (address == MemoryManager.BadAddress) + { + return null; + } + + int gobBlocksInY = copyTexture.MemoryLayout.UnpackGobBlocksInY(); + int gobBlocksInZ = copyTexture.MemoryLayout.UnpackGobBlocksInZ(); + + FormatInfo formatInfo = copyTexture.Format.Convert(); + + int width; + + if (copyTexture.LinearLayout) + { + width = copyTexture.Stride / formatInfo.BytesPerPixel; + } + else + { + width = copyTexture.Width; + } + + TextureInfo info = new TextureInfo( + address, + width, + copyTexture.Height, + copyTexture.Depth, + 1, + 1, + 1, + copyTexture.Stride, + copyTexture.LinearLayout, + gobBlocksInY, + gobBlocksInZ, + 1, + Target.Texture2D, + formatInfo); + + Texture texture = FindOrCreateTexture(info, TextureSearchFlags.IgnoreMs); + + texture.SynchronizeMemory(); + + return texture; + } + + /// + /// Tries to find an existing texture, or create a new one if not found. + /// + /// Color buffer texture to find or create + /// Number of samples in the X direction, for MSAA + /// Number of samples in the Y direction, for MSAA + /// The texture + public Texture FindOrCreateTexture(RtColorState colorState, int samplesInX, int samplesInY) + { + ulong address = _context.MemoryManager.Translate(colorState.Address.Pack()); + + if (address == MemoryManager.BadAddress) + { + return null; + } + + bool isLinear = colorState.MemoryLayout.UnpackIsLinear(); + + int gobBlocksInY = colorState.MemoryLayout.UnpackGobBlocksInY(); + int gobBlocksInZ = colorState.MemoryLayout.UnpackGobBlocksInZ(); + + Target target; + + if (colorState.MemoryLayout.UnpackIsTarget3D()) + { + target = Target.Texture3D; + } + else if ((samplesInX | samplesInY) != 1) + { + target = colorState.Depth > 1 + ? Target.Texture2DMultisampleArray + : Target.Texture2DMultisample; + } + else + { + target = colorState.Depth > 1 + ? Target.Texture2DArray + : Target.Texture2D; + } + + FormatInfo formatInfo = colorState.Format.Convert(); + + int width, stride; + + // For linear textures, the width value is actually the stride. + // We can easily get the width by dividing the stride by the bpp, + // since the stride is the total number of bytes occupied by a + // line. The stride should also meet alignment constraints however, + // so the width we get here is the aligned width. + if (isLinear) + { + width = colorState.WidthOrStride / formatInfo.BytesPerPixel; + stride = colorState.WidthOrStride; + } + else + { + width = colorState.WidthOrStride; + stride = 0; + } + + TextureInfo info = new TextureInfo( + address, + width, + colorState.Height, + colorState.Depth, + 1, + samplesInX, + samplesInY, + stride, + isLinear, + gobBlocksInY, + gobBlocksInZ, + 1, + target, + formatInfo); + + Texture texture = FindOrCreateTexture(info); + + texture.SynchronizeMemory(); + + return texture; + } + + /// + /// Tries to find an existing texture, or create a new one if not found. + /// + /// Depth-stencil buffer texture to find or create + /// Size of the depth-stencil texture + /// Number of samples in the X direction, for MSAA + /// Number of samples in the Y direction, for MSAA + /// The texture + public Texture FindOrCreateTexture(RtDepthStencilState dsState, Size3D size, int samplesInX, int samplesInY) + { + ulong address = _context.MemoryManager.Translate(dsState.Address.Pack()); + + if (address == MemoryManager.BadAddress) + { + return null; + } + + int gobBlocksInY = dsState.MemoryLayout.UnpackGobBlocksInY(); + int gobBlocksInZ = dsState.MemoryLayout.UnpackGobBlocksInZ(); + + Target target = (samplesInX | samplesInY) != 1 + ? Target.Texture2DMultisample + : Target.Texture2D; + + FormatInfo formatInfo = dsState.Format.Convert(); + + TextureInfo info = new TextureInfo( + address, + size.Width, + size.Height, + size.Depth, + 1, + samplesInX, + samplesInY, + 0, + false, + gobBlocksInY, + gobBlocksInZ, + 1, + target, + formatInfo); + + Texture texture = FindOrCreateTexture(info); + + texture.SynchronizeMemory(); + + return texture; + } + + /// + /// Tries to find an existing texture, or create a new one if not found. + /// + /// Texture information of the texture to be found or created + /// The texture search flags, defines texture comparison rules + /// The texture + public Texture FindOrCreateTexture(TextureInfo info, TextureSearchFlags flags = TextureSearchFlags.None) + { + bool isSamplerTexture = (flags & TextureSearchFlags.Sampler) != 0; + + // Try to find a perfect texture match, with the same address and parameters. + int sameAddressOverlapsCount = _textures.FindOverlaps(info.Address, ref _textureOverlaps); + + for (int index = 0; index < sameAddressOverlapsCount; index++) + { + Texture overlap = _textureOverlaps[index]; + + if (overlap.IsPerfectMatch(info, flags)) + { + if (!isSamplerTexture) + { + // If not a sampler texture, it is managed by the auto delete + // cache, ensure that it is on the "top" of the list to avoid + // deletion. + _cache.Lift(overlap); + } + else if (!overlap.SizeMatches(info)) + { + // If this is used for sampling, the size must match, + // otherwise the shader would sample garbage data. + // To fix that, we create a new texture with the correct + // size, and copy the data from the old one to the new one. + overlap.ChangeSize(info.Width, info.Height, info.DepthOrLayers); + } + + return overlap; + } + } + + // Calculate texture sizes, used to find all overlapping textures. + SizeInfo sizeInfo; + + if (info.IsLinear) + { + sizeInfo = SizeCalculator.GetLinearTextureSize( + info.Stride, + info.Height, + info.FormatInfo.BlockHeight); + } + else + { + sizeInfo = SizeCalculator.GetBlockLinearTextureSize( + info.Width, + info.Height, + info.GetDepth(), + info.Levels, + info.GetLayers(), + info.FormatInfo.BlockWidth, + info.FormatInfo.BlockHeight, + info.FormatInfo.BytesPerPixel, + info.GobBlocksInY, + info.GobBlocksInZ, + info.GobBlocksInTileX); + } + + // Find view compatible matches. + ulong size = (ulong)sizeInfo.TotalSize; + + int overlapsCount = _textures.FindOverlaps(info.Address, size, ref _textureOverlaps); + + Texture texture = null; + + for (int index = 0; index < overlapsCount; index++) + { + Texture overlap = _textureOverlaps[index]; + + if (overlap.IsViewCompatible(info, size, out int firstLayer, out int firstLevel)) + { + if (!isSamplerTexture) + { + info = AdjustSizes(overlap, info, firstLevel); + } + + texture = overlap.CreateView(info, sizeInfo, firstLayer, firstLevel); + + // The size only matters (and is only really reliable) when the + // texture is used on a sampler, because otherwise the size will be + // aligned. + if (!overlap.SizeMatches(info, firstLevel) && isSamplerTexture) + { + texture.ChangeSize(info.Width, info.Height, info.DepthOrLayers); + } + + break; + } + } + + // No match, create a new texture. + if (texture == null) + { + texture = new Texture(_context, info, sizeInfo); + + // We need to synchronize before copying the old view data to the texture, + // otherwise the copied data would be overwritten by a future synchronization. + texture.SynchronizeMemory(); + + for (int index = 0; index < overlapsCount; index++) + { + Texture overlap = _textureOverlaps[index]; + + if (texture.IsViewCompatible(overlap.Info, overlap.Size, out int firstLayer, out int firstLevel)) + { + TextureInfo overlapInfo = AdjustSizes(texture, overlap.Info, firstLevel); + + TextureCreateInfo createInfo = GetCreateInfo(overlapInfo, _context.Capabilities); + + ITexture newView = texture.HostTexture.CreateView(createInfo, firstLayer, firstLevel); + + overlap.HostTexture.CopyTo(newView, 0, 0); + + overlap.ReplaceView(texture, overlapInfo, newView); + } + } + + // If the texture is a 3D texture, we need to additionally copy any slice + // of the 3D texture to the newly created 3D texture. + if (info.Target == Target.Texture3D) + { + for (int index = 0; index < overlapsCount; index++) + { + Texture overlap = _textureOverlaps[index]; + + if (texture.IsViewCompatible( + overlap.Info, + overlap.Size, + isCopy: true, + out int firstLayer, + out int firstLevel)) + { + overlap.HostTexture.CopyTo(texture.HostTexture, firstLayer, firstLevel); + } + } + } + } + + // Sampler textures are managed by the texture pool, all other textures + // are managed by the auto delete cache. + if (!isSamplerTexture) + { + _cache.Add(texture); + } + + _textures.Add(texture); + + ShrinkOverlapsBufferIfNeeded(); + + return texture; + } + + /// + /// Resizes the temporary buffer used for range list intersection results, if it has grown too much. + /// + private void ShrinkOverlapsBufferIfNeeded() + { + if (_textureOverlaps.Length > OverlapsBufferMaxCapacity) + { + Array.Resize(ref _textureOverlaps, OverlapsBufferMaxCapacity); + } + } + + /// + /// Adjusts the size of the texture information for a given mipmap level, + /// based on the size of a parent texture. + /// + /// The parent texture + /// The texture information to be adjusted + /// The first level of the texture view + /// The adjusted texture information with the new size + private static TextureInfo AdjustSizes(Texture parent, TextureInfo info, int firstLevel) + { + // When the texture is used as view of another texture, we must + // ensure that the sizes are valid, otherwise data uploads would fail + // (and the size wouldn't match the real size used on the host API). + // Given a parent texture from where the view is created, we have the + // following rules: + // - The view size must be equal to the parent size, divided by (2 ^ l), + // where l is the first mipmap level of the view. The division result must + // be rounded down, and the result must be clamped to 1. + // - If the parent format is compressed, and the view format isn't, the + // view size is calculated as above, but the width and height of the + // view must be also divided by the compressed format block width and height. + // - If the parent format is not compressed, and the view is, the view + // size is calculated as described on the first point, but the width and height + // of the view must be also multiplied by the block width and height. + int width = Math.Max(1, parent.Info.Width >> firstLevel); + int height = Math.Max(1, parent.Info.Height >> firstLevel); + + if (parent.Info.FormatInfo.IsCompressed && !info.FormatInfo.IsCompressed) + { + width = BitUtils.DivRoundUp(width, parent.Info.FormatInfo.BlockWidth); + height = BitUtils.DivRoundUp(height, parent.Info.FormatInfo.BlockHeight); + } + else if (!parent.Info.FormatInfo.IsCompressed && info.FormatInfo.IsCompressed) + { + width *= info.FormatInfo.BlockWidth; + height *= info.FormatInfo.BlockHeight; + } + + int depthOrLayers; + + if (info.Target == Target.Texture3D) + { + depthOrLayers = Math.Max(1, parent.Info.DepthOrLayers >> firstLevel); + } + else + { + depthOrLayers = info.DepthOrLayers; + } + + return new TextureInfo( + info.Address, + width, + height, + depthOrLayers, + info.Levels, + info.SamplesInX, + info.SamplesInY, + info.Stride, + info.IsLinear, + info.GobBlocksInY, + info.GobBlocksInZ, + info.GobBlocksInTileX, + info.Target, + info.FormatInfo, + info.DepthStencilMode, + info.SwizzleR, + info.SwizzleG, + info.SwizzleB, + info.SwizzleA); + } + + + /// + /// Gets a texture creation information from texture information. + /// This can be used to create new host textures. + /// + /// Texture information + /// GPU capabilities + /// The texture creation information + public static TextureCreateInfo GetCreateInfo(TextureInfo info, Capabilities caps) + { + FormatInfo formatInfo = info.FormatInfo; + + if (!caps.SupportsAstcCompression) + { + if (formatInfo.Format.IsAstcUnorm()) + { + formatInfo = new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4); + } + else if (formatInfo.Format.IsAstcSrgb()) + { + formatInfo = new FormatInfo(Format.R8G8B8A8Srgb, 1, 1, 4); + } + } + + int width = info.Width / info.SamplesInX; + int height = info.Height / info.SamplesInY; + + int depth = info.GetDepth() * info.GetLayers(); + + return new TextureCreateInfo( + width, + height, + depth, + info.Levels, + info.Samples, + formatInfo.BlockWidth, + formatInfo.BlockHeight, + formatInfo.BytesPerPixel, + formatInfo.Format, + info.DepthStencilMode, + info.Target, + info.SwizzleR, + info.SwizzleG, + info.SwizzleB, + info.SwizzleA); + } + + /// + /// Flushes all the textures in the cache that have been modified since the last call. + /// + public void Flush() + { + foreach (Texture texture in _cache) + { + if (texture.Info.IsLinear && texture.Modified) + { + texture.Flush(); + + texture.Modified = false; + } + } + } + + /// + /// Flushes the textures in the cache inside a given range that have been modified since the last call. + /// + /// The range start address + /// The range size + public void Flush(ulong address, ulong size) + { + foreach (Texture texture in _cache) + { + if (texture.OverlapsWith(address, size) && texture.Modified) + { + texture.Flush(); + + texture.Modified = false; + } + } + } + + /// + /// Removes a texture from the cache. + /// + /// + /// This only removes the texture from the internal list, not from the auto-deletion cache. + /// It may still have live references after the removal. + /// + /// The texture to be removed + public void RemoveTextureFromCache(Texture texture) + { + _textures.Remove(texture); + } + + /// + /// Disposes all textures in the cache. + /// It's an error to use the texture manager after disposal. + /// + public void Dispose() + { + foreach (Texture texture in _textures) + { + texture.Dispose(); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Image/TextureMsaaMode.cs b/Ryujinx.Graphics.Gpu/Image/TextureMsaaMode.cs new file mode 100644 index 0000000000..0461888f11 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Image/TextureMsaaMode.cs @@ -0,0 +1,68 @@ +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// Multisampled texture samples count. + /// + enum TextureMsaaMode + { + Ms1x1 = 0, + Ms2x2 = 2, + Ms4x2 = 4, + Ms2x1 = 5, + Ms4x4 = 6 + } + + static class TextureMsaaModeConverter + { + /// + /// Returns the total number of samples from the MSAA mode. + /// + /// The MSAA mode + /// The total number of samples + public static int SamplesCount(this TextureMsaaMode msaaMode) + { + return msaaMode switch + { + TextureMsaaMode.Ms2x1 => 2, + TextureMsaaMode.Ms2x2 => 4, + TextureMsaaMode.Ms4x2 => 8, + TextureMsaaMode.Ms4x4 => 16, + _ => 1 + }; + } + + /// + /// Returns the number of samples in the X direction from the MSAA mode. + /// + /// The MSAA mode + /// The number of samples in the X direction + public static int SamplesInX(this TextureMsaaMode msaaMode) + { + return msaaMode switch + { + TextureMsaaMode.Ms2x1 => 2, + TextureMsaaMode.Ms2x2 => 2, + TextureMsaaMode.Ms4x2 => 4, + TextureMsaaMode.Ms4x4 => 4, + _ => 1 + }; + } + + /// + /// Returns the number of samples in the Y direction from the MSAA mode. + /// + /// The MSAA mode + /// The number of samples in the Y direction + public static int SamplesInY(this TextureMsaaMode msaaMode) + { + return msaaMode switch + { + TextureMsaaMode.Ms2x1 => 1, + TextureMsaaMode.Ms2x2 => 2, + TextureMsaaMode.Ms4x2 => 2, + TextureMsaaMode.Ms4x4 => 4, + _ => 1 + }; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Image/TexturePool.cs b/Ryujinx.Graphics.Gpu/Image/TexturePool.cs new file mode 100644 index 0000000000..a4f54c5206 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Image/TexturePool.cs @@ -0,0 +1,265 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Memory; +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// Texture pool. + /// + class TexturePool : Pool + { + private int _sequenceNumber; + + /// + /// Intrusive linked list node used on the texture pool cache. + /// + public LinkedListNode CacheNode { get; set; } + + /// + /// Constructs a new instance of the texture pool. + /// + /// GPU context that the texture pool belongs to + /// Address of the texture pool in guest memory + /// Maximum texture ID of the texture pool (equal to maximum textures minus one) + public TexturePool(GpuContext context, ulong address, int maximumId) : base(context, address, maximumId) { } + + /// + /// Gets the texture with the given ID. + /// + /// ID of the texture. This is effectively a zero-based index + /// The texture with the given ID + public override Texture Get(int id) + { + if ((uint)id >= Items.Length) + { + return null; + } + + if (_sequenceNumber != Context.SequenceNumber) + { + _sequenceNumber = Context.SequenceNumber; + + SynchronizeMemory(); + } + + Texture texture = Items[id]; + + if (texture == null) + { + TextureDescriptor descriptor = GetDescriptor(id); + + TextureInfo info = GetInfo(descriptor); + + // Bad address. We can't add a texture with a invalid address + // to the cache. + if (info.Address == MemoryManager.BadAddress) + { + return null; + } + + texture = Context.Methods.TextureManager.FindOrCreateTexture(info, TextureSearchFlags.Sampler); + + texture.IncrementReferenceCount(); + + Items[id] = texture; + } + else + { + // Memory is automatically synchronized on texture creation. + texture.SynchronizeMemory(); + } + + return texture; + } + + /// + /// Gets the texture descriptor from a given texture ID. + /// + /// ID of the texture. This is effectively a zero-based index + /// The texture descriptor + public TextureDescriptor GetDescriptor(int id) + { + ulong address = Address + (ulong)(uint)id * DescriptorSize; + + ReadOnlySpan data = Context.PhysicalMemory.GetSpan(address, DescriptorSize); + + return MemoryMarshal.Cast(data)[0]; + } + + /// + /// Implementation of the texture pool range invalidation. + /// + /// Start address of the range of the texture pool + /// Size of the range being invalidated + protected override void InvalidateRangeImpl(ulong address, ulong size) + { + ulong endAddress = address + size; + + for (; address < endAddress; address += DescriptorSize) + { + int id = (int)((address - Address) / DescriptorSize); + + Texture texture = Items[id]; + + if (texture != null) + { + ReadOnlySpan data = Context.PhysicalMemory.GetSpan(address, DescriptorSize); + + TextureDescriptor descriptor = MemoryMarshal.Cast(data)[0]; + + // If the descriptors are the same, the texture is the same, + // we don't need to remove as it was not modified. Just continue. + if (texture.IsPerfectMatch(GetInfo(descriptor), TextureSearchFlags.Strict)) + { + continue; + } + + texture.DecrementReferenceCount(); + + Items[id] = null; + } + } + } + + /// + /// Gets texture information from a texture descriptor. + /// + /// The texture descriptor + /// The texture information + private TextureInfo GetInfo(TextureDescriptor descriptor) + { + ulong address = Context.MemoryManager.Translate(descriptor.UnpackAddress()); + + int width = descriptor.UnpackWidth(); + int height = descriptor.UnpackHeight(); + int depthOrLayers = descriptor.UnpackDepth(); + int levels = descriptor.UnpackLevels(); + + TextureMsaaMode msaaMode = descriptor.UnpackTextureMsaaMode(); + + int samplesInX = msaaMode.SamplesInX(); + int samplesInY = msaaMode.SamplesInY(); + + int stride = descriptor.UnpackStride(); + + TextureDescriptorType descriptorType = descriptor.UnpackTextureDescriptorType(); + + bool isLinear = descriptorType == TextureDescriptorType.Linear; + + Target target = descriptor.UnpackTextureTarget().Convert((samplesInX | samplesInY) != 1); + + uint format = descriptor.UnpackFormat(); + bool srgb = descriptor.UnpackSrgb(); + + if (!FormatTable.TryGetTextureFormat(format, srgb, out FormatInfo formatInfo)) + { + Logger.PrintDebug(LogClass.Gpu, $"Invalid texture format 0x{format:X} (sRGB: {srgb})."); + + formatInfo = FormatInfo.Default; + } + + int gobBlocksInY = descriptor.UnpackGobBlocksInY(); + int gobBlocksInZ = descriptor.UnpackGobBlocksInZ(); + + int gobBlocksInTileX = descriptor.UnpackGobBlocksInTileX(); + + SwizzleComponent swizzleR = descriptor.UnpackSwizzleR().Convert(); + SwizzleComponent swizzleG = descriptor.UnpackSwizzleG().Convert(); + SwizzleComponent swizzleB = descriptor.UnpackSwizzleB().Convert(); + SwizzleComponent swizzleA = descriptor.UnpackSwizzleA().Convert(); + + DepthStencilMode depthStencilMode = GetDepthStencilMode( + formatInfo.Format, + swizzleR, + swizzleG, + swizzleB, + swizzleA); + + return new TextureInfo( + address, + width, + height, + depthOrLayers, + levels, + samplesInX, + samplesInY, + stride, + isLinear, + gobBlocksInY, + gobBlocksInZ, + gobBlocksInTileX, + target, + formatInfo, + depthStencilMode, + swizzleR, + swizzleG, + swizzleB, + swizzleA); + } + + /// + /// Gets the texture depth-stencil mode, based on the swizzle components of each color channel. + /// The depth-stencil mode is determined based on how the driver sets those parameters. + /// + /// The format of the texture + /// The texture swizzle components + /// The depth-stencil mode + private static DepthStencilMode GetDepthStencilMode(Format format, params SwizzleComponent[] components) + { + // R = Depth, G = Stencil. + // On 24-bits depth formats, this is inverted (Stencil is R etc). + // NVN setup: + // For depth, A is set to 1.0f, the other components are set to Depth. + // For stencil, all components are set to Stencil. + SwizzleComponent component = components[0]; + + for (int index = 1; index < 4 && !IsRG(component); index++) + { + component = components[index]; + } + + if (!IsRG(component)) + { + return DepthStencilMode.Depth; + } + + if (format == Format.D24X8Unorm || format == Format.D24UnormS8Uint) + { + return component == SwizzleComponent.Red + ? DepthStencilMode.Stencil + : DepthStencilMode.Depth; + } + else + { + return component == SwizzleComponent.Red + ? DepthStencilMode.Depth + : DepthStencilMode.Stencil; + } + } + + /// + /// Checks if the swizzle component is equal to the red or green channels. + /// + /// The swizzle component to check + /// True if the swizzle component is equal to the red or green, false otherwise + private static bool IsRG(SwizzleComponent component) + { + return component == SwizzleComponent.Red || + component == SwizzleComponent.Green; + } + + /// + /// Decrements the reference count of the texture. + /// This indicates that the texture pool is not using it anymore. + /// + /// The texture to be deleted + protected override void Delete(Texture item) + { + item?.DecrementReferenceCount(); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Image/TexturePoolCache.cs b/Ryujinx.Graphics.Gpu/Image/TexturePoolCache.cs new file mode 100644 index 0000000000..2e5c2b5d3c --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Image/TexturePoolCache.cs @@ -0,0 +1,91 @@ +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// Texture pool cache. + /// This can keep multiple texture pools, and return the current one as needed. + /// It is useful for applications that uses multiple texture pools. + /// + class TexturePoolCache + { + private const int MaxCapacity = 4; + + private GpuContext _context; + + private LinkedList _pools; + + /// + /// Constructs a new instance of the texture pool. + /// + /// GPU context that the texture pool belongs to + public TexturePoolCache(GpuContext context) + { + _context = context; + + _pools = new LinkedList(); + } + + /// + /// Finds a cache texture pool, or creates a new one if not found. + /// + /// Start address of the texture pool + /// Maximum ID of the texture pool + /// The found or newly created texture pool + public TexturePool FindOrCreate(ulong address, int maximumId) + { + TexturePool pool; + + // First we try to find the pool. + for (LinkedListNode node = _pools.First; node != null; node = node.Next) + { + pool = node.Value; + + if (pool.Address == address) + { + if (pool.CacheNode != _pools.Last) + { + _pools.Remove(pool.CacheNode); + + pool.CacheNode = _pools.AddLast(pool); + } + + return pool; + } + } + + // If not found, create a new one. + pool = new TexturePool(_context, address, maximumId); + + pool.CacheNode = _pools.AddLast(pool); + + if (_pools.Count > MaxCapacity) + { + TexturePool oldestPool = _pools.First.Value; + + _pools.RemoveFirst(); + + oldestPool.Dispose(); + + oldestPool.CacheNode = null; + } + + return pool; + } + + /// + /// Invalidates a memory range of all intersecting texture pools on the cache. + /// + /// Start address of the range to invalidate + /// Size of the range to invalidate + public void InvalidateRange(ulong address, ulong size) + { + for (LinkedListNode node = _pools.First; node != null; node = node.Next) + { + TexturePool pool = node.Value; + + pool.InvalidateRange(address, size); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Image/TextureSearchFlags.cs b/Ryujinx.Graphics.Gpu/Image/TextureSearchFlags.cs new file mode 100644 index 0000000000..daf726f1d2 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Image/TextureSearchFlags.cs @@ -0,0 +1,16 @@ +using System; + +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// Texture search flags, defines texture information comparison rules. + /// + [Flags] + enum TextureSearchFlags + { + None = 0, + IgnoreMs = 1 << 0, + Strict = 1 << 1 | Sampler, + Sampler = 1 << 2 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Image/TextureTarget.cs b/Ryujinx.Graphics.Gpu/Image/TextureTarget.cs new file mode 100644 index 0000000000..301fc87b2b --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Image/TextureTarget.cs @@ -0,0 +1,58 @@ +using Ryujinx.Graphics.GAL; + +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// Texture target. + /// + enum TextureTarget + { + Texture1D, + Texture2D, + Texture3D, + Cubemap, + Texture1DArray, + Texture2DArray, + TextureBuffer, + Texture2DRect, + CubemapArray + } + + static class TextureTargetConverter + { + /// + /// Converts the texture target enum to a host compatible, Graphics Abstraction Layer enum. + /// + /// The target enum to convert + /// True if the texture is a multisampled texture + /// The host compatible texture target + public static Target Convert(this TextureTarget target, bool isMultisample) + { + if (isMultisample) + { + switch (target) + { + case TextureTarget.Texture2D: return Target.Texture2DMultisample; + case TextureTarget.Texture2DArray: return Target.Texture2DMultisampleArray; + } + } + else + { + switch (target) + { + case TextureTarget.Texture1D: return Target.Texture1D; + case TextureTarget.Texture2D: return Target.Texture2D; + case TextureTarget.Texture2DRect: return Target.Texture2D; + case TextureTarget.Texture3D: return Target.Texture3D; + case TextureTarget.Texture1DArray: return Target.Texture1DArray; + case TextureTarget.Texture2DArray: return Target.Texture2DArray; + case TextureTarget.Cubemap: return Target.Cubemap; + case TextureTarget.CubemapArray: return Target.CubemapArray; + case TextureTarget.TextureBuffer: return Target.TextureBuffer; + } + } + + return Target.Texture1D; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/MacroInterpreter.cs b/Ryujinx.Graphics.Gpu/MacroInterpreter.cs new file mode 100644 index 0000000000..c853da73fb --- /dev/null +++ b/Ryujinx.Graphics.Gpu/MacroInterpreter.cs @@ -0,0 +1,486 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.Gpu.State; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Gpu +{ + /// + /// Macro code interpreter. + /// + class MacroInterpreter + { + private enum AssignmentOperation + { + IgnoreAndFetch = 0, + Move = 1, + MoveAndSetMaddr = 2, + FetchAndSend = 3, + MoveAndSend = 4, + FetchAndSetMaddr = 5, + MoveAndSetMaddrThenFetchAndSend = 6, + MoveAndSetMaddrThenSendHigh = 7 + } + + private enum AluOperation + { + AluReg = 0, + AddImmediate = 1, + BitfieldReplace = 2, + BitfieldExtractLslImm = 3, + BitfieldExtractLslReg = 4, + ReadImmediate = 5 + } + + private enum AluRegOperation + { + Add = 0, + AddWithCarry = 1, + Subtract = 2, + SubtractWithBorrow = 3, + BitwiseExclusiveOr = 8, + BitwiseOr = 9, + BitwiseAnd = 10, + BitwiseAndNot = 11, + BitwiseNotAnd = 12 + } + + public Queue Fifo { get; private set; } + + private int[] _gprs; + + private int _methAddr; + private int _methIncr; + + private bool _carry; + + private int _opCode; + + private int _pipeOp; + + private int _pc; + + /// + /// Creates a new instance of the macro code interpreter. + /// + public MacroInterpreter() + { + Fifo = new Queue(); + + _gprs = new int[8]; + } + + /// + /// Executes a macro program until it exits. + /// + /// Code of the program to execute + /// Start position to execute + /// Optional argument passed to the program, 0 if not used + /// Current GPU state + public void Execute(int[] mme, int position, int param, GpuState state) + { + Reset(); + + _gprs[1] = param; + + _pc = position; + + FetchOpCode(mme); + + while (Step(mme, state)); + + // Due to the delay slot, we still need to execute + // one more instruction before we actually exit. + Step(mme, state); + } + + /// + /// Resets the internal interpreter state. + /// Call each time you run a new program. + /// + private void Reset() + { + for (int index = 0; index < _gprs.Length; index++) + { + _gprs[index] = 0; + } + + _methAddr = 0; + _methIncr = 0; + + _carry = false; + } + + /// + /// Executes a single instruction of the program. + /// + /// Program code to execute + /// Current GPU state + /// True to continue execution, false if the program exited + private bool Step(int[] mme, GpuState state) + { + int baseAddr = _pc - 1; + + FetchOpCode(mme); + + if ((_opCode & 7) < 7) + { + // Operation produces a value. + AssignmentOperation asgOp = (AssignmentOperation)((_opCode >> 4) & 7); + + int result = GetAluResult(state); + + switch (asgOp) + { + // Fetch parameter and ignore result. + case AssignmentOperation.IgnoreAndFetch: + { + SetDstGpr(FetchParam()); + + break; + } + + // Move result. + case AssignmentOperation.Move: + { + SetDstGpr(result); + + break; + } + + // Move result and use as Method Address. + case AssignmentOperation.MoveAndSetMaddr: + { + SetDstGpr(result); + + SetMethAddr(result); + + break; + } + + // Fetch parameter and send result. + case AssignmentOperation.FetchAndSend: + { + SetDstGpr(FetchParam()); + + Send(state, result); + + break; + } + + // Move and send result. + case AssignmentOperation.MoveAndSend: + { + SetDstGpr(result); + + Send(state, result); + + break; + } + + // Fetch parameter and use result as Method Address. + case AssignmentOperation.FetchAndSetMaddr: + { + SetDstGpr(FetchParam()); + + SetMethAddr(result); + + break; + } + + // Move result and use as Method Address, then fetch and send parameter. + case AssignmentOperation.MoveAndSetMaddrThenFetchAndSend: + { + SetDstGpr(result); + + SetMethAddr(result); + + Send(state, FetchParam()); + + break; + } + + // Move result and use as Method Address, then send bits 17:12 of result. + case AssignmentOperation.MoveAndSetMaddrThenSendHigh: + { + SetDstGpr(result); + + SetMethAddr(result); + + Send(state, (result >> 12) & 0x3f); + + break; + } + } + } + else + { + // Branch. + bool onNotZero = ((_opCode >> 4) & 1) != 0; + + bool taken = onNotZero + ? GetGprA() != 0 + : GetGprA() == 0; + + if (taken) + { + _pc = baseAddr + GetImm(); + + bool noDelays = (_opCode & 0x20) != 0; + + if (noDelays) + { + FetchOpCode(mme); + } + + return true; + } + } + + bool exit = (_opCode & 0x80) != 0; + + return !exit; + } + + /// + /// Fetches a single operation code from the program code. + /// + /// Program code + private void FetchOpCode(int[] mme) + { + _opCode = _pipeOp; + _pipeOp = mme[_pc++]; + } + + /// + /// Gets the result of the current Arithmetic and Logic unit operation. + /// + /// Current GPU state + /// Operation result + private int GetAluResult(GpuState state) + { + AluOperation op = (AluOperation)(_opCode & 7); + + switch (op) + { + case AluOperation.AluReg: + { + AluRegOperation aluOp = (AluRegOperation)((_opCode >> 17) & 0x1f); + + return GetAluResult(aluOp, GetGprA(), GetGprB()); + } + + case AluOperation.AddImmediate: + { + return GetGprA() + GetImm(); + } + + case AluOperation.BitfieldReplace: + case AluOperation.BitfieldExtractLslImm: + case AluOperation.BitfieldExtractLslReg: + { + int bfSrcBit = (_opCode >> 17) & 0x1f; + int bfSize = (_opCode >> 22) & 0x1f; + int bfDstBit = (_opCode >> 27) & 0x1f; + + int bfMask = (1 << bfSize) - 1; + + int dst = GetGprA(); + int src = GetGprB(); + + switch (op) + { + case AluOperation.BitfieldReplace: + { + src = (int)((uint)src >> bfSrcBit) & bfMask; + + dst &= ~(bfMask << bfDstBit); + + dst |= src << bfDstBit; + + return dst; + } + + case AluOperation.BitfieldExtractLslImm: + { + src = (int)((uint)src >> dst) & bfMask; + + return src << bfDstBit; + } + + case AluOperation.BitfieldExtractLslReg: + { + src = (int)((uint)src >> bfSrcBit) & bfMask; + + return src << dst; + } + } + + break; + } + + case AluOperation.ReadImmediate: + { + return Read(state, GetGprA() + GetImm()); + } + } + + throw new ArgumentException(nameof(_opCode)); + } + + /// + /// Gets the result of an Arithmetic and Logic operation using registers. + /// + /// Arithmetic and Logic unit operation with registers + /// First operand value + /// Second operand value + /// Operation result + private int GetAluResult(AluRegOperation aluOp, int a, int b) + { + switch (aluOp) + { + case AluRegOperation.Add: + { + ulong result = (ulong)a + (ulong)b; + + _carry = result > 0xffffffff; + + return (int)result; + } + + case AluRegOperation.AddWithCarry: + { + ulong result = (ulong)a + (ulong)b + (_carry ? 1UL : 0UL); + + _carry = result > 0xffffffff; + + return (int)result; + } + + case AluRegOperation.Subtract: + { + ulong result = (ulong)a - (ulong)b; + + _carry = result < 0x100000000; + + return (int)result; + } + + case AluRegOperation.SubtractWithBorrow: + { + ulong result = (ulong)a - (ulong)b - (_carry ? 0UL : 1UL); + + _carry = result < 0x100000000; + + return (int)result; + } + + case AluRegOperation.BitwiseExclusiveOr: return a ^ b; + case AluRegOperation.BitwiseOr: return a | b; + case AluRegOperation.BitwiseAnd: return a & b; + case AluRegOperation.BitwiseAndNot: return a & ~b; + case AluRegOperation.BitwiseNotAnd: return ~(a & b); + } + + throw new ArgumentOutOfRangeException(nameof(aluOp)); + } + + /// + /// Extracts a 32-bits signed integer constant from the current operation code. + /// + /// The 32-bits immediate value encoded at the current operation code + private int GetImm() + { + // Note: The immediate is signed, the sign-extension is intended here. + return _opCode >> 14; + } + + /// + /// Sets the current method address, for method calls. + /// + /// Packed address and increment value + private void SetMethAddr(int value) + { + _methAddr = (value >> 0) & 0xfff; + _methIncr = (value >> 12) & 0x3f; + } + + /// + /// Sets the destination register value. + /// + /// Value to set (usually the operation result) + private void SetDstGpr(int value) + { + _gprs[(_opCode >> 8) & 7] = value; + } + + /// + /// Gets first operand value from the respective register. + /// + /// Operand value + private int GetGprA() + { + return GetGprValue((_opCode >> 11) & 7); + } + + /// + /// Gets second operand value from the respective register. + /// + /// Operand value + private int GetGprB() + { + return GetGprValue((_opCode >> 14) & 7); + } + + /// + /// Gets the value from a register, or 0 if the R0 register is specified. + /// + /// Index of the register + /// Register value + private int GetGprValue(int index) + { + return index != 0 ? _gprs[index] : 0; + } + + /// + /// Fetches a call argument from the call argument FIFO. + /// + /// The call argument, or 0 if the FIFO is empty + private int FetchParam() + { + if (!Fifo.TryDequeue(out int value)) + { + Logger.PrintWarning(LogClass.Gpu, "Macro attempted to fetch an inexistent argument."); + + return 0; + } + + return value; + } + + /// + /// Reads data from a GPU register. + /// + /// Current GPU state + /// Register offset to read + /// GPU register value + private int Read(GpuState state, int reg) + { + return state.Read(reg); + } + + /// + /// Performs a GPU method call. + /// + /// Current GPU state + /// Call argument + private void Send(GpuState state, int value) + { + MethodParams meth = new MethodParams(_methAddr, value); + + state.CallMethod(meth); + + _methAddr += _methIncr; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Memory/Buffer.cs b/Ryujinx.Graphics.Gpu/Memory/Buffer.cs new file mode 100644 index 0000000000..a0339cce69 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Memory/Buffer.cs @@ -0,0 +1,171 @@ +using Ryujinx.Graphics.GAL; +using System; + +namespace Ryujinx.Graphics.Gpu.Memory +{ + /// + /// Buffer, used to store vertex and index data, uniform and storage buffers, and others. + /// + class Buffer : IRange, IDisposable + { + private readonly GpuContext _context; + + /// + /// Host buffer object. + /// + public IBuffer HostBuffer { get; } + + /// + /// Start address of the buffer in guest memory. + /// + public ulong Address { get; } + + /// + /// Size of the buffer in bytes. + /// + public ulong Size { get; } + + /// + /// End address of the buffer in guest memory. + /// + public ulong EndAddress => Address + Size; + + private int[] _sequenceNumbers; + + /// + /// Creates a new instance of the buffer. + /// + /// GPU context that the buffer belongs to + /// Start address of the buffer + /// Size of the buffer in bytes + public Buffer(GpuContext context, ulong address, ulong size) + { + _context = context; + Address = address; + Size = size; + + HostBuffer = context.Renderer.CreateBuffer((int)size); + + _sequenceNumbers = new int[size / MemoryManager.PageSize]; + + Invalidate(); + } + + /// + /// Gets a sub-range from the buffer. + /// + /// + /// This can be used to bind and use sub-ranges of the buffer on the host API. + /// + /// Start address of the sub-range, must be greater than or equal to the buffer address + /// Size in bytes of the sub-range, must be less than or equal to the buffer size + /// The buffer sub-range + public BufferRange GetRange(ulong address, ulong size) + { + int offset = (int)(address - Address); + + return new BufferRange(HostBuffer, offset, (int)size); + } + + /// + /// Checks if a given range overlaps with the buffer. + /// + /// Start address of the range + /// Size in bytes of the range + /// True if the range overlaps, false otherwise + public bool OverlapsWith(ulong address, ulong size) + { + return Address < address + size && address < EndAddress; + } + + /// + /// Performs guest to host memory synchronization of the buffer data. + /// + /// + /// This causes the buffer data to be overwritten if a write was detected from the CPU, + /// since the last call to this method. + /// + /// Start address of the range to synchronize + /// Size in bytes of the range to synchronize + public void SynchronizeMemory(ulong address, ulong size) + { + int currentSequenceNumber = _context.SequenceNumber; + + bool needsSync = false; + + ulong buffOffset = address - Address; + + ulong buffEndOffset = (buffOffset + size + MemoryManager.PageMask) & ~MemoryManager.PageMask; + + int startIndex = (int)(buffOffset / MemoryManager.PageSize); + int endIndex = (int)(buffEndOffset / MemoryManager.PageSize); + + for (int index = startIndex; index < endIndex; index++) + { + if (_sequenceNumbers[index] != currentSequenceNumber) + { + _sequenceNumbers[index] = currentSequenceNumber; + + needsSync = true; + } + } + + if (!needsSync) + { + return; + } + + (ulong, ulong)[] modifiedRanges = _context.PhysicalMemory.GetModifiedRanges(address, size, ResourceName.Buffer); + + for (int index = 0; index < modifiedRanges.Length; index++) + { + (ulong mAddress, ulong mSize) = modifiedRanges[index]; + + int offset = (int)(mAddress - Address); + + HostBuffer.SetData(offset, _context.PhysicalMemory.GetSpan(mAddress, mSize)); + } + } + + /// + /// Performs copy of all the buffer data from one buffer to another. + /// + /// The destination buffer to copy the data into + /// The offset of the destination buffer to copy into + public void CopyTo(Buffer destination, int dstOffset) + { + HostBuffer.CopyTo(destination.HostBuffer, 0, dstOffset, (int)Size); + } + + /// + /// Flushes a range of the buffer. + /// This writes the range data back into guest memory. + /// + /// Start address of the range + /// Size in bytes of the range + public void Flush(ulong address, ulong size) + { + int offset = (int)(address - Address); + + byte[] data = HostBuffer.GetData(offset, (int)size); + + _context.PhysicalMemory.Write(address, data); + } + + /// + /// Invalidates all the buffer data, causing it to be read from guest memory. + /// + public void Invalidate() + { + HostBuffer.SetData(0, _context.PhysicalMemory.GetSpan(Address, Size)); + } + + /// + /// Disposes the host buffer. + /// + public void Dispose() + { + HostBuffer.Dispose(); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Memory/BufferBounds.cs b/Ryujinx.Graphics.Gpu/Memory/BufferBounds.cs new file mode 100644 index 0000000000..42500342fc --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Memory/BufferBounds.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.Graphics.Gpu.Memory +{ + /// + /// Memory range used for buffers. + /// + struct BufferBounds + { + public ulong Address; + public ulong Size; + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs b/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs new file mode 100644 index 0000000000..0acbd94ad0 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs @@ -0,0 +1,701 @@ +using Ryujinx.Common; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.State; +using Ryujinx.Graphics.Shader; +using System; + +namespace Ryujinx.Graphics.Gpu.Memory +{ + /// + /// Buffer manager. + /// + class BufferManager + { + private const int OverlapsBufferInitialCapacity = 10; + private const int OverlapsBufferMaxCapacity = 10000; + + private const ulong BufferAlignmentSize = 0x1000; + private const ulong BufferAlignmentMask = BufferAlignmentSize - 1; + + private GpuContext _context; + + private RangeList _buffers; + + private Buffer[] _bufferOverlaps; + + private IndexBuffer _indexBuffer; + + private VertexBuffer[] _vertexBuffers; + + private class BuffersPerStage + { + public uint EnableMask { get; set; } + + public BufferBounds[] Buffers { get; } + + public BuffersPerStage(int count) + { + Buffers = new BufferBounds[count]; + } + + public void Bind(int index, ulong address, ulong size) + { + Buffers[index].Address = address; + Buffers[index].Size = size; + } + } + + private BuffersPerStage _cpStorageBuffers; + private BuffersPerStage _cpUniformBuffers; + private BuffersPerStage[] _gpStorageBuffers; + private BuffersPerStage[] _gpUniformBuffers; + + private bool _gpStorageBuffersDirty; + private bool _gpUniformBuffersDirty; + + private bool _indexBufferDirty; + private bool _vertexBuffersDirty; + private uint _vertexBuffersEnableMask; + + private bool _rebind; + + /// + /// Creates a new instance of the buffer manager. + /// + /// The GPU context that the buffer manager belongs to + public BufferManager(GpuContext context) + { + _context = context; + + _buffers = new RangeList(); + + _bufferOverlaps = new Buffer[OverlapsBufferInitialCapacity]; + + _vertexBuffers = new VertexBuffer[Constants.TotalVertexBuffers]; + + _cpStorageBuffers = new BuffersPerStage(Constants.TotalCpStorageBuffers); + _cpUniformBuffers = new BuffersPerStage(Constants.TotalCpUniformBuffers); + + _gpStorageBuffers = new BuffersPerStage[Constants.ShaderStages]; + _gpUniformBuffers = new BuffersPerStage[Constants.ShaderStages]; + + for (int index = 0; index < Constants.ShaderStages; index++) + { + _gpStorageBuffers[index] = new BuffersPerStage(Constants.TotalGpStorageBuffers); + _gpUniformBuffers[index] = new BuffersPerStage(Constants.TotalGpUniformBuffers); + } + } + + /// + /// Sets the memory range with the index buffer data, to be used for subsequent draw calls. + /// + /// Start GPU virtual address of the index buffer + /// Size, in bytes, of the index buffer + /// Type of each index buffer element + public void SetIndexBuffer(ulong gpuVa, ulong size, IndexType type) + { + ulong address = TranslateAndCreateBuffer(gpuVa, size); + + _indexBuffer.Address = address; + _indexBuffer.Size = size; + _indexBuffer.Type = type; + + _indexBufferDirty = true; + } + + /// + /// Sets the memory range with vertex buffer data, to be used for subsequent draw calls. + /// + /// Index of the vertex buffer (up to 16) + /// GPU virtual address of the buffer + /// Size in bytes of the buffer + /// Stride of the buffer, defined as the number of bytes of each vertex + /// Vertex divisor of the buffer, for instanced draws + public void SetVertexBuffer(int index, ulong gpuVa, ulong size, int stride, int divisor) + { + ulong address = TranslateAndCreateBuffer(gpuVa, size); + + _vertexBuffers[index].Address = address; + _vertexBuffers[index].Size = size; + _vertexBuffers[index].Stride = stride; + _vertexBuffers[index].Divisor = divisor; + + _vertexBuffersDirty = true; + + if (address != 0) + { + _vertexBuffersEnableMask |= 1u << index; + } + else + { + _vertexBuffersEnableMask &= ~(1u << index); + } + } + + /// + /// Sets a storage buffer on the compute pipeline. + /// Storage buffers can be read and written to on shaders. + /// + /// Index of the storage buffer + /// Start GPU virtual address of the buffer + /// Size in bytes of the storage buffer + public void SetComputeStorageBuffer(int index, ulong gpuVa, ulong size) + { + size += gpuVa & ((ulong)_context.Capabilities.StorageBufferOffsetAlignment - 1); + + gpuVa = BitUtils.AlignDown(gpuVa, _context.Capabilities.StorageBufferOffsetAlignment); + + ulong address = TranslateAndCreateBuffer(gpuVa, size); + + _cpStorageBuffers.Bind(index, address, size); + } + + /// + /// Sets a storage buffer on the graphics pipeline. + /// Storage buffers can be read and written to on shaders. + /// + /// Index of the shader stage + /// Index of the storage buffer + /// Start GPU virtual address of the buffer + /// Size in bytes of the storage buffer + public void SetGraphicsStorageBuffer(int stage, int index, ulong gpuVa, ulong size) + { + size += gpuVa & ((ulong)_context.Capabilities.StorageBufferOffsetAlignment - 1); + + gpuVa = BitUtils.AlignDown(gpuVa, _context.Capabilities.StorageBufferOffsetAlignment); + + ulong address = TranslateAndCreateBuffer(gpuVa, size); + + if (_gpStorageBuffers[stage].Buffers[index].Address != address || + _gpStorageBuffers[stage].Buffers[index].Size != size) + { + _gpStorageBuffersDirty = true; + } + + _gpStorageBuffers[stage].Bind(index, address, size); + } + + /// + /// Sets a uniform buffer on the compute pipeline. + /// Uniform buffers are read-only from shaders, and have a small capacity. + /// + /// Index of the uniform buffer + /// Start GPU virtual address of the buffer + /// Size in bytes of the storage buffer + public void SetComputeUniformBuffer(int index, ulong gpuVa, ulong size) + { + ulong address = TranslateAndCreateBuffer(gpuVa, size); + + _cpUniformBuffers.Bind(index, address, size); + } + + /// + /// Sets a uniform buffer on the graphics pipeline. + /// Uniform buffers are read-only from shaders, and have a small capacity. + /// + /// Index of the shader stage + /// Index of the uniform buffer + /// Start GPU virtual address of the buffer + /// Size in bytes of the storage buffer + public void SetGraphicsUniformBuffer(int stage, int index, ulong gpuVa, ulong size) + { + ulong address = TranslateAndCreateBuffer(gpuVa, size); + + _gpUniformBuffers[stage].Bind(index, address, size); + + _gpUniformBuffersDirty = true; + } + + /// + /// Sets the enabled storage buffers mask on the compute pipeline. + /// Each bit set on the mask indicates that the respective buffer index is enabled. + /// + /// Buffer enable mask + public void SetComputeStorageBufferEnableMask(uint mask) + { + _cpStorageBuffers.EnableMask = mask; + } + + /// + /// Sets the enabled storage buffers mask on the graphics pipeline. + /// Each bit set on the mask indicates that the respective buffer index is enabled. + /// + /// Index of the shader stage + /// Buffer enable mask + public void SetGraphicsStorageBufferEnableMask(int stage, uint mask) + { + _gpStorageBuffers[stage].EnableMask = mask; + + _gpStorageBuffersDirty = true; + } + + /// + /// Sets the enabled uniform buffers mask on the compute pipeline. + /// Each bit set on the mask indicates that the respective buffer index is enabled. + /// + /// Buffer enable mask + public void SetComputeUniformBufferEnableMask(uint mask) + { + _cpUniformBuffers.EnableMask = mask; + } + + /// + /// Sets the enabled uniform buffers mask on the graphics pipeline. + /// Each bit set on the mask indicates that the respective buffer index is enabled. + /// + /// Index of the shader stage + /// Buffer enable mask + public void SetGraphicsUniformBufferEnableMask(int stage, uint mask) + { + _gpUniformBuffers[stage].EnableMask = mask; + + _gpUniformBuffersDirty = true; + } + + /// + /// Performs address translation of the GPU virtual address, and creates a + /// new buffer, if needed, for the specified range. + /// + /// Start GPU virtual address of the buffer + /// Size in bytes of the buffer + /// CPU virtual address of the buffer, after address translation + private ulong TranslateAndCreateBuffer(ulong gpuVa, ulong size) + { + if (gpuVa == 0) + { + return 0; + } + + ulong address = _context.MemoryManager.Translate(gpuVa); + + if (address == MemoryManager.BadAddress) + { + return 0; + } + + ulong endAddress = address + size; + + ulong alignedAddress = address & ~BufferAlignmentMask; + + ulong alignedEndAddress = (endAddress + BufferAlignmentMask) & ~BufferAlignmentMask; + + // The buffer must have the size of at least one page. + if (alignedEndAddress == alignedAddress) + { + alignedEndAddress += BufferAlignmentSize; + } + + CreateBuffer(alignedAddress, alignedEndAddress - alignedAddress); + + return address; + } + + /// + /// Creates a new buffer for the specified range, if needed. + /// If a buffer where this range can be fully contained already exists, + /// then the creation of a new buffer is not necessary. + /// + /// Address of the buffer in guest memory + /// Size in bytes of the buffer + private void CreateBuffer(ulong address, ulong size) + { + int overlapsCount = _buffers.FindOverlapsNonOverlapping(address, size, ref _bufferOverlaps); + + if (overlapsCount != 0) + { + // The buffer already exists. We can just return the existing buffer + // if the buffer we need is fully contained inside the overlapping buffer. + // Otherwise, we must delete the overlapping buffers and create a bigger buffer + // that fits all the data we need. We also need to copy the contents from the + // old buffer(s) to the new buffer. + ulong endAddress = address + size; + + if (_bufferOverlaps[0].Address > address || _bufferOverlaps[0].EndAddress < endAddress) + { + for (int index = 0; index < overlapsCount; index++) + { + Buffer buffer = _bufferOverlaps[index]; + + address = Math.Min(address, buffer.Address); + endAddress = Math.Max(endAddress, buffer.EndAddress); + + buffer.SynchronizeMemory(buffer.Address, buffer.Size); + + _buffers.Remove(buffer); + } + + Buffer newBuffer = new Buffer(_context, address, endAddress - address); + + _buffers.Add(newBuffer); + + for (int index = 0; index < overlapsCount; index++) + { + Buffer buffer = _bufferOverlaps[index]; + + int dstOffset = (int)(buffer.Address - newBuffer.Address); + + buffer.CopyTo(newBuffer, dstOffset); + + buffer.Dispose(); + } + + _rebind = true; + } + } + else + { + // No overlap, just create a new buffer. + Buffer buffer = new Buffer(_context, address, size); + + _buffers.Add(buffer); + } + + ShrinkOverlapsBufferIfNeeded(); + } + + /// + /// Resizes the temporary buffer used for range list intersection results, if it has grown too much. + /// + private void ShrinkOverlapsBufferIfNeeded() + { + if (_bufferOverlaps.Length > OverlapsBufferMaxCapacity) + { + Array.Resize(ref _bufferOverlaps, OverlapsBufferMaxCapacity); + } + } + + /// + /// Gets the address of the compute uniform buffer currently bound at the given index. + /// + /// Index of the uniform buffer binding + /// The uniform buffer address, or an undefined value if the buffer is not currently bound + public ulong GetComputeUniformBufferAddress(int index) + { + return _cpUniformBuffers.Buffers[index].Address; + } + + /// + /// Gets the address of the graphics uniform buffer currently bound at the given index. + /// + /// Index of the shader stage + /// Index of the uniform buffer binding + /// The uniform buffer address, or an undefined value if the buffer is not currently bound + public ulong GetGraphicsUniformBufferAddress(int stage, int index) + { + return _gpUniformBuffers[stage].Buffers[index].Address; + } + + /// + /// Ensures that the compute engine bindings are visible to the host GPU. + /// Note: this actually performs the binding using the host graphics API. + /// + public void CommitComputeBindings() + { + uint enableMask = _cpStorageBuffers.EnableMask; + + for (int index = 0; (enableMask >> index) != 0; index++) + { + if ((enableMask & (1u << index)) == 0) + { + continue; + } + + BufferBounds bounds = _cpStorageBuffers.Buffers[index]; + + if (bounds.Address == 0) + { + continue; + } + + BufferRange buffer = GetBufferRange(bounds.Address, bounds.Size); + + _context.Renderer.Pipeline.SetStorageBuffer(index, ShaderStage.Compute, buffer); + } + + enableMask = _cpUniformBuffers.EnableMask; + + for (int index = 0; (enableMask >> index) != 0; index++) + { + if ((enableMask & (1u << index)) == 0) + { + continue; + } + + BufferBounds bounds = _cpUniformBuffers.Buffers[index]; + + if (bounds.Address == 0) + { + continue; + } + + BufferRange buffer = GetBufferRange(bounds.Address, bounds.Size); + + _context.Renderer.Pipeline.SetUniformBuffer(index, ShaderStage.Compute, buffer); + } + + // Force rebind after doing compute work. + _rebind = true; + } + + /// + /// Ensures that the graphics engine bindings are visible to the host GPU. + /// Note: this actually performs the binding using the host graphics API. + /// + public void CommitBindings() + { + if (_indexBufferDirty || _rebind) + { + _indexBufferDirty = false; + + if (_indexBuffer.Address != 0) + { + BufferRange buffer = GetBufferRange(_indexBuffer.Address, _indexBuffer.Size); + + _context.Renderer.Pipeline.SetIndexBuffer(buffer, _indexBuffer.Type); + } + } + else if (_indexBuffer.Address != 0) + { + SynchronizeBufferRange(_indexBuffer.Address, _indexBuffer.Size); + } + + uint vbEnableMask = _vertexBuffersEnableMask; + + if (_vertexBuffersDirty || _rebind) + { + _vertexBuffersDirty = false; + + VertexBufferDescriptor[] vertexBuffers = new VertexBufferDescriptor[Constants.TotalVertexBuffers]; + + for (int index = 0; (vbEnableMask >> index) != 0; index++) + { + VertexBuffer vb = _vertexBuffers[index]; + + if (vb.Address == 0) + { + continue; + } + + BufferRange buffer = GetBufferRange(vb.Address, vb.Size); + + vertexBuffers[index] = new VertexBufferDescriptor(buffer, vb.Stride, vb.Divisor); + } + + _context.Renderer.Pipeline.SetVertexBuffers(vertexBuffers); + } + else + { + for (int index = 0; (vbEnableMask >> index) != 0; index++) + { + VertexBuffer vb = _vertexBuffers[index]; + + if (vb.Address == 0) + { + continue; + } + + SynchronizeBufferRange(vb.Address, vb.Size); + } + } + + if (_gpStorageBuffersDirty || _rebind) + { + _gpStorageBuffersDirty = false; + + BindBuffers(_gpStorageBuffers, isStorage: true); + } + else + { + UpdateBuffers(_gpStorageBuffers); + } + + if (_gpUniformBuffersDirty || _rebind) + { + _gpUniformBuffersDirty = false; + + BindBuffers(_gpUniformBuffers, isStorage: false); + } + else + { + UpdateBuffers(_gpUniformBuffers); + } + + _rebind = false; + } + + /// + /// Bind respective buffer bindings on the host API. + /// + /// Bindings to bind + /// True to bind as storage buffer, false to bind as uniform buffers + private void BindBuffers(BuffersPerStage[] bindings, bool isStorage) + { + BindOrUpdateBuffers(bindings, bind: true, isStorage); + } + + /// + /// Updates data for the already bound buffer bindings. + /// + /// Bindings to update + private void UpdateBuffers(BuffersPerStage[] bindings) + { + BindOrUpdateBuffers(bindings, bind: false); + } + + /// + /// This binds buffers into the host API, or updates data for already bound buffers. + /// + /// Bindings to bind or update + /// True to bind, false to update + /// True to bind as storage buffer, false to bind as uniform buffer + private void BindOrUpdateBuffers(BuffersPerStage[] bindings, bool bind, bool isStorage = false) + { + for (ShaderStage stage = ShaderStage.Vertex; stage <= ShaderStage.Fragment; stage++) + { + uint enableMask = bindings[(int)stage - 1].EnableMask; + + if (enableMask == 0) + { + continue; + } + + for (int index = 0; (enableMask >> index) != 0; index++) + { + if ((enableMask & (1u << index)) == 0) + { + continue; + } + + BufferBounds bounds = bindings[(int)stage - 1].Buffers[index]; + + if (bounds.Address == 0) + { + continue; + } + + if (bind) + { + BindBuffer(index, stage, bounds, isStorage); + } + else + { + SynchronizeBufferRange(bounds.Address, bounds.Size); + } + } + } + } + + /// + /// Binds a buffer on the host API. + /// + /// Index to bind the buffer into + /// Shader stage to bind the buffer into + /// Buffer address and size + /// True to bind as storage buffer, false to bind as uniform buffer + private void BindBuffer(int index, ShaderStage stage, BufferBounds bounds, bool isStorage) + { + BufferRange buffer = GetBufferRange(bounds.Address, bounds.Size); + + if (isStorage) + { + _context.Renderer.Pipeline.SetStorageBuffer(index, stage, buffer); + } + else + { + _context.Renderer.Pipeline.SetUniformBuffer(index, stage, buffer); + } + } + + /// + /// Copy a buffer data from a given address to another. + /// + /// + /// This does a GPU side copy. + /// + /// GPU virtual address of the copy source + /// GPU virtual address of the copy destination + /// Size in bytes of the copy + public void CopyBuffer(GpuVa srcVa, GpuVa dstVa, ulong size) + { + ulong srcAddress = TranslateAndCreateBuffer(srcVa.Pack(), size); + ulong dstAddress = TranslateAndCreateBuffer(dstVa.Pack(), size); + + Buffer srcBuffer = GetBuffer(srcAddress, size); + Buffer dstBuffer = GetBuffer(dstAddress, size); + + int srcOffset = (int)(srcAddress - srcBuffer.Address); + int dstOffset = (int)(dstAddress - dstBuffer.Address); + + srcBuffer.HostBuffer.CopyTo( + dstBuffer.HostBuffer, + srcOffset, + dstOffset, + (int)size); + + dstBuffer.Flush(dstAddress, size); + } + + /// + /// Gets a buffer sub-range for a given memory range. + /// + /// Start address of the memory range + /// Size in bytes of the memory range + /// The buffer sub-range for the given range + private BufferRange GetBufferRange(ulong address, ulong size) + { + return GetBuffer(address, size).GetRange(address, size); + } + + /// + /// Gets a buffer for a given memory range. + /// A buffer overlapping with the specified range is assumed to already exist on the cache. + /// + /// Start address of the memory range + /// Size in bytes of the memory range + /// The buffer where the range is fully contained + private Buffer GetBuffer(ulong address, ulong size) + { + Buffer buffer; + + if (size != 0) + { + buffer = _buffers.FindFirstOverlap(address, size); + + buffer.SynchronizeMemory(address, size); + } + else + { + buffer = _buffers.FindFirstOverlap(address, 1); + } + + return buffer; + } + + /// + /// Performs guest to host memory synchronization of a given memory range. + /// + /// Start address of the memory range + /// Size in bytes of the memory range + private void SynchronizeBufferRange(ulong address, ulong size) + { + if (size != 0) + { + Buffer buffer = _buffers.FindFirstOverlap(address, size); + + buffer.SynchronizeMemory(address, size); + } + } + + /// + /// Disposes all buffers in the cache. + /// It's an error to use the buffer manager after disposal. + /// + public void Dispose() + { + foreach (Buffer buffer in _buffers) + { + buffer.Dispose(); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Memory/IRange.cs b/Ryujinx.Graphics.Gpu/Memory/IRange.cs new file mode 100644 index 0000000000..9d5eee0b0d --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Memory/IRange.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.Graphics.Gpu.Memory +{ + /// + /// Range of memory. + /// + interface IRange + { + ulong Address { get; } + ulong Size { get; } + + bool OverlapsWith(ulong address, ulong size); + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Memory/IndexBuffer.cs b/Ryujinx.Graphics.Gpu/Memory/IndexBuffer.cs new file mode 100644 index 0000000000..7765e89943 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Memory/IndexBuffer.cs @@ -0,0 +1,15 @@ +using Ryujinx.Graphics.GAL; + +namespace Ryujinx.Graphics.Gpu.Memory +{ + /// + /// GPU Index Buffer information. + /// + struct IndexBuffer + { + public ulong Address; + public ulong Size; + + public IndexType Type; + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Memory/MemoryAccessor.cs b/Ryujinx.Graphics.Gpu/Memory/MemoryAccessor.cs new file mode 100644 index 0000000000..17c0006288 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Memory/MemoryAccessor.cs @@ -0,0 +1,124 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Gpu.Memory +{ + /// + /// GPU mapped memory accessor. + /// + public class MemoryAccessor + { + private GpuContext _context; + + /// + /// Creates a new instance of the GPU memory accessor. + /// + /// GPU context that the memory accessor belongs to + public MemoryAccessor(GpuContext context) + { + _context = context; + } + + /// + /// Reads a byte array from GPU mapped memory. + /// + /// GPU virtual address where the data is located + /// Size of the data in bytes + /// Byte array with the data + public byte[] ReadBytes(ulong gpuVa, ulong size) + { + return GetSpan(gpuVa, size).ToArray(); + } + + /// + /// Gets a read-only span of data from GPU mapped memory. + /// This reads as much data as possible, up to the specified maximum size. + /// + /// GPU virtual address where the data is located + /// Maximum size of the data + /// The span of the data at the specified memory location + public ReadOnlySpan GetSpan(ulong gpuVa, ulong maxSize) + { + ulong processVa = _context.MemoryManager.Translate(gpuVa); + + ulong size = Math.Min(_context.MemoryManager.GetSubSize(gpuVa), maxSize); + + return _context.PhysicalMemory.GetSpan(processVa, size); + } + + /// + /// Reads a structure from GPU mapped memory. + /// + /// Type of the structure + /// GPU virtual address where the structure is located + /// The structure at the specified memory location + public T Read(ulong gpuVa) where T : struct + { + ulong processVa = _context.MemoryManager.Translate(gpuVa); + + ulong size = (uint)Marshal.SizeOf(); + + return MemoryMarshal.Cast(_context.PhysicalMemory.GetSpan(processVa, size))[0]; + } + + /// + /// Reads a 32-bits signed integer from GPU mapped memory. + /// + /// GPU virtual address where the value is located + /// The value at the specified memory location + public int ReadInt32(ulong gpuVa) + { + ulong processVa = _context.MemoryManager.Translate(gpuVa); + + return BitConverter.ToInt32(_context.PhysicalMemory.GetSpan(processVa, 4)); + } + + /// + /// Reads a 64-bits unsigned integer from GPU mapped memory. + /// + /// GPU virtual address where the value is located + /// The value at the specified memory location + public ulong ReadUInt64(ulong gpuVa) + { + ulong processVa = _context.MemoryManager.Translate(gpuVa); + + return BitConverter.ToUInt64(_context.PhysicalMemory.GetSpan(processVa, 8)); + } + + /// + /// Reads a 8-bits unsigned integer from GPU mapped memory. + /// + /// GPU virtual address where the value is located + /// The value to be written + public void WriteByte(ulong gpuVa, byte value) + { + ulong processVa = _context.MemoryManager.Translate(gpuVa); + + _context.PhysicalMemory.Write(processVa, MemoryMarshal.CreateSpan(ref value, 1)); + } + + /// + /// Writes a 32-bits signed integer to GPU mapped memory. + /// + /// GPU virtual address to write the value into + /// The value to be written + public void Write(ulong gpuVa, int value) + { + ulong processVa = _context.MemoryManager.Translate(gpuVa); + + _context.PhysicalMemory.Write(processVa, BitConverter.GetBytes(value)); + } + + /// + /// Writes data to GPU mapped memory. + /// + /// GPU virtual address to write the data into + /// The data to be written + public void Write(ulong gpuVa, Span data) + { + ulong processVa = _context.MemoryManager.Translate(gpuVa); + + _context.PhysicalMemory.Write(processVa, data); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs b/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs new file mode 100644 index 0000000000..ffca6f339b --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs @@ -0,0 +1,353 @@ +namespace Ryujinx.Graphics.Gpu.Memory +{ + /// + /// GPU memory manager. + /// + public class MemoryManager + { + private const ulong AddressSpaceSize = 1UL << 40; + + public const ulong BadAddress = ulong.MaxValue; + + private const int PtLvl0Bits = 14; + private const int PtLvl1Bits = 14; + public const int PtPageBits = 12; + + private const ulong PtLvl0Size = 1UL << PtLvl0Bits; + private const ulong PtLvl1Size = 1UL << PtLvl1Bits; + public const ulong PageSize = 1UL << PtPageBits; + + private const ulong PtLvl0Mask = PtLvl0Size - 1; + private const ulong PtLvl1Mask = PtLvl1Size - 1; + public const ulong PageMask = PageSize - 1; + + private const int PtLvl0Bit = PtPageBits + PtLvl1Bits; + private const int PtLvl1Bit = PtPageBits; + + private const ulong PteUnmapped = 0xffffffff_ffffffff; + private const ulong PteReserved = 0xffffffff_fffffffe; + + private ulong[][] _pageTable; + + /// + /// Creates a new instance of the GPU memory manager. + /// + public MemoryManager() + { + _pageTable = new ulong[PtLvl0Size][]; + } + + /// + /// Maps a given range of pages to the specified CPU virtual address. + /// + /// + /// All addresses and sizes must be page aligned. + /// + /// CPU virtual address to map into + /// GPU virtual address to be mapped + /// Size in bytes of the mapping + /// GPU virtual address of the mapping + public ulong Map(ulong pa, ulong va, ulong size) + { + lock (_pageTable) + { + for (ulong offset = 0; offset < size; offset += PageSize) + { + SetPte(va + offset, pa + offset); + } + } + + return va; + } + + /// + /// Maps a given range of pages to an allocated GPU virtual address. + /// The memory is automatically allocated by the memory manager. + /// + /// CPU virtual address to map into + /// Size in bytes of the mapping + /// Required alignment of the GPU virtual address in bytes + /// GPU virtual address where the range was mapped, or an all ones mask in case of failure + public ulong MapAllocate(ulong pa, ulong size, ulong alignment) + { + lock (_pageTable) + { + ulong va = GetFreePosition(size, alignment); + + if (va != PteUnmapped) + { + for (ulong offset = 0; offset < size; offset += PageSize) + { + SetPte(va + offset, pa + offset); + } + } + + return va; + } + } + + /// + /// Maps a given range of pages to an allocated GPU virtual address. + /// The memory is automatically allocated by the memory manager. + /// This also ensures that the mapping is always done in the first 4GB of GPU address space. + /// + /// CPU virtual address to map into + /// Size in bytes of the mapping + /// GPU virtual address where the range was mapped, or an all ones mask in case of failure + public ulong MapLow(ulong pa, ulong size) + { + lock (_pageTable) + { + ulong va = GetFreePosition(size, 1, PageSize); + + if (va != PteUnmapped && va <= uint.MaxValue && (va + size) <= uint.MaxValue) + { + for (ulong offset = 0; offset < size; offset += PageSize) + { + SetPte(va + offset, pa + offset); + } + } + else + { + va = PteUnmapped; + } + + return va; + } + } + + /// + /// Reserves memory at a fixed GPU memory location. + /// This prevents the reserved region from being used for memory allocation for map. + /// + /// GPU virtual address to reserve + /// Size in bytes of the reservation + /// GPU virtual address of the reservation, or an all ones mask in case of failure + public ulong ReserveFixed(ulong va, ulong size) + { + lock (_pageTable) + { + for (ulong offset = 0; offset < size; offset += PageSize) + { + if (IsPageInUse(va + offset)) + { + return PteUnmapped; + } + } + + for (ulong offset = 0; offset < size; offset += PageSize) + { + SetPte(va + offset, PteReserved); + } + } + + return va; + } + + /// + /// Reserves memory at any GPU memory location. + /// + /// Size in bytes of the reservation + /// Reservation address alignment in bytes + /// GPU virtual address of the reservation, or an all ones mask in case of failure + public ulong Reserve(ulong size, ulong alignment) + { + lock (_pageTable) + { + ulong address = GetFreePosition(size, alignment); + + if (address != PteUnmapped) + { + for (ulong offset = 0; offset < size; offset += PageSize) + { + SetPte(address + offset, PteReserved); + } + } + + return address; + } + } + + /// + /// Frees memory that was previously allocated by a map or reserved. + /// + /// GPU virtual address to free + /// Size in bytes of the region being freed + public void Free(ulong va, ulong size) + { + lock (_pageTable) + { + for (ulong offset = 0; offset < size; offset += PageSize) + { + SetPte(va + offset, PteUnmapped); + } + } + } + + /// + /// Gets the address of an unused (free) region of the specified size. + /// + /// Size of the region in bytes + /// Required alignment of the region address in bytes + /// Start address of the search on the address space + /// GPU virtual address of the allocation, or an all ones mask in case of failure + private ulong GetFreePosition(ulong size, ulong alignment = 1, ulong start = 1UL << 32) + { + // Note: Address 0 is not considered valid by the driver, + // when 0 is returned it's considered a mapping error. + ulong address = start; + ulong freeSize = 0; + + if (alignment == 0) + { + alignment = 1; + } + + alignment = (alignment + PageMask) & ~PageMask; + + while (address + freeSize < AddressSpaceSize) + { + if (!IsPageInUse(address + freeSize)) + { + freeSize += PageSize; + + if (freeSize >= size) + { + return address; + } + } + else + { + address += freeSize + PageSize; + freeSize = 0; + + ulong remainder = address % alignment; + + if (remainder != 0) + { + address = (address - remainder) + alignment; + } + } + } + + return PteUnmapped; + } + + /// + /// Gets the number of mapped or reserved pages on a given region. + /// + /// Start GPU virtual address of the region + /// Mapped size in bytes of the specified region + internal ulong GetSubSize(ulong gpuVa) + { + ulong size = 0; + + while (GetPte(gpuVa + size) != PteUnmapped) + { + size += PageSize; + } + + return size; + } + + /// + /// Translates a GPU virtual address to a CPU virtual address. + /// + /// GPU virtual address to be translated + /// CPU virtual address + public ulong Translate(ulong gpuVa) + { + ulong baseAddress = GetPte(gpuVa); + + if (baseAddress == PteUnmapped || baseAddress == PteReserved) + { + return PteUnmapped; + } + + return baseAddress + (gpuVa & PageMask); + } + + /// + /// Checks if a given memory region is currently unmapped. + /// + /// Start GPU virtual address of the region + /// Size in bytes of the region + /// True if the region is unmapped (free), false otherwise + public bool IsRegionFree(ulong gpuVa, ulong size) + { + for (ulong offset = 0; offset < size; offset += PageSize) + { + if (IsPageInUse(gpuVa + offset)) + { + return false; + } + } + + return true; + } + + /// + /// Checks if a given memory page is mapped or reserved. + /// + /// GPU virtual address of the page + /// True if the page is mapped or reserved, false otherwise + private bool IsPageInUse(ulong gpuVa) + { + if (gpuVa >> PtLvl0Bits + PtLvl1Bits + PtPageBits != 0) + { + return false; + } + + ulong l0 = (gpuVa >> PtLvl0Bit) & PtLvl0Mask; + ulong l1 = (gpuVa >> PtLvl1Bit) & PtLvl1Mask; + + if (_pageTable[l0] == null) + { + return false; + } + + return _pageTable[l0][l1] != PteUnmapped; + } + + /// + /// Gets the Page Table entry for a given GPU virtual address. + /// + /// GPU virtual address + /// Page table entry (CPU virtual address) + private ulong GetPte(ulong gpuVa) + { + ulong l0 = (gpuVa >> PtLvl0Bit) & PtLvl0Mask; + ulong l1 = (gpuVa >> PtLvl1Bit) & PtLvl1Mask; + + if (_pageTable[l0] == null) + { + return PteUnmapped; + } + + return _pageTable[l0][l1]; + } + + /// + /// Sets a Page Table entry at a given GPU virtual address. + /// + /// GPU virtual address + /// Page table entry (CPU virtual address) + private void SetPte(ulong gpuVa, ulong pte) + { + ulong l0 = (gpuVa >> PtLvl0Bit) & PtLvl0Mask; + ulong l1 = (gpuVa >> PtLvl1Bit) & PtLvl1Mask; + + if (_pageTable[l0] == null) + { + _pageTable[l0] = new ulong[PtLvl1Size]; + + for (ulong index = 0; index < PtLvl1Size; index++) + { + _pageTable[l0][index] = PteUnmapped; + } + } + + _pageTable[l0][l1] = pte; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs b/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs new file mode 100644 index 0000000000..ca28f31d11 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs @@ -0,0 +1,57 @@ +using System; + +namespace Ryujinx.Graphics.Gpu.Memory +{ + using CpuMemoryManager = ARMeilleure.Memory.MemoryManager; + + /// + /// Represents physical memory, accessible from the GPU. + /// This is actually working CPU virtual addresses, of memory mapped on the application process. + /// + class PhysicalMemory + { + private readonly CpuMemoryManager _cpuMemory; + + /// + /// Creates a new instance of the physical memory. + /// + /// CPU memory manager of the application process + public PhysicalMemory(CpuMemoryManager cpuMemory) + { + _cpuMemory = cpuMemory; + } + + /// + /// Gets a span of data from the application process. + /// + /// Start address of the range + /// Size in bytes to be range + /// A read only span of the data at the specified memory location + public ReadOnlySpan GetSpan(ulong address, ulong size) + { + return _cpuMemory.GetSpan(address, size); + } + + /// + /// Writes data to the application process. + /// + /// Address to write into + /// Data to be written + public void Write(ulong address, ReadOnlySpan data) + { + _cpuMemory.WriteBytes((long)address, data.ToArray()); + } + + /// + /// Gets the modified ranges for a given range of the application process mapped memory. + /// + /// Start address of the range + /// Size, in bytes, of the range + /// Name of the GPU resource being checked + /// Ranges, composed of address and size, modified by the application process, form the CPU + public (ulong, ulong)[] GetModifiedRanges(ulong address, ulong size, ResourceName name) + { + return _cpuMemory.GetModifiedRanges(address, size, (int)name); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Memory/RangeList.cs b/Ryujinx.Graphics.Gpu/Memory/RangeList.cs new file mode 100644 index 0000000000..6af440c054 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Memory/RangeList.cs @@ -0,0 +1,343 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Gpu.Memory +{ + /// + /// List of GPU resources with data on guest memory. + /// + /// Type of the GPU resource + class RangeList : IEnumerable where T : IRange + { + private const int ArrayGrowthSize = 32; + + private readonly List _items; + + /// + /// Creates a new GPU resources list. + /// + public RangeList() + { + _items = new List(); + } + + /// + /// Adds a new item to the list. + /// + /// The item to be added + public void Add(T item) + { + int index = BinarySearch(item.Address); + + if (index < 0) + { + index = ~index; + } + + _items.Insert(index, item); + } + + /// + /// Removes an item from the list. + /// + /// The item to be removed + /// True if the item was removed, or false if it was not found + public bool Remove(T item) + { + int index = BinarySearch(item.Address); + + if (index >= 0) + { + while (index > 0 && _items[index - 1].Address == item.Address) + { + index--; + } + + while (index < _items.Count) + { + if (_items[index].Equals(item)) + { + _items.RemoveAt(index); + + return true; + } + + if (_items[index].Address > item.Address) + { + break; + } + + index++; + } + } + + return false; + } + + /// + /// Gets the first item on the list overlapping in memory with the specified item. + /// + /// + /// Despite the name, this has no ordering guarantees of the returned item. + /// It only ensures that the item returned overlaps the specified item. + /// + /// Item to check for overlaps + /// The overlapping item, or the default value for the type if none found + public T FindFirstOverlap(T item) + { + return FindFirstOverlap(item.Address, item.Size); + } + + /// + /// Gets the first item on the list overlapping the specified memory range. + /// + /// + /// Despite the name, this has no ordering guarantees of the returned item. + /// It only ensures that the item returned overlaps the specified memory range. + /// + /// Start address of the range + /// Size in bytes of the range + /// The overlapping item, or the default value for the type if none found + public T FindFirstOverlap(ulong address, ulong size) + { + int index = BinarySearch(address, size); + + if (index < 0) + { + return default(T); + } + + return _items[index]; + } + + /// + /// Gets all items overlapping with the specified item in memory. + /// + /// Item to check for overlaps + /// Output array where matches will be written. It is automatically resized to fit the results + /// The number of overlapping items found + public int FindOverlaps(T item, ref T[] output) + { + return FindOverlaps(item.Address, item.Size, ref output); + } + + /// + /// Gets all items on the list overlapping the specified memory range. + /// + /// Start address of the range + /// Size in bytes of the range + /// Output array where matches will be written. It is automatically resized to fit the results + /// The number of overlapping items found + public int FindOverlaps(ulong address, ulong size, ref T[] output) + { + int outputIndex = 0; + + ulong endAddress = address + size; + + lock (_items) + { + foreach (T item in _items) + { + if (item.Address >= endAddress) + { + break; + } + + if (item.OverlapsWith(address, size)) + { + if (outputIndex == output.Length) + { + Array.Resize(ref output, outputIndex + ArrayGrowthSize); + } + + output[outputIndex++] = item; + } + } + } + + return outputIndex; + } + + /// + /// Gets all items overlapping with the specified item in memory. + /// + /// + /// This method only returns correct results if none of the items on the list overlaps with + /// each other. If that is not the case, this method should not be used. + /// This method is faster than the regular method to find all overlaps. + /// + /// Item to check for overlaps + /// Output array where matches will be written. It is automatically resized to fit the results + /// The number of overlapping items found + public int FindOverlapsNonOverlapping(T item, ref T[] output) + { + return FindOverlapsNonOverlapping(item.Address, item.Size, ref output); + } + + /// + /// Gets all items on the list overlapping the specified memory range. + /// + /// + /// This method only returns correct results if none of the items on the list overlaps with + /// each other. If that is not the case, this method should not be used. + /// This method is faster than the regular method to find all overlaps. + /// + /// Start address of the range + /// Size in bytes of the range + /// Output array where matches will be written. It is automatically resized to fit the results + /// The number of overlapping items found + public int FindOverlapsNonOverlapping(ulong address, ulong size, ref T[] output) + { + // This is a bit faster than FindOverlaps, but only works + // when none of the items on the list overlaps with each other. + int outputIndex = 0; + + int index = BinarySearch(address, size); + + if (index >= 0) + { + while (index > 0 && _items[index - 1].OverlapsWith(address, size)) + { + index--; + } + + do + { + if (outputIndex == output.Length) + { + Array.Resize(ref output, outputIndex + ArrayGrowthSize); + } + + output[outputIndex++] = _items[index++]; + } + while (index < _items.Count && _items[index].OverlapsWith(address, size)); + } + + return outputIndex; + } + + /// + /// Gets all items on the list with the specified memory address. + /// + /// Address to find + /// Output array where matches will be written. It is automatically resized to fit the results + /// The number of matches found + public int FindOverlaps(ulong address, ref T[] output) + { + int index = BinarySearch(address); + + int outputIndex = 0; + + if (index >= 0) + { + while (index > 0 && _items[index - 1].Address == address) + { + index--; + } + + while (index < _items.Count) + { + T overlap = _items[index++]; + + if (overlap.Address != address) + { + break; + } + + if (outputIndex == output.Length) + { + Array.Resize(ref output, outputIndex + ArrayGrowthSize); + } + + output[outputIndex++] = overlap; + } + } + + return outputIndex; + } + + /// + /// Performs binary search on the internal list of items. + /// + /// Address to find + /// List index of the item, or complement index of nearest item with lower value on the list + private int BinarySearch(ulong address) + { + int left = 0; + int right = _items.Count - 1; + + while (left <= right) + { + int range = right - left; + + int middle = left + (range >> 1); + + T item = _items[middle]; + + if (item.Address == address) + { + return middle; + } + + if (address < item.Address) + { + right = middle - 1; + } + else + { + left = middle + 1; + } + } + + return ~left; + } + + /// + /// Performs binary search for items overlapping a given memory range. + /// + /// Start address of the range + /// Size in bytes of the range + /// List index of the item, or complement index of nearest item with lower value on the list + private int BinarySearch(ulong address, ulong size) + { + int left = 0; + int right = _items.Count - 1; + + while (left <= right) + { + int range = right - left; + + int middle = left + (range >> 1); + + T item = _items[middle]; + + if (item.OverlapsWith(address, size)) + { + return middle; + } + + if (address < item.Address) + { + right = middle - 1; + } + else + { + left = middle + 1; + } + } + + return ~left; + } + + public IEnumerator GetEnumerator() + { + return _items.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return _items.GetEnumerator(); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Memory/ResourceName.cs b/Ryujinx.Graphics.Gpu/Memory/ResourceName.cs new file mode 100644 index 0000000000..c3d2dc77a1 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Memory/ResourceName.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.Graphics.Gpu.Memory +{ + /// + /// Name of a GPU resource. + /// + public enum ResourceName + { + Buffer, + Texture, + TexturePool, + SamplerPool + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Memory/VertexBuffer.cs b/Ryujinx.Graphics.Gpu/Memory/VertexBuffer.cs new file mode 100644 index 0000000000..8f08912513 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Memory/VertexBuffer.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.Graphics.Gpu.Memory +{ + /// + /// GPU Vertex Buffer information. + /// + struct VertexBuffer + { + public ulong Address; + public ulong Size; + public int Stride; + public int Divisor; + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/MethodParams.cs b/Ryujinx.Graphics.Gpu/MethodParams.cs new file mode 100644 index 0000000000..dd60f77cd0 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/MethodParams.cs @@ -0,0 +1,52 @@ +namespace Ryujinx.Graphics +{ + /// + /// Method call parameters. + /// + struct MethodParams + { + /// + /// Method offset. + /// + public int Method { get; } + + /// + /// Method call argument. + /// + public int Argument { get; } + + /// + /// Sub-channel where the call should be sent. + /// + public int SubChannel { get; } + + /// + /// For multiple calls to the same method, this is the remaining calls count. + /// + public int MethodCount { get; } + + /// + /// Indicates if the current call is the last one from a batch of calls to the same method. + /// + public bool IsLastCall => MethodCount <= 1; + + /// + /// Constructs the method call parameters structure. + /// + /// Method offset + /// Method call argument + /// Optional sub-channel where the method should be sent (not required for macro calls) + /// Optional remaining calls count (not required for macro calls) + public MethodParams( + int method, + int argument, + int subChannel = 0, + int methodCount = 0) + { + Method = method; + Argument = argument; + SubChannel = subChannel; + MethodCount = methodCount; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/NvGpuFifo.cs b/Ryujinx.Graphics.Gpu/NvGpuFifo.cs new file mode 100644 index 0000000000..11a9e3fba4 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/NvGpuFifo.cs @@ -0,0 +1,219 @@ +using Ryujinx.Graphics.Gpu.State; + +namespace Ryujinx.Graphics.Gpu +{ + /// + /// GPU commands FIFO. + /// + class NvGpuFifo + { + private const int MacrosCount = 0x80; + private const int MacroIndexMask = MacrosCount - 1; + + // Note: The size of the macro memory is unknown, we just make + // a guess here and use 256kb as the size. Increase if needed. + private const int MmeWords = 256 * 256; + + private GpuContext _context; + + /// + /// Cached GPU macro program. + /// + private struct CachedMacro + { + /// + /// Word offset of the code on the code memory. + /// + public int Position { get; } + + private bool _executionPending; + private int _argument; + + private MacroInterpreter _interpreter; + + /// + /// Creates a new instance of the GPU cached macro program. + /// + /// Macro code start position + public CachedMacro(int position) + { + Position = position; + + _executionPending = false; + _argument = 0; + + _interpreter = new MacroInterpreter(); + } + + /// + /// Sets the first argument for the macro call. + /// + /// First argument + public void StartExecution(int argument) + { + _argument = argument; + + _executionPending = true; + } + + /// + /// Starts executing the macro program code. + /// + /// Program code + /// Current GPU state + public void Execute(int[] mme, GpuState state) + { + if (_executionPending) + { + _executionPending = false; + + _interpreter?.Execute(mme, Position, _argument, state); + } + } + + /// + /// Pushes an argument to the macro call argument FIFO. + /// + /// Argument to be pushed + public void PushArgument(int argument) + { + _interpreter?.Fifo.Enqueue(argument); + } + } + + private int _currMacroPosition; + private int _currMacroBindIndex; + + private CachedMacro[] _macros; + + private int[] _mme; + + /// + /// GPU sub-channel information. + /// + private class SubChannel + { + /// + /// Sub-channel GPU state. + /// + public GpuState State { get; } + + /// + /// Engine bound to the sub-channel. + /// + public ClassId Class { get; set; } + + /// + /// Creates a new instance of the GPU sub-channel. + /// + public SubChannel() + { + State = new GpuState(); + } + } + + private SubChannel[] _subChannels; + + /// + /// Creates a new instance of the GPU commands FIFO. + /// + /// GPU emulation context + public NvGpuFifo(GpuContext context) + { + _context = context; + + _macros = new CachedMacro[MacrosCount]; + + _mme = new int[MmeWords]; + + _subChannels = new SubChannel[8]; + + for (int index = 0; index < _subChannels.Length; index++) + { + _subChannels[index] = new SubChannel(); + + context.Methods.RegisterCallbacks(_subChannels[index].State); + } + } + + /// + /// Calls a GPU method. + /// + /// GPU method call parameters + public void CallMethod(MethodParams meth) + { + if ((NvGpuFifoMeth)meth.Method == NvGpuFifoMeth.BindChannel) + { + _subChannels[meth.SubChannel].Class = (ClassId)meth.Argument; + } + else if (meth.Method < 0x60) + { + switch ((NvGpuFifoMeth)meth.Method) + { + case NvGpuFifoMeth.WaitForIdle: + { + _context.Methods.PerformDeferredDraws(); + + _context.Renderer.Pipeline.Barrier(); + + break; + } + + case NvGpuFifoMeth.SetMacroUploadAddress: + { + _currMacroPosition = meth.Argument; + + break; + } + + case NvGpuFifoMeth.SendMacroCodeData: + { + _mme[_currMacroPosition++] = meth.Argument; + + break; + } + + case NvGpuFifoMeth.SetMacroBindingIndex: + { + _currMacroBindIndex = meth.Argument; + + break; + } + + case NvGpuFifoMeth.BindMacro: + { + int position = meth.Argument; + + _macros[_currMacroBindIndex++] = new CachedMacro(position); + + break; + } + } + } + else if (meth.Method < 0xe00) + { + _subChannels[meth.SubChannel].State.CallMethod(meth); + } + else + { + int macroIndex = (meth.Method >> 1) & MacroIndexMask; + + if ((meth.Method & 1) != 0) + { + _macros[macroIndex].PushArgument(meth.Argument); + } + else + { + _macros[macroIndex].StartExecution(meth.Argument); + } + + if (meth.IsLastCall) + { + _macros[macroIndex].Execute(_mme, _subChannels[meth.SubChannel].State); + + _context.Methods.PerformDeferredDraws(); + } + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/Gpu/Engines/NvGpuFifoMeth.cs b/Ryujinx.Graphics.Gpu/NvGpuFifoMeth.cs similarity index 59% rename from Ryujinx.HLE/Gpu/Engines/NvGpuFifoMeth.cs rename to Ryujinx.Graphics.Gpu/NvGpuFifoMeth.cs index ffd179f261..89023407e9 100644 --- a/Ryujinx.HLE/Gpu/Engines/NvGpuFifoMeth.cs +++ b/Ryujinx.Graphics.Gpu/NvGpuFifoMeth.cs @@ -1,8 +1,12 @@ -namespace Ryujinx.HLE.Gpu.Engines +namespace Ryujinx.Graphics.Gpu { + /// + /// GPU commands FIFO processor commands. + /// enum NvGpuFifoMeth { BindChannel = 0, + WaitForIdle = 0x44, SetMacroUploadAddress = 0x45, SendMacroCodeData = 0x46, SetMacroBindingIndex = 0x47, diff --git a/Ryujinx.Graphics.Gpu/Ryujinx.Graphics.Gpu.csproj b/Ryujinx.Graphics.Gpu/Ryujinx.Graphics.Gpu.csproj new file mode 100644 index 0000000000..b9751508ef --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Ryujinx.Graphics.Gpu.csproj @@ -0,0 +1,16 @@ + + + + + + + + + + + + netcoreapp3.0 + win-x64;osx-x64;linux-x64 + + + diff --git a/Ryujinx.Graphics.Gpu/Shader/CachedShader.cs b/Ryujinx.Graphics.Gpu/Shader/CachedShader.cs new file mode 100644 index 0000000000..f849404564 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Shader/CachedShader.cs @@ -0,0 +1,37 @@ +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Shader; + +namespace Ryujinx.Graphics.Gpu.Shader +{ + /// + /// Cached shader code for a single shader stage. + /// + class CachedShader + { + /// + /// Shader program containing translated code. + /// + public ShaderProgram Program { get; } + + /// + /// Host shader object. + /// + public IShader HostShader { get; set; } + + /// + /// Maxwell binary shader code. + /// + public int[] Code { get; } + + /// + /// Creates a new instace of the cached shader. + /// + /// Shader program + /// Maxwell binary shader code + public CachedShader(ShaderProgram program, int[] code) + { + Program = program; + Code = code; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Shader/ComputeShader.cs b/Ryujinx.Graphics.Gpu/Shader/ComputeShader.cs new file mode 100644 index 0000000000..fcc38d0488 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Shader/ComputeShader.cs @@ -0,0 +1,31 @@ +using Ryujinx.Graphics.GAL; + +namespace Ryujinx.Graphics.Gpu.Shader +{ + /// + /// Cached compute shader code. + /// + class ComputeShader + { + /// + /// Host shader program object. + /// + public IProgram HostProgram { get; } + + /// + /// Cached shader. + /// + public CachedShader Shader { get; } + + /// + /// Creates a new instance of the compute shader. + /// + /// Host shader program + /// Cached shader + public ComputeShader(IProgram hostProgram, CachedShader shader) + { + HostProgram = hostProgram; + Shader = shader; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Shader/GraphicsShader.cs b/Ryujinx.Graphics.Gpu/Shader/GraphicsShader.cs new file mode 100644 index 0000000000..e348f304f3 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Shader/GraphicsShader.cs @@ -0,0 +1,28 @@ +using Ryujinx.Graphics.GAL; + +namespace Ryujinx.Graphics.Gpu.Shader +{ + /// + /// Cached graphics shader code for all stages. + /// + class GraphicsShader + { + /// + /// Host shader program object. + /// + public IProgram HostProgram { get; set; } + + /// + /// Compiled shader for each shader stage. + /// + public CachedShader[] Shaders { get; } + + /// + /// Creates a new instance of cached graphics shader. + /// + public GraphicsShader() + { + Shaders = new CachedShader[Constants.ShaderStages]; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Shader/ShaderAddresses.cs b/Ryujinx.Graphics.Gpu/Shader/ShaderAddresses.cs new file mode 100644 index 0000000000..76ea32482d --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Shader/ShaderAddresses.cs @@ -0,0 +1,51 @@ +using System; + +namespace Ryujinx.Graphics.Gpu.Shader +{ + /// + /// Shader code addresses in memory for each shader stage. + /// + struct ShaderAddresses : IEquatable + { + public ulong VertexA; + public ulong Vertex; + public ulong TessControl; + public ulong TessEvaluation; + public ulong Geometry; + public ulong Fragment; + + /// + /// Check if the addresses are equal. + /// + /// Shader addresses structure to compare with + /// True if they are equal, false otherwise + public override bool Equals(object other) + { + return other is ShaderAddresses addresses && Equals(addresses); + } + + /// + /// Check if the addresses are equal. + /// + /// Shader addresses structure to compare with + /// True if they are equal, false otherwise + public bool Equals(ShaderAddresses other) + { + return VertexA == other.VertexA && + Vertex == other.Vertex && + TessControl == other.TessControl && + TessEvaluation == other.TessEvaluation && + Geometry == other.Geometry && + Fragment == other.Fragment; + } + + /// + /// Computes hash code from the addresses. + /// + /// Hash code + public override int GetHashCode() + { + return HashCode.Combine(VertexA, Vertex, TessControl, TessEvaluation, Geometry, Fragment); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs b/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs new file mode 100644 index 0000000000..dad1b0ac2e --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs @@ -0,0 +1,532 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Image; +using Ryujinx.Graphics.Gpu.State; +using Ryujinx.Graphics.Shader; +using Ryujinx.Graphics.Shader.Translation; +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Gpu.Shader +{ + using TextureDescriptor = Image.TextureDescriptor; + + /// + /// Memory cache of shader code. + /// + class ShaderCache : IDisposable + { + private const int MaxProgramSize = 0x100000; + + private const TranslationFlags DefaultFlags = TranslationFlags.DebugMode; + + private GpuContext _context; + + private ShaderDumper _dumper; + + private Dictionary> _cpPrograms; + + private Dictionary> _gpPrograms; + + /// + /// Creates a new instance of the shader cache. + /// + /// GPU context that the shader cache belongs to + public ShaderCache(GpuContext context) + { + _context = context; + + _dumper = new ShaderDumper(); + + _cpPrograms = new Dictionary>(); + + _gpPrograms = new Dictionary>(); + } + + /// + /// Gets a compute shader from the cache. + /// + /// + /// This automatically translates, compiles and adds the code to the cache if not present. + /// + /// GPU virtual address of the binary shader code + /// Shared memory size of the compute shader + /// Local group size X of the computer shader + /// Local group size Y of the computer shader + /// Local group size Z of the computer shader + /// Compiled compute shader code + public ComputeShader GetComputeShader(ulong gpuVa, int sharedMemorySize, int localSizeX, int localSizeY, int localSizeZ) + { + bool isCached = _cpPrograms.TryGetValue(gpuVa, out List list); + + if (isCached) + { + foreach (ComputeShader cachedCpShader in list) + { + if (!IsShaderDifferent(cachedCpShader, gpuVa)) + { + return cachedCpShader; + } + } + } + + CachedShader shader = TranslateComputeShader(gpuVa, sharedMemorySize, localSizeX, localSizeY, localSizeZ); + + shader.HostShader = _context.Renderer.CompileShader(shader.Program); + + IProgram hostProgram = _context.Renderer.CreateProgram(new IShader[] { shader.HostShader }); + + ComputeShader cpShader = new ComputeShader(hostProgram, shader); + + if (!isCached) + { + list = new List(); + + _cpPrograms.Add(gpuVa, list); + } + + list.Add(cpShader); + + return cpShader; + } + + /// + /// Gets a graphics shader program from the shader cache. + /// This includes all the specified shader stages. + /// + /// + /// This automatically translates, compiles and adds the code to the cache if not present. + /// + /// Current GPU state + /// Addresses of the shaders for each stage + /// Compiled graphics shader code + public GraphicsShader GetGraphicsShader(GpuState state, ShaderAddresses addresses) + { + bool isCached = _gpPrograms.TryGetValue(addresses, out List list); + + if (isCached) + { + foreach (GraphicsShader cachedGpShaders in list) + { + if (!IsShaderDifferent(cachedGpShaders, addresses)) + { + return cachedGpShaders; + } + } + } + + GraphicsShader gpShaders = new GraphicsShader(); + + if (addresses.VertexA != 0) + { + gpShaders.Shaders[0] = TranslateGraphicsShader(state, ShaderStage.Vertex, addresses.Vertex, addresses.VertexA); + } + else + { + gpShaders.Shaders[0] = TranslateGraphicsShader(state, ShaderStage.Vertex, addresses.Vertex); + } + + gpShaders.Shaders[1] = TranslateGraphicsShader(state, ShaderStage.TessellationControl, addresses.TessControl); + gpShaders.Shaders[2] = TranslateGraphicsShader(state, ShaderStage.TessellationEvaluation, addresses.TessEvaluation); + gpShaders.Shaders[3] = TranslateGraphicsShader(state, ShaderStage.Geometry, addresses.Geometry); + gpShaders.Shaders[4] = TranslateGraphicsShader(state, ShaderStage.Fragment, addresses.Fragment); + + BackpropQualifiers(gpShaders); + + List hostShaders = new List(); + + for (int stage = 0; stage < gpShaders.Shaders.Length; stage++) + { + ShaderProgram program = gpShaders.Shaders[stage]?.Program; + + if (program == null) + { + continue; + } + + IShader hostShader = _context.Renderer.CompileShader(program); + + gpShaders.Shaders[stage].HostShader = hostShader; + + hostShaders.Add(hostShader); + } + + gpShaders.HostProgram = _context.Renderer.CreateProgram(hostShaders.ToArray()); + + if (!isCached) + { + list = new List(); + + _gpPrograms.Add(addresses, list); + } + + list.Add(gpShaders); + + return gpShaders; + } + + /// + /// Checks if compute shader code in memory is different from the cached shader. + /// + /// Cached compute shader + /// GPU virtual address of the shader code in memory + /// True if the code is different, false otherwise + private bool IsShaderDifferent(ComputeShader cpShader, ulong gpuVa) + { + return IsShaderDifferent(cpShader.Shader, gpuVa); + } + + /// + /// Checks if graphics shader code from all stages in memory is different from the cached shaders. + /// + /// Cached graphics shaders + /// GPU virtual addresses of all enabled shader stages + /// True if the code is different, false otherwise + private bool IsShaderDifferent(GraphicsShader gpShaders, ShaderAddresses addresses) + { + for (int stage = 0; stage < gpShaders.Shaders.Length; stage++) + { + CachedShader shader = gpShaders.Shaders[stage]; + + ulong gpuVa = 0; + + switch (stage) + { + case 0: gpuVa = addresses.Vertex; break; + case 1: gpuVa = addresses.TessControl; break; + case 2: gpuVa = addresses.TessEvaluation; break; + case 3: gpuVa = addresses.Geometry; break; + case 4: gpuVa = addresses.Fragment; break; + } + + if (IsShaderDifferent(shader, gpuVa)) + { + return true; + } + } + + return false; + } + + /// + /// Checks if the code of the specified cached shader is different from the code in memory. + /// + /// Cached shader to compare with + /// GPU virtual address of the binary shader code + /// True if the code is different, false otherwise + private bool IsShaderDifferent(CachedShader shader, ulong gpuVa) + { + if (shader == null) + { + return false; + } + + for (int index = 0; index < shader.Code.Length; index++) + { + if (_context.MemoryAccessor.ReadInt32(gpuVa + (ulong)index * 4) != shader.Code[index]) + { + return true; + } + } + + return false; + } + + /// + /// Translates the binary Maxwell shader code to something that the host API accepts. + /// + /// GPU virtual address of the binary shader code + /// Shared memory size of the compute shader + /// Local group size X of the computer shader + /// Local group size Y of the computer shader + /// Local group size Z of the computer shader + /// Compiled compute shader code + private CachedShader TranslateComputeShader(ulong gpuVa, int sharedMemorySize, int localSizeX, int localSizeY, int localSizeZ) + { + if (gpuVa == 0) + { + return null; + } + + int QueryInfo(QueryInfoName info, int index) + { + return info switch + { + QueryInfoName.ComputeLocalSizeX => localSizeX, + QueryInfoName.ComputeLocalSizeY => localSizeY, + QueryInfoName.ComputeLocalSizeZ => localSizeZ, + QueryInfoName.ComputeSharedMemorySize => sharedMemorySize, + _ => QueryInfoCommon(info) + }; + } + + TranslatorCallbacks callbacks = new TranslatorCallbacks(QueryInfo, PrintLog); + + ShaderProgram program; + + ReadOnlySpan code = _context.MemoryAccessor.GetSpan(gpuVa, MaxProgramSize); + + program = Translator.Translate(code, callbacks, DefaultFlags | TranslationFlags.Compute); + + int[] codeCached = MemoryMarshal.Cast(code.Slice(0, program.Size)).ToArray(); + + _dumper.Dump(code, compute: true, out string fullPath, out string codePath); + + if (fullPath != null && codePath != null) + { + program.Prepend("// " + codePath); + program.Prepend("// " + fullPath); + } + + return new CachedShader(program, codeCached); + } + + /// + /// Translates the binary Maxwell shader code to something that the host API accepts. + /// + /// + /// This will combine the "Vertex A" and "Vertex B" shader stages, if specified, into one shader. + /// + /// Current GPU state + /// Shader stage + /// GPU virtual address of the shader code + /// Optional GPU virtual address of the "Vertex A" shader code + /// Compiled graphics shader code + private CachedShader TranslateGraphicsShader(GpuState state, ShaderStage stage, ulong gpuVa, ulong gpuVaA = 0) + { + if (gpuVa == 0) + { + return null; + } + + int QueryInfo(QueryInfoName info, int index) + { + return info switch + { + QueryInfoName.IsTextureBuffer => Convert.ToInt32(QueryIsTextureBuffer(state, (int)stage - 1, index)), + QueryInfoName.IsTextureRectangle => Convert.ToInt32(QueryIsTextureRectangle(state, (int)stage - 1, index)), + QueryInfoName.PrimitiveTopology => (int)GetPrimitiveTopology(), + _ => QueryInfoCommon(info) + }; + } + + TranslatorCallbacks callbacks = new TranslatorCallbacks(QueryInfo, PrintLog); + + ShaderProgram program; + + int[] codeCached = null; + + if (gpuVaA != 0) + { + ReadOnlySpan codeA = _context.MemoryAccessor.GetSpan(gpuVaA, MaxProgramSize); + ReadOnlySpan codeB = _context.MemoryAccessor.GetSpan(gpuVa, MaxProgramSize); + + program = Translator.Translate(codeA, codeB, callbacks, DefaultFlags); + + // TODO: We should also take "codeA" into account. + codeCached = MemoryMarshal.Cast(codeB.Slice(0, program.Size)).ToArray(); + + _dumper.Dump(codeA, compute: false, out string fullPathA, out string codePathA); + _dumper.Dump(codeB, compute: false, out string fullPathB, out string codePathB); + + if (fullPathA != null && fullPathB != null && codePathA != null && codePathB != null) + { + program.Prepend("// " + codePathB); + program.Prepend("// " + fullPathB); + program.Prepend("// " + codePathA); + program.Prepend("// " + fullPathA); + } + } + else + { + ReadOnlySpan code = _context.MemoryAccessor.GetSpan(gpuVa, MaxProgramSize); + + program = Translator.Translate(code, callbacks, DefaultFlags); + + codeCached = MemoryMarshal.Cast(code.Slice(0, program.Size)).ToArray(); + + _dumper.Dump(code, compute: false, out string fullPath, out string codePath); + + if (fullPath != null && codePath != null) + { + program.Prepend("// " + codePath); + program.Prepend("// " + fullPath); + } + } + + ulong address = _context.MemoryManager.Translate(gpuVa); + + return new CachedShader(program, codeCached); + } + + /// + /// Performs backwards propagation of interpolation qualifiers or later shader stages input, + /// to ealier shader stages output. + /// This is required by older versions of OpenGL (pre-4.3). + /// + /// Graphics shader cached code + private void BackpropQualifiers(GraphicsShader program) + { + ShaderProgram fragmentShader = program.Shaders[4]?.Program; + + bool isFirst = true; + + for (int stage = 3; stage >= 0; stage--) + { + if (program.Shaders[stage] == null) + { + continue; + } + + // We need to iterate backwards, since we do name replacement, + // and it would otherwise replace a subset of the longer names. + for (int attr = 31; attr >= 0; attr--) + { + string iq = fragmentShader?.Info.InterpolationQualifiers[attr].ToGlslQualifier() ?? string.Empty; + + if (isFirst && !string.IsNullOrEmpty(iq)) + { + program.Shaders[stage].Program.Replace($"{DefineNames.OutQualifierPrefixName}{attr}", iq); + } + else + { + program.Shaders[stage].Program.Replace($"{DefineNames.OutQualifierPrefixName}{attr} ", string.Empty); + } + } + + isFirst = false; + } + } + + /// + /// Gets the primitive topology for the current draw. + /// This is required by geometry shaders. + /// + /// Primitive topology + private InputTopology GetPrimitiveTopology() + { + switch (_context.Methods.PrimitiveType) + { + case PrimitiveType.Points: + return InputTopology.Points; + case PrimitiveType.Lines: + case PrimitiveType.LineLoop: + case PrimitiveType.LineStrip: + return InputTopology.Lines; + case PrimitiveType.LinesAdjacency: + case PrimitiveType.LineStripAdjacency: + return InputTopology.LinesAdjacency; + case PrimitiveType.Triangles: + case PrimitiveType.TriangleStrip: + case PrimitiveType.TriangleFan: + return InputTopology.Triangles; + case PrimitiveType.TrianglesAdjacency: + case PrimitiveType.TriangleStripAdjacency: + return InputTopology.TrianglesAdjacency; + } + + return InputTopology.Points; + } + + /// + /// Check if the target of a given texture is texture buffer. + /// This is required as 1D textures and buffer textures shares the same sampler type on binary shader code, + /// but not on GLSL. + /// + /// Current GPU state + /// Index of the shader stage + /// Index of the texture (this is the shader "fake" handle) + /// True if the texture is a buffer texture, false otherwise + private bool QueryIsTextureBuffer(GpuState state, int stageIndex, int index) + { + return GetTextureDescriptor(state, stageIndex, index).UnpackTextureTarget() == TextureTarget.TextureBuffer; + } + + /// + /// Check if the target of a given texture is texture rectangle. + /// This is required as 2D textures and rectangle textures shares the same sampler type on binary shader code, + /// but not on GLSL. + /// + /// Current GPU state + /// Index of the shader stage + /// Index of the texture (this is the shader "fake" handle) + /// True if the texture is a rectangle texture, false otherwise + private bool QueryIsTextureRectangle(GpuState state, int stageIndex, int index) + { + var descriptor = GetTextureDescriptor(state, stageIndex, index); + + TextureTarget target = descriptor.UnpackTextureTarget(); + + bool is2DTexture = target == TextureTarget.Texture2D || + target == TextureTarget.Texture2DRect; + + return !descriptor.UnpackTextureCoordNormalized() && is2DTexture; + } + + /// + /// Gets the texture descriptor for a given texture on the pool. + /// + /// Current GPU state + /// Index of the shader stage + /// Index of the texture (this is the shader "fake" handle) + /// Texture descriptor + private TextureDescriptor GetTextureDescriptor(GpuState state, int stageIndex, int index) + { + return _context.Methods.TextureManager.GetGraphicsTextureDescriptor(state, stageIndex, index); + } + + /// + /// Returns information required by both compute and graphics shader compilation. + /// + /// Information queried + /// Requested information + private int QueryInfoCommon(QueryInfoName info) + { + return info switch + { + QueryInfoName.StorageBufferOffsetAlignment => _context.Capabilities.StorageBufferOffsetAlignment, + QueryInfoName.SupportsNonConstantTextureOffset => Convert.ToInt32(_context.Capabilities.SupportsNonConstantTextureOffset), + _ => 0 + }; + } + + /// + /// Prints a warning from the shader code translator. + /// + /// Warning message + private static void PrintLog(string message) + { + Logger.PrintWarning(LogClass.Gpu, $"Shader translator: {message}"); + } + + /// + /// Disposes the shader cache, deleting all the cached shaders. + /// It's an error to use the shader cache after disposal. + /// + public void Dispose() + { + foreach (List list in _cpPrograms.Values) + { + foreach (ComputeShader shader in list) + { + shader.HostProgram.Dispose(); + shader.Shader?.HostShader.Dispose(); + } + } + + foreach (List list in _gpPrograms.Values) + { + foreach (GraphicsShader shader in list) + { + shader.HostProgram.Dispose(); + + foreach (CachedShader cachedShader in shader.Shaders) + { + cachedShader?.HostShader.Dispose(); + } + } + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Shader/ShaderDumper.cs b/Ryujinx.Graphics.Gpu/Shader/ShaderDumper.cs new file mode 100644 index 0000000000..0e22b07e3a --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Shader/ShaderDumper.cs @@ -0,0 +1,131 @@ +using Ryujinx.Graphics.Shader.Translation; +using System; +using System.IO; + +namespace Ryujinx.Graphics.Gpu.Shader +{ + /// + /// Shader dumper, writes binary shader code to disk. + /// + class ShaderDumper + { + private string _runtimeDir; + private string _dumpPath; + private int _dumpIndex; + + public int CurrentDumpIndex => _dumpIndex; + + public ShaderDumper() + { + _dumpIndex = 1; + } + + /// + /// Dumps shader code to disk. + /// + /// Code to be dumped + /// True for compute shader code, false for graphics shader code + /// Output path for the shader code with header included + /// Output path for the shader code without header + public void Dump(ReadOnlySpan code, bool compute, out string fullPath, out string codePath) + { + _dumpPath = GraphicsConfig.ShadersDumpPath; + + if (string.IsNullOrWhiteSpace(_dumpPath)) + { + fullPath = null; + codePath = null; + + return; + } + + string fileName = "Shader" + _dumpIndex.ToString("d4") + ".bin"; + + fullPath = Path.Combine(FullDir(), fileName); + codePath = Path.Combine(CodeDir(), fileName); + + _dumpIndex++; + + code = Translator.ExtractCode(code, compute, out int headerSize); + + using (MemoryStream stream = new MemoryStream(code.ToArray())) + { + BinaryReader codeReader = new BinaryReader(stream); + + using (FileStream fullFile = File.Create(fullPath)) + using (FileStream codeFile = File.Create(codePath)) + { + BinaryWriter fullWriter = new BinaryWriter(fullFile); + BinaryWriter codeWriter = new BinaryWriter(codeFile); + + fullWriter.Write(codeReader.ReadBytes(headerSize)); + + byte[] temp = codeReader.ReadBytes(code.Length - headerSize); + + fullWriter.Write(temp); + codeWriter.Write(temp); + + // Align to meet nvdisasm requirements. + while (codeFile.Length % 0x20 != 0) + { + codeWriter.Write(0); + } + } + } + } + + /// + /// Returns the output directory for shader code with header. + /// + /// Directory path + private string FullDir() + { + return CreateAndReturn(Path.Combine(DumpDir(), "Full")); + } + + /// + /// Returns the output directory for shader code without header. + /// + /// Directory path + private string CodeDir() + { + return CreateAndReturn(Path.Combine(DumpDir(), "Code")); + } + + /// + /// Returns the full output directory for the current shader dump. + /// + /// Directory path + private string DumpDir() + { + if (string.IsNullOrEmpty(_runtimeDir)) + { + int index = 1; + + do + { + _runtimeDir = Path.Combine(_dumpPath, "Dumps" + index.ToString("d2")); + + index++; + } + while (Directory.Exists(_runtimeDir)); + + Directory.CreateDirectory(_runtimeDir); + } + + return _runtimeDir; + } + + /// + /// Creates a new specified directory if needed. + /// + /// The directory to create + /// The same directory passed to the method + private static string CreateAndReturn(string dir) + { + Directory.CreateDirectory(dir); + + return dir; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/State/BlendState.cs b/Ryujinx.Graphics.Gpu/State/BlendState.cs new file mode 100644 index 0000000000..609bcc2937 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/BlendState.cs @@ -0,0 +1,19 @@ +using Ryujinx.Graphics.GAL; + +namespace Ryujinx.Graphics.Gpu.State +{ + /// + /// Color buffer blending parameters. + /// + struct BlendState + { + public Boolean32 SeparateAlpha; + public BlendOp ColorOp; + public BlendFactor ColorSrcFactor; + public BlendFactor ColorDstFactor; + public BlendOp AlphaOp; + public BlendFactor AlphaSrcFactor; + public BlendFactor AlphaDstFactor; + public uint Padding; + } +} diff --git a/Ryujinx.Graphics.Gpu/State/BlendStateCommon.cs b/Ryujinx.Graphics.Gpu/State/BlendStateCommon.cs new file mode 100644 index 0000000000..a11cd5b10b --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/BlendStateCommon.cs @@ -0,0 +1,19 @@ +using Ryujinx.Graphics.GAL; + +namespace Ryujinx.Graphics.Gpu.State +{ + /// + /// Color buffer blending parameters, shared by all color buffers. + /// + struct BlendStateCommon + { + public Boolean32 SeparateAlpha; + public BlendOp ColorOp; + public BlendFactor ColorSrcFactor; + public BlendFactor ColorDstFactor; + public BlendOp AlphaOp; + public BlendFactor AlphaSrcFactor; + public uint Unknown0x1354; + public BlendFactor AlphaDstFactor; + } +} diff --git a/Ryujinx.Graphics.Gpu/State/Boolean32.cs b/Ryujinx.Graphics.Gpu/State/Boolean32.cs new file mode 100644 index 0000000000..ff701d9e46 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/Boolean32.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.Graphics.Gpu.State +{ + /// + /// Boolean value, stored as a 32-bits integer in memory. + /// + struct Boolean32 + { + private uint _value; + + public static implicit operator bool(Boolean32 value) + { + return (value._value & 1) != 0; + } + } +} diff --git a/Ryujinx.Graphics.Gpu/State/ClearColors.cs b/Ryujinx.Graphics.Gpu/State/ClearColors.cs new file mode 100644 index 0000000000..b9f7b68476 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/ClearColors.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.Graphics.Gpu.State +{ + /// + /// Color buffer clear color. + /// + struct ClearColors + { + public float Red; + public float Green; + public float Blue; + public float Alpha; + } +} diff --git a/Ryujinx.Graphics.Gpu/State/Condition.cs b/Ryujinx.Graphics.Gpu/State/Condition.cs new file mode 100644 index 0000000000..5afdbe3edb --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/Condition.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.Graphics.Gpu.State +{ + /// + /// Condition for conditional rendering. + /// + enum Condition + { + Never, + Always, + ResultNonZero, + Equal, + NotEqual + } +} diff --git a/Ryujinx.Graphics.Gpu/State/ConditionState.cs b/Ryujinx.Graphics.Gpu/State/ConditionState.cs new file mode 100644 index 0000000000..bad266d9e6 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/ConditionState.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.Graphics.Gpu.State +{ + /// + /// Condition parameters for conditional rendering. + /// + struct ConditionState + { + public GpuVa Address; + public Condition Condition; + } +} diff --git a/Ryujinx.Graphics.Gpu/State/CopyBufferParams.cs b/Ryujinx.Graphics.Gpu/State/CopyBufferParams.cs new file mode 100644 index 0000000000..7393c9692b --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/CopyBufferParams.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.Graphics.Gpu.State +{ + /// + /// Buffer to buffer copy parameters. + /// + struct CopyBufferParams + { + public GpuVa SrcAddress; + public GpuVa DstAddress; + public int SrcStride; + public int DstStride; + public int XCount; + public int YCount; + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/State/CopyBufferSwizzle.cs b/Ryujinx.Graphics.Gpu/State/CopyBufferSwizzle.cs new file mode 100644 index 0000000000..5b3d7076d9 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/CopyBufferSwizzle.cs @@ -0,0 +1,37 @@ +namespace Ryujinx.Graphics.Gpu.State +{ + /// + /// Buffer to buffer copy vector swizzle parameters. + /// + struct CopyBufferSwizzle + { + public uint Swizzle; + + /// + /// Unpacks the size of each vector component of the copy. + /// + /// Vector component size + public int UnpackComponentSize() + { + return (int)((Swizzle >> 16) & 3) + 1; + } + + /// + /// Unpacks the number of components of the source vector of the copy. + /// + /// Number of vector components + public int UnpackSrcComponentsCount() + { + return (int)((Swizzle >> 20) & 7) + 1; + } + + /// + /// Unpacks the number of components of the destination vector of the copy. + /// + /// Number of vector components + public int UnpackDstComponentsCount() + { + return (int)((Swizzle >> 24) & 7) + 1; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/State/CopyBufferTexture.cs b/Ryujinx.Graphics.Gpu/State/CopyBufferTexture.cs new file mode 100644 index 0000000000..98d113a354 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/CopyBufferTexture.cs @@ -0,0 +1,16 @@ +namespace Ryujinx.Graphics.Gpu.State +{ + /// + /// Buffer to texture copy parameters. + /// + struct CopyBufferTexture + { + public MemoryLayout MemoryLayout; + public int Width; + public int Height; + public int Depth; + public int RegionZ; + public ushort RegionX; + public ushort RegionY; + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/State/CopyRegion.cs b/Ryujinx.Graphics.Gpu/State/CopyRegion.cs new file mode 100644 index 0000000000..a256594b35 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/CopyRegion.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.Graphics.Gpu.State +{ + /// + /// Texture copy region. + /// + struct CopyRegion + { + public int DstX; + public int DstY; + public int DstWidth; + public int DstHeight; + public long SrcWidthRF; + public long SrcHeightRF; + public long SrcXF; + public long SrcYF; + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/State/CopyTexture.cs b/Ryujinx.Graphics.Gpu/State/CopyTexture.cs new file mode 100644 index 0000000000..df723d0d12 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/CopyTexture.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Graphics.Gpu.State +{ + /// + /// Texture to texture (with optional resizing) copy parameters. + /// + struct CopyTexture + { + public RtFormat Format; + public Boolean32 LinearLayout; + public MemoryLayout MemoryLayout; + public int Depth; + public int Layer; + public int Stride; + public int Width; + public int Height; + public GpuVa Address; + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/State/CopyTextureControl.cs b/Ryujinx.Graphics.Gpu/State/CopyTextureControl.cs new file mode 100644 index 0000000000..cfc64fc45d --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/CopyTextureControl.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.Graphics.Gpu.State +{ + /// + /// Texture to texture copy control. + /// + struct CopyTextureControl + { + public uint Packed; + + public bool UnpackLinearFilter() + { + return (Packed & (1u << 4)) != 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/State/DepthBiasState.cs b/Ryujinx.Graphics.Gpu/State/DepthBiasState.cs new file mode 100644 index 0000000000..752706252b --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/DepthBiasState.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.Gpu.State +{ + /// + /// Depth bias (also called polygon offset) parameters. + /// + struct DepthBiasState + { + public Boolean32 PointEnable; + public Boolean32 LineEnable; + public Boolean32 FillEnable; + } +} diff --git a/Ryujinx.Graphics.Gpu/State/FaceState.cs b/Ryujinx.Graphics.Gpu/State/FaceState.cs new file mode 100644 index 0000000000..10dec3b91f --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/FaceState.cs @@ -0,0 +1,14 @@ +using Ryujinx.Graphics.GAL; + +namespace Ryujinx.Graphics.Gpu.State +{ + /// + /// Face culling and orientation parameters. + /// + struct FaceState + { + public Boolean32 CullEnable; + public FrontFace FrontFace; + public Face CullFace; + } +} diff --git a/Ryujinx.Graphics.Gpu/State/GpuState.cs b/Ryujinx.Graphics.Gpu/State/GpuState.cs new file mode 100644 index 0000000000..c6052f4a65 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/GpuState.cs @@ -0,0 +1,289 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Gpu.State +{ + /// + /// GPU state. + /// + class GpuState + { + private const int RegistersCount = 0xe00; + + public delegate void MethodCallback(GpuState state, int argument); + + private int[] _backingMemory; + + /// + /// GPU register information. + /// + private struct Register + { + public MethodCallback Callback; + + public MethodOffset BaseOffset; + + public int Stride; + public int Count; + + public bool Modified; + } + + private Register[] _registers; + + /// + /// Creates a new instance of the GPU state. + /// + public GpuState() + { + _backingMemory = new int[RegistersCount]; + + _registers = new Register[RegistersCount]; + + for (int index = 0; index < _registers.Length; index++) + { + _registers[index].BaseOffset = (MethodOffset)index; + _registers[index].Stride = 1; + _registers[index].Count = 1; + _registers[index].Modified = true; + } + + foreach (var item in GpuStateTable.Table) + { + int totalRegs = item.Size * item.Count; + + for (int regOffset = 0; regOffset < totalRegs; regOffset++) + { + int index = (int)item.Offset + regOffset; + + _registers[index].BaseOffset = item.Offset; + _registers[index].Stride = item.Size; + _registers[index].Count = item.Count; + } + } + + InitializeDefaultState(); + } + + /// + /// Calls a GPU method, using this state. + /// + /// The GPU method to be called + public void CallMethod(MethodParams meth) + { + Register register = _registers[meth.Method]; + + if (_backingMemory[meth.Method] != meth.Argument) + { + _registers[(int)register.BaseOffset].Modified = true; + } + + _backingMemory[meth.Method] = meth.Argument; + + register.Callback?.Invoke(this, meth.Argument); + } + + /// + /// Reads data from a GPU register at the given offset. + /// + /// Offset to be read + /// Data at the register + public int Read(int offset) + { + return _backingMemory[offset]; + } + + /// + /// Writes an offset value at the uniform buffer offset register. + /// + /// The offset to be written + public void SetUniformBufferOffset(int offset) + { + _backingMemory[(int)MethodOffset.UniformBufferState + 3] = offset; + } + + /// + /// Initializes registers with the default state. + /// + private void InitializeDefaultState() + { + // Depth ranges. + for (int index = 0; index < 8; index++) + { + _backingMemory[(int)MethodOffset.ViewportExtents + index * 4 + 2] = 0; + _backingMemory[(int)MethodOffset.ViewportExtents + index * 4 + 3] = 0x3F800000; + } + + // Viewport transform enable. + _backingMemory[(int)MethodOffset.ViewportTransformEnable] = 1; + + // Default front stencil mask. + _backingMemory[0x4e7] = 0xff; + + // Default color mask. + for (int index = 0; index < Constants.TotalRenderTargets; index++) + { + _backingMemory[(int)MethodOffset.RtColorMask + index] = 0x1111; + } + } + + /// + /// Registers a callback that is called every time a GPU method, or methods are called. + /// + /// Offset of the method + /// Word count of the methods region + /// Calllback to be called + public void RegisterCallback(MethodOffset offset, int count, MethodCallback callback) + { + for (int index = 0; index < count; index++) + { + _registers[(int)offset + index].Callback = callback; + } + } + + /// + /// Registers a callback that is called every time a GPU method is called. + /// + /// Offset of the method + /// Calllback to be called + public void RegisterCallback(MethodOffset offset, MethodCallback callback) + { + _registers[(int)offset].Callback = callback; + } + + /// + /// Checks if a given register has been modified since the last call to this method. + /// + /// Register offset + /// True if modified, false otherwise + public bool QueryModified(MethodOffset offset) + { + bool modified = _registers[(int)offset].Modified; + + _registers[(int)offset].Modified = false; + + return modified; + } + + /// + /// Checks if two registers have been modified since the last call to this method. + /// + /// First register offset + /// Second register offset + /// True if any register was modified, false otherwise + public bool QueryModified(MethodOffset m1, MethodOffset m2) + { + bool modified = _registers[(int)m1].Modified || + _registers[(int)m2].Modified; + + _registers[(int)m1].Modified = false; + _registers[(int)m2].Modified = false; + + return modified; + } + + /// + /// Checks if two registers have been modified since the last call to this method. + /// + /// First register offset + /// Second register offset + /// Third register offset + /// True if any register was modified, false otherwise + public bool QueryModified(MethodOffset m1, MethodOffset m2, MethodOffset m3) + { + bool modified = _registers[(int)m1].Modified || + _registers[(int)m2].Modified || + _registers[(int)m3].Modified; + + _registers[(int)m1].Modified = false; + _registers[(int)m2].Modified = false; + _registers[(int)m3].Modified = false; + + return modified; + } + + /// + /// Checks if two registers have been modified since the last call to this method. + /// + /// First register offset + /// Second register offset + /// Third register offset + /// Fourth register offset + /// True if any register was modified, false otherwise + public bool QueryModified(MethodOffset m1, MethodOffset m2, MethodOffset m3, MethodOffset m4) + { + bool modified = _registers[(int)m1].Modified || + _registers[(int)m2].Modified || + _registers[(int)m3].Modified || + _registers[(int)m4].Modified; + + _registers[(int)m1].Modified = false; + _registers[(int)m2].Modified = false; + _registers[(int)m3].Modified = false; + _registers[(int)m4].Modified = false; + + return modified; + } + + /// + /// Checks if two registers have been modified since the last call to this method. + /// + /// First register offset + /// Second register offset + /// Third register offset + /// Fourth register offset + /// Fifth register offset + /// True if any register was modified, false otherwise + public bool QueryModified( + MethodOffset m1, + MethodOffset m2, + MethodOffset m3, + MethodOffset m4, + MethodOffset m5) + { + bool modified = _registers[(int)m1].Modified || + _registers[(int)m2].Modified || + _registers[(int)m3].Modified || + _registers[(int)m4].Modified || + _registers[(int)m5].Modified; + + _registers[(int)m1].Modified = false; + _registers[(int)m2].Modified = false; + _registers[(int)m3].Modified = false; + _registers[(int)m4].Modified = false; + _registers[(int)m5].Modified = false; + + return modified; + } + + /// + /// Gets indexed data from a given register offset. + /// + /// Type of the data + /// Register offset + /// Index for indexed data + /// The data at the specified location + public T Get(MethodOffset offset, int index) where T : struct + { + Register register = _registers[(int)offset]; + + if ((uint)index >= register.Count) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + return Get(offset + index * register.Stride); + } + + /// + /// Gets data from a given register offset. + /// + /// Type of the data + /// Register offset + /// The data at the specified location + public T Get(MethodOffset offset) where T : struct + { + return MemoryMarshal.Cast(_backingMemory.AsSpan().Slice((int)offset))[0]; + } + } +} diff --git a/Ryujinx.Graphics.Gpu/State/GpuStateTable.cs b/Ryujinx.Graphics.Gpu/State/GpuStateTable.cs new file mode 100644 index 0000000000..db8d141e77 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/GpuStateTable.cs @@ -0,0 +1,81 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Gpu.State +{ + /// + /// GPU State item sizes table. + /// + static class GpuStateTable + { + /// + /// GPU state table item, with size for structures, and count for indexed state data. + /// + public struct TableItem + { + /// + /// Offset of the data. + /// + public MethodOffset Offset { get; } + + /// + /// Size in words. + /// + public int Size { get; } + + /// + /// Count for indexed data, or 1 if not indexed. + /// + public int Count { get; } + + /// + /// Constructs the table item structure. + /// + /// Data offset + /// Data type + /// Data count, for indexed data + public TableItem(MethodOffset offset, Type type, int count) + { + int sizeInBytes = Marshal.SizeOf(type); + + Debug.Assert((sizeInBytes & 3) == 0); + + Offset = offset; + Size = sizeInBytes / 4; + Count = count; + } + } + + /// + /// Table of GPU state structure sizes and counts. + /// + public static TableItem[] Table = new TableItem[] + { + new TableItem(MethodOffset.RtColorState, typeof(RtColorState), 8), + new TableItem(MethodOffset.ViewportTransform, typeof(ViewportTransform), 8), + new TableItem(MethodOffset.ViewportExtents, typeof(ViewportExtents), 8), + new TableItem(MethodOffset.VertexBufferDrawState, typeof(VertexBufferDrawState), 1), + new TableItem(MethodOffset.DepthBiasState, typeof(DepthBiasState), 1), + new TableItem(MethodOffset.StencilBackMasks, typeof(StencilBackMasks), 1), + new TableItem(MethodOffset.RtDepthStencilState, typeof(RtDepthStencilState), 1), + new TableItem(MethodOffset.VertexAttribState, typeof(VertexAttribState), 16), + new TableItem(MethodOffset.RtDepthStencilSize, typeof(Size3D), 1), + new TableItem(MethodOffset.BlendEnable, typeof(Boolean32), 8), + new TableItem(MethodOffset.StencilTestState, typeof(StencilTestState), 1), + new TableItem(MethodOffset.SamplerPoolState, typeof(PoolState), 1), + new TableItem(MethodOffset.TexturePoolState, typeof(PoolState), 1), + new TableItem(MethodOffset.StencilBackTestState, typeof(StencilBackTestState), 1), + new TableItem(MethodOffset.ShaderBaseAddress, typeof(GpuVa), 1), + new TableItem(MethodOffset.PrimitiveRestartState, typeof(PrimitiveRestartState), 1), + new TableItem(MethodOffset.IndexBufferState, typeof(IndexBufferState), 1), + new TableItem(MethodOffset.VertexBufferInstanced, typeof(Boolean32), 16), + new TableItem(MethodOffset.FaceState, typeof(FaceState), 1), + new TableItem(MethodOffset.RtColorMask, typeof(RtColorMask), 8), + new TableItem(MethodOffset.VertexBufferState, typeof(VertexBufferState), 16), + new TableItem(MethodOffset.BlendState, typeof(BlendState), 8), + new TableItem(MethodOffset.VertexBufferEndAddress, typeof(GpuVa), 16), + new TableItem(MethodOffset.ShaderState, typeof(ShaderState), 6), + }; + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/State/GpuVa.cs b/Ryujinx.Graphics.Gpu/State/GpuVa.cs new file mode 100644 index 0000000000..76a2fddfd2 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/GpuVa.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.Graphics.Gpu.State +{ + /// + /// Split GPU virtual address. + /// + struct GpuVa + { + public uint High; + public uint Low; + + /// + /// Packs the split address into a 64-bits address value. + /// + /// The 64-bits address value + public ulong Pack() + { + return Low | ((ulong)High << 32); + } + } +} diff --git a/Ryujinx.Graphics.Gpu/State/IndexBufferState.cs b/Ryujinx.Graphics.Gpu/State/IndexBufferState.cs new file mode 100644 index 0000000000..b7868bb9fe --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/IndexBufferState.cs @@ -0,0 +1,17 @@ +using Ryujinx.Graphics.GAL; + +namespace Ryujinx.Graphics.Gpu.State +{ + /// + /// GPU index buffer state. + /// This is used on indexed draws. + /// + struct IndexBufferState + { + public GpuVa Address; + public GpuVa EndAddress; + public IndexType Type; + public int First; + public int Count; + } +} diff --git a/Ryujinx.Graphics.Gpu/State/Inline2MemoryParams.cs b/Ryujinx.Graphics.Gpu/State/Inline2MemoryParams.cs new file mode 100644 index 0000000000..45555be20b --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/Inline2MemoryParams.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.Graphics.Gpu.State +{ + /// + /// Inline-to-memory copy parameters. + /// + struct Inline2MemoryParams + { + public int LineLengthIn; + public int LineCount; + public GpuVa DstAddress; + public int DstStride; + public MemoryLayout DstMemoryLayout; + public int DstWidth; + public int DstHeight; + public int DstDepth; + public int DstZ; + public int DstX; + public int DstY; + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/State/MemoryLayout.cs b/Ryujinx.Graphics.Gpu/State/MemoryLayout.cs new file mode 100644 index 0000000000..9b72b097c9 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/MemoryLayout.cs @@ -0,0 +1,35 @@ +namespace Ryujinx.Graphics.Gpu.State +{ + /// + /// Memory layout parameters, for block linear textures. + /// + struct MemoryLayout + { + public uint Packed; + + public int UnpackGobBlocksInX() + { + return 1 << (int)(Packed & 0xf); + } + + public int UnpackGobBlocksInY() + { + return 1 << (int)((Packed >> 4) & 0xf); + } + + public int UnpackGobBlocksInZ() + { + return 1 << (int)((Packed >> 8) & 0xf); + } + + public bool UnpackIsLinear() + { + return (Packed & 0x1000) != 0; + } + + public bool UnpackIsTarget3D() + { + return (Packed & 0x10000) != 0; + } + } +} diff --git a/Ryujinx.Graphics.Gpu/State/MethodOffset.cs b/Ryujinx.Graphics.Gpu/State/MethodOffset.cs new file mode 100644 index 0000000000..904cf8ff49 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/MethodOffset.cs @@ -0,0 +1,92 @@ +namespace Ryujinx.Graphics.Gpu.State +{ + /// + /// GPU method offset. + /// + /// + /// This is indexed in 32 bits word. + /// + enum MethodOffset + { + I2mParams = 0x60, + LaunchDma = 0x6c, + LoadInlineData = 0x6d, + CopyDstTexture = 0x80, + CopySrcTexture = 0x8c, + DispatchParamsAddress = 0xad, + Dispatch = 0xaf, + CopyBuffer = 0xc0, + CopyBufferParams = 0x100, + CopyBufferSwizzle = 0x1c2, + CopyBufferDstTexture = 0x1c3, + CopyBufferSrcTexture = 0x1ca, + RtColorState = 0x200, + CopyTextureControl = 0x223, + CopyRegion = 0x22c, + CopyTexture = 0x237, + ViewportTransform = 0x280, + ViewportExtents = 0x300, + VertexBufferDrawState = 0x35d, + DepthMode = 0x35f, + ClearColors = 0x360, + ClearDepthValue = 0x364, + ClearStencilValue = 0x368, + DepthBiasState = 0x370, + TextureBarrier = 0x378, + StencilBackMasks = 0x3d5, + InvalidateTextures = 0x3dd, + TextureBarrierTiled = 0x3df, + RtColorMaskShared = 0x3e4, + RtDepthStencilState = 0x3f8, + VertexAttribState = 0x458, + RtControl = 0x487, + RtDepthStencilSize = 0x48a, + SamplerIndex = 0x48d, + DepthTestEnable = 0x4b3, + BlendIndependent = 0x4b9, + DepthWriteEnable = 0x4ba, + DepthTestFunc = 0x4c3, + BlendStateCommon = 0x4cf, + BlendEnableCommon = 0x4d7, + BlendEnable = 0x4d8, + StencilTestState = 0x4e0, + YControl = 0x4eb, + FirstVertex = 0x50d, + FirstInstance = 0x50e, + ResetCounter = 0x54c, + RtDepthStencilEnable = 0x54e, + ConditionState = 0x554, + SamplerPoolState = 0x557, + DepthBiasFactor = 0x55b, + TexturePoolState = 0x55d, + StencilBackTestState = 0x565, + DepthBiasUnits = 0x56f, + RtMsaaMode = 0x574, + ShaderBaseAddress = 0x582, + DrawEnd = 0x585, + DrawBegin = 0x586, + PrimitiveRestartState = 0x591, + IndexBufferState = 0x5f2, + IndexBufferCount = 0x5f8, + DepthBiasClamp = 0x61f, + VertexBufferInstanced = 0x620, + FaceState = 0x646, + ViewportTransformEnable = 0x64b, + Clear = 0x674, + RtColorMask = 0x680, + ReportState = 0x6c0, + Report = 0x6c3, + VertexBufferState = 0x700, + BlendState = 0x780, + VertexBufferEndAddress = 0x7c0, + ShaderState = 0x800, + UniformBufferState = 0x8e0, + UniformBufferUpdateData = 0x8e4, + UniformBufferBindVertex = 0x904, + UniformBufferBindTessControl = 0x90c, + UniformBufferBindTessEvaluation = 0x914, + UniformBufferBindGeometry = 0x91c, + UniformBufferBindFragment = 0x924, + TextureBufferIndex = 0x982 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/State/PoolState.cs b/Ryujinx.Graphics.Gpu/State/PoolState.cs new file mode 100644 index 0000000000..eeb5691867 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/PoolState.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.Graphics.Gpu.State +{ + /// + /// Texture or sampler pool state. + /// + struct PoolState + { + public GpuVa Address; + public int MaximumId; + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/State/PrimitiveRestartState.cs b/Ryujinx.Graphics.Gpu/State/PrimitiveRestartState.cs new file mode 100644 index 0000000000..96795083ee --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/PrimitiveRestartState.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.Graphics.Gpu.State +{ + /// + /// Primitive restart state. + /// + struct PrimitiveRestartState + { + public Boolean32 Enable; + public int Index; + } +} diff --git a/Ryujinx.Graphics.Gpu/State/PrimitiveTopology.cs b/Ryujinx.Graphics.Gpu/State/PrimitiveTopology.cs new file mode 100644 index 0000000000..340991c2e3 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/PrimitiveTopology.cs @@ -0,0 +1,57 @@ +using Ryujinx.Graphics.GAL; + +namespace Ryujinx.Graphics.Gpu.State +{ + /// + /// Draw primitive type. + /// + enum PrimitiveType + { + Points, + Lines, + LineLoop, + LineStrip, + Triangles, + TriangleStrip, + TriangleFan, + Quads, + QuadStrip, + Polygon, + LinesAdjacency, + LineStripAdjacency, + TrianglesAdjacency, + TriangleStripAdjacency, + Patches + } + + static class PrimitiveTypeConverter + { + /// + /// Converts the primitive type into something that can be used with the host API. + /// + /// The primitive type to convert + /// A host compatible enum value + public static PrimitiveTopology Convert(this PrimitiveType type) + { + return type switch + { + PrimitiveType.Points => PrimitiveTopology.Points, + PrimitiveType.Lines => PrimitiveTopology.Lines, + PrimitiveType.LineLoop => PrimitiveTopology.LineLoop, + PrimitiveType.LineStrip => PrimitiveTopology.LineStrip, + PrimitiveType.Triangles => PrimitiveTopology.Triangles, + PrimitiveType.TriangleStrip => PrimitiveTopology.TriangleStrip, + PrimitiveType.TriangleFan => PrimitiveTopology.TriangleFan, + PrimitiveType.Quads => PrimitiveTopology.Quads, + PrimitiveType.QuadStrip => PrimitiveTopology.QuadStrip, + PrimitiveType.Polygon => PrimitiveTopology.Polygon, + PrimitiveType.LinesAdjacency => PrimitiveTopology.LinesAdjacency, + PrimitiveType.LineStripAdjacency => PrimitiveTopology.LineStripAdjacency, + PrimitiveType.TrianglesAdjacency => PrimitiveTopology.TrianglesAdjacency, + PrimitiveType.TriangleStripAdjacency => PrimitiveTopology.TriangleStripAdjacency, + PrimitiveType.Patches => PrimitiveTopology.Patches, + _ => PrimitiveTopology.Triangles + }; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/State/ReportCounterType.cs b/Ryujinx.Graphics.Gpu/State/ReportCounterType.cs new file mode 100644 index 0000000000..cface55d03 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/ReportCounterType.cs @@ -0,0 +1,28 @@ +namespace Ryujinx.Graphics.Gpu.State +{ + /// + /// Counter type for GPU counter reporting. + /// + enum ReportCounterType + { + Zero = 0, + InputVertices = 1, + InputPrimitives = 3, + VertexShaderInvocations = 5, + GeometryShaderInvocations = 7, + GeometryShaderPrimitives = 9, + TransformFeedbackPrimitivesWritten = 0xb, + ClipperInputPrimitives = 0xf, + ClipperOutputPrimitives = 0x11, + PrimitivesGenerated = 0x12, + FragmentShaderInvocations = 0x13, + SamplesPassed = 0x15, + TessControlShaderInvocations = 0x1b, + TessEvaluationShaderInvocations = 0x1d, + TessEvaluationShaderPrimitives = 0x1f, + ZcullStats0 = 0x2a, + ZcullStats1 = 0x2c, + ZcullStats2 = 0x2e, + ZcullStats3 = 0x30 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/State/ReportMode.cs b/Ryujinx.Graphics.Gpu/State/ReportMode.cs new file mode 100644 index 0000000000..e557f4ca4a --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/ReportMode.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.Graphics.Gpu.State +{ + /// + /// GPU counter report mode. + /// + enum ReportMode + { + Semaphore = 0, + Counter = 2 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/State/ReportState.cs b/Ryujinx.Graphics.Gpu/State/ReportState.cs new file mode 100644 index 0000000000..7430ea3161 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/ReportState.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.Gpu.State +{ + /// + /// GPU counter report state. + /// + struct ReportState + { + public GpuVa Address; + public int Payload; + public uint Control; + } +} diff --git a/Ryujinx.Graphics.Gpu/State/ResetCounterType.cs b/Ryujinx.Graphics.Gpu/State/ResetCounterType.cs new file mode 100644 index 0000000000..aaf575e1b4 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/ResetCounterType.cs @@ -0,0 +1,24 @@ +namespace Ryujinx.Graphics.Gpu.State +{ + /// + /// Counter type for GPU counter reset. + /// + enum ResetCounterType + { + SamplesPassed = 1, + ZcullStats = 2, + TransformFeedbackPrimitivesWritten = 0x10, + InputVertices = 0x12, + InputPrimitives = 0x13, + VertexShaderInvocations = 0x15, + TessControlShaderInvocations = 0x16, + TessEvaluationShaderInvocations = 0x17, + TessEvaluationShaderPrimitives = 0x18, + GeometryShaderInvocations = 0x1a, + GeometryShaderPrimitives = 0x1b, + ClipperInputPrimitives = 0x1c, + ClipperOutputPrimitives = 0x1d, + FragmentShaderInvocations = 0x1e, + PrimitivesGenerated = 0x1f + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/State/RtColorMask.cs b/Ryujinx.Graphics.Gpu/State/RtColorMask.cs new file mode 100644 index 0000000000..4aa5c33132 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/RtColorMask.cs @@ -0,0 +1,47 @@ +namespace Ryujinx.Graphics.Gpu.State +{ + /// + /// Render target color buffer mask. + /// This defines which color channels are written to the color buffer. + /// + struct RtColorMask + { + public uint Packed; + + /// + /// Unpacks red channel enable. + /// + /// True to write the new red channel color, false to keep the old value + public bool UnpackRed() + { + return (Packed & 0x1) != 0; + } + + /// + /// Unpacks green channel enable. + /// + /// True to write the new green channel color, false to keep the old value + public bool UnpackGreen() + { + return (Packed & 0x10) != 0; + } + + /// + /// Unpacks blue channel enable. + /// + /// True to write the new blue channel color, false to keep the old value + public bool UnpackBlue() + { + return (Packed & 0x100) != 0; + } + + /// + /// Unpacks alpha channel enable. + /// + /// True to write the new alpha channel color, false to keep the old value + public bool UnpackAlpha() + { + return (Packed & 0x1000) != 0; + } + } +} diff --git a/Ryujinx.Graphics.Gpu/State/RtColorState.cs b/Ryujinx.Graphics.Gpu/State/RtColorState.cs new file mode 100644 index 0000000000..9261526a25 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/RtColorState.cs @@ -0,0 +1,24 @@ +namespace Ryujinx.Graphics.Gpu.State +{ + /// + /// Render target color buffer state. + /// + struct RtColorState + { + public GpuVa Address; + public int WidthOrStride; + public int Height; + public RtFormat Format; + public MemoryLayout MemoryLayout; + public int Depth; + public int LayerSize; + public int BaseLayer; + public int Unknown0x24; + public int Padding0; + public int Padding1; + public int Padding2; + public int Padding3; + public int Padding4; + public int Padding5; + } +} diff --git a/Ryujinx.Graphics.Gpu/State/RtControl.cs b/Ryujinx.Graphics.Gpu/State/RtControl.cs new file mode 100644 index 0000000000..2e28660bf6 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/RtControl.cs @@ -0,0 +1,29 @@ +namespace Ryujinx.Graphics.Gpu.State +{ + /// + /// Render target draw buffers control. + /// + struct RtControl + { + public uint Packed; + + /// + /// Unpacks the number of active draw buffers. + /// + /// Number of active draw buffers + public int UnpackCount() + { + return (int)(Packed & 0xf); + } + + /// + /// Unpacks the color attachment index for a given draw buffer. + /// + /// Index of the draw buffer + /// Attachment index + public int UnpackPermutationIndex(int index) + { + return (int)((Packed >> (4 + index * 3)) & 7); + } + } +} diff --git a/Ryujinx.Graphics.Gpu/State/RtDepthStencilState.cs b/Ryujinx.Graphics.Gpu/State/RtDepthStencilState.cs new file mode 100644 index 0000000000..abc28fca14 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/RtDepthStencilState.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.Graphics.Gpu.State +{ + /// + /// Render target depth-stencil buffer state. + /// + struct RtDepthStencilState + { + public GpuVa Address; + public RtFormat Format; + public MemoryLayout MemoryLayout; + public int LayerSize; + } +} diff --git a/Ryujinx.Graphics.Gpu/State/RtFormat.cs b/Ryujinx.Graphics.Gpu/State/RtFormat.cs new file mode 100644 index 0000000000..ffd2492b64 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/RtFormat.cs @@ -0,0 +1,148 @@ +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Image; + +namespace Ryujinx.Graphics.Gpu.State +{ + /// + /// Render target buffer texture format. + /// + enum RtFormat + { + D32Float = 0xa, + D16Unorm = 0x13, + D24UnormS8Uint = 0x14, + D24Unorm = 0x15, + S8UintD24Unorm = 0x16, + S8Uint = 0x17, + D32FloatS8Uint = 0x19, + R32G32B32A32Float = 0xc0, + R32G32B32A32Sint = 0xc1, + R32G32B32A32Uint = 0xc2, + R32G32B32X32Float = 0xc3, + R32G32B32X32Sint = 0xc4, + R32G32B32X32Uint = 0xc5, + R16G16B16X16Unorm = 0xc6, + R16G16B16X16Snorm = 0xc7, + R16G16B16X16Sint = 0xc8, + R16G16B16X16Uint = 0xc9, + R16G16B16A16Float = 0xca, + R32G32Float = 0xcb, + R32G32Sint = 0xcc, + R32G32Uint = 0xcd, + R16G16B16X16Float = 0xce, + B8G8R8A8Unorm = 0xcf, + B8G8R8A8Srgb = 0xd0, + R10G10B10A2Unorm = 0xd1, + R10G10B10A2Uint = 0xd2, + R8G8B8A8Unorm = 0xd5, + R8G8B8A8Srgb = 0xd6, + R8G8B8X8Snorm = 0xd7, + R8G8B8X8Sint = 0xd8, + R8G8B8X8Uint = 0xd9, + R16G16Unorm = 0xda, + R16G16Snorm = 0xdb, + R16G16Sint = 0xdc, + R16G16Uint = 0xdd, + R16G16Float = 0xde, + R11G11B10Float = 0xe0, + R32Sint = 0xe3, + R32Uint = 0xe4, + R32Float = 0xe5, + B8G8R8X8Unorm = 0xe6, + B8G8R8X8Srgb = 0xe7, + B5G6R5Unorm = 0xe8, + B5G5R5A1Unorm = 0xe9, + R8G8Unorm = 0xea, + R8G8Snorm = 0xeb, + R8G8Sint = 0xec, + R8G8Uint = 0xed, + R16Unorm = 0xee, + R16Snorm = 0xef, + R16Sint = 0xf0, + R16Uint = 0xf1, + R16Float = 0xf2, + R8Unorm = 0xf3, + R8Snorm = 0xf4, + R8Sint = 0xf5, + R8Uint = 0xf6, + B5G5R5X1Unorm = 0xf8, + R8G8B8X8Unorm = 0xf9, + R8G8B8X8Srgb = 0xfa + } + + static class RtFormatConverter + { + /// + /// Converts the render target buffer texture format to a host compatible format. + /// + /// Render target format + /// Host compatible format enum value + public static FormatInfo Convert(this RtFormat format) + { + return format switch + { + RtFormat.D32Float => new FormatInfo(Format.D32Float, 1, 1, 4), + RtFormat.D16Unorm => new FormatInfo(Format.D16Unorm, 1, 1, 2), + RtFormat.D24UnormS8Uint => new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4), + RtFormat.D24Unorm => new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4), + RtFormat.S8UintD24Unorm => new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4), + RtFormat.S8Uint => new FormatInfo(Format.S8Uint, 1, 1, 1), + RtFormat.D32FloatS8Uint => new FormatInfo(Format.D32FloatS8Uint, 1, 1, 8), + RtFormat.R32G32B32A32Float => new FormatInfo(Format.R32G32B32A32Float, 1, 1, 16), + RtFormat.R32G32B32A32Sint => new FormatInfo(Format.R32G32B32A32Sint, 1, 1, 16), + RtFormat.R32G32B32A32Uint => new FormatInfo(Format.R32G32B32A32Uint, 1, 1, 16), + RtFormat.R32G32B32X32Float => new FormatInfo(Format.R32G32B32A32Float, 1, 1, 16), + RtFormat.R32G32B32X32Sint => new FormatInfo(Format.R32G32B32A32Sint, 1, 1, 16), + RtFormat.R32G32B32X32Uint => new FormatInfo(Format.R32G32B32A32Uint, 1, 1, 16), + RtFormat.R16G16B16X16Unorm => new FormatInfo(Format.R16G16B16A16Unorm, 1, 1, 8), + RtFormat.R16G16B16X16Snorm => new FormatInfo(Format.R16G16B16A16Snorm, 1, 1, 8), + RtFormat.R16G16B16X16Sint => new FormatInfo(Format.R16G16B16A16Sint, 1, 1, 8), + RtFormat.R16G16B16X16Uint => new FormatInfo(Format.R16G16B16A16Uint, 1, 1, 8), + RtFormat.R16G16B16A16Float => new FormatInfo(Format.R16G16B16A16Float, 1, 1, 8), + RtFormat.R32G32Float => new FormatInfo(Format.R32G32Float, 1, 1, 8), + RtFormat.R32G32Sint => new FormatInfo(Format.R32G32Sint, 1, 1, 8), + RtFormat.R32G32Uint => new FormatInfo(Format.R32G32Uint, 1, 1, 8), + RtFormat.R16G16B16X16Float => new FormatInfo(Format.R16G16B16A16Float, 1, 1, 8), + RtFormat.B8G8R8A8Unorm => new FormatInfo(Format.B8G8R8A8Unorm, 1, 1, 4), + RtFormat.B8G8R8A8Srgb => new FormatInfo(Format.B8G8R8A8Srgb, 1, 1, 4), + RtFormat.R10G10B10A2Unorm => new FormatInfo(Format.R10G10B10A2Unorm, 1, 1, 4), + RtFormat.R10G10B10A2Uint => new FormatInfo(Format.R10G10B10A2Uint, 1, 1, 4), + RtFormat.R8G8B8A8Unorm => new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4), + RtFormat.R8G8B8A8Srgb => new FormatInfo(Format.R8G8B8A8Srgb, 1, 1, 4), + RtFormat.R8G8B8X8Snorm => new FormatInfo(Format.R8G8B8A8Snorm, 1, 1, 4), + RtFormat.R8G8B8X8Sint => new FormatInfo(Format.R8G8B8A8Sint, 1, 1, 4), + RtFormat.R8G8B8X8Uint => new FormatInfo(Format.R8G8B8A8Uint, 1, 1, 4), + RtFormat.R16G16Unorm => new FormatInfo(Format.R16G16Unorm, 1, 1, 4), + RtFormat.R16G16Snorm => new FormatInfo(Format.R16G16Snorm, 1, 1, 4), + RtFormat.R16G16Sint => new FormatInfo(Format.R16G16Sint, 1, 1, 4), + RtFormat.R16G16Uint => new FormatInfo(Format.R16G16Uint, 1, 1, 4), + RtFormat.R16G16Float => new FormatInfo(Format.R16G16Float, 1, 1, 4), + RtFormat.R11G11B10Float => new FormatInfo(Format.R11G11B10Float, 1, 1, 4), + RtFormat.R32Sint => new FormatInfo(Format.R32Sint, 1, 1, 4), + RtFormat.R32Uint => new FormatInfo(Format.R32Uint, 1, 1, 4), + RtFormat.R32Float => new FormatInfo(Format.R32Float, 1, 1, 4), + RtFormat.B8G8R8X8Unorm => new FormatInfo(Format.B8G8R8A8Unorm, 1, 1, 4), + RtFormat.B8G8R8X8Srgb => new FormatInfo(Format.B8G8R8A8Srgb, 1, 1, 4), + RtFormat.B5G6R5Unorm => new FormatInfo(Format.B5G6R5Unorm, 1, 1, 2), + RtFormat.B5G5R5A1Unorm => new FormatInfo(Format.B5G5R5A1Unorm, 1, 1, 2), + RtFormat.R8G8Unorm => new FormatInfo(Format.R8G8Unorm, 1, 1, 2), + RtFormat.R8G8Snorm => new FormatInfo(Format.R8G8Snorm, 1, 1, 2), + RtFormat.R8G8Sint => new FormatInfo(Format.R8G8Sint, 1, 1, 2), + RtFormat.R8G8Uint => new FormatInfo(Format.R8G8Uint, 1, 1, 2), + RtFormat.R16Unorm => new FormatInfo(Format.R16Unorm, 1, 1, 2), + RtFormat.R16Snorm => new FormatInfo(Format.R16Snorm, 1, 1, 2), + RtFormat.R16Sint => new FormatInfo(Format.R16Sint, 1, 1, 2), + RtFormat.R16Uint => new FormatInfo(Format.R16Uint, 1, 1, 2), + RtFormat.R16Float => new FormatInfo(Format.R16Float, 1, 1, 2), + RtFormat.R8Unorm => new FormatInfo(Format.R8Unorm, 1, 1, 1), + RtFormat.R8Snorm => new FormatInfo(Format.R8Snorm, 1, 1, 1), + RtFormat.R8Sint => new FormatInfo(Format.R8Sint, 1, 1, 1), + RtFormat.R8Uint => new FormatInfo(Format.R8Uint, 1, 1, 1), + RtFormat.B5G5R5X1Unorm => new FormatInfo(Format.B5G5R5X1Unorm, 1, 1, 2), + RtFormat.R8G8B8X8Unorm => new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4), + RtFormat.R8G8B8X8Srgb => new FormatInfo(Format.R8G8B8A8Srgb, 1, 1, 4), + _ => FormatInfo.Default + }; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/State/SamplerIndex.cs b/Ryujinx.Graphics.Gpu/State/SamplerIndex.cs new file mode 100644 index 0000000000..c2aaff43f1 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/SamplerIndex.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.Graphics.Gpu.State +{ + /// + /// Sampler pool indexing mode. + /// + enum SamplerIndex + { + Independently = 0, + ViaHeaderIndex = 1 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/State/ShaderState.cs b/Ryujinx.Graphics.Gpu/State/ShaderState.cs new file mode 100644 index 0000000000..62c7ed4dab --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/ShaderState.cs @@ -0,0 +1,35 @@ +namespace Ryujinx.Graphics.Gpu.State +{ + /// + /// Graphics shader stage state. + /// + struct ShaderState + { + public uint Control; + public uint Offset; + public uint Unknown0x8; + public int MaxRegisters; + public ShaderType Type; + public uint Unknown0x14; + public uint Unknown0x18; + public uint Unknown0x1c; + public uint Unknown0x20; + public uint Unknown0x24; + public uint Unknown0x28; + public uint Unknown0x2c; + public uint Unknown0x30; + public uint Unknown0x34; + public uint Unknown0x38; + public uint Unknown0x3c; + + /// + /// Unpacks shader enable information. + /// Must be ignored for vertex shaders, those are always enabled. + /// + /// True if the stage is enabled, false otherwise + public bool UnpackEnable() + { + return (Control & 1) != 0; + } + } +} diff --git a/Ryujinx.Graphics.Gpu/State/ShaderType.cs b/Ryujinx.Graphics.Gpu/State/ShaderType.cs new file mode 100644 index 0000000000..58506821de --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/ShaderType.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.Graphics.Gpu.State +{ + /// + /// Shader stage name. + /// + enum ShaderType + { + Vertex, + TessellationControl, + TessellationEvaluation, + Geometry, + Fragment + } +} diff --git a/Ryujinx.Graphics.Gpu/State/Size3D.cs b/Ryujinx.Graphics.Gpu/State/Size3D.cs new file mode 100644 index 0000000000..0fa3314a3d --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/Size3D.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.Gpu.State +{ + /// + /// 3D, 2D or 1D texture size. + /// + struct Size3D + { + public int Width; + public int Height; + public int Depth; + } +} diff --git a/Ryujinx.Graphics.Gpu/State/StencilBackMasks.cs b/Ryujinx.Graphics.Gpu/State/StencilBackMasks.cs new file mode 100644 index 0000000000..f9ee613f4c --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/StencilBackMasks.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.Gpu.State +{ + /// + /// Stencil test masks for back tests. + /// + struct StencilBackMasks + { + public int FuncRef; + public int Mask; + public int FuncMask; + } +} diff --git a/Ryujinx.Graphics.Gpu/State/StencilBackTestState.cs b/Ryujinx.Graphics.Gpu/State/StencilBackTestState.cs new file mode 100644 index 0000000000..0169b5e777 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/StencilBackTestState.cs @@ -0,0 +1,16 @@ +using Ryujinx.Graphics.GAL; + +namespace Ryujinx.Graphics.Gpu.State +{ + /// + /// Stencil back test state. + /// + struct StencilBackTestState + { + public Boolean32 TwoSided; + public StencilOp BackSFail; + public StencilOp BackDpFail; + public StencilOp BackDpPass; + public CompareOp BackFunc; + } +} diff --git a/Ryujinx.Graphics.Gpu/State/StencilTestState.cs b/Ryujinx.Graphics.Gpu/State/StencilTestState.cs new file mode 100644 index 0000000000..a50de6e6c6 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/StencilTestState.cs @@ -0,0 +1,19 @@ +using Ryujinx.Graphics.GAL; + +namespace Ryujinx.Graphics.Gpu.State +{ + /// + /// Stencil front test state and masks. + /// + struct StencilTestState + { + public Boolean32 Enable; + public StencilOp FrontSFail; + public StencilOp FrontDpFail; + public StencilOp FrontDpPass; + public CompareOp FrontFunc; + public int FrontFuncRef; + public int FrontFuncMask; + public int FrontMask; + } +} diff --git a/Ryujinx.Graphics.Gpu/State/UniformBufferState.cs b/Ryujinx.Graphics.Gpu/State/UniformBufferState.cs new file mode 100644 index 0000000000..15fe556e9d --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/UniformBufferState.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.Gpu.State +{ + /// + /// Uniform buffer state for the uniform buffer currently being modified. + /// + struct UniformBufferState + { + public int Size; + public GpuVa Address; + public int Offset; + } +} diff --git a/Ryujinx.Graphics.Gpu/State/VertexAttribState.cs b/Ryujinx.Graphics.Gpu/State/VertexAttribState.cs new file mode 100644 index 0000000000..897da79753 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/VertexAttribState.cs @@ -0,0 +1,37 @@ +namespace Ryujinx.Graphics.Gpu.State +{ + /// + /// Vertex buffer attribute state. + /// + struct VertexAttribState + { + public uint Attribute; + + /// + /// Unpacks the index of the vertex buffer this attribute belongs to. + /// + /// Vertex buffer index + public int UnpackBufferIndex() + { + return (int)(Attribute & 0x1f); + } + + /// + /// Unpacks the offset, in bytes, of the attribute on the vertex buffer. + /// + /// Attribute offset in bytes + public int UnpackOffset() + { + return (int)((Attribute >> 7) & 0x3fff); + } + + /// + /// Unpacks the Maxwell attribute format integer. + /// + /// Attribute format integer + public uint UnpackFormat() + { + return Attribute & 0x3fe00000; + } + } +} diff --git a/Ryujinx.Graphics.Gpu/State/VertexBufferDrawState.cs b/Ryujinx.Graphics.Gpu/State/VertexBufferDrawState.cs new file mode 100644 index 0000000000..b1c9a087aa --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/VertexBufferDrawState.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.Graphics.Gpu.State +{ + /// + /// Draw state for non-indexed draws. + /// + struct VertexBufferDrawState + { + public int First; + public int Count; + } +} diff --git a/Ryujinx.Graphics.Gpu/State/VertexBufferState.cs b/Ryujinx.Graphics.Gpu/State/VertexBufferState.cs new file mode 100644 index 0000000000..e514f2a90a --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/VertexBufferState.cs @@ -0,0 +1,30 @@ +namespace Ryujinx.Graphics.Gpu.State +{ + /// + /// Vertex buffer state. + /// + struct VertexBufferState + { + public uint Control; + public GpuVa Address; + public int Divisor; + + /// + /// Vertex buffer stride, defined as the number of bytes occupied by each vertex in memory. + /// + /// Vertex buffer stride + public int UnpackStride() + { + return (int)(Control & 0xfff); + } + + /// + /// Vertex buffer enable. + /// + /// True if the vertex buffer is enabled, false otherwise + public bool UnpackEnable() + { + return (Control & (1 << 12)) != 0; + } + } +} diff --git a/Ryujinx.Graphics.Gpu/State/ViewportExtents.cs b/Ryujinx.Graphics.Gpu/State/ViewportExtents.cs new file mode 100644 index 0000000000..6675c909b9 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/ViewportExtents.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.Graphics.Gpu.State +{ + /// + /// Viewport extents for viewport clipping, also includes depth range. + /// + struct ViewportExtents + { + public ushort X; + public ushort Width; + public ushort Y; + public ushort Height; + public float DepthNear; + public float DepthFar; + } +} diff --git a/Ryujinx.Graphics.Gpu/State/ViewportTransform.cs b/Ryujinx.Graphics.Gpu/State/ViewportTransform.cs new file mode 100644 index 0000000000..c7db311de9 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/State/ViewportTransform.cs @@ -0,0 +1,55 @@ +using Ryujinx.Graphics.GAL; + +namespace Ryujinx.Graphics.Gpu.State +{ + /// + /// Viewport transform parameters, for viewport transformation. + /// + struct ViewportTransform + { + public float ScaleX; + public float ScaleY; + public float ScaleZ; + public float TranslateX; + public float TranslateY; + public float TranslateZ; + public uint Swizzle; + public uint SubpixelPrecisionBias; + + /// + /// Unpacks viewport swizzle of the position X component. + /// + /// Swizzle enum value + public ViewportSwizzle UnpackSwizzleX() + { + return (ViewportSwizzle)(Swizzle & 7); + } + + /// + /// Unpacks viewport swizzle of the position Y component. + /// + /// Swizzle enum value + public ViewportSwizzle UnpackSwizzleY() + { + return (ViewportSwizzle)((Swizzle >> 4) & 7); + } + + /// + /// Unpacks viewport swizzle of the position Z component. + /// + /// Swizzle enum value + public ViewportSwizzle UnpackSwizzleZ() + { + return (ViewportSwizzle)((Swizzle >> 8) & 7); + } + + /// + /// Unpacks viewport swizzle of the position W component. + /// + /// Swizzle enum value + public ViewportSwizzle UnpackSwizzleW() + { + return (ViewportSwizzle)((Swizzle >> 12) & 7); + } + } +} diff --git a/Ryujinx.Graphics.Gpu/Window.cs b/Ryujinx.Graphics.Gpu/Window.cs new file mode 100644 index 0000000000..29c3624866 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Window.cs @@ -0,0 +1,149 @@ +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Image; +using System; +using System.Collections.Concurrent; + +namespace Ryujinx.Graphics.Gpu +{ + using Texture = Image.Texture; + + /// + /// GPU image presentation window. + /// + public class Window + { + private readonly GpuContext _context; + + /// + /// Texture presented on the window. + /// + private struct PresentationTexture + { + /// + /// Texture information. + /// + public TextureInfo Info { get; } + + /// + /// Texture crop region. + /// + public ImageCrop Crop { get; } + + /// + /// Texture release callback. + /// + public Action Callback { get; } + + /// + /// User defined object, passed to the release callback. + /// + public object UserObj { get; } + + /// + /// Creates a new instance of the presentation texture. + /// + /// Information of the texture to be presented + /// Texture crop region + /// Texture release callback + /// User defined object passed to the release callback, can be used to identify the texture + public PresentationTexture( + TextureInfo info, + ImageCrop crop, + Action callback, + object userObj) + { + Info = info; + Crop = crop; + Callback = callback; + UserObj = userObj; + } + } + + private readonly ConcurrentQueue _frameQueue; + + /// + /// Creates a new instance of the GPU presentation window. + /// + /// GPU emulation context + public Window(GpuContext context) + { + _context = context; + + _frameQueue = new ConcurrentQueue(); + } + + /// + /// Enqueues a frame for presentation. + /// This method is thread safe and can be called from any thread. + /// When the texture is presented and not needed anymore, the release callback is called. + /// It's an error to modify the texture after calling this method, before the release callback is called. + /// + /// CPU virtual address of the texture data + /// Texture width + /// Texture height + /// Texture stride for linear texture, should be zero otherwise + /// Indicates if the texture is linear, normally false + /// GOB blocks in the Y direction, for block linear textures + /// Texture format + /// Texture format bytes per pixel (must match the format) + /// Texture crop region + /// Texture release callback + /// User defined object passed to the release callback + public void EnqueueFrameThreadSafe( + ulong address, + int width, + int height, + int stride, + bool isLinear, + int gobBlocksInY, + Format format, + int bytesPerPixel, + ImageCrop crop, + Action callback, + object userObj) + { + FormatInfo formatInfo = new FormatInfo(format, 1, 1, bytesPerPixel); + + TextureInfo info = new TextureInfo( + address, + width, + height, + 1, + 1, + 1, + 1, + stride, + isLinear, + gobBlocksInY, + 1, + 1, + Target.Texture2D, + formatInfo); + + _frameQueue.Enqueue(new PresentationTexture(info, crop, callback, userObj)); + } + + /// + /// Presents a texture on the queue. + /// If the queue is empty, then no texture is presented. + /// + /// Callback method to call when a new texture should be presented on the screen + public void Present(Action swapBuffersCallback) + { + _context.AdvanceSequence(); + + if (_frameQueue.TryDequeue(out PresentationTexture pt)) + { + Texture texture = _context.Methods.TextureManager.FindOrCreateTexture(pt.Info); + + texture.SynchronizeMemory(); + + _context.Renderer.Window.Present(texture.HostTexture, pt.Crop); + + swapBuffersCallback(); + + pt.Callback(pt.UserObj); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Nvdec/CdmaProcessor.cs b/Ryujinx.Graphics.Nvdec/CdmaProcessor.cs new file mode 100644 index 0000000000..c54a95f9b0 --- /dev/null +++ b/Ryujinx.Graphics.Nvdec/CdmaProcessor.cs @@ -0,0 +1,103 @@ +using Ryujinx.Graphics.Gpu; +using Ryujinx.Graphics.VDec; +using Ryujinx.Graphics.Vic; +using System.Collections.Generic; + +namespace Ryujinx.Graphics +{ + public class CdmaProcessor + { + private const int MethSetMethod = 0x10; + private const int MethSetData = 0x11; + + private readonly VideoDecoder _videoDecoder; + private readonly VideoImageComposer _videoImageComposer; + + public CdmaProcessor() + { + _videoDecoder = new VideoDecoder(); + _videoImageComposer = new VideoImageComposer(_videoDecoder); + } + + public void PushCommands(GpuContext gpu, int[] cmdBuffer) + { + List commands = new List(); + + ChClassId currentClass = 0; + + for (int index = 0; index < cmdBuffer.Length; index++) + { + int cmd = cmdBuffer[index]; + + int value = (cmd >> 0) & 0xffff; + int methodOffset = (cmd >> 16) & 0xfff; + + ChSubmissionMode submissionMode = (ChSubmissionMode)((cmd >> 28) & 0xf); + + switch (submissionMode) + { + case ChSubmissionMode.SetClass: currentClass = (ChClassId)(value >> 6); break; + + case ChSubmissionMode.Incrementing: + { + int count = value; + + for (int argIdx = 0; argIdx < count; argIdx++) + { + int argument = cmdBuffer[++index]; + + commands.Add(new ChCommand(currentClass, methodOffset + argIdx, argument)); + } + + break; + } + + case ChSubmissionMode.NonIncrementing: + { + int count = value; + + int[] arguments = new int[count]; + + for (int argIdx = 0; argIdx < count; argIdx++) + { + arguments[argIdx] = cmdBuffer[++index]; + } + + commands.Add(new ChCommand(currentClass, methodOffset, arguments)); + + break; + } + } + } + + ProcessCommands(gpu, commands.ToArray()); + } + + private void ProcessCommands(GpuContext gpu, ChCommand[] commands) + { + int methodOffset = 0; + + foreach (ChCommand command in commands) + { + switch (command.MethodOffset) + { + case MethSetMethod: methodOffset = command.Arguments[0]; break; + + case MethSetData: + { + if (command.ClassId == ChClassId.NvDec) + { + _videoDecoder.Process(gpu, methodOffset, command.Arguments); + } + else if (command.ClassId == ChClassId.GraphicsVic) + { + _videoImageComposer.Process(gpu, methodOffset, command.Arguments); + } + + break; + } + } + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Nvdec/ChClassId.cs b/Ryujinx.Graphics.Nvdec/ChClassId.cs new file mode 100644 index 0000000000..115f0b89c3 --- /dev/null +++ b/Ryujinx.Graphics.Nvdec/ChClassId.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.Graphics +{ + enum ChClassId + { + Host1X = 0x1, + VideoEncodeMpeg = 0x20, + VideoEncodeNvEnc = 0x21, + VideoStreamingVi = 0x30, + VideoStreamingIsp = 0x32, + VideoStreamingIspB = 0x34, + VideoStreamingViI2c = 0x36, + GraphicsVic = 0x5d, + Graphics3D = 0x60, + GraphicsGpu = 0x61, + Tsec = 0xe0, + TsecB = 0xe1, + NvJpg = 0xc0, + NvDec = 0xf0 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Nvdec/ChCommandEntry.cs b/Ryujinx.Graphics.Nvdec/ChCommandEntry.cs new file mode 100644 index 0000000000..b01b77eda5 --- /dev/null +++ b/Ryujinx.Graphics.Nvdec/ChCommandEntry.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Graphics +{ + struct ChCommand + { + public ChClassId ClassId { get; private set; } + + public int MethodOffset { get; private set; } + + public int[] Arguments { get; private set; } + + public ChCommand(ChClassId classId, int methodOffset, params int[] arguments) + { + ClassId = classId; + MethodOffset = methodOffset; + Arguments = arguments; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Nvdec/ChSubmissionMode.cs b/Ryujinx.Graphics.Nvdec/ChSubmissionMode.cs new file mode 100644 index 0000000000..5c65301961 --- /dev/null +++ b/Ryujinx.Graphics.Nvdec/ChSubmissionMode.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.Graphics +{ + enum ChSubmissionMode + { + SetClass = 0, + Incrementing = 1, + NonIncrementing = 2, + Mask = 3, + Immediate = 4, + Restart = 5, + Gather = 6 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Nvdec/Ryujinx.Graphics.Nvdec.csproj b/Ryujinx.Graphics.Nvdec/Ryujinx.Graphics.Nvdec.csproj new file mode 100644 index 0000000000..63289e53d4 --- /dev/null +++ b/Ryujinx.Graphics.Nvdec/Ryujinx.Graphics.Nvdec.csproj @@ -0,0 +1,24 @@ + + + + netcoreapp3.0 + win-x64;osx-x64;linux-x64 + + + + true + + + + true + + + + + + + + + + + diff --git a/Ryujinx.Graphics.Nvdec/VDec/BitStreamWriter.cs b/Ryujinx.Graphics.Nvdec/VDec/BitStreamWriter.cs new file mode 100644 index 0000000000..db2d39e593 --- /dev/null +++ b/Ryujinx.Graphics.Nvdec/VDec/BitStreamWriter.cs @@ -0,0 +1,75 @@ +using System.IO; + +namespace Ryujinx.Graphics.VDec +{ + class BitStreamWriter + { + private const int BufferSize = 8; + + private Stream _baseStream; + + private int _buffer; + private int _bufferPos; + + public BitStreamWriter(Stream baseStream) + { + _baseStream = baseStream; + } + + public void WriteBit(bool value) + { + WriteBits(value ? 1 : 0, 1); + } + + public void WriteBits(int value, int valueSize) + { + int valuePos = 0; + + int remaining = valueSize; + + while (remaining > 0) + { + int copySize = remaining; + + int free = GetFreeBufferBits(); + + if (copySize > free) + { + copySize = free; + } + + int mask = (1 << copySize) - 1; + + int srcShift = (valueSize - valuePos) - copySize; + int dstShift = (BufferSize - _bufferPos) - copySize; + + _buffer |= ((value >> srcShift) & mask) << dstShift; + + valuePos += copySize; + _bufferPos += copySize; + remaining -= copySize; + } + } + + private int GetFreeBufferBits() + { + if (_bufferPos == BufferSize) + { + Flush(); + } + + return BufferSize - _bufferPos; + } + + public void Flush() + { + if (_bufferPos != 0) + { + _baseStream.WriteByte((byte)_buffer); + + _buffer = 0; + _bufferPos = 0; + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Nvdec/VDec/DecoderHelper.cs b/Ryujinx.Graphics.Nvdec/VDec/DecoderHelper.cs new file mode 100644 index 0000000000..4f17d8d109 --- /dev/null +++ b/Ryujinx.Graphics.Nvdec/VDec/DecoderHelper.cs @@ -0,0 +1,17 @@ +using System; + +namespace Ryujinx.Graphics.VDec +{ + static class DecoderHelper + { + public static byte[] Combine(byte[] arr0, byte[] arr1) + { + byte[] output = new byte[arr0.Length + arr1.Length]; + + Buffer.BlockCopy(arr0, 0, output, 0, arr0.Length); + Buffer.BlockCopy(arr1, 0, output, arr0.Length, arr1.Length); + + return output; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Nvdec/VDec/FFmpeg.cs b/Ryujinx.Graphics.Nvdec/VDec/FFmpeg.cs new file mode 100644 index 0000000000..ccd01f0d3d --- /dev/null +++ b/Ryujinx.Graphics.Nvdec/VDec/FFmpeg.cs @@ -0,0 +1,168 @@ +using FFmpeg.AutoGen; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.VDec +{ + static unsafe class FFmpegWrapper + { + private static AVCodec* _codec; + private static AVCodecContext* _context; + private static AVFrame* _frame; + private static SwsContext* _scalerCtx; + + private static int _scalerWidth; + private static int _scalerHeight; + + public static bool IsInitialized { get; private set; } + + public static void H264Initialize() + { + EnsureCodecInitialized(AVCodecID.AV_CODEC_ID_H264); + } + + public static void Vp9Initialize() + { + EnsureCodecInitialized(AVCodecID.AV_CODEC_ID_VP9); + } + + private static void EnsureCodecInitialized(AVCodecID codecId) + { + if (IsInitialized) + { + Uninitialize(); + } + + _codec = ffmpeg.avcodec_find_decoder(codecId); + _context = ffmpeg.avcodec_alloc_context3(_codec); + _frame = ffmpeg.av_frame_alloc(); + + ffmpeg.avcodec_open2(_context, _codec, null); + + IsInitialized = true; + } + + public static int DecodeFrame(byte[] data) + { + if (!IsInitialized) + { + throw new InvalidOperationException("Tried to use uninitialized codec!"); + } + + AVPacket packet; + + ffmpeg.av_init_packet(&packet); + + fixed (byte* ptr = data) + { + packet.data = ptr; + packet.size = data.Length; + + ffmpeg.avcodec_send_packet(_context, &packet); + } + + return ffmpeg.avcodec_receive_frame(_context, _frame); + } + + public static FFmpegFrame GetFrame() + { + if (!IsInitialized) + { + throw new InvalidOperationException("Tried to use uninitialized codec!"); + } + + AVFrame managedFrame = Marshal.PtrToStructure((IntPtr)_frame); + + byte*[] data = managedFrame.data.ToArray(); + + return new FFmpegFrame() + { + Width = managedFrame.width, + Height = managedFrame.height, + + LumaPtr = data[0], + ChromaBPtr = data[1], + ChromaRPtr = data[2] + }; + } + + public static FFmpegFrame GetFrameRgba() + { + if (!IsInitialized) + { + throw new InvalidOperationException("Tried to use uninitialized codec!"); + } + + AVFrame managedFrame = Marshal.PtrToStructure((IntPtr)_frame); + + EnsureScalerSetup(managedFrame.width, managedFrame.height); + + byte*[] data = managedFrame.data.ToArray(); + + int[] lineSizes = managedFrame.linesize.ToArray(); + + byte[] dst = new byte[managedFrame.width * managedFrame.height * 4]; + + fixed (byte* ptr = dst) + { + byte*[] dstData = new byte*[] { ptr }; + + int[] dstLineSizes = new int[] { managedFrame.width * 4 }; + + ffmpeg.sws_scale(_scalerCtx, data, lineSizes, 0, managedFrame.height, dstData, dstLineSizes); + } + + return new FFmpegFrame() + { + Width = managedFrame.width, + Height = managedFrame.height, + + Data = dst + }; + } + + private static void EnsureScalerSetup(int width, int height) + { + if (width == 0 || height == 0) + { + return; + } + + if (_scalerCtx == null || _scalerWidth != width || _scalerHeight != height) + { + FreeScaler(); + + _scalerCtx = ffmpeg.sws_getContext( + width, height, AVPixelFormat.AV_PIX_FMT_YUV420P, + width, height, AVPixelFormat.AV_PIX_FMT_RGBA, 0, null, null, null); + + _scalerWidth = width; + _scalerHeight = height; + } + } + + public static void Uninitialize() + { + if (IsInitialized) + { + ffmpeg.av_frame_unref(_frame); + ffmpeg.av_free(_frame); + ffmpeg.avcodec_close(_context); + + FreeScaler(); + + IsInitialized = false; + } + } + + private static void FreeScaler() + { + if (_scalerCtx != null) + { + ffmpeg.sws_freeContext(_scalerCtx); + + _scalerCtx = null; + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Nvdec/VDec/FFmpegFrame.cs b/Ryujinx.Graphics.Nvdec/VDec/FFmpegFrame.cs new file mode 100644 index 0000000000..535a70c94e --- /dev/null +++ b/Ryujinx.Graphics.Nvdec/VDec/FFmpegFrame.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.Graphics.VDec +{ + unsafe struct FFmpegFrame + { + public int Width; + public int Height; + + public byte* LumaPtr; + public byte* ChromaBPtr; + public byte* ChromaRPtr; + + public byte[] Data; + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Nvdec/VDec/H264BitStreamWriter.cs b/Ryujinx.Graphics.Nvdec/VDec/H264BitStreamWriter.cs new file mode 100644 index 0000000000..b4fad59be3 --- /dev/null +++ b/Ryujinx.Graphics.Nvdec/VDec/H264BitStreamWriter.cs @@ -0,0 +1,79 @@ +using System.IO; + +namespace Ryujinx.Graphics.VDec +{ + class H264BitStreamWriter : BitStreamWriter + { + public H264BitStreamWriter(Stream baseStream) : base(baseStream) { } + + public void WriteU(int value, int valueSize) + { + WriteBits(value, valueSize); + } + + public void WriteSe(int value) + { + WriteExpGolombCodedInt(value); + } + + public void WriteUe(int value) + { + WriteExpGolombCodedUInt((uint)value); + } + + public void End() + { + WriteBit(true); + + Flush(); + } + + private void WriteExpGolombCodedInt(int value) + { + int sign = value <= 0 ? 0 : 1; + + if (value < 0) + { + value = -value; + } + + value = (value << 1) - sign; + + WriteExpGolombCodedUInt((uint)value); + } + + private void WriteExpGolombCodedUInt(uint value) + { + int size = 32 - CountLeadingZeros((int)value + 1); + + WriteBits(1, size); + + value -= (1u << (size - 1)) - 1; + + WriteBits((int)value, size - 1); + } + + private static readonly byte[] ClzNibbleTbl = { 4, 3, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0 }; + + private static int CountLeadingZeros(int value) + { + if (value == 0) + { + return 32; + } + + int nibbleIdx = 32; + int preCount, count = 0; + + do + { + nibbleIdx -= 4; + preCount = ClzNibbleTbl[(value >> nibbleIdx) & 0b1111]; + count += preCount; + } + while (preCount == 4); + + return count; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Nvdec/VDec/H264Decoder.cs b/Ryujinx.Graphics.Nvdec/VDec/H264Decoder.cs new file mode 100644 index 0000000000..24c7e0b927 --- /dev/null +++ b/Ryujinx.Graphics.Nvdec/VDec/H264Decoder.cs @@ -0,0 +1,238 @@ +using System.IO; + +namespace Ryujinx.Graphics.VDec +{ + class H264Decoder + { + private int _log2MaxPicOrderCntLsbMinus4; + private bool _deltaPicOrderAlwaysZeroFlag; + private bool _frameMbsOnlyFlag; + private int _picWidthInMbs; + private int _picHeightInMapUnits; + private bool _entropyCodingModeFlag; + private bool _bottomFieldPicOrderInFramePresentFlag; + private int _numRefIdxL0DefaultActiveMinus1; + private int _numRefIdxL1DefaultActiveMinus1; + private bool _deblockingFilterControlPresentFlag; + private bool _redundantPicCntPresentFlag; + private bool _transform8x8ModeFlag; + private bool _mbAdaptiveFrameFieldFlag; + private bool _direct8x8InferenceFlag; + private bool _weightedPredFlag; + private bool _constrainedIntraPredFlag; + private bool _fieldPicFlag; + private bool _bottomFieldFlag; + private int _log2MaxFrameNumMinus4; + private int _chromaFormatIdc; + private int _picOrderCntType; + private int _picInitQpMinus26; + private int _chromaQpIndexOffset; + private int _chromaQpIndexOffset2; + private int _weightedBipredIdc; + private int _frameNumber; + private byte[] _scalingMatrix4; + private byte[] _scalingMatrix8; + + public void Decode(H264ParameterSets Params, H264Matrices matrices, byte[] frameData) + { + _log2MaxPicOrderCntLsbMinus4 = Params.Log2MaxPicOrderCntLsbMinus4; + _deltaPicOrderAlwaysZeroFlag = Params.DeltaPicOrderAlwaysZeroFlag; + _frameMbsOnlyFlag = Params.FrameMbsOnlyFlag; + _picWidthInMbs = Params.PicWidthInMbs; + _picHeightInMapUnits = Params.PicHeightInMapUnits; + _entropyCodingModeFlag = Params.EntropyCodingModeFlag; + _bottomFieldPicOrderInFramePresentFlag = Params.BottomFieldPicOrderInFramePresentFlag; + _numRefIdxL0DefaultActiveMinus1 = Params.NumRefIdxL0DefaultActiveMinus1; + _numRefIdxL1DefaultActiveMinus1 = Params.NumRefIdxL1DefaultActiveMinus1; + _deblockingFilterControlPresentFlag = Params.DeblockingFilterControlPresentFlag; + _redundantPicCntPresentFlag = Params.RedundantPicCntPresentFlag; + _transform8x8ModeFlag = Params.Transform8x8ModeFlag; + + _mbAdaptiveFrameFieldFlag = ((Params.Flags >> 0) & 1) != 0; + _direct8x8InferenceFlag = ((Params.Flags >> 1) & 1) != 0; + _weightedPredFlag = ((Params.Flags >> 2) & 1) != 0; + _constrainedIntraPredFlag = ((Params.Flags >> 3) & 1) != 0; + _fieldPicFlag = ((Params.Flags >> 5) & 1) != 0; + _bottomFieldFlag = ((Params.Flags >> 6) & 1) != 0; + + _log2MaxFrameNumMinus4 = (int)(Params.Flags >> 8) & 0xf; + _chromaFormatIdc = (int)(Params.Flags >> 12) & 0x3; + _picOrderCntType = (int)(Params.Flags >> 14) & 0x3; + _picInitQpMinus26 = (int)(Params.Flags >> 16) & 0x3f; + _chromaQpIndexOffset = (int)(Params.Flags >> 22) & 0x1f; + _chromaQpIndexOffset2 = (int)(Params.Flags >> 27) & 0x1f; + _weightedBipredIdc = (int)(Params.Flags >> 32) & 0x3; + _frameNumber = (int)(Params.Flags >> 46) & 0x1ffff; + + _picInitQpMinus26 = (_picInitQpMinus26 << 26) >> 26; + _chromaQpIndexOffset = (_chromaQpIndexOffset << 27) >> 27; + _chromaQpIndexOffset2 = (_chromaQpIndexOffset2 << 27) >> 27; + + _scalingMatrix4 = matrices.ScalingMatrix4; + _scalingMatrix8 = matrices.ScalingMatrix8; + + if (FFmpegWrapper.IsInitialized) + { + FFmpegWrapper.DecodeFrame(frameData); + } + else + { + FFmpegWrapper.H264Initialize(); + + FFmpegWrapper.DecodeFrame(DecoderHelper.Combine(EncodeHeader(), frameData)); + } + } + + private byte[] EncodeHeader() + { + using (MemoryStream data = new MemoryStream()) + { + H264BitStreamWriter writer = new H264BitStreamWriter(data); + + // Sequence Parameter Set. + writer.WriteU(1, 24); + writer.WriteU(0, 1); + writer.WriteU(3, 2); + writer.WriteU(7, 5); + writer.WriteU(100, 8); + writer.WriteU(0, 8); + writer.WriteU(31, 8); + writer.WriteUe(0); + writer.WriteUe(_chromaFormatIdc); + + if (_chromaFormatIdc == 3) + { + writer.WriteBit(false); + } + + writer.WriteUe(0); + writer.WriteUe(0); + writer.WriteBit(false); + writer.WriteBit(false); //Scaling matrix present flag + + writer.WriteUe(_log2MaxFrameNumMinus4); + writer.WriteUe(_picOrderCntType); + + if (_picOrderCntType == 0) + { + writer.WriteUe(_log2MaxPicOrderCntLsbMinus4); + } + else if (_picOrderCntType == 1) + { + writer.WriteBit(_deltaPicOrderAlwaysZeroFlag); + + writer.WriteSe(0); + writer.WriteSe(0); + writer.WriteUe(0); + } + + int picHeightInMbs = _picHeightInMapUnits / (_frameMbsOnlyFlag ? 1 : 2); + + writer.WriteUe(16); + writer.WriteBit(false); + writer.WriteUe(_picWidthInMbs - 1); + writer.WriteUe(picHeightInMbs - 1); + writer.WriteBit(_frameMbsOnlyFlag); + + if (!_frameMbsOnlyFlag) + { + writer.WriteBit(_mbAdaptiveFrameFieldFlag); + } + + writer.WriteBit(_direct8x8InferenceFlag); + writer.WriteBit(false); //Frame cropping flag + writer.WriteBit(false); //VUI parameter present flag + + writer.End(); + + // Picture Parameter Set. + writer.WriteU(1, 24); + writer.WriteU(0, 1); + writer.WriteU(3, 2); + writer.WriteU(8, 5); + + writer.WriteUe(0); + writer.WriteUe(0); + + writer.WriteBit(_entropyCodingModeFlag); + writer.WriteBit(false); + writer.WriteUe(0); + writer.WriteUe(_numRefIdxL0DefaultActiveMinus1); + writer.WriteUe(_numRefIdxL1DefaultActiveMinus1); + writer.WriteBit(_weightedPredFlag); + writer.WriteU(_weightedBipredIdc, 2); + writer.WriteSe(_picInitQpMinus26); + writer.WriteSe(0); + writer.WriteSe(_chromaQpIndexOffset); + writer.WriteBit(_deblockingFilterControlPresentFlag); + writer.WriteBit(_constrainedIntraPredFlag); + writer.WriteBit(_redundantPicCntPresentFlag); + writer.WriteBit(_transform8x8ModeFlag); + + writer.WriteBit(true); + + for (int index = 0; index < 6; index++) + { + writer.WriteBit(true); + + WriteScalingList(writer, _scalingMatrix4, index * 16, 16); + } + + if (_transform8x8ModeFlag) + { + for (int index = 0; index < 2; index++) + { + writer.WriteBit(true); + + WriteScalingList(writer, _scalingMatrix8, index * 64, 64); + } + } + + writer.WriteSe(_chromaQpIndexOffset2); + + writer.End(); + + return data.ToArray(); + } + } + + // ZigZag LUTs from libavcodec. + private static readonly byte[] ZigZagDirect = new byte[] + { + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63 + }; + + private static readonly byte[] ZigZagScan = new byte[] + { + 0 + 0 * 4, 1 + 0 * 4, 0 + 1 * 4, 0 + 2 * 4, + 1 + 1 * 4, 2 + 0 * 4, 3 + 0 * 4, 2 + 1 * 4, + 1 + 2 * 4, 0 + 3 * 4, 1 + 3 * 4, 2 + 2 * 4, + 3 + 1 * 4, 3 + 2 * 4, 2 + 3 * 4, 3 + 3 * 4 + }; + + private static void WriteScalingList(H264BitStreamWriter writer, byte[] list, int start, int count) + { + byte[] scan = count == 16 ? ZigZagScan : ZigZagDirect; + + int lastScale = 8; + + for (int index = 0; index < count; index++) + { + byte value = list[start + scan[index]]; + + int deltaScale = value - lastScale; + + writer.WriteSe(deltaScale); + + lastScale = value; + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Nvdec/VDec/H264Matrices.cs b/Ryujinx.Graphics.Nvdec/VDec/H264Matrices.cs new file mode 100644 index 0000000000..a1524214f1 --- /dev/null +++ b/Ryujinx.Graphics.Nvdec/VDec/H264Matrices.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Graphics.VDec +{ + struct H264Matrices + { + public byte[] ScalingMatrix4; + public byte[] ScalingMatrix8; + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Nvdec/VDec/H264ParameterSets.cs b/Ryujinx.Graphics.Nvdec/VDec/H264ParameterSets.cs new file mode 100644 index 0000000000..f242f0f245 --- /dev/null +++ b/Ryujinx.Graphics.Nvdec/VDec/H264ParameterSets.cs @@ -0,0 +1,34 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.VDec +{ + [StructLayout(LayoutKind.Sequential, Pack = 4)] + struct H264ParameterSets + { + public int Log2MaxPicOrderCntLsbMinus4; + public bool DeltaPicOrderAlwaysZeroFlag; + public bool FrameMbsOnlyFlag; + public int PicWidthInMbs; + public int PicHeightInMapUnits; + public int Reserved6C; + public bool EntropyCodingModeFlag; + public bool BottomFieldPicOrderInFramePresentFlag; + public int NumRefIdxL0DefaultActiveMinus1; + public int NumRefIdxL1DefaultActiveMinus1; + public bool DeblockingFilterControlPresentFlag; + public bool RedundantPicCntPresentFlag; + public bool Transform8x8ModeFlag; + public int Unknown8C; + public int Unknown90; + public int Reserved94; + public int Unknown98; + public int Reserved9C; + public int ReservedA0; + public int UnknownA4; + public int ReservedA8; + public int UnknownAC; + public long Flags; + public int FrameNumber; + public int FrameNumber2; + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Nvdec/VDec/VideoCodec.cs b/Ryujinx.Graphics.Nvdec/VDec/VideoCodec.cs new file mode 100644 index 0000000000..f031919dc1 --- /dev/null +++ b/Ryujinx.Graphics.Nvdec/VDec/VideoCodec.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Graphics.VDec +{ + enum VideoCodec + { + H264 = 3, + Vp8 = 5, + H265 = 7, + Vp9 = 9 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Nvdec/VDec/VideoDecoder.cs b/Ryujinx.Graphics.Nvdec/VDec/VideoDecoder.cs new file mode 100644 index 0000000000..b4a89d8f3d --- /dev/null +++ b/Ryujinx.Graphics.Nvdec/VDec/VideoDecoder.cs @@ -0,0 +1,266 @@ +using Ryujinx.Graphics.Gpu; +using Ryujinx.Graphics.Vic; +using System; + +namespace Ryujinx.Graphics.VDec +{ + unsafe class VideoDecoder + { + private H264Decoder _h264Decoder; + private Vp9Decoder _vp9Decoder; + + private VideoCodec _currentVideoCodec; + + private ulong _decoderContextAddress; + private ulong _frameDataAddress; + private ulong _vpxCurrLumaAddress; + private ulong _vpxRef0LumaAddress; + private ulong _vpxRef1LumaAddress; + private ulong _vpxRef2LumaAddress; + private ulong _vpxCurrChromaAddress; + private ulong _vpxRef0ChromaAddress; + private ulong _vpxRef1ChromaAddress; + private ulong _vpxRef2ChromaAddress; + private ulong _vpxProbTablesAddress; + + public VideoDecoder() + { + _h264Decoder = new H264Decoder(); + _vp9Decoder = new Vp9Decoder(); + } + + public void Process(GpuContext gpu, int methodOffset, int[] arguments) + { + VideoDecoderMeth method = (VideoDecoderMeth)methodOffset; + + switch (method) + { + case VideoDecoderMeth.SetVideoCodec: SetVideoCodec(arguments); break; + case VideoDecoderMeth.Execute: Execute(gpu); break; + case VideoDecoderMeth.SetDecoderCtxAddr: SetDecoderCtxAddr(arguments); break; + case VideoDecoderMeth.SetFrameDataAddr: SetFrameDataAddr(arguments); break; + case VideoDecoderMeth.SetVpxCurrLumaAddr: SetVpxCurrLumaAddr(arguments); break; + case VideoDecoderMeth.SetVpxRef0LumaAddr: SetVpxRef0LumaAddr(arguments); break; + case VideoDecoderMeth.SetVpxRef1LumaAddr: SetVpxRef1LumaAddr(arguments); break; + case VideoDecoderMeth.SetVpxRef2LumaAddr: SetVpxRef2LumaAddr(arguments); break; + case VideoDecoderMeth.SetVpxCurrChromaAddr: SetVpxCurrChromaAddr(arguments); break; + case VideoDecoderMeth.SetVpxRef0ChromaAddr: SetVpxRef0ChromaAddr(arguments); break; + case VideoDecoderMeth.SetVpxRef1ChromaAddr: SetVpxRef1ChromaAddr(arguments); break; + case VideoDecoderMeth.SetVpxRef2ChromaAddr: SetVpxRef2ChromaAddr(arguments); break; + case VideoDecoderMeth.SetVpxProbTablesAddr: SetVpxProbTablesAddr(arguments); break; + } + } + + private void SetVideoCodec(int[] arguments) + { + _currentVideoCodec = (VideoCodec)arguments[0]; + } + + private void Execute(GpuContext gpu) + { + if (_currentVideoCodec == VideoCodec.H264) + { + int frameDataSize = gpu.MemoryAccessor.ReadInt32(_decoderContextAddress + 0x48); + + H264ParameterSets Params = gpu.MemoryAccessor.Read(_decoderContextAddress + 0x58); + + H264Matrices matrices = new H264Matrices() + { + ScalingMatrix4 = gpu.MemoryAccessor.ReadBytes(_decoderContextAddress + 0x1c0, 6 * 16), + ScalingMatrix8 = gpu.MemoryAccessor.ReadBytes(_decoderContextAddress + 0x220, 2 * 64) + }; + + byte[] frameData = gpu.MemoryAccessor.ReadBytes(_frameDataAddress, (ulong)frameDataSize); + + _h264Decoder.Decode(Params, matrices, frameData); + } + else if (_currentVideoCodec == VideoCodec.Vp9) + { + int frameDataSize = gpu.MemoryAccessor.ReadInt32(_decoderContextAddress + 0x30); + + Vp9FrameKeys keys = new Vp9FrameKeys() + { + CurrKey = (long)gpu.MemoryManager.Translate(_vpxCurrLumaAddress), + Ref0Key = (long)gpu.MemoryManager.Translate(_vpxRef0LumaAddress), + Ref1Key = (long)gpu.MemoryManager.Translate(_vpxRef1LumaAddress), + Ref2Key = (long)gpu.MemoryManager.Translate(_vpxRef2LumaAddress) + }; + + Vp9FrameHeader header = gpu.MemoryAccessor.Read(_decoderContextAddress + 0x48); + + Vp9ProbabilityTables probs = new Vp9ProbabilityTables() + { + SegmentationTreeProbs = gpu.MemoryAccessor.ReadBytes(_vpxProbTablesAddress + 0x387, 0x7), + SegmentationPredProbs = gpu.MemoryAccessor.ReadBytes(_vpxProbTablesAddress + 0x38e, 0x3), + Tx8x8Probs = gpu.MemoryAccessor.ReadBytes(_vpxProbTablesAddress + 0x470, 0x2), + Tx16x16Probs = gpu.MemoryAccessor.ReadBytes(_vpxProbTablesAddress + 0x472, 0x4), + Tx32x32Probs = gpu.MemoryAccessor.ReadBytes(_vpxProbTablesAddress + 0x476, 0x6), + CoefProbs = gpu.MemoryAccessor.ReadBytes(_vpxProbTablesAddress + 0x5a0, 0x900), + SkipProbs = gpu.MemoryAccessor.ReadBytes(_vpxProbTablesAddress + 0x537, 0x3), + InterModeProbs = gpu.MemoryAccessor.ReadBytes(_vpxProbTablesAddress + 0x400, 0x1c), + InterpFilterProbs = gpu.MemoryAccessor.ReadBytes(_vpxProbTablesAddress + 0x52a, 0x8), + IsInterProbs = gpu.MemoryAccessor.ReadBytes(_vpxProbTablesAddress + 0x41c, 0x4), + CompModeProbs = gpu.MemoryAccessor.ReadBytes(_vpxProbTablesAddress + 0x532, 0x5), + SingleRefProbs = gpu.MemoryAccessor.ReadBytes(_vpxProbTablesAddress + 0x580, 0xa), + CompRefProbs = gpu.MemoryAccessor.ReadBytes(_vpxProbTablesAddress + 0x58a, 0x5), + YModeProbs0 = gpu.MemoryAccessor.ReadBytes(_vpxProbTablesAddress + 0x480, 0x20), + YModeProbs1 = gpu.MemoryAccessor.ReadBytes(_vpxProbTablesAddress + 0x47c, 0x4), + PartitionProbs = gpu.MemoryAccessor.ReadBytes(_vpxProbTablesAddress + 0x4e0, 0x40), + MvJointProbs = gpu.MemoryAccessor.ReadBytes(_vpxProbTablesAddress + 0x53b, 0x3), + MvSignProbs = gpu.MemoryAccessor.ReadBytes(_vpxProbTablesAddress + 0x53e, 0x3), + MvClassProbs = gpu.MemoryAccessor.ReadBytes(_vpxProbTablesAddress + 0x54c, 0x14), + MvClass0BitProbs = gpu.MemoryAccessor.ReadBytes(_vpxProbTablesAddress + 0x540, 0x3), + MvBitsProbs = gpu.MemoryAccessor.ReadBytes(_vpxProbTablesAddress + 0x56c, 0x14), + MvClass0FrProbs = gpu.MemoryAccessor.ReadBytes(_vpxProbTablesAddress + 0x560, 0xc), + MvFrProbs = gpu.MemoryAccessor.ReadBytes(_vpxProbTablesAddress + 0x542, 0x6), + MvClass0HpProbs = gpu.MemoryAccessor.ReadBytes(_vpxProbTablesAddress + 0x548, 0x2), + MvHpProbs = gpu.MemoryAccessor.ReadBytes(_vpxProbTablesAddress + 0x54a, 0x2) + }; + + byte[] frameData = gpu.MemoryAccessor.ReadBytes(_frameDataAddress, (ulong)frameDataSize); + + _vp9Decoder.Decode(keys, header, probs, frameData); + } + else + { + ThrowUnimplementedCodec(); + } + } + + private void SetDecoderCtxAddr(int[] arguments) + { + _decoderContextAddress = GetAddress(arguments); + } + + private void SetFrameDataAddr(int[] arguments) + { + _frameDataAddress = GetAddress(arguments); + } + + private void SetVpxCurrLumaAddr(int[] arguments) + { + _vpxCurrLumaAddress = GetAddress(arguments); + } + + private void SetVpxRef0LumaAddr(int[] arguments) + { + _vpxRef0LumaAddress = GetAddress(arguments); + } + + private void SetVpxRef1LumaAddr(int[] arguments) + { + _vpxRef1LumaAddress = GetAddress(arguments); + } + + private void SetVpxRef2LumaAddr(int[] arguments) + { + _vpxRef2LumaAddress = GetAddress(arguments); + } + + private void SetVpxCurrChromaAddr(int[] arguments) + { + _vpxCurrChromaAddress = GetAddress(arguments); + } + + private void SetVpxRef0ChromaAddr(int[] arguments) + { + _vpxRef0ChromaAddress = GetAddress(arguments); + } + + private void SetVpxRef1ChromaAddr(int[] arguments) + { + _vpxRef1ChromaAddress = GetAddress(arguments); + } + + private void SetVpxRef2ChromaAddr(int[] arguments) + { + _vpxRef2ChromaAddress = GetAddress(arguments); + } + + private void SetVpxProbTablesAddr(int[] arguments) + { + _vpxProbTablesAddress = GetAddress(arguments); + } + + private static ulong GetAddress(int[] arguments) + { + return (ulong)(uint)arguments[0] << 8; + } + + internal void CopyPlanes(GpuContext gpu, SurfaceOutputConfig outputConfig) + { + switch (outputConfig.PixelFormat) + { + case SurfacePixelFormat.Rgba8: CopyPlanesRgba8 (gpu, outputConfig); break; + case SurfacePixelFormat.Yuv420P: CopyPlanesYuv420P(gpu, outputConfig); break; + + default: ThrowUnimplementedPixelFormat(outputConfig.PixelFormat); break; + } + } + + private void CopyPlanesRgba8(GpuContext gpu, SurfaceOutputConfig outputConfig) + { + FFmpegFrame frame = FFmpegWrapper.GetFrameRgba(); + + if ((frame.Width | frame.Height) == 0) + { + return; + } + + throw new NotImplementedException(); + } + + private void CopyPlanesYuv420P(GpuContext gpu, SurfaceOutputConfig outputConfig) + { + FFmpegFrame frame = FFmpegWrapper.GetFrame(); + + if ((frame.Width | frame.Height) == 0) + { + return; + } + + int halfSrcWidth = frame.Width / 2; + + int halfWidth = frame.Width / 2; + int halfHeight = frame.Height / 2; + + int alignedWidth = (outputConfig.SurfaceWidth + 0xff) & ~0xff; + + for (int y = 0; y < frame.Height; y++) + { + int src = y * frame.Width; + int dst = y * alignedWidth; + + int size = frame.Width; + + for (int offset = 0; offset < size; offset++) + { + gpu.MemoryAccessor.WriteByte(outputConfig.SurfaceLumaAddress + (ulong)dst + (ulong)offset, *(frame.LumaPtr + src + offset)); + } + } + + // Copy chroma data from both channels with interleaving. + for (int y = 0; y < halfHeight; y++) + { + int src = y * halfSrcWidth; + int dst = y * alignedWidth; + + for (int x = 0; x < halfWidth; x++) + { + gpu.MemoryAccessor.WriteByte(outputConfig.SurfaceChromaUAddress + (ulong)dst + (ulong)x * 2 + 0, *(frame.ChromaBPtr + src + x)); + gpu.MemoryAccessor.WriteByte(outputConfig.SurfaceChromaUAddress + (ulong)dst + (ulong)x * 2 + 1, *(frame.ChromaRPtr + src + x)); + } + } + } + + private void ThrowUnimplementedCodec() + { + throw new NotImplementedException($"Codec \"{_currentVideoCodec}\" is not supported!"); + } + + private void ThrowUnimplementedPixelFormat(SurfacePixelFormat pixelFormat) + { + throw new NotImplementedException($"Pixel format \"{pixelFormat}\" is not supported!"); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Nvdec/VDec/VideoDecoderMeth.cs b/Ryujinx.Graphics.Nvdec/VDec/VideoDecoderMeth.cs new file mode 100644 index 0000000000..12286386a1 --- /dev/null +++ b/Ryujinx.Graphics.Nvdec/VDec/VideoDecoderMeth.cs @@ -0,0 +1,19 @@ +namespace Ryujinx.Graphics.VDec +{ + enum VideoDecoderMeth + { + SetVideoCodec = 0x80, + Execute = 0xc0, + SetDecoderCtxAddr = 0x101, + SetFrameDataAddr = 0x102, + SetVpxRef0LumaAddr = 0x10c, + SetVpxRef1LumaAddr = 0x10d, + SetVpxRef2LumaAddr = 0x10e, + SetVpxCurrLumaAddr = 0x10f, + SetVpxRef0ChromaAddr = 0x11d, + SetVpxRef1ChromaAddr = 0x11e, + SetVpxRef2ChromaAddr = 0x11f, + SetVpxCurrChromaAddr = 0x120, + SetVpxProbTablesAddr = 0x170 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Nvdec/VDec/Vp9Decoder.cs b/Ryujinx.Graphics.Nvdec/VDec/Vp9Decoder.cs new file mode 100644 index 0000000000..b20a40bed7 --- /dev/null +++ b/Ryujinx.Graphics.Nvdec/VDec/Vp9Decoder.cs @@ -0,0 +1,879 @@ +using System.Collections.Generic; +using System.IO; + +namespace Ryujinx.Graphics.VDec +{ + class Vp9Decoder + { + private const int DiffUpdateProbability = 252; + + private const int FrameSyncCode = 0x498342; + + private static readonly int[] MapLut = new int[] + { + 20, 21, 22, 23, 24, 25, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, + 35, 36, 37, 1, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, + 2, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 3, 62, 63, + 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 4, 74, 75, 76, 77, 78, + 79, 80, 81, 82, 83, 84, 85, 5, 86, 87, 88, 89, 90, 91, 92, 93, + 94, 95, 96, 97, 6, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, + 109, 7, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 8, 122, + 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 9, 134, 135, 136, 137, + 138, 139, 140, 141, 142, 143, 144, 145, 10, 146, 147, 148, 149, 150, 151, 152, + 153, 154, 155, 156, 157, 11, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, + 168, 169, 12, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 13, + 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 14, 194, 195, 196, + 197, 198, 199, 200, 201, 202, 203, 204, 205, 15, 206, 207, 208, 209, 210, 211, + 212, 213, 214, 215, 216, 217, 16, 218, 219, 220, 221, 222, 223, 224, 225, 226, + 227, 228, 229, 17, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, + 18, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 19 + }; + + private byte[] DefaultTx8x8Probs = new byte[] { 100, 66 }; + private byte[] DefaultTx16x16Probs = new byte[] { 20, 152, 15, 101 }; + private byte[] DefaultTx32x32Probs = new byte[] { 3, 136, 37, 5, 52, 13 }; + + private byte[] _defaultCoefProbs = new byte[] + { + 195, 29, 183, 0, 84, 49, 136, 0, 8, 42, 71, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 31, 107, 169, 0, 35, 99, 159, 0, + 17, 82, 140, 0, 8, 66, 114, 0, 2, 44, 76, 0, 1, 19, 32, 0, + 40, 132, 201, 0, 29, 114, 187, 0, 13, 91, 157, 0, 7, 75, 127, 0, + 3, 58, 95, 0, 1, 28, 47, 0, 69, 142, 221, 0, 42, 122, 201, 0, + 15, 91, 159, 0, 6, 67, 121, 0, 1, 42, 77, 0, 1, 17, 31, 0, + 102, 148, 228, 0, 67, 117, 204, 0, 17, 82, 154, 0, 6, 59, 114, 0, + 2, 39, 75, 0, 1, 15, 29, 0, 156, 57, 233, 0, 119, 57, 212, 0, + 58, 48, 163, 0, 29, 40, 124, 0, 12, 30, 81, 0, 3, 12, 31, 0, + 191, 107, 226, 0, 124, 117, 204, 0, 25, 99, 155, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 29, 148, 210, 0, 37, 126, 194, 0, + 8, 93, 157, 0, 2, 68, 118, 0, 1, 39, 69, 0, 1, 17, 33, 0, + 41, 151, 213, 0, 27, 123, 193, 0, 3, 82, 144, 0, 1, 58, 105, 0, + 1, 32, 60, 0, 1, 13, 26, 0, 59, 159, 220, 0, 23, 126, 198, 0, + 4, 88, 151, 0, 1, 66, 114, 0, 1, 38, 71, 0, 1, 18, 34, 0, + 114, 136, 232, 0, 51, 114, 207, 0, 11, 83, 155, 0, 3, 56, 105, 0, + 1, 33, 65, 0, 1, 17, 34, 0, 149, 65, 234, 0, 121, 57, 215, 0, + 61, 49, 166, 0, 28, 36, 114, 0, 12, 25, 76, 0, 3, 16, 42, 0, + 214, 49, 220, 0, 132, 63, 188, 0, 42, 65, 137, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 85, 137, 221, 0, 104, 131, 216, 0, + 49, 111, 192, 0, 21, 87, 155, 0, 2, 49, 87, 0, 1, 16, 28, 0, + 89, 163, 230, 0, 90, 137, 220, 0, 29, 100, 183, 0, 10, 70, 135, 0, + 2, 42, 81, 0, 1, 17, 33, 0, 108, 167, 237, 0, 55, 133, 222, 0, + 15, 97, 179, 0, 4, 72, 135, 0, 1, 45, 85, 0, 1, 19, 38, 0, + 124, 146, 240, 0, 66, 124, 224, 0, 17, 88, 175, 0, 4, 58, 122, 0, + 1, 36, 75, 0, 1, 18, 37, 0, 141, 79, 241, 0, 126, 70, 227, 0, + 66, 58, 182, 0, 30, 44, 136, 0, 12, 34, 96, 0, 2, 20, 47, 0, + 229, 99, 249, 0, 143, 111, 235, 0, 46, 109, 192, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 82, 158, 236, 0, 94, 146, 224, 0, + 25, 117, 191, 0, 9, 87, 149, 0, 3, 56, 99, 0, 1, 33, 57, 0, + 83, 167, 237, 0, 68, 145, 222, 0, 10, 103, 177, 0, 2, 72, 131, 0, + 1, 41, 79, 0, 1, 20, 39, 0, 99, 167, 239, 0, 47, 141, 224, 0, + 10, 104, 178, 0, 2, 73, 133, 0, 1, 44, 85, 0, 1, 22, 47, 0, + 127, 145, 243, 0, 71, 129, 228, 0, 17, 93, 177, 0, 3, 61, 124, 0, + 1, 41, 84, 0, 1, 21, 52, 0, 157, 78, 244, 0, 140, 72, 231, 0, + 69, 58, 184, 0, 31, 44, 137, 0, 14, 38, 105, 0, 8, 23, 61, 0, + 125, 34, 187, 0, 52, 41, 133, 0, 6, 31, 56, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 37, 109, 153, 0, 51, 102, 147, 0, + 23, 87, 128, 0, 8, 67, 101, 0, 1, 41, 63, 0, 1, 19, 29, 0, + 31, 154, 185, 0, 17, 127, 175, 0, 6, 96, 145, 0, 2, 73, 114, 0, + 1, 51, 82, 0, 1, 28, 45, 0, 23, 163, 200, 0, 10, 131, 185, 0, + 2, 93, 148, 0, 1, 67, 111, 0, 1, 41, 69, 0, 1, 14, 24, 0, + 29, 176, 217, 0, 12, 145, 201, 0, 3, 101, 156, 0, 1, 69, 111, 0, + 1, 39, 63, 0, 1, 14, 23, 0, 57, 192, 233, 0, 25, 154, 215, 0, + 6, 109, 167, 0, 3, 78, 118, 0, 1, 48, 69, 0, 1, 21, 29, 0, + 202, 105, 245, 0, 108, 106, 216, 0, 18, 90, 144, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 33, 172, 219, 0, 64, 149, 206, 0, + 14, 117, 177, 0, 5, 90, 141, 0, 2, 61, 95, 0, 1, 37, 57, 0, + 33, 179, 220, 0, 11, 140, 198, 0, 1, 89, 148, 0, 1, 60, 104, 0, + 1, 33, 57, 0, 1, 12, 21, 0, 30, 181, 221, 0, 8, 141, 198, 0, + 1, 87, 145, 0, 1, 58, 100, 0, 1, 31, 55, 0, 1, 12, 20, 0, + 32, 186, 224, 0, 7, 142, 198, 0, 1, 86, 143, 0, 1, 58, 100, 0, + 1, 31, 55, 0, 1, 12, 22, 0, 57, 192, 227, 0, 20, 143, 204, 0, + 3, 96, 154, 0, 1, 68, 112, 0, 1, 42, 69, 0, 1, 19, 32, 0, + 212, 35, 215, 0, 113, 47, 169, 0, 29, 48, 105, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 74, 129, 203, 0, 106, 120, 203, 0, + 49, 107, 178, 0, 19, 84, 144, 0, 4, 50, 84, 0, 1, 15, 25, 0, + 71, 172, 217, 0, 44, 141, 209, 0, 15, 102, 173, 0, 6, 76, 133, 0, + 2, 51, 89, 0, 1, 24, 42, 0, 64, 185, 231, 0, 31, 148, 216, 0, + 8, 103, 175, 0, 3, 74, 131, 0, 1, 46, 81, 0, 1, 18, 30, 0, + 65, 196, 235, 0, 25, 157, 221, 0, 5, 105, 174, 0, 1, 67, 120, 0, + 1, 38, 69, 0, 1, 15, 30, 0, 65, 204, 238, 0, 30, 156, 224, 0, + 7, 107, 177, 0, 2, 70, 124, 0, 1, 42, 73, 0, 1, 18, 34, 0, + 225, 86, 251, 0, 144, 104, 235, 0, 42, 99, 181, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 85, 175, 239, 0, 112, 165, 229, 0, + 29, 136, 200, 0, 12, 103, 162, 0, 6, 77, 123, 0, 2, 53, 84, 0, + 75, 183, 239, 0, 30, 155, 221, 0, 3, 106, 171, 0, 1, 74, 128, 0, + 1, 44, 76, 0, 1, 17, 28, 0, 73, 185, 240, 0, 27, 159, 222, 0, + 2, 107, 172, 0, 1, 75, 127, 0, 1, 42, 73, 0, 1, 17, 29, 0, + 62, 190, 238, 0, 21, 159, 222, 0, 2, 107, 172, 0, 1, 72, 122, 0, + 1, 40, 71, 0, 1, 18, 32, 0, 61, 199, 240, 0, 27, 161, 226, 0, + 4, 113, 180, 0, 1, 76, 129, 0, 1, 46, 80, 0, 1, 23, 41, 0, + 7, 27, 153, 0, 5, 30, 95, 0, 1, 16, 30, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 50, 75, 127, 0, 57, 75, 124, 0, + 27, 67, 108, 0, 10, 54, 86, 0, 1, 33, 52, 0, 1, 12, 18, 0, + 43, 125, 151, 0, 26, 108, 148, 0, 7, 83, 122, 0, 2, 59, 89, 0, + 1, 38, 60, 0, 1, 17, 27, 0, 23, 144, 163, 0, 13, 112, 154, 0, + 2, 75, 117, 0, 1, 50, 81, 0, 1, 31, 51, 0, 1, 14, 23, 0, + 18, 162, 185, 0, 6, 123, 171, 0, 1, 78, 125, 0, 1, 51, 86, 0, + 1, 31, 54, 0, 1, 14, 23, 0, 15, 199, 227, 0, 3, 150, 204, 0, + 1, 91, 146, 0, 1, 55, 95, 0, 1, 30, 53, 0, 1, 11, 20, 0, + 19, 55, 240, 0, 19, 59, 196, 0, 3, 52, 105, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 41, 166, 207, 0, 104, 153, 199, 0, + 31, 123, 181, 0, 14, 101, 152, 0, 5, 72, 106, 0, 1, 36, 52, 0, + 35, 176, 211, 0, 12, 131, 190, 0, 2, 88, 144, 0, 1, 60, 101, 0, + 1, 36, 60, 0, 1, 16, 28, 0, 28, 183, 213, 0, 8, 134, 191, 0, + 1, 86, 142, 0, 1, 56, 96, 0, 1, 30, 53, 0, 1, 12, 20, 0, + 20, 190, 215, 0, 4, 135, 192, 0, 1, 84, 139, 0, 1, 53, 91, 0, + 1, 28, 49, 0, 1, 11, 20, 0, 13, 196, 216, 0, 2, 137, 192, 0, + 1, 86, 143, 0, 1, 57, 99, 0, 1, 32, 56, 0, 1, 13, 24, 0, + 211, 29, 217, 0, 96, 47, 156, 0, 22, 43, 87, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 78, 120, 193, 0, 111, 116, 186, 0, + 46, 102, 164, 0, 15, 80, 128, 0, 2, 49, 76, 0, 1, 18, 28, 0, + 71, 161, 203, 0, 42, 132, 192, 0, 10, 98, 150, 0, 3, 69, 109, 0, + 1, 44, 70, 0, 1, 18, 29, 0, 57, 186, 211, 0, 30, 140, 196, 0, + 4, 93, 146, 0, 1, 62, 102, 0, 1, 38, 65, 0, 1, 16, 27, 0, + 47, 199, 217, 0, 14, 145, 196, 0, 1, 88, 142, 0, 1, 57, 98, 0, + 1, 36, 62, 0, 1, 15, 26, 0, 26, 219, 229, 0, 5, 155, 207, 0, + 1, 94, 151, 0, 1, 60, 104, 0, 1, 36, 62, 0, 1, 16, 28, 0, + 233, 29, 248, 0, 146, 47, 220, 0, 43, 52, 140, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 100, 163, 232, 0, 179, 161, 222, 0, + 63, 142, 204, 0, 37, 113, 174, 0, 26, 89, 137, 0, 18, 68, 97, 0, + 85, 181, 230, 0, 32, 146, 209, 0, 7, 100, 164, 0, 3, 71, 121, 0, + 1, 45, 77, 0, 1, 18, 30, 0, 65, 187, 230, 0, 20, 148, 207, 0, + 2, 97, 159, 0, 1, 68, 116, 0, 1, 40, 70, 0, 1, 14, 29, 0, + 40, 194, 227, 0, 8, 147, 204, 0, 1, 94, 155, 0, 1, 65, 112, 0, + 1, 39, 66, 0, 1, 14, 26, 0, 16, 208, 228, 0, 3, 151, 207, 0, + 1, 98, 160, 0, 1, 67, 117, 0, 1, 41, 74, 0, 1, 17, 31, 0, + 17, 38, 140, 0, 7, 34, 80, 0, 1, 17, 29, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 37, 75, 128, 0, 41, 76, 128, 0, + 26, 66, 116, 0, 12, 52, 94, 0, 2, 32, 55, 0, 1, 10, 16, 0, + 50, 127, 154, 0, 37, 109, 152, 0, 16, 82, 121, 0, 5, 59, 85, 0, + 1, 35, 54, 0, 1, 13, 20, 0, 40, 142, 167, 0, 17, 110, 157, 0, + 2, 71, 112, 0, 1, 44, 72, 0, 1, 27, 45, 0, 1, 11, 17, 0, + 30, 175, 188, 0, 9, 124, 169, 0, 1, 74, 116, 0, 1, 48, 78, 0, + 1, 30, 49, 0, 1, 11, 18, 0, 10, 222, 223, 0, 2, 150, 194, 0, + 1, 83, 128, 0, 1, 48, 79, 0, 1, 27, 45, 0, 1, 11, 17, 0, + 36, 41, 235, 0, 29, 36, 193, 0, 10, 27, 111, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 85, 165, 222, 0, 177, 162, 215, 0, + 110, 135, 195, 0, 57, 113, 168, 0, 23, 83, 120, 0, 10, 49, 61, 0, + 85, 190, 223, 0, 36, 139, 200, 0, 5, 90, 146, 0, 1, 60, 103, 0, + 1, 38, 65, 0, 1, 18, 30, 0, 72, 202, 223, 0, 23, 141, 199, 0, + 2, 86, 140, 0, 1, 56, 97, 0, 1, 36, 61, 0, 1, 16, 27, 0, + 55, 218, 225, 0, 13, 145, 200, 0, 1, 86, 141, 0, 1, 57, 99, 0, + 1, 35, 61, 0, 1, 13, 22, 0, 15, 235, 212, 0, 1, 132, 184, 0, + 1, 84, 139, 0, 1, 57, 97, 0, 1, 34, 56, 0, 1, 14, 23, 0, + 181, 21, 201, 0, 61, 37, 123, 0, 10, 38, 71, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 47, 106, 172, 0, 95, 104, 173, 0, + 42, 93, 159, 0, 18, 77, 131, 0, 4, 50, 81, 0, 1, 17, 23, 0, + 62, 147, 199, 0, 44, 130, 189, 0, 28, 102, 154, 0, 18, 75, 115, 0, + 2, 44, 65, 0, 1, 12, 19, 0, 55, 153, 210, 0, 24, 130, 194, 0, + 3, 93, 146, 0, 1, 61, 97, 0, 1, 31, 50, 0, 1, 10, 16, 0, + 49, 186, 223, 0, 17, 148, 204, 0, 1, 96, 142, 0, 1, 53, 83, 0, + 1, 26, 44, 0, 1, 11, 17, 0, 13, 217, 212, 0, 2, 136, 180, 0, + 1, 78, 124, 0, 1, 50, 83, 0, 1, 29, 49, 0, 1, 14, 23, 0, + 197, 13, 247, 0, 82, 17, 222, 0, 25, 17, 162, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 126, 186, 247, 0, 234, 191, 243, 0, + 176, 177, 234, 0, 104, 158, 220, 0, 66, 128, 186, 0, 55, 90, 137, 0, + 111, 197, 242, 0, 46, 158, 219, 0, 9, 104, 171, 0, 2, 65, 125, 0, + 1, 44, 80, 0, 1, 17, 91, 0, 104, 208, 245, 0, 39, 168, 224, 0, + 3, 109, 162, 0, 1, 79, 124, 0, 1, 50, 102, 0, 1, 43, 102, 0, + 84, 220, 246, 0, 31, 177, 231, 0, 2, 115, 180, 0, 1, 79, 134, 0, + 1, 55, 77, 0, 1, 60, 79, 0, 43, 243, 240, 0, 8, 180, 217, 0, + 1, 115, 166, 0, 1, 84, 121, 0, 1, 51, 67, 0, 1, 16, 6, 0 + }; + + private byte[] _defaultSkipProbs = new byte[] { 192, 128, 64 }; + + private byte[] _defaultInterModeProbs = new byte[] + { + 2, 173, 34, 0, 7, 145, 85, 0, 7, 166, 63, 0, 7, 94, 66, 0, + 8, 64, 46, 0, 17, 81, 31, 0, 25, 29, 30, 0 + }; + + private byte[] _defaultInterpFilterProbs = new byte[] + { + 235, 162, 36, 255, 34, 3, 149, 144 + }; + + private byte[] _defaultIsInterProbs = new byte[] { 9, 102, 187, 225 }; + + private byte[] _defaultCompModeProbs = new byte[] { 239, 183, 119, 96, 41 }; + + private byte[] _defaultSingleRefProbs = new byte[] + { + 33, 16, 77, 74, 142, 142, 172, 170, 238, 247 + }; + + private byte[] _defaultCompRefProbs = new byte[] { 50, 126, 123, 221, 226 }; + + private byte[] _defaultYModeProbs0 = new byte[] + { + 65, 32, 18, 144, 162, 194, 41, 51, 132, 68, 18, 165, 217, 196, 45, 40, + 173, 80, 19, 176, 240, 193, 64, 35, 221, 135, 38, 194, 248, 121, 96, 85 + }; + + private byte[] _defaultYModeProbs1 = new byte[] { 98, 78, 46, 29 }; + + private byte[] _defaultPartitionProbs = new byte[] + { + 199, 122, 141, 0, 147, 63, 159, 0, 148, 133, 118, 0, 121, 104, 114, 0, + 174, 73, 87, 0, 92, 41, 83, 0, 82, 99, 50, 0, 53, 39, 39, 0, + 177, 58, 59, 0, 68, 26, 63, 0, 52, 79, 25, 0, 17, 14, 12, 0, + 222, 34, 30, 0, 72, 16, 44, 0, 58, 32, 12, 0, 10, 7, 6, 0 + }; + + private byte[] _defaultMvJointProbs = new byte[] { 32, 64, 96 }; + + private byte[] _defaultMvSignProbs = new byte[] { 128, 128 }; + + private byte[] _defaultMvClassProbs = new byte[] + { + 224, 144, 192, 168, 192, 176, 192, 198, 198, 245, 216, 128, 176, 160, 176, 176, + 192, 198, 198, 208 + }; + + private byte[] _defaultMvClass0BitProbs = new byte[] { 216, 208 }; + + private byte[] _defaultMvBitsProbs = new byte[] + { + 136, 140, 148, 160, 176, 192, 224, 234, 234, 240, 136, 140, 148, 160, 176, 192, + 224, 234, 234, 240 + }; + + private byte[] _defaultMvClass0FrProbs = new byte[] + { + 128, 128, 64, 96, 112, 64, 128, 128, 64, 96, 112, 64 + }; + + private byte[] _defaultMvFrProbs = new byte[] { 64, 96, 64, 64, 96, 64 }; + + private byte[] _defaultMvClass0HpProbs = new byte[] { 160, 160 }; + + private byte[] _defaultMvHpProbs = new byte[] { 128, 128 }; + + private sbyte[] _loopFilterRefDeltas; + private sbyte[] _loopFilterModeDeltas; + + private LinkedList _frameSlotByLastUse; + + private Dictionary> _cachedRefFrames; + + public Vp9Decoder() + { + _loopFilterRefDeltas = new sbyte[4]; + _loopFilterModeDeltas = new sbyte[2]; + + _frameSlotByLastUse = new LinkedList(); + + for (int slot = 0; slot < 8; slot++) + { + _frameSlotByLastUse.AddFirst(slot); + } + + _cachedRefFrames = new Dictionary>(); + } + + public void Decode( + Vp9FrameKeys keys, + Vp9FrameHeader header, + Vp9ProbabilityTables probs, + byte[] frameData) + { + bool isKeyFrame = ((header.Flags >> 0) & 1) != 0; + bool lastIsKeyFrame = ((header.Flags >> 1) & 1) != 0; + bool frameSizeChanged = ((header.Flags >> 2) & 1) != 0; + bool errorResilientMode = ((header.Flags >> 3) & 1) != 0; + bool lastShowFrame = ((header.Flags >> 4) & 1) != 0; + bool isFrameIntra = ((header.Flags >> 5) & 1) != 0; + + bool showFrame = !isFrameIntra; + + // Write compressed header. + byte[] compressedHeaderData; + + using (MemoryStream compressedHeader = new MemoryStream()) + { + VpxRangeEncoder writer = new VpxRangeEncoder(compressedHeader); + + if (!header.Lossless) + { + if ((uint)header.TxMode >= 3) + { + writer.Write(3, 2); + writer.Write(header.TxMode == 4); + } + else + { + writer.Write(header.TxMode, 2); + } + } + + if (header.TxMode == 4) + { + WriteProbabilityUpdate(writer, probs.Tx8x8Probs, DefaultTx8x8Probs); + WriteProbabilityUpdate(writer, probs.Tx16x16Probs, DefaultTx16x16Probs); + WriteProbabilityUpdate(writer, probs.Tx32x32Probs, DefaultTx32x32Probs); + } + + WriteCoefProbabilityUpdate(writer, header.TxMode, probs.CoefProbs, _defaultCoefProbs); + + WriteProbabilityUpdate(writer, probs.SkipProbs, _defaultSkipProbs); + + if (!isFrameIntra) + { + WriteProbabilityUpdateAligned4(writer, probs.InterModeProbs, _defaultInterModeProbs); + + if (header.RawInterpolationFilter == 4) + { + WriteProbabilityUpdate(writer, probs.InterpFilterProbs, _defaultInterpFilterProbs); + } + + WriteProbabilityUpdate(writer, probs.IsInterProbs, _defaultIsInterProbs); + + if ((header.RefFrameSignBias[1] & 1) != (header.RefFrameSignBias[2] & 1) || + (header.RefFrameSignBias[1] & 1) != (header.RefFrameSignBias[3] & 1)) + { + if ((uint)header.CompPredMode >= 1) + { + writer.Write(1, 1); + writer.Write(header.CompPredMode == 2); + } + else + { + writer.Write(0, 1); + } + } + + if (header.CompPredMode == 2) + { + WriteProbabilityUpdate(writer, probs.CompModeProbs, _defaultCompModeProbs); + } + + if (header.CompPredMode != 1) + { + WriteProbabilityUpdate(writer, probs.SingleRefProbs, _defaultSingleRefProbs); + } + + if (header.CompPredMode != 0) + { + WriteProbabilityUpdate(writer, probs.CompRefProbs, _defaultCompRefProbs); + } + + for (int index = 0; index < 4; index++) + { + int i = index * 8; + int j = index; + + WriteProbabilityUpdate(writer, probs.YModeProbs0[i + 0], _defaultYModeProbs0[i + 0]); + WriteProbabilityUpdate(writer, probs.YModeProbs0[i + 1], _defaultYModeProbs0[i + 1]); + WriteProbabilityUpdate(writer, probs.YModeProbs0[i + 2], _defaultYModeProbs0[i + 2]); + WriteProbabilityUpdate(writer, probs.YModeProbs0[i + 3], _defaultYModeProbs0[i + 3]); + WriteProbabilityUpdate(writer, probs.YModeProbs0[i + 4], _defaultYModeProbs0[i + 4]); + WriteProbabilityUpdate(writer, probs.YModeProbs0[i + 5], _defaultYModeProbs0[i + 5]); + WriteProbabilityUpdate(writer, probs.YModeProbs0[i + 6], _defaultYModeProbs0[i + 6]); + WriteProbabilityUpdate(writer, probs.YModeProbs0[i + 7], _defaultYModeProbs0[i + 7]); + WriteProbabilityUpdate(writer, probs.YModeProbs1[j + 0], _defaultYModeProbs1[j + 0]); + } + + WriteProbabilityUpdateAligned4(writer, probs.PartitionProbs, _defaultPartitionProbs); + + for (int i = 0; i < 3; i++) + { + WriteMvProbabilityUpdate(writer, probs.MvJointProbs[i], _defaultMvJointProbs[i]); + } + + for (int i = 0; i < 2; i++) + { + WriteMvProbabilityUpdate(writer, probs.MvSignProbs[i], _defaultMvSignProbs[i]); + + for (int j = 0; j < 10; j++) + { + int index = i * 10 + j; + + WriteMvProbabilityUpdate(writer, probs.MvClassProbs[index], _defaultMvClassProbs[index]); + } + + WriteMvProbabilityUpdate(writer, probs.MvClass0BitProbs[i], _defaultMvClass0BitProbs[i]); + + for (int j = 0; j < 10; j++) + { + int index = i * 10 + j; + + WriteMvProbabilityUpdate(writer, probs.MvBitsProbs[index], _defaultMvBitsProbs[index]); + } + } + + for (int i = 0; i < 2; i++) + { + for (int j = 0; j < 2; j++) + { + for (int k = 0; k < 3; k++) + { + int index = i * 2 * 3 + j * 3 + k; + + WriteMvProbabilityUpdate(writer, probs.MvClass0FrProbs[index], _defaultMvClass0FrProbs[index]); + } + } + + for (int j = 0; j < 3; j++) + { + int index = i * 3 + j; + + WriteMvProbabilityUpdate(writer, probs.MvFrProbs[index], _defaultMvFrProbs[index]); + } + } + + if (header.AllowHighPrecisionMv) + { + for (int index = 0; index < 2; index++) + { + WriteMvProbabilityUpdate(writer, probs.MvClass0HpProbs[index], _defaultMvClass0HpProbs[index]); + WriteMvProbabilityUpdate(writer, probs.MvHpProbs[index], _defaultMvHpProbs[index]); + } + } + } + + writer.End(); + + compressedHeaderData = compressedHeader.ToArray(); + } + + // Write uncompressed header. + using (MemoryStream encodedHeader = new MemoryStream()) + { + VpxBitStreamWriter writer = new VpxBitStreamWriter(encodedHeader); + + writer.WriteU(2, 2); //Frame marker. + writer.WriteU(0, 2); //Profile. + writer.WriteBit(false); //Show existing frame. + writer.WriteBit(!isKeyFrame); + writer.WriteBit(showFrame); + writer.WriteBit(errorResilientMode); + + if (isKeyFrame) + { + writer.WriteU(FrameSyncCode, 24); + writer.WriteU(0, 3); //Color space. + writer.WriteU(0, 1); //Color range. + writer.WriteU(header.CurrentFrame.Width - 1, 16); + writer.WriteU(header.CurrentFrame.Height - 1, 16); + writer.WriteBit(false); //Render and frame size different. + + _cachedRefFrames.Clear(); + + // On key frames, all frame slots are set to the current frame, + // so the value of the selected slot doesn't really matter. + GetNewFrameSlot(keys.CurrKey); + } + else + { + if (!showFrame) + { + writer.WriteBit(isFrameIntra); + } + + if (!errorResilientMode) + { + writer.WriteU(0, 2); //Reset frame context. + } + + int refreshFrameFlags = 1 << GetNewFrameSlot(keys.CurrKey); + + if (isFrameIntra) + { + writer.WriteU(FrameSyncCode, 24); + writer.WriteU(refreshFrameFlags, 8); + writer.WriteU(header.CurrentFrame.Width - 1, 16); + writer.WriteU(header.CurrentFrame.Height - 1, 16); + writer.WriteBit(false); //Render and frame size different. + } + else + { + writer.WriteU(refreshFrameFlags, 8); + + int[] refFrameIndex = new int[] + { + GetFrameSlot(keys.Ref0Key), + GetFrameSlot(keys.Ref1Key), + GetFrameSlot(keys.Ref2Key) + }; + + byte[] refFrameSignBias = header.RefFrameSignBias; + + for (int index = 1; index < 4; index++) + { + writer.WriteU(refFrameIndex[index - 1], 3); + writer.WriteU(refFrameSignBias[index], 1); + } + + writer.WriteBit(true); //Frame size with refs. + writer.WriteBit(false); //Render and frame size different. + writer.WriteBit(header.AllowHighPrecisionMv); + writer.WriteBit(header.RawInterpolationFilter == 4); + + if (header.RawInterpolationFilter != 4) + { + writer.WriteU(header.RawInterpolationFilter, 2); + } + } + } + + if (!errorResilientMode) + { + writer.WriteBit(false); //Refresh frame context. + writer.WriteBit(true); //Frame parallel decoding mode. + } + + writer.WriteU(0, 2); //Frame context index. + + writer.WriteU(header.LoopFilterLevel, 6); + writer.WriteU(header.LoopFilterSharpness, 3); + writer.WriteBit(header.LoopFilterDeltaEnabled); + + if (header.LoopFilterDeltaEnabled) + { + bool[] updateLoopFilterRefDeltas = new bool[4]; + bool[] updateLoopFilterModeDeltas = new bool[2]; + + bool loopFilterDeltaUpdate = false; + + for (int index = 0; index < header.LoopFilterRefDeltas.Length; index++) + { + sbyte old = _loopFilterRefDeltas[index]; + sbyte New = header.LoopFilterRefDeltas[index]; + + loopFilterDeltaUpdate |= (updateLoopFilterRefDeltas[index] = old != New); + } + + for (int index = 0; index < header.LoopFilterModeDeltas.Length; index++) + { + sbyte old = _loopFilterModeDeltas[index]; + sbyte New = header.LoopFilterModeDeltas[index]; + + loopFilterDeltaUpdate |= (updateLoopFilterModeDeltas[index] = old != New); + } + + writer.WriteBit(loopFilterDeltaUpdate); + + if (loopFilterDeltaUpdate) + { + for (int index = 0; index < header.LoopFilterRefDeltas.Length; index++) + { + writer.WriteBit(updateLoopFilterRefDeltas[index]); + + if (updateLoopFilterRefDeltas[index]) + { + writer.WriteS(header.LoopFilterRefDeltas[index], 6); + } + } + + for (int index = 0; index < header.LoopFilterModeDeltas.Length; index++) + { + writer.WriteBit(updateLoopFilterModeDeltas[index]); + + if (updateLoopFilterModeDeltas[index]) + { + writer.WriteS(header.LoopFilterModeDeltas[index], 6); + } + } + } + } + + writer.WriteU(header.BaseQIndex, 8); + + writer.WriteDeltaQ(header.DeltaQYDc); + writer.WriteDeltaQ(header.DeltaQUvDc); + writer.WriteDeltaQ(header.DeltaQUvAc); + + writer.WriteBit(false); //Segmentation enabled (TODO). + + int minTileColsLog2 = CalcMinLog2TileCols(header.CurrentFrame.Width); + int maxTileColsLog2 = CalcMaxLog2TileCols(header.CurrentFrame.Width); + + int tileColsLog2Diff = header.TileColsLog2 - minTileColsLog2; + + int tileColsLog2IncMask = (1 << tileColsLog2Diff) - 1; + + // If it's less than the maximum, we need to add an extra 0 on the bitstream + // to indicate that it should stop reading. + if (header.TileColsLog2 < maxTileColsLog2) + { + writer.WriteU(tileColsLog2IncMask << 1, tileColsLog2Diff + 1); + } + else + { + writer.WriteU(tileColsLog2IncMask, tileColsLog2Diff); + } + + bool tileRowsLog2IsNonZero = header.TileRowsLog2 != 0; + + writer.WriteBit(tileRowsLog2IsNonZero); + + if (tileRowsLog2IsNonZero) + { + writer.WriteBit(header.TileRowsLog2 > 1); + } + + writer.WriteU(compressedHeaderData.Length, 16); + + writer.Flush(); + + encodedHeader.Write(compressedHeaderData, 0, compressedHeaderData.Length); + + if (!FFmpegWrapper.IsInitialized) + { + FFmpegWrapper.Vp9Initialize(); + } + + FFmpegWrapper.DecodeFrame(DecoderHelper.Combine(encodedHeader.ToArray(), frameData)); + } + + _loopFilterRefDeltas = header.LoopFilterRefDeltas; + _loopFilterModeDeltas = header.LoopFilterModeDeltas; + } + + private int GetNewFrameSlot(long key) + { + LinkedListNode node = _frameSlotByLastUse.Last; + + _frameSlotByLastUse.RemoveLast(); + _frameSlotByLastUse.AddFirst(node); + + _cachedRefFrames[key] = node; + + return node.Value; + } + + private int GetFrameSlot(long key) + { + if (_cachedRefFrames.TryGetValue(key, out LinkedListNode node)) + { + _frameSlotByLastUse.Remove(node); + _frameSlotByLastUse.AddFirst(node); + + return node.Value; + } + + // Reference frame was lost. + // What we should do in this case? + return 0; + } + + private void WriteProbabilityUpdate(VpxRangeEncoder writer, byte[] New, byte[] old) + { + for (int offset = 0; offset < New.Length; offset++) + { + WriteProbabilityUpdate(writer, New[offset], old[offset]); + } + } + + private void WriteCoefProbabilityUpdate(VpxRangeEncoder writer, int txMode, byte[] New, byte[] old) + { + // Note: There's 1 byte added on each packet for alignment, + // this byte is ignored when doing updates. + const int blockBytes = 2 * 2 * 6 * 6 * 4; + + bool NeedsUpdate(int baseIndex) + { + int index = baseIndex; + + for (int i = 0; i < 2; i++) + for (int j = 0; j < 2; j++) + for (int k = 0; k < 6; k++) + for (int l = 0; l < 6; l++) + { + if (New[index + 0] != old[index + 0] || + New[index + 1] != old[index + 1] || + New[index + 2] != old[index + 2]) + { + return true; + } + + index += 4; + } + + return false; + } + + for (int blockIndex = 0; blockIndex < 4; blockIndex++) + { + int baseIndex = blockIndex * blockBytes; + + bool update = NeedsUpdate(baseIndex); + + writer.Write(update); + + if (update) + { + int index = baseIndex; + + for (int i = 0; i < 2; i++) + for (int j = 0; j < 2; j++) + for (int k = 0; k < 6; k++) + for (int l = 0; l < 6; l++) + { + if (k != 0 || l < 3) + { + WriteProbabilityUpdate(writer, New[index + 0], old[index + 0]); + WriteProbabilityUpdate(writer, New[index + 1], old[index + 1]); + WriteProbabilityUpdate(writer, New[index + 2], old[index + 2]); + } + + index += 4; + } + } + + if (blockIndex == txMode) + { + break; + } + } + } + + private void WriteProbabilityUpdateAligned4(VpxRangeEncoder writer, byte[] New, byte[] old) + { + for (int offset = 0; offset < New.Length; offset += 4) + { + WriteProbabilityUpdate(writer, New[offset + 0], old[offset + 0]); + WriteProbabilityUpdate(writer, New[offset + 1], old[offset + 1]); + WriteProbabilityUpdate(writer, New[offset + 2], old[offset + 2]); + } + } + + private void WriteProbabilityUpdate(VpxRangeEncoder writer, byte New, byte old) + { + bool update = New != old; + + writer.Write(update, DiffUpdateProbability); + + if (update) + { + WriteProbabilityDelta(writer, New, old); + } + } + + private void WriteProbabilityDelta(VpxRangeEncoder writer, int New, int old) + { + int delta = RemapProbability(New, old); + + EncodeTermSubExp(writer, delta); + } + + private int RemapProbability(int New, int old) + { + New--; + old--; + + int index; + + if (old * 2 <= 0xff) + { + index = RecenterNonNeg(New, old) - 1; + } + else + { + index = RecenterNonNeg(0xff - 1 - New, 0xff - 1 - old) - 1; + } + + return MapLut[index]; + } + + private int RecenterNonNeg(int New, int old) + { + if (New > old * 2) + { + return New; + } + else if (New >= old) + { + return (New - old) * 2; + } + else /* if (New < Old) */ + { + return (old - New) * 2 - 1; + } + } + + private void EncodeTermSubExp(VpxRangeEncoder writer, int value) + { + if (WriteLessThan(writer, value, 16)) + { + writer.Write(value, 4); + } + else if (WriteLessThan(writer, value, 32)) + { + writer.Write(value - 16, 4); + } + else if (WriteLessThan(writer, value, 64)) + { + writer.Write(value - 32, 5); + } + else + { + value -= 64; + + const int size = 8; + + int mask = (1 << size) - 191; + + int delta = value - mask; + + if (delta < 0) + { + writer.Write(value, size - 1); + } + else + { + writer.Write(delta / 2 + mask, size - 1); + writer.Write(delta & 1, 1); + } + } + } + + private bool WriteLessThan(VpxRangeEncoder writer, int value, int test) + { + bool isLessThan = value < test; + + writer.Write(!isLessThan); + + return isLessThan; + } + + private void WriteMvProbabilityUpdate(VpxRangeEncoder writer, byte New, byte old) + { + bool update = New != old; + + writer.Write(update, DiffUpdateProbability); + + if (update) + { + writer.Write(New >> 1, 7); + } + } + + private static int CalcMinLog2TileCols(int frameWidth) + { + int sb64Cols = (frameWidth + 63) / 64; + int minLog2 = 0; + + while ((64 << minLog2) < sb64Cols) + { + minLog2++; + } + + return minLog2; + } + + private static int CalcMaxLog2TileCols(int frameWidth) + { + int sb64Cols = (frameWidth + 63) / 64; + int maxLog2 = 1; + + while ((sb64Cols >> maxLog2) >= 4) + { + maxLog2++; + } + + return maxLog2 - 1; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Nvdec/VDec/Vp9FrameHeader.cs b/Ryujinx.Graphics.Nvdec/VDec/Vp9FrameHeader.cs new file mode 100644 index 0000000000..bdba6de5a8 --- /dev/null +++ b/Ryujinx.Graphics.Nvdec/VDec/Vp9FrameHeader.cs @@ -0,0 +1,79 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.VDec +{ + [StructLayout(LayoutKind.Sequential, Pack = 2)] + struct Vp9FrameDimensions + { + public short Width; + public short Height; + public short SubsamplingX; //? + public short SubsamplingY; //? + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct Vp9FrameHeader + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public Vp9FrameDimensions[] RefFrames; + + public Vp9FrameDimensions CurrentFrame; + + public int Flags; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] RefFrameSignBias; + + public byte LoopFilterLevel; + public byte LoopFilterSharpness; + + public byte BaseQIndex; + public sbyte DeltaQYDc; + public sbyte DeltaQUvDc; + public sbyte DeltaQUvAc; + + [MarshalAs(UnmanagedType.I1)] + public bool Lossless; + + public byte TxMode; + + [MarshalAs(UnmanagedType.I1)] + public bool AllowHighPrecisionMv; + + public byte RawInterpolationFilter; + public byte CompPredMode; + public byte FixCompRef; + public byte VarCompRef0; + public byte VarCompRef1; + + public byte TileColsLog2; + public byte TileRowsLog2; + + [MarshalAs(UnmanagedType.I1)] + public bool SegmentationEnabled; + + [MarshalAs(UnmanagedType.I1)] + public bool SegmentationUpdate; + + [MarshalAs(UnmanagedType.I1)] + public bool SegmentationTemporalUpdate; + + [MarshalAs(UnmanagedType.I1)] + public bool SegmentationAbsOrDeltaUpdate; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8 * 4, ArraySubType = UnmanagedType.I1)] + public bool[] FeatureEnabled; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8 * 4)] + public short[] FeatureData; + + [MarshalAs(UnmanagedType.I1)] + public bool LoopFilterDeltaEnabled; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public sbyte[] LoopFilterRefDeltas; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] + public sbyte[] LoopFilterModeDeltas; + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Nvdec/VDec/Vp9FrameKeys.cs b/Ryujinx.Graphics.Nvdec/VDec/Vp9FrameKeys.cs new file mode 100644 index 0000000000..dfc31ea315 --- /dev/null +++ b/Ryujinx.Graphics.Nvdec/VDec/Vp9FrameKeys.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Graphics.VDec +{ + struct Vp9FrameKeys + { + public long CurrKey; + public long Ref0Key; + public long Ref1Key; + public long Ref2Key; + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Nvdec/VDec/Vp9ProbabilityTables.cs b/Ryujinx.Graphics.Nvdec/VDec/Vp9ProbabilityTables.cs new file mode 100644 index 0000000000..5a6dd0cf2b --- /dev/null +++ b/Ryujinx.Graphics.Nvdec/VDec/Vp9ProbabilityTables.cs @@ -0,0 +1,31 @@ +namespace Ryujinx.Graphics.VDec +{ + struct Vp9ProbabilityTables + { + public byte[] SegmentationTreeProbs; + public byte[] SegmentationPredProbs; + public byte[] Tx8x8Probs; + public byte[] Tx16x16Probs; + public byte[] Tx32x32Probs; + public byte[] CoefProbs; + public byte[] SkipProbs; + public byte[] InterModeProbs; + public byte[] InterpFilterProbs; + public byte[] IsInterProbs; + public byte[] CompModeProbs; + public byte[] SingleRefProbs; + public byte[] CompRefProbs; + public byte[] YModeProbs0; + public byte[] YModeProbs1; + public byte[] PartitionProbs; + public byte[] MvJointProbs; + public byte[] MvSignProbs; + public byte[] MvClassProbs; + public byte[] MvClass0BitProbs; + public byte[] MvBitsProbs; + public byte[] MvClass0FrProbs; + public byte[] MvFrProbs; + public byte[] MvClass0HpProbs; + public byte[] MvHpProbs; + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Nvdec/VDec/VpxBitStreamWriter.cs b/Ryujinx.Graphics.Nvdec/VDec/VpxBitStreamWriter.cs new file mode 100644 index 0000000000..97ada333e7 --- /dev/null +++ b/Ryujinx.Graphics.Nvdec/VDec/VpxBitStreamWriter.cs @@ -0,0 +1,38 @@ +using System.IO; + +namespace Ryujinx.Graphics.VDec +{ + class VpxBitStreamWriter : BitStreamWriter + { + public VpxBitStreamWriter(Stream baseStream) : base(baseStream) { } + + public void WriteU(int value, int valueSize) + { + WriteBits(value, valueSize); + } + + public void WriteS(int value, int valueSize) + { + bool sign = value < 0; + + if (sign) + { + value = -value; + } + + WriteBits((value << 1) | (sign ? 1 : 0), valueSize + 1); + } + + public void WriteDeltaQ(int value) + { + bool deltaCoded = value != 0; + + WriteBit(deltaCoded); + + if (deltaCoded) + { + WriteBits(value, 4); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Nvdec/VDec/VpxRangeEncoder.cs b/Ryujinx.Graphics.Nvdec/VDec/VpxRangeEncoder.cs new file mode 100644 index 0000000000..c854c9d9de --- /dev/null +++ b/Ryujinx.Graphics.Nvdec/VDec/VpxRangeEncoder.cs @@ -0,0 +1,134 @@ +using System.IO; + +namespace Ryujinx.Graphics.VDec +{ + class VpxRangeEncoder + { + private const int HalfProbability = 128; + + private static readonly int[] NormLut = new int[] + { + 0, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + + private Stream _baseStream; + + private uint _lowValue; + private uint _range; + private int _count; + + public VpxRangeEncoder(Stream baseStream) + { + _baseStream = baseStream; + + _range = 0xff; + _count = -24; + + Write(false); + } + + public void WriteByte(byte value) + { + Write(value, 8); + } + + public void Write(int value, int valueSize) + { + for (int bit = valueSize - 1; bit >= 0; bit--) + { + Write(((value >> bit) & 1) != 0); + } + } + + public void Write(bool bit) + { + Write(bit, HalfProbability); + } + + public void Write(bool bit, int probability) + { + uint range = _range; + + uint split = 1 + (((range - 1) * (uint)probability) >> 8); + + range = split; + + if (bit) + { + _lowValue += split; + range = _range - split; + } + + int shift = NormLut[range]; + + range <<= shift; + _count += shift; + + if (_count >= 0) + { + int offset = shift - _count; + + if (((_lowValue << (offset - 1)) >> 31) != 0) + { + long currentPos = _baseStream.Position; + + _baseStream.Seek(-1, SeekOrigin.Current); + + while (_baseStream.Position >= 0 && PeekByte() == 0xff) + { + _baseStream.WriteByte(0); + + _baseStream.Seek(-2, SeekOrigin.Current); + } + + _baseStream.WriteByte((byte)(PeekByte() + 1)); + + _baseStream.Seek(currentPos, SeekOrigin.Begin); + } + + _baseStream.WriteByte((byte)(_lowValue >> (24 - offset))); + + _lowValue <<= offset; + shift = _count; + _lowValue &= 0xffffff; + _count -= 8; + } + + _lowValue <<= shift; + + _range = range; + } + + private byte PeekByte() + { + byte value = (byte)_baseStream.ReadByte(); + + _baseStream.Seek(-1, SeekOrigin.Current); + + return value; + } + + public void End() + { + for (int index = 0; index < 32; index++) + { + Write(false); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Nvdec/Vic/StructUnpacker.cs b/Ryujinx.Graphics.Nvdec/Vic/StructUnpacker.cs new file mode 100644 index 0000000000..4957e6b63c --- /dev/null +++ b/Ryujinx.Graphics.Nvdec/Vic/StructUnpacker.cs @@ -0,0 +1,69 @@ +using Ryujinx.Graphics.Gpu.Memory; +using System; + +namespace Ryujinx.Graphics.Vic +{ + class StructUnpacker + { + private MemoryAccessor _vmm; + + private ulong _position; + + private ulong _buffer; + private int _buffPos; + + public StructUnpacker(MemoryAccessor vmm, ulong position) + { + _vmm = vmm; + _position = position; + + _buffPos = 64; + } + + public int Read(int bits) + { + if ((uint)bits > 32) + { + throw new ArgumentOutOfRangeException(nameof(bits)); + } + + int value = 0; + + while (bits > 0) + { + RefillBufferIfNeeded(); + + int readBits = bits; + + int maxReadBits = 64 - _buffPos; + + if (readBits > maxReadBits) + { + readBits = maxReadBits; + } + + value <<= readBits; + + value |= (int)(_buffer >> _buffPos) & (int)(0xffffffff >> (32 - readBits)); + + _buffPos += readBits; + + bits -= readBits; + } + + return value; + } + + private void RefillBufferIfNeeded() + { + if (_buffPos >= 64) + { + _buffer = _vmm.ReadUInt64(_position); + + _position += 8; + + _buffPos = 0; + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Nvdec/Vic/SurfaceOutputConfig.cs b/Ryujinx.Graphics.Nvdec/Vic/SurfaceOutputConfig.cs new file mode 100644 index 0000000000..bcb01e70b7 --- /dev/null +++ b/Ryujinx.Graphics.Nvdec/Vic/SurfaceOutputConfig.cs @@ -0,0 +1,33 @@ +namespace Ryujinx.Graphics.Vic +{ + struct SurfaceOutputConfig + { + public SurfacePixelFormat PixelFormat; + + public int SurfaceWidth; + public int SurfaceHeight; + public int GobBlockHeight; + + public ulong SurfaceLumaAddress; + public ulong SurfaceChromaUAddress; + public ulong SurfaceChromaVAddress; + + public SurfaceOutputConfig( + SurfacePixelFormat pixelFormat, + int surfaceWidth, + int surfaceHeight, + int gobBlockHeight, + ulong outputSurfaceLumaAddress, + ulong outputSurfaceChromaUAddress, + ulong outputSurfaceChromaVAddress) + { + PixelFormat = pixelFormat; + SurfaceWidth = surfaceWidth; + SurfaceHeight = surfaceHeight; + GobBlockHeight = gobBlockHeight; + SurfaceLumaAddress = outputSurfaceLumaAddress; + SurfaceChromaUAddress = outputSurfaceChromaUAddress; + SurfaceChromaVAddress = outputSurfaceChromaVAddress; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Nvdec/Vic/SurfacePixelFormat.cs b/Ryujinx.Graphics.Nvdec/Vic/SurfacePixelFormat.cs new file mode 100644 index 0000000000..8dabd09423 --- /dev/null +++ b/Ryujinx.Graphics.Nvdec/Vic/SurfacePixelFormat.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Graphics.Vic +{ + enum SurfacePixelFormat + { + Rgba8 = 0x1f, + Yuv420P = 0x44 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Nvdec/Vic/VideoImageComposer.cs b/Ryujinx.Graphics.Nvdec/Vic/VideoImageComposer.cs new file mode 100644 index 0000000000..39e18fa697 --- /dev/null +++ b/Ryujinx.Graphics.Nvdec/Vic/VideoImageComposer.cs @@ -0,0 +1,94 @@ +using Ryujinx.Graphics.Gpu; +using Ryujinx.Graphics.VDec; + +namespace Ryujinx.Graphics.Vic +{ + class VideoImageComposer + { + private ulong _configStructAddress; + private ulong _outputSurfaceLumaAddress; + private ulong _outputSurfaceChromaUAddress; + private ulong _outputSurfaceChromaVAddress; + + private VideoDecoder _vdec; + + public VideoImageComposer(VideoDecoder vdec) + { + _vdec = vdec; + } + + public void Process(GpuContext gpu, int methodOffset, int[] arguments) + { + VideoImageComposerMeth method = (VideoImageComposerMeth)methodOffset; + + switch (method) + { + case VideoImageComposerMeth.Execute: Execute(gpu); break; + case VideoImageComposerMeth.SetConfigStructOffset: SetConfigStructOffset(arguments); break; + case VideoImageComposerMeth.SetOutputSurfaceLumaOffset: SetOutputSurfaceLumaOffset(arguments); break; + case VideoImageComposerMeth.SetOutputSurfaceChromaUOffset: SetOutputSurfaceChromaUOffset(arguments); break; + case VideoImageComposerMeth.SetOutputSurfaceChromaVOffset: SetOutputSurfaceChromaVOffset(arguments); break; + } + } + + private void Execute(GpuContext gpu) + { + StructUnpacker unpacker = new StructUnpacker(gpu.MemoryAccessor, _configStructAddress + 0x20); + + SurfacePixelFormat pixelFormat = (SurfacePixelFormat)unpacker.Read(7); + + int chromaLocHoriz = unpacker.Read(2); + int chromaLocVert = unpacker.Read(2); + + int blockLinearKind = unpacker.Read(4); + int blockLinearHeightLog2 = unpacker.Read(4); + + int reserved0 = unpacker.Read(3); + int reserved1 = unpacker.Read(10); + + int surfaceWidthMinus1 = unpacker.Read(14); + int surfaceHeightMinus1 = unpacker.Read(14); + + int gobBlockHeight = 1 << blockLinearHeightLog2; + + int surfaceWidth = surfaceWidthMinus1 + 1; + int surfaceHeight = surfaceHeightMinus1 + 1; + + SurfaceOutputConfig outputConfig = new SurfaceOutputConfig( + pixelFormat, + surfaceWidth, + surfaceHeight, + gobBlockHeight, + _outputSurfaceLumaAddress, + _outputSurfaceChromaUAddress, + _outputSurfaceChromaVAddress); + + _vdec.CopyPlanes(gpu, outputConfig); + } + + private void SetConfigStructOffset(int[] arguments) + { + _configStructAddress = GetAddress(arguments); + } + + private void SetOutputSurfaceLumaOffset(int[] arguments) + { + _outputSurfaceLumaAddress = GetAddress(arguments); + } + + private void SetOutputSurfaceChromaUOffset(int[] arguments) + { + _outputSurfaceChromaUAddress = GetAddress(arguments); + } + + private void SetOutputSurfaceChromaVOffset(int[] arguments) + { + _outputSurfaceChromaVAddress = GetAddress(arguments); + } + + private static ulong GetAddress(int[] arguments) + { + return (ulong)(uint)arguments[0] << 8; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Nvdec/Vic/VideoImageComposerMeth.cs b/Ryujinx.Graphics.Nvdec/Vic/VideoImageComposerMeth.cs new file mode 100644 index 0000000000..b30cabeaae --- /dev/null +++ b/Ryujinx.Graphics.Nvdec/Vic/VideoImageComposerMeth.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.Vic +{ + enum VideoImageComposerMeth + { + Execute = 0xc0, + SetControlParams = 0x1c1, + SetConfigStructOffset = 0x1c2, + SetOutputSurfaceLumaOffset = 0x1c8, + SetOutputSurfaceChromaUOffset = 0x1c9, + SetOutputSurfaceChromaVOffset = 0x1ca + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.OpenGL/Buffer.cs b/Ryujinx.Graphics.OpenGL/Buffer.cs new file mode 100644 index 0000000000..db3e94ba59 --- /dev/null +++ b/Ryujinx.Graphics.OpenGL/Buffer.cs @@ -0,0 +1,74 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Graphics.GAL; +using System; + +namespace Ryujinx.Graphics.OpenGL +{ + class Buffer : IBuffer + { + public int Handle { get; } + + public Buffer(int size) + { + Handle = GL.GenBuffer(); + + GL.BindBuffer(BufferTarget.CopyWriteBuffer, Handle); + GL.BufferData(BufferTarget.CopyWriteBuffer, size, IntPtr.Zero, BufferUsageHint.DynamicDraw); + } + + public void CopyTo(IBuffer destination, int srcOffset, int dstOffset, int size) + { + GL.BindBuffer(BufferTarget.CopyReadBuffer, Handle); + GL.BindBuffer(BufferTarget.CopyWriteBuffer, ((Buffer)destination).Handle); + + GL.CopyBufferSubData( + BufferTarget.CopyReadBuffer, + BufferTarget.CopyWriteBuffer, + (IntPtr)srcOffset, + (IntPtr)dstOffset, + (IntPtr)size); + } + + public byte[] GetData(int offset, int size) + { + GL.BindBuffer(BufferTarget.CopyReadBuffer, Handle); + + byte[] data = new byte[size]; + + GL.GetBufferSubData(BufferTarget.CopyReadBuffer, (IntPtr)offset, size, data); + + return data; + } + + public void SetData(ReadOnlySpan data) + { + unsafe + { + GL.BindBuffer(BufferTarget.CopyWriteBuffer, Handle); + + fixed (byte* ptr = data) + { + GL.BufferData(BufferTarget.CopyWriteBuffer, data.Length, (IntPtr)ptr, BufferUsageHint.DynamicDraw); + } + } + } + + public void SetData(int offset, ReadOnlySpan data) + { + GL.BindBuffer(BufferTarget.CopyWriteBuffer, Handle); + + unsafe + { + fixed (byte* ptr = data) + { + GL.BufferSubData(BufferTarget.CopyWriteBuffer, (IntPtr)offset, data.Length, (IntPtr)ptr); + } + } + } + + public void Dispose() + { + GL.DeleteBuffer(Handle); + } + } +} diff --git a/Ryujinx.Graphics.OpenGL/Counters.cs b/Ryujinx.Graphics.OpenGL/Counters.cs new file mode 100644 index 0000000000..e82a040f0e --- /dev/null +++ b/Ryujinx.Graphics.OpenGL/Counters.cs @@ -0,0 +1,77 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Graphics.GAL; +using System; + +namespace Ryujinx.Graphics.OpenGL +{ + class Counters + { + private int[] _queryObjects; + + private ulong[] _accumulatedCounters; + + public Counters() + { + int count = Enum.GetNames(typeof(CounterType)).Length; + + _queryObjects = new int[count]; + + _accumulatedCounters = new ulong[count]; + } + + public void Initialize() + { + for (int index = 0; index < _queryObjects.Length; index++) + { + int handle = GL.GenQuery(); + + _queryObjects[index] = handle; + + CounterType type = (CounterType)index; + + GL.BeginQuery(GetTarget(type), handle); + } + } + + public ulong GetCounter(CounterType type) + { + UpdateAccumulatedCounter(type); + + return _accumulatedCounters[(int)type]; + } + + public void ResetCounter(CounterType type) + { + UpdateAccumulatedCounter(type); + + _accumulatedCounters[(int)type] = 0; + } + + private void UpdateAccumulatedCounter(CounterType type) + { + int handle = _queryObjects[(int)type]; + + QueryTarget target = GetTarget(type); + + GL.EndQuery(target); + + GL.GetQueryObject(handle, GetQueryObjectParam.QueryResult, out long result); + + _accumulatedCounters[(int)type] += (ulong)result; + + GL.BeginQuery(target, handle); + } + + private static QueryTarget GetTarget(CounterType type) + { + switch (type) + { + case CounterType.SamplesPassed: return QueryTarget.SamplesPassed; + case CounterType.PrimitivesGenerated: return QueryTarget.PrimitivesGenerated; + case CounterType.TransformFeedbackPrimitivesWritten: return QueryTarget.TransformFeedbackPrimitivesWritten; + } + + return QueryTarget.SamplesPassed; + } + } +} diff --git a/Ryujinx.Graphics.OpenGL/Debugger.cs b/Ryujinx.Graphics.OpenGL/Debugger.cs new file mode 100644 index 0000000000..f34a5048a1 --- /dev/null +++ b/Ryujinx.Graphics.OpenGL/Debugger.cs @@ -0,0 +1,48 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Common.Logging; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.OpenGL +{ + public static class Debugger + { + private static DebugProc _debugCallback; + + public static void Initialize() + { + GL.Enable(EnableCap.DebugOutputSynchronous); + + GL.DebugMessageControl(DebugSourceControl.DontCare, DebugTypeControl.DontCare, DebugSeverityControl.DontCare, 0, (int[])null, true); + + _debugCallback = GLDebugHandler; + + GL.DebugMessageCallback(_debugCallback, IntPtr.Zero); + } + + private static void GLDebugHandler( + DebugSource source, + DebugType type, + int id, + DebugSeverity severity, + int length, + IntPtr message, + IntPtr userParam) + { + string fullMessage = $"{type} {severity} {source} {Marshal.PtrToStringAnsi(message)}"; + + switch (type) + { + case DebugType.DebugTypeError: + Logger.PrintDebug(LogClass.Gpu, fullMessage); + break; + case DebugType.DebugTypePerformance: + Logger.PrintWarning(LogClass.Gpu, fullMessage); + break; + default: + Logger.PrintDebug(LogClass.Gpu, fullMessage); + break; + } + } + } +} diff --git a/Ryujinx.Graphics.OpenGL/EnumConversion.cs b/Ryujinx.Graphics.OpenGL/EnumConversion.cs new file mode 100644 index 0000000000..0d5ea823b3 --- /dev/null +++ b/Ryujinx.Graphics.OpenGL/EnumConversion.cs @@ -0,0 +1,420 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.GAL; + +namespace Ryujinx.Graphics.OpenGL +{ + static class EnumConversion + { + public static TextureWrapMode Convert(this AddressMode mode) + { + switch (mode) + { + case AddressMode.Clamp: + return TextureWrapMode.Clamp; + case AddressMode.Repeat: + return TextureWrapMode.Repeat; + case AddressMode.MirrorClamp: + return (TextureWrapMode)ExtTextureMirrorClamp.MirrorClampExt; + case AddressMode.MirrorClampToEdge: + return (TextureWrapMode)ExtTextureMirrorClamp.MirrorClampToEdgeExt; + case AddressMode.MirrorClampToBorder: + return (TextureWrapMode)ExtTextureMirrorClamp.MirrorClampToBorderExt; + case AddressMode.ClampToBorder: + return TextureWrapMode.ClampToBorder; + case AddressMode.MirroredRepeat: + return TextureWrapMode.MirroredRepeat; + case AddressMode.ClampToEdge: + return TextureWrapMode.ClampToEdge; + } + + Logger.PrintDebug(LogClass.Gpu, $"Invalid {nameof(AddressMode)} enum value: {mode}."); + + return TextureWrapMode.Clamp; + } + + public static All Convert(this BlendFactor factor) + { + switch (factor) + { + case BlendFactor.Zero: + case BlendFactor.ZeroGl: + return All.Zero; + case BlendFactor.One: + case BlendFactor.OneGl: + return All.One; + case BlendFactor.SrcColor: + case BlendFactor.SrcColorGl: + return All.SrcColor; + case BlendFactor.OneMinusSrcColor: + case BlendFactor.OneMinusSrcColorGl: + return All.OneMinusSrcColor; + case BlendFactor.SrcAlpha: + case BlendFactor.SrcAlphaGl: + return All.SrcAlpha; + case BlendFactor.OneMinusSrcAlpha: + case BlendFactor.OneMinusSrcAlphaGl: + return All.OneMinusSrcAlpha; + case BlendFactor.DstAlpha: + case BlendFactor.DstAlphaGl: + return All.DstAlpha; + case BlendFactor.OneMinusDstAlpha: + case BlendFactor.OneMinusDstAlphaGl: + return All.OneMinusDstAlpha; + case BlendFactor.DstColor: + case BlendFactor.DstColorGl: + return All.DstColor; + case BlendFactor.OneMinusDstColor: + case BlendFactor.OneMinusDstColorGl: + return All.OneMinusDstColor; + case BlendFactor.SrcAlphaSaturate: + case BlendFactor.SrcAlphaSaturateGl: + return All.SrcAlphaSaturate; + case BlendFactor.Src1Color: + case BlendFactor.Src1ColorGl: + return All.Src1Color; + case BlendFactor.OneMinusSrc1Color: + case BlendFactor.OneMinusSrc1ColorGl: + return All.OneMinusSrc1Color; + case BlendFactor.Src1Alpha: + case BlendFactor.Src1AlphaGl: + return All.Src1Alpha; + case BlendFactor.OneMinusSrc1Alpha: + case BlendFactor.OneMinusSrc1AlphaGl: + return All.OneMinusSrc1Alpha; + case BlendFactor.ConstantColor: + return All.ConstantColor; + case BlendFactor.OneMinusConstantColor: + return All.OneMinusConstantColor; + case BlendFactor.ConstantAlpha: + return All.ConstantAlpha; + case BlendFactor.OneMinusConstantAlpha: + return All.OneMinusConstantAlpha; + } + + Logger.PrintDebug(LogClass.Gpu, $"Invalid {nameof(BlendFactor)} enum value: {factor}."); + + return All.Zero; + } + + public static BlendEquationMode Convert(this BlendOp op) + { + switch (op) + { + case BlendOp.Add: + case BlendOp.AddGl: + return BlendEquationMode.FuncAdd; + case BlendOp.Subtract: + case BlendOp.SubtractGl: + return BlendEquationMode.FuncSubtract; + case BlendOp.ReverseSubtract: + case BlendOp.ReverseSubtractGl: + return BlendEquationMode.FuncReverseSubtract; + case BlendOp.Minimum: + case BlendOp.MinimumGl: + return BlendEquationMode.Min; + case BlendOp.Maximum: + case BlendOp.MaximumGl: + return BlendEquationMode.Max; + } + + Logger.PrintDebug(LogClass.Gpu, $"Invalid {nameof(BlendOp)} enum value: {op}."); + + return BlendEquationMode.FuncAdd; + } + + public static TextureCompareMode Convert(this CompareMode mode) + { + switch (mode) + { + case CompareMode.None: + return TextureCompareMode.None; + case CompareMode.CompareRToTexture: + return TextureCompareMode.CompareRToTexture; + } + + Logger.PrintDebug(LogClass.Gpu, $"Invalid {nameof(CompareMode)} enum value: {mode}."); + + return TextureCompareMode.None; + } + + public static All Convert(this CompareOp op) + { + switch (op) + { + case CompareOp.Never: + case CompareOp.NeverGl: + return All.Never; + case CompareOp.Less: + case CompareOp.LessGl: + return All.Less; + case CompareOp.Equal: + case CompareOp.EqualGl: + return All.Equal; + case CompareOp.LessOrEqual: + case CompareOp.LessOrEqualGl: + return All.Lequal; + case CompareOp.Greater: + case CompareOp.GreaterGl: + return All.Greater; + case CompareOp.NotEqual: + case CompareOp.NotEqualGl: + return All.Notequal; + case CompareOp.GreaterOrEqual: + case CompareOp.GreaterOrEqualGl: + return All.Gequal; + case CompareOp.Always: + case CompareOp.AlwaysGl: + return All.Always; + } + + Logger.PrintDebug(LogClass.Gpu, $"Invalid {nameof(CompareOp)} enum value: {op}."); + + return All.Never; + } + + public static ClipDepthMode Convert(this DepthMode mode) + { + switch (mode) + { + case DepthMode.MinusOneToOne: + return ClipDepthMode.NegativeOneToOne; + case DepthMode.ZeroToOne: + return ClipDepthMode.ZeroToOne; + } + + Logger.PrintDebug(LogClass.Gpu, $"Invalid {nameof(DepthMode)} enum value: {mode}."); + + return ClipDepthMode.NegativeOneToOne; + } + + public static All Convert(this DepthStencilMode mode) + { + switch (mode) + { + case DepthStencilMode.Depth: + return All.Depth; + case DepthStencilMode.Stencil: + return All.Stencil; + } + + Logger.PrintDebug(LogClass.Gpu, $"Invalid {nameof(DepthStencilMode)} enum value: {mode}."); + + return All.Depth; + } + + public static CullFaceMode Convert(this Face face) + { + switch (face) + { + case Face.Back: + return CullFaceMode.Back; + case Face.Front: + return CullFaceMode.Front; + case Face.FrontAndBack: + return CullFaceMode.FrontAndBack; + } + + Logger.PrintDebug(LogClass.Gpu, $"Invalid {nameof(Face)} enum value: {face}."); + + return CullFaceMode.Back; + } + + public static FrontFaceDirection Convert(this FrontFace frontFace) + { + switch (frontFace) + { + case FrontFace.Clockwise: + return FrontFaceDirection.Cw; + case FrontFace.CounterClockwise: + return FrontFaceDirection.Ccw; + } + + Logger.PrintDebug(LogClass.Gpu, $"Invalid {nameof(FrontFace)} enum value: {frontFace}."); + + return FrontFaceDirection.Cw; + } + + public static DrawElementsType Convert(this IndexType type) + { + switch (type) + { + case IndexType.UByte: + return DrawElementsType.UnsignedByte; + case IndexType.UShort: + return DrawElementsType.UnsignedShort; + case IndexType.UInt: + return DrawElementsType.UnsignedInt; + } + + Logger.PrintDebug(LogClass.Gpu, $"Invalid {nameof(IndexType)} enum value: {type}."); + + return DrawElementsType.UnsignedByte; + } + + public static TextureMagFilter Convert(this MagFilter filter) + { + switch (filter) + { + case MagFilter.Nearest: + return TextureMagFilter.Nearest; + case MagFilter.Linear: + return TextureMagFilter.Linear; + } + + Logger.PrintDebug(LogClass.Gpu, $"Invalid {nameof(MagFilter)} enum value: {filter}."); + + return TextureMagFilter.Nearest; + } + + public static TextureMinFilter Convert(this MinFilter filter) + { + switch (filter) + { + case MinFilter.Nearest: + return TextureMinFilter.Nearest; + case MinFilter.Linear: + return TextureMinFilter.Linear; + case MinFilter.NearestMipmapNearest: + return TextureMinFilter.NearestMipmapNearest; + case MinFilter.LinearMipmapNearest: + return TextureMinFilter.LinearMipmapNearest; + case MinFilter.NearestMipmapLinear: + return TextureMinFilter.NearestMipmapLinear; + case MinFilter.LinearMipmapLinear: + return TextureMinFilter.LinearMipmapLinear; + } + + Logger.PrintDebug(LogClass.Gpu, $"Invalid {nameof(MinFilter)} enum value: {filter}."); + + return TextureMinFilter.Nearest; + } + + public static PrimitiveType Convert(this PrimitiveTopology topology) + { + switch (topology) + { + case PrimitiveTopology.Points: + return PrimitiveType.Points; + case PrimitiveTopology.Lines: + return PrimitiveType.Lines; + case PrimitiveTopology.LineLoop: + return PrimitiveType.LineLoop; + case PrimitiveTopology.LineStrip: + return PrimitiveType.LineStrip; + case PrimitiveTopology.Triangles: + return PrimitiveType.Triangles; + case PrimitiveTopology.TriangleStrip: + return PrimitiveType.TriangleStrip; + case PrimitiveTopology.TriangleFan: + return PrimitiveType.TriangleFan; + case PrimitiveTopology.Quads: + return PrimitiveType.Quads; + case PrimitiveTopology.QuadStrip: + return PrimitiveType.QuadStrip; + case PrimitiveTopology.Polygon: + return PrimitiveType.Polygon; + case PrimitiveTopology.LinesAdjacency: + return PrimitiveType.LinesAdjacency; + case PrimitiveTopology.LineStripAdjacency: + return PrimitiveType.LineStripAdjacency; + case PrimitiveTopology.TrianglesAdjacency: + return PrimitiveType.TrianglesAdjacency; + case PrimitiveTopology.TriangleStripAdjacency: + return PrimitiveType.TriangleStripAdjacency; + case PrimitiveTopology.Patches: + return PrimitiveType.Patches; + } + + Logger.PrintDebug(LogClass.Gpu, $"Invalid {nameof(PrimitiveTopology)} enum value: {topology}."); + + return PrimitiveType.Points; + } + + public static OpenTK.Graphics.OpenGL.StencilOp Convert(this GAL.StencilOp op) + { + switch (op) + { + case GAL.StencilOp.Keep: + return OpenTK.Graphics.OpenGL.StencilOp.Keep; + case GAL.StencilOp.Zero: + return OpenTK.Graphics.OpenGL.StencilOp.Zero; + case GAL.StencilOp.Replace: + return OpenTK.Graphics.OpenGL.StencilOp.Replace; + case GAL.StencilOp.IncrementAndClamp: + return OpenTK.Graphics.OpenGL.StencilOp.Incr; + case GAL.StencilOp.DecrementAndClamp: + return OpenTK.Graphics.OpenGL.StencilOp.Decr; + case GAL.StencilOp.Invert: + return OpenTK.Graphics.OpenGL.StencilOp.Invert; + case GAL.StencilOp.IncrementAndWrap: + return OpenTK.Graphics.OpenGL.StencilOp.IncrWrap; + case GAL.StencilOp.DecrementAndWrap: + return OpenTK.Graphics.OpenGL.StencilOp.DecrWrap; + } + + Logger.PrintDebug(LogClass.Gpu, $"Invalid {nameof(GAL.StencilOp)} enum value: {op}."); + + return OpenTK.Graphics.OpenGL.StencilOp.Keep; + } + + public static All Convert(this SwizzleComponent swizzleComponent) + { + switch (swizzleComponent) + { + case SwizzleComponent.Zero: + return All.Zero; + case SwizzleComponent.One: + return All.One; + case SwizzleComponent.Red: + return All.Red; + case SwizzleComponent.Green: + return All.Green; + case SwizzleComponent.Blue: + return All.Blue; + case SwizzleComponent.Alpha: + return All.Alpha; + } + + Logger.PrintDebug(LogClass.Gpu, $"Invalid {nameof(SwizzleComponent)} enum value: {swizzleComponent}."); + + return All.Zero; + } + + public static ImageTarget ConvertToImageTarget(this Target target) + { + return (ImageTarget)target.Convert(); + } + + public static TextureTarget Convert(this Target target) + { + switch (target) + { + case Target.Texture1D: + return TextureTarget.Texture1D; + case Target.Texture2D: + return TextureTarget.Texture2D; + case Target.Texture3D: + return TextureTarget.Texture3D; + case Target.Texture1DArray: + return TextureTarget.Texture1DArray; + case Target.Texture2DArray: + return TextureTarget.Texture2DArray; + case Target.Texture2DMultisample: + return TextureTarget.Texture2DMultisample; + case Target.Rectangle: + return TextureTarget.TextureRectangle; + case Target.Cubemap: + return TextureTarget.TextureCubeMap; + case Target.CubemapArray: + return TextureTarget.TextureCubeMapArray; + case Target.TextureBuffer: + return TextureTarget.TextureBuffer; + } + + Logger.PrintDebug(LogClass.Gpu, $"Invalid {nameof(Target)} enum value: {target}."); + + return TextureTarget.Texture2D; + } + } +} diff --git a/Ryujinx.Graphics.OpenGL/FormatInfo.cs b/Ryujinx.Graphics.OpenGL/FormatInfo.cs new file mode 100644 index 0000000000..6aa70691d3 --- /dev/null +++ b/Ryujinx.Graphics.OpenGL/FormatInfo.cs @@ -0,0 +1,45 @@ +using OpenTK.Graphics.OpenGL; + +namespace Ryujinx.Graphics.OpenGL +{ + struct FormatInfo + { + public int Components { get; } + public bool Normalized { get; } + public bool Scaled { get; } + + public PixelInternalFormat PixelInternalFormat { get; } + public PixelFormat PixelFormat { get; } + public PixelType PixelType { get; } + + public bool IsCompressed { get; } + + public FormatInfo( + int components, + bool normalized, + bool scaled, + All pixelInternalFormat, + PixelFormat pixelFormat, + PixelType pixelType) + { + Components = components; + Normalized = normalized; + Scaled = scaled; + PixelInternalFormat = (PixelInternalFormat)pixelInternalFormat; + PixelFormat = pixelFormat; + PixelType = pixelType; + IsCompressed = false; + } + + public FormatInfo(int components, bool normalized, bool scaled, All pixelFormat) + { + Components = components; + Normalized = normalized; + Scaled = scaled; + PixelInternalFormat = 0; + PixelFormat = (PixelFormat)pixelFormat; + PixelType = 0; + IsCompressed = true; + } + } +} diff --git a/Ryujinx.Graphics.OpenGL/FormatTable.cs b/Ryujinx.Graphics.OpenGL/FormatTable.cs new file mode 100644 index 0000000000..b178553cd8 --- /dev/null +++ b/Ryujinx.Graphics.OpenGL/FormatTable.cs @@ -0,0 +1,183 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Graphics.GAL; +using System; + +namespace Ryujinx.Graphics.OpenGL +{ + struct FormatTable + { + private static FormatInfo[] _table; + + static FormatTable() + { + _table = new FormatInfo[Enum.GetNames(typeof(Format)).Length]; + + Add(Format.R8Unorm, new FormatInfo(1, true, false, All.R8, PixelFormat.Red, PixelType.UnsignedByte)); + Add(Format.R8Snorm, new FormatInfo(1, true, false, All.R8Snorm, PixelFormat.Red, PixelType.Byte)); + Add(Format.R8Uint, new FormatInfo(1, false, false, All.R8ui, PixelFormat.RedInteger, PixelType.UnsignedByte)); + Add(Format.R8Sint, new FormatInfo(1, false, false, All.R8i, PixelFormat.RedInteger, PixelType.Byte)); + Add(Format.R16Float, new FormatInfo(1, false, false, All.R16f, PixelFormat.Red, PixelType.HalfFloat)); + Add(Format.R16Unorm, new FormatInfo(1, true, false, All.R16, PixelFormat.Red, PixelType.UnsignedShort)); + Add(Format.R16Snorm, new FormatInfo(1, true, false, All.R16Snorm, PixelFormat.Red, PixelType.Short)); + Add(Format.R16Uint, new FormatInfo(1, false, false, All.R16ui, PixelFormat.RedInteger, PixelType.UnsignedShort)); + Add(Format.R16Sint, new FormatInfo(1, false, false, All.R16i, PixelFormat.RedInteger, PixelType.Short)); + Add(Format.R32Float, new FormatInfo(1, false, false, All.R32f, PixelFormat.Red, PixelType.Float)); + Add(Format.R32Uint, new FormatInfo(1, false, false, All.R32ui, PixelFormat.RedInteger, PixelType.UnsignedInt)); + Add(Format.R32Sint, new FormatInfo(1, false, false, All.R32i, PixelFormat.RedInteger, PixelType.Int)); + Add(Format.R8G8Unorm, new FormatInfo(2, true, false, All.Rg8, PixelFormat.Rg, PixelType.UnsignedByte)); + Add(Format.R8G8Snorm, new FormatInfo(2, true, false, All.Rg8Snorm, PixelFormat.Rg, PixelType.Byte)); + Add(Format.R8G8Uint, new FormatInfo(2, false, false, All.Rg8ui, PixelFormat.RgInteger, PixelType.UnsignedByte)); + Add(Format.R8G8Sint, new FormatInfo(2, false, false, All.Rg8i, PixelFormat.RgInteger, PixelType.Byte)); + Add(Format.R16G16Float, new FormatInfo(2, false, false, All.Rg16f, PixelFormat.Rg, PixelType.HalfFloat)); + Add(Format.R16G16Unorm, new FormatInfo(2, true, false, All.Rg16, PixelFormat.Rg, PixelType.UnsignedShort)); + Add(Format.R16G16Snorm, new FormatInfo(2, true, false, All.Rg16Snorm, PixelFormat.Rg, PixelType.Short)); + Add(Format.R16G16Uint, new FormatInfo(2, false, false, All.Rg16ui, PixelFormat.RgInteger, PixelType.UnsignedShort)); + Add(Format.R16G16Sint, new FormatInfo(2, false, false, All.Rg16i, PixelFormat.RgInteger, PixelType.Short)); + Add(Format.R32G32Float, new FormatInfo(2, false, false, All.Rg32f, PixelFormat.Rg, PixelType.Float)); + Add(Format.R32G32Uint, new FormatInfo(2, false, false, All.Rg32ui, PixelFormat.RgInteger, PixelType.UnsignedInt)); + Add(Format.R32G32Sint, new FormatInfo(2, false, false, All.Rg32i, PixelFormat.RgInteger, PixelType.Int)); + Add(Format.R8G8B8Unorm, new FormatInfo(3, true, false, All.Rgb8, PixelFormat.Rgb, PixelType.UnsignedByte)); + Add(Format.R8G8B8Snorm, new FormatInfo(3, true, false, All.Rgb8Snorm, PixelFormat.Rgb, PixelType.Byte)); + Add(Format.R8G8B8Uint, new FormatInfo(3, false, false, All.Rgb8ui, PixelFormat.RgbInteger, PixelType.UnsignedByte)); + Add(Format.R8G8B8Sint, new FormatInfo(3, false, false, All.Rgb8i, PixelFormat.RgbInteger, PixelType.Byte)); + Add(Format.R16G16B16Float, new FormatInfo(3, false, false, All.Rgb16f, PixelFormat.Rgb, PixelType.HalfFloat)); + Add(Format.R16G16B16Unorm, new FormatInfo(3, true, false, All.Rgb16, PixelFormat.Rgb, PixelType.UnsignedShort)); + Add(Format.R16G16B16Snorm, new FormatInfo(3, true, false, All.Rgb16Snorm, PixelFormat.Rgb, PixelType.Short)); + Add(Format.R16G16B16Uint, new FormatInfo(3, false, false, All.Rgb16ui, PixelFormat.RgbInteger, PixelType.UnsignedShort)); + Add(Format.R16G16B16Sint, new FormatInfo(3, false, false, All.Rgb16i, PixelFormat.RgbInteger, PixelType.Short)); + Add(Format.R32G32B32Float, new FormatInfo(3, false, false, All.Rgb32f, PixelFormat.Rgb, PixelType.Float)); + Add(Format.R32G32B32Uint, new FormatInfo(3, false, false, All.Rgb32ui, PixelFormat.RgbInteger, PixelType.UnsignedInt)); + Add(Format.R32G32B32Sint, new FormatInfo(3, false, false, All.Rgb32i, PixelFormat.RgbInteger, PixelType.Int)); + Add(Format.R8G8B8A8Unorm, new FormatInfo(4, true, false, All.Rgba8, PixelFormat.Rgba, PixelType.UnsignedByte)); + Add(Format.R8G8B8A8Snorm, new FormatInfo(4, true, false, All.Rgba8Snorm, PixelFormat.Rgba, PixelType.Byte)); + Add(Format.R8G8B8A8Uint, new FormatInfo(4, false, false, All.Rgba8ui, PixelFormat.RgbaInteger, PixelType.UnsignedByte)); + Add(Format.R8G8B8A8Sint, new FormatInfo(4, false, false, All.Rgba8i, PixelFormat.RgbaInteger, PixelType.Byte)); + Add(Format.R16G16B16A16Float, new FormatInfo(4, false, false, All.Rgba16f, PixelFormat.Rgba, PixelType.HalfFloat)); + Add(Format.R16G16B16A16Unorm, new FormatInfo(4, true, false, All.Rgba16, PixelFormat.Rgba, PixelType.UnsignedShort)); + Add(Format.R16G16B16A16Snorm, new FormatInfo(4, true, false, All.Rgba16Snorm, PixelFormat.Rgba, PixelType.Short)); + Add(Format.R16G16B16A16Uint, new FormatInfo(4, false, false, All.Rgba16ui, PixelFormat.RgbaInteger, PixelType.UnsignedShort)); + Add(Format.R16G16B16A16Sint, new FormatInfo(4, false, false, All.Rgba16i, PixelFormat.RgbaInteger, PixelType.Short)); + Add(Format.R32G32B32A32Float, new FormatInfo(4, false, false, All.Rgba32f, PixelFormat.Rgba, PixelType.Float)); + Add(Format.R32G32B32A32Uint, new FormatInfo(4, false, false, All.Rgba32ui, PixelFormat.RgbaInteger, PixelType.UnsignedInt)); + Add(Format.R32G32B32A32Sint, new FormatInfo(4, false, false, All.Rgba32i, PixelFormat.RgbaInteger, PixelType.Int)); + Add(Format.S8Uint, new FormatInfo(1, false, false, All.StencilIndex8, PixelFormat.StencilIndex, PixelType.UnsignedByte)); + Add(Format.D16Unorm, new FormatInfo(1, false, false, All.DepthComponent16, PixelFormat.DepthComponent, PixelType.UnsignedShort)); + Add(Format.D24X8Unorm, new FormatInfo(1, false, false, All.DepthComponent24, PixelFormat.DepthComponent, PixelType.UnsignedInt)); + Add(Format.D32Float, new FormatInfo(1, false, false, All.DepthComponent32f, PixelFormat.DepthComponent, PixelType.Float)); + Add(Format.D24UnormS8Uint, new FormatInfo(1, false, false, All.Depth24Stencil8, PixelFormat.DepthStencil, PixelType.UnsignedInt248)); + Add(Format.D32FloatS8Uint, new FormatInfo(1, false, false, All.Depth32fStencil8, PixelFormat.DepthStencil, PixelType.Float32UnsignedInt248Rev)); + Add(Format.R8G8B8X8Srgb, new FormatInfo(4, false, false, All.Srgb8, PixelFormat.Rgba, PixelType.UnsignedByte)); + Add(Format.R8G8B8A8Srgb, new FormatInfo(4, false, false, All.Srgb8Alpha8, PixelFormat.Rgba, PixelType.UnsignedByte)); + Add(Format.R4G4B4A4Unorm, new FormatInfo(4, true, false, All.Rgba4, PixelFormat.Rgba, PixelType.UnsignedShort4444Reversed)); + Add(Format.R5G5B5X1Unorm, new FormatInfo(4, true, false, All.Rgb5, PixelFormat.Rgb, PixelType.UnsignedShort1555Reversed)); + Add(Format.R5G5B5A1Unorm, new FormatInfo(4, true, false, All.Rgb5A1, PixelFormat.Rgba, PixelType.UnsignedShort1555Reversed)); + Add(Format.R5G6B5Unorm, new FormatInfo(3, true, false, All.Rgb565, PixelFormat.Rgb, PixelType.UnsignedShort565Reversed)); + Add(Format.R10G10B10A2Unorm, new FormatInfo(4, true, false, All.Rgb10A2, PixelFormat.Rgba, PixelType.UnsignedInt2101010Reversed)); + Add(Format.R10G10B10A2Uint, new FormatInfo(4, false, false, All.Rgb10A2ui, PixelFormat.RgbaInteger, PixelType.UnsignedInt2101010Reversed)); + Add(Format.R11G11B10Float, new FormatInfo(3, false, false, All.R11fG11fB10f, PixelFormat.Rgb, PixelType.UnsignedInt10F11F11FRev)); + Add(Format.R9G9B9E5Float, new FormatInfo(3, false, false, All.Rgb9E5, PixelFormat.Rgb, PixelType.UnsignedInt5999Rev)); + Add(Format.Bc1RgbUnorm, new FormatInfo(2, true, false, All.CompressedRgbS3tcDxt1Ext)); + Add(Format.Bc1RgbaUnorm, new FormatInfo(1, true, false, All.CompressedRgbaS3tcDxt1Ext)); + Add(Format.Bc2Unorm, new FormatInfo(1, true, false, All.CompressedRgbaS3tcDxt3Ext)); + Add(Format.Bc3Unorm, new FormatInfo(1, true, false, All.CompressedRgbaS3tcDxt5Ext)); + Add(Format.Bc1RgbSrgb, new FormatInfo(2, false, false, All.CompressedSrgbS3tcDxt1Ext)); + Add(Format.Bc1RgbaSrgb, new FormatInfo(1, true, false, All.CompressedSrgbAlphaS3tcDxt1Ext)); + Add(Format.Bc2Srgb, new FormatInfo(1, false, false, All.CompressedSrgbAlphaS3tcDxt3Ext)); + Add(Format.Bc3Srgb, new FormatInfo(1, false, false, All.CompressedSrgbAlphaS3tcDxt5Ext)); + Add(Format.Bc4Unorm, new FormatInfo(1, true, false, All.CompressedRedRgtc1)); + Add(Format.Bc4Snorm, new FormatInfo(1, true, false, All.CompressedSignedRedRgtc1)); + Add(Format.Bc5Unorm, new FormatInfo(1, true, false, All.CompressedRgRgtc2)); + Add(Format.Bc5Snorm, new FormatInfo(1, true, false, All.CompressedSignedRgRgtc2)); + Add(Format.Bc7Unorm, new FormatInfo(1, true, false, All.CompressedRgbaBptcUnorm)); + Add(Format.Bc7Srgb, new FormatInfo(1, false, false, All.CompressedSrgbAlphaBptcUnorm)); + Add(Format.Bc6HSfloat, new FormatInfo(1, false, false, All.CompressedRgbBptcSignedFloat)); + Add(Format.Bc6HUfloat, new FormatInfo(1, false, false, All.CompressedRgbBptcUnsignedFloat)); + Add(Format.R8Uscaled, new FormatInfo(1, false, true, All.R8ui, PixelFormat.RedInteger, PixelType.UnsignedByte)); + Add(Format.R8Sscaled, new FormatInfo(1, false, true, All.R8i, PixelFormat.RedInteger, PixelType.Byte)); + Add(Format.R16Uscaled, new FormatInfo(1, false, true, All.R16ui, PixelFormat.RedInteger, PixelType.UnsignedShort)); + Add(Format.R16Sscaled, new FormatInfo(1, false, true, All.R16i, PixelFormat.RedInteger, PixelType.Short)); + Add(Format.R32Uscaled, new FormatInfo(1, false, true, All.R32ui, PixelFormat.RedInteger, PixelType.UnsignedInt)); + Add(Format.R32Sscaled, new FormatInfo(1, false, true, All.R32i, PixelFormat.RedInteger, PixelType.Int)); + Add(Format.R8G8Uscaled, new FormatInfo(2, false, true, All.Rg8ui, PixelFormat.RgInteger, PixelType.UnsignedByte)); + Add(Format.R8G8Sscaled, new FormatInfo(2, false, true, All.Rg8i, PixelFormat.RgInteger, PixelType.Byte)); + Add(Format.R16G16Uscaled, new FormatInfo(2, false, true, All.Rg16ui, PixelFormat.RgInteger, PixelType.UnsignedShort)); + Add(Format.R16G16Sscaled, new FormatInfo(2, false, true, All.Rg16i, PixelFormat.RgInteger, PixelType.Short)); + Add(Format.R32G32Uscaled, new FormatInfo(2, false, true, All.Rg32ui, PixelFormat.RgInteger, PixelType.UnsignedInt)); + Add(Format.R32G32Sscaled, new FormatInfo(2, false, true, All.Rg32i, PixelFormat.RgInteger, PixelType.Int)); + Add(Format.R8G8B8Uscaled, new FormatInfo(3, false, true, All.Rgb8ui, PixelFormat.RgbInteger, PixelType.UnsignedByte)); + Add(Format.R8G8B8Sscaled, new FormatInfo(3, false, true, All.Rgb8i, PixelFormat.RgbInteger, PixelType.Byte)); + Add(Format.R16G16B16Uscaled, new FormatInfo(3, false, true, All.Rgb16ui, PixelFormat.RgbInteger, PixelType.UnsignedShort)); + Add(Format.R16G16B16Sscaled, new FormatInfo(3, false, true, All.Rgb16i, PixelFormat.RgbInteger, PixelType.Short)); + Add(Format.R32G32B32Uscaled, new FormatInfo(3, false, true, All.Rgb32ui, PixelFormat.RgbInteger, PixelType.UnsignedInt)); + Add(Format.R32G32B32Sscaled, new FormatInfo(3, false, true, All.Rgb32i, PixelFormat.RgbInteger, PixelType.Int)); + Add(Format.R8G8B8A8Uscaled, new FormatInfo(4, false, true, All.Rgba8ui, PixelFormat.RgbaInteger, PixelType.UnsignedByte)); + Add(Format.R8G8B8A8Sscaled, new FormatInfo(4, false, true, All.Rgba8i, PixelFormat.RgbaInteger, PixelType.Byte)); + Add(Format.R16G16B16A16Uscaled, new FormatInfo(4, false, true, All.Rgba16ui, PixelFormat.RgbaInteger, PixelType.UnsignedShort)); + Add(Format.R16G16B16A16Sscaled, new FormatInfo(4, false, true, All.Rgba16i, PixelFormat.RgbaInteger, PixelType.Short)); + Add(Format.R32G32B32A32Uscaled, new FormatInfo(4, false, true, All.Rgba32ui, PixelFormat.RgbaInteger, PixelType.UnsignedInt)); + Add(Format.R32G32B32A32Sscaled, new FormatInfo(4, false, true, All.Rgba32i, PixelFormat.RgbaInteger, PixelType.Int)); + Add(Format.R10G10B10A2Snorm, new FormatInfo(4, true, false, All.Rgb10A2, PixelFormat.Rgba, (PixelType)All.Int2101010Rev)); + Add(Format.R10G10B10A2Sint, new FormatInfo(4, false, false, All.Rgb10A2, PixelFormat.RgbaInteger, (PixelType)All.Int2101010Rev)); + Add(Format.R10G10B10A2Uscaled, new FormatInfo(4, false, true, All.Rgb10A2ui, PixelFormat.RgbaInteger, PixelType.UnsignedInt2101010Reversed)); + Add(Format.R10G10B10A2Sscaled, new FormatInfo(4, false, true, All.Rgb10A2, PixelFormat.RgbaInteger, PixelType.UnsignedInt2101010Reversed)); + Add(Format.R8G8B8X8Unorm, new FormatInfo(4, true, false, All.Rgb8, PixelFormat.Rgba, PixelType.UnsignedByte)); + Add(Format.R8G8B8X8Snorm, new FormatInfo(4, true, false, All.Rgb8Snorm, PixelFormat.Rgba, PixelType.Byte)); + Add(Format.R8G8B8X8Uint, new FormatInfo(4, false, false, All.Rgb8ui, PixelFormat.RgbaInteger, PixelType.UnsignedByte)); + Add(Format.R8G8B8X8Sint, new FormatInfo(4, false, false, All.Rgb8i, PixelFormat.RgbaInteger, PixelType.Byte)); + Add(Format.R16G16B16X16Float, new FormatInfo(4, false, false, All.Rgb16f, PixelFormat.Rgba, PixelType.HalfFloat)); + Add(Format.R16G16B16X16Unorm, new FormatInfo(4, true, false, All.Rgb16, PixelFormat.Rgba, PixelType.UnsignedShort)); + Add(Format.R16G16B16X16Snorm, new FormatInfo(4, true, false, All.Rgb16Snorm, PixelFormat.Rgba, PixelType.Short)); + Add(Format.R16G16B16X16Uint, new FormatInfo(4, false, false, All.Rgb16ui, PixelFormat.RgbaInteger, PixelType.UnsignedShort)); + Add(Format.R16G16B16X16Sint, new FormatInfo(4, false, false, All.Rgb16i, PixelFormat.RgbaInteger, PixelType.Short)); + Add(Format.R32G32B32X32Float, new FormatInfo(4, false, false, All.Rgb32f, PixelFormat.Rgba, PixelType.Float)); + Add(Format.R32G32B32X32Uint, new FormatInfo(4, false, false, All.Rgb32ui, PixelFormat.RgbaInteger, PixelType.UnsignedInt)); + Add(Format.R32G32B32X32Sint, new FormatInfo(4, false, false, All.Rgb32i, PixelFormat.RgbaInteger, PixelType.Int)); + Add(Format.Astc4x4Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc4X4Khr)); + Add(Format.Astc5x4Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc5X4Khr)); + Add(Format.Astc5x5Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc5X5Khr)); + Add(Format.Astc6x5Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc6X5Khr)); + Add(Format.Astc6x6Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc6X6Khr)); + Add(Format.Astc8x5Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc8X5Khr)); + Add(Format.Astc8x6Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc8X6Khr)); + Add(Format.Astc8x8Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc8X8Khr)); + Add(Format.Astc10x5Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc10X5Khr)); + Add(Format.Astc10x6Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc10X6Khr)); + Add(Format.Astc10x8Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc10X8Khr)); + Add(Format.Astc10x10Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc10X10Khr)); + Add(Format.Astc12x10Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc12X10Khr)); + Add(Format.Astc12x12Unorm, new FormatInfo(1, true, false, All.CompressedRgbaAstc12X12Khr)); + Add(Format.Astc4x4Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc4X4Khr)); + Add(Format.Astc5x4Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc5X4Khr)); + Add(Format.Astc5x5Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc5X5Khr)); + Add(Format.Astc6x5Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc6X5Khr)); + Add(Format.Astc6x6Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc6X6Khr)); + Add(Format.Astc8x5Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc8X5Khr)); + Add(Format.Astc8x6Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc8X6Khr)); + Add(Format.Astc8x8Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc8X8Khr)); + Add(Format.Astc10x5Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc10X5Khr)); + Add(Format.Astc10x6Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc10X6Khr)); + Add(Format.Astc10x8Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc10X8Khr)); + Add(Format.Astc10x10Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc10X10Khr)); + Add(Format.Astc12x10Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc12X10Khr)); + Add(Format.Astc12x12Srgb, new FormatInfo(1, false, false, All.CompressedSrgb8Alpha8Astc12X12Khr)); + Add(Format.B5G6R5Unorm, new FormatInfo(3, true, false, All.Rgb565, PixelFormat.Rgb, PixelType.UnsignedShort565)); + Add(Format.B5G5R5X1Unorm, new FormatInfo(4, true, false, All.Rgb5, PixelFormat.Bgra, PixelType.UnsignedShort5551)); + Add(Format.B5G5R5A1Unorm, new FormatInfo(4, true, false, All.Rgb5A1, PixelFormat.Bgra, PixelType.UnsignedShort5551)); + Add(Format.A1B5G5R5Unorm, new FormatInfo(4, true, false, All.Rgb5A1, PixelFormat.Bgra, PixelType.UnsignedShort1555Reversed)); + Add(Format.B8G8R8X8Unorm, new FormatInfo(4, true, false, All.Rgba8, PixelFormat.Bgra, PixelType.UnsignedByte)); + Add(Format.B8G8R8A8Unorm, new FormatInfo(4, true, false, All.Rgba8, PixelFormat.Bgra, PixelType.UnsignedByte)); + Add(Format.B8G8R8X8Srgb, new FormatInfo(4, false, false, All.Srgb8, PixelFormat.BgraInteger, PixelType.UnsignedByte)); + Add(Format.B8G8R8A8Srgb, new FormatInfo(4, false, false, All.Srgb8Alpha8, PixelFormat.BgraInteger, PixelType.UnsignedByte)); + } + + private static void Add(Format format, FormatInfo info) + { + _table[(int)format] = info; + } + + public static FormatInfo GetFormatInfo(Format format) + { + return _table[(int)format]; + } + } +} diff --git a/Ryujinx.Graphics.OpenGL/Framebuffer.cs b/Ryujinx.Graphics.OpenGL/Framebuffer.cs new file mode 100644 index 0000000000..d416bd03ff --- /dev/null +++ b/Ryujinx.Graphics.OpenGL/Framebuffer.cs @@ -0,0 +1,106 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Graphics.GAL; +using System; + +namespace Ryujinx.Graphics.OpenGL +{ + class Framebuffer : IDisposable + { + public int Handle { get; private set; } + + private FramebufferAttachment _lastDsAttachment; + + public Framebuffer() + { + Handle = GL.GenFramebuffer(); + } + + public void Bind() + { + GL.BindFramebuffer(FramebufferTarget.Framebuffer, Handle); + } + + public void AttachColor(int index, TextureView color) + { + GL.FramebufferTexture( + FramebufferTarget.Framebuffer, + FramebufferAttachment.ColorAttachment0 + index, + color?.Handle ?? 0, + 0); + } + + public void AttachDepthStencil(TextureView depthStencil) + { + // Detach the last depth/stencil buffer if there is any. + if (_lastDsAttachment != 0) + { + GL.FramebufferTexture(FramebufferTarget.Framebuffer, _lastDsAttachment, 0, 0); + } + + if (depthStencil != null) + { + FramebufferAttachment attachment; + + if (IsPackedDepthStencilFormat(depthStencil.Format)) + { + attachment = FramebufferAttachment.DepthStencilAttachment; + } + else if (IsDepthOnlyFormat(depthStencil.Format)) + { + attachment = FramebufferAttachment.DepthAttachment; + } + else + { + attachment = FramebufferAttachment.StencilAttachment; + } + + GL.FramebufferTexture( + FramebufferTarget.Framebuffer, + attachment, + depthStencil.Handle, + 0); + + _lastDsAttachment = attachment; + } + else + { + _lastDsAttachment = 0; + } + } + + public void SetDrawBuffers(int colorsCount) + { + DrawBuffersEnum[] drawBuffers = new DrawBuffersEnum[colorsCount]; + + for (int index = 0; index < colorsCount; index++) + { + drawBuffers[index] = DrawBuffersEnum.ColorAttachment0 + index; + } + + GL.DrawBuffers(colorsCount, drawBuffers); + } + + private static bool IsPackedDepthStencilFormat(Format format) + { + return format == Format.D24UnormS8Uint || + format == Format.D32FloatS8Uint; + } + + private static bool IsDepthOnlyFormat(Format format) + { + return format == Format.D16Unorm || + format == Format.D24X8Unorm || + format == Format.D32Float; + } + + public void Dispose() + { + if (Handle != 0) + { + GL.DeleteFramebuffer(Handle); + + Handle = 0; + } + } + } +} diff --git a/Ryujinx.Graphics.OpenGL/HwCapabilities.cs b/Ryujinx.Graphics.OpenGL/HwCapabilities.cs new file mode 100644 index 0000000000..f97bd2ea47 --- /dev/null +++ b/Ryujinx.Graphics.OpenGL/HwCapabilities.cs @@ -0,0 +1,46 @@ +using OpenTK.Graphics.OpenGL; +using System; + +namespace Ryujinx.Graphics.OpenGL +{ + static class HwCapabilities + { + private static Lazy _supportsAstcCompression = new Lazy(() => HasExtension("GL_KHR_texture_compression_astc_ldr")); + + private static Lazy _maximumComputeSharedMemorySize = new Lazy(() => GetLimit(All.MaxComputeSharedMemorySize)); + private static Lazy _storageBufferOffsetAlignment = new Lazy(() => GetLimit(All.ShaderStorageBufferOffsetAlignment)); + + private static Lazy _isNvidiaDriver = new Lazy(() => IsNvidiaDriver()); + + public static bool SupportsAstcCompression => _supportsAstcCompression.Value; + public static bool SupportsNonConstantTextureOffset => _isNvidiaDriver.Value; + + public static int MaximumComputeSharedMemorySize => _maximumComputeSharedMemorySize.Value; + public static int StorageBufferOffsetAlignment => _storageBufferOffsetAlignment.Value; + + private static bool HasExtension(string name) + { + int numExtensions = GL.GetInteger(GetPName.NumExtensions); + + for (int extension = 0; extension < numExtensions; extension++) + { + if (GL.GetString(StringNameIndexed.Extensions, extension) == name) + { + return true; + } + } + + return false; + } + + private static int GetLimit(All name) + { + return GL.GetInteger((GetPName)name); + } + + private static bool IsNvidiaDriver() + { + return GL.GetString(StringName.Vendor).Equals("NVIDIA Corporation"); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.OpenGL/Pipeline.cs b/Ryujinx.Graphics.OpenGL/Pipeline.cs new file mode 100644 index 0000000000..e308becfe6 --- /dev/null +++ b/Ryujinx.Graphics.OpenGL/Pipeline.cs @@ -0,0 +1,932 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Shader; +using System; + +namespace Ryujinx.Graphics.OpenGL +{ + class Pipeline : IPipeline, IDisposable + { + private Program _program; + + private VertexArray _vertexArray; + private Framebuffer _framebuffer; + + private IntPtr _indexBaseOffset; + + private DrawElementsType _elementsType; + + private PrimitiveType _primitiveType; + + private int _stencilFrontMask; + private bool _depthMask; + private bool _depthTest; + private bool _hasDepthBuffer; + + private TextureView _unit0Texture; + + private ClipOrigin _clipOrigin; + private ClipDepthMode _clipDepthMode; + + private uint[] _componentMasks; + + internal Pipeline() + { + _clipOrigin = ClipOrigin.LowerLeft; + _clipDepthMode = ClipDepthMode.NegativeOneToOne; + } + + public void Barrier() + { + GL.MemoryBarrier(MemoryBarrierFlags.AllBarrierBits); + } + + public void ClearRenderTargetColor(int index, uint componentMask, ColorF color) + { + GL.ColorMask( + index, + (componentMask & 1) != 0, + (componentMask & 2) != 0, + (componentMask & 4) != 0, + (componentMask & 8) != 0); + + float[] colors = new float[] { color.Red, color.Green, color.Blue, color.Alpha }; + + GL.ClearBuffer(ClearBuffer.Color, index, colors); + + RestoreComponentMask(index); + } + + public void ClearRenderTargetDepthStencil(float depthValue, bool depthMask, int stencilValue, int stencilMask) + { + bool stencilMaskChanged = + stencilMask != 0 && + stencilMask != _stencilFrontMask; + + bool depthMaskChanged = depthMask && depthMask != _depthMask; + + if (stencilMaskChanged) + { + GL.StencilMaskSeparate(StencilFace.Front, stencilMask); + } + + if (depthMaskChanged) + { + GL.DepthMask(depthMask); + } + + if (depthMask && stencilMask != 0) + { + GL.ClearBuffer(ClearBufferCombined.DepthStencil, 0, depthValue, stencilValue); + } + else if (depthMask) + { + GL.ClearBuffer(ClearBuffer.Depth, 0, ref depthValue); + } + else if (stencilMask != 0) + { + GL.ClearBuffer(ClearBuffer.Stencil, 0, ref stencilValue); + } + + if (stencilMaskChanged) + { + GL.StencilMaskSeparate(StencilFace.Front, _stencilFrontMask); + } + + if (depthMaskChanged) + { + GL.DepthMask(_depthMask); + } + } + + public void DispatchCompute(int groupsX, int groupsY, int groupsZ) + { + if (!_program.IsLinked) + { + Logger.PrintDebug(LogClass.Gpu, "Dispatch error, shader not linked."); + + return; + } + + PrepareForDispatch(); + + GL.DispatchCompute(groupsX, groupsY, groupsZ); + } + + public void Draw(int vertexCount, int instanceCount, int firstVertex, int firstInstance) + { + if (!_program.IsLinked) + { + Logger.PrintDebug(LogClass.Gpu, "Draw error, shader not linked."); + + return; + } + + PrepareForDraw(); + + if (_primitiveType == PrimitiveType.Quads) + { + DrawQuadsImpl(vertexCount, instanceCount, firstVertex, firstInstance); + } + else if (_primitiveType == PrimitiveType.QuadStrip) + { + DrawQuadStripImpl(vertexCount, instanceCount, firstVertex, firstInstance); + } + else + { + DrawImpl(vertexCount, instanceCount, firstVertex, firstInstance); + } + } + + private void DrawQuadsImpl( + int vertexCount, + int instanceCount, + int firstVertex, + int firstInstance) + { + // TODO: Instanced rendering. + int quadsCount = vertexCount / 4; + + int[] firsts = new int[quadsCount]; + int[] counts = new int[quadsCount]; + + for (int quadIndex = 0; quadIndex < quadsCount; quadIndex++) + { + firsts[quadIndex] = firstVertex + quadIndex * 4; + counts[quadIndex] = 4; + } + + GL.MultiDrawArrays( + PrimitiveType.TriangleFan, + firsts, + counts, + quadsCount); + } + + private void DrawQuadStripImpl( + int vertexCount, + int instanceCount, + int firstVertex, + int firstInstance) + { + int quadsCount = (vertexCount - 2) / 2; + + if (firstInstance != 0 || instanceCount != 1) + { + for (int quadIndex = 0; quadIndex < quadsCount; quadIndex++) + { + GL.DrawArraysInstancedBaseInstance(PrimitiveType.TriangleFan, firstVertex + quadIndex * 2, 4, instanceCount, firstInstance); + } + } + else + { + int[] firsts = new int[quadsCount]; + int[] counts = new int[quadsCount]; + + firsts[0] = firstVertex; + counts[0] = 4; + + for (int quadIndex = 1; quadIndex < quadsCount; quadIndex++) + { + firsts[quadIndex] = firstVertex + quadIndex * 2; + counts[quadIndex] = 4; + } + + GL.MultiDrawArrays( + PrimitiveType.TriangleFan, + firsts, + counts, + quadsCount); + } + } + + private void DrawImpl( + int vertexCount, + int instanceCount, + int firstVertex, + int firstInstance) + { + if (firstInstance == 0 && instanceCount == 1) + { + GL.DrawArrays(_primitiveType, firstVertex, vertexCount); + } + else if (firstInstance == 0) + { + GL.DrawArraysInstanced(_primitiveType, firstVertex, vertexCount, instanceCount); + } + else + { + GL.DrawArraysInstancedBaseInstance( + _primitiveType, + firstVertex, + vertexCount, + instanceCount, + firstInstance); + } + } + + public void DrawIndexed( + int indexCount, + int instanceCount, + int firstIndex, + int firstVertex, + int firstInstance) + { + if (!_program.IsLinked) + { + Logger.PrintDebug(LogClass.Gpu, "Draw error, shader not linked."); + + return; + } + + PrepareForDraw(); + + int indexElemSize = 1; + + switch (_elementsType) + { + case DrawElementsType.UnsignedShort: indexElemSize = 2; break; + case DrawElementsType.UnsignedInt: indexElemSize = 4; break; + } + + IntPtr indexBaseOffset = _indexBaseOffset + firstIndex * indexElemSize; + + if (_primitiveType == PrimitiveType.Quads) + { + DrawQuadsIndexedImpl( + indexCount, + instanceCount, + indexBaseOffset, + indexElemSize, + firstVertex, + firstInstance); + } + else if (_primitiveType == PrimitiveType.QuadStrip) + { + DrawQuadStripIndexedImpl( + indexCount, + instanceCount, + indexBaseOffset, + indexElemSize, + firstVertex, + firstInstance); + } + else + { + DrawIndexedImpl( + indexCount, + instanceCount, + indexBaseOffset, + firstVertex, + firstInstance); + } + } + + private void DrawQuadsIndexedImpl( + int indexCount, + int instanceCount, + IntPtr indexBaseOffset, + int indexElemSize, + int firstVertex, + int firstInstance) + { + int quadsCount = indexCount / 4; + + if (firstInstance != 0 || instanceCount != 1) + { + if (firstVertex != 0 && firstInstance != 0) + { + for (int quadIndex = 0; quadIndex < quadsCount; quadIndex++) + { + GL.DrawElementsInstancedBaseVertexBaseInstance( + PrimitiveType.TriangleFan, + 4, + _elementsType, + indexBaseOffset + quadIndex * 4 * indexElemSize, + instanceCount, + firstVertex, + firstInstance); + } + } + else if (firstInstance != 0) + { + for (int quadIndex = 0; quadIndex < quadsCount; quadIndex++) + { + GL.DrawElementsInstancedBaseInstance( + PrimitiveType.TriangleFan, + 4, + _elementsType, + indexBaseOffset + quadIndex * 4 * indexElemSize, + instanceCount, + firstInstance); + } + } + else + { + for (int quadIndex = 0; quadIndex < quadsCount; quadIndex++) + { + GL.DrawElementsInstanced( + PrimitiveType.TriangleFan, + 4, + _elementsType, + indexBaseOffset + quadIndex * 4 * indexElemSize, + instanceCount); + } + } + } + else + { + IntPtr[] indices = new IntPtr[quadsCount]; + + int[] counts = new int[quadsCount]; + + int[] baseVertices = new int[quadsCount]; + + for (int quadIndex = 0; quadIndex < quadsCount; quadIndex++) + { + indices[quadIndex] = indexBaseOffset + quadIndex * 4 * indexElemSize; + + counts[quadIndex] = 4; + + baseVertices[quadIndex] = firstVertex; + } + + GL.MultiDrawElementsBaseVertex( + PrimitiveType.TriangleFan, + counts, + _elementsType, + indices, + quadsCount, + baseVertices); + } + } + + private void DrawQuadStripIndexedImpl( + int indexCount, + int instanceCount, + IntPtr indexBaseOffset, + int indexElemSize, + int firstVertex, + int firstInstance) + { + // TODO: Instanced rendering. + int quadsCount = (indexCount - 2) / 2; + + IntPtr[] indices = new IntPtr[quadsCount]; + + int[] counts = new int[quadsCount]; + + int[] baseVertices = new int[quadsCount]; + + indices[0] = indexBaseOffset; + + counts[0] = 4; + + baseVertices[0] = firstVertex; + + for (int quadIndex = 1; quadIndex < quadsCount; quadIndex++) + { + indices[quadIndex] = indexBaseOffset + quadIndex * 2 * indexElemSize; + + counts[quadIndex] = 4; + + baseVertices[quadIndex] = firstVertex; + } + + GL.MultiDrawElementsBaseVertex( + PrimitiveType.TriangleFan, + counts, + _elementsType, + indices, + quadsCount, + baseVertices); + } + + private void DrawIndexedImpl( + int indexCount, + int instanceCount, + IntPtr indexBaseOffset, + int firstVertex, + int firstInstance) + { + if (firstInstance == 0 && firstVertex == 0 && instanceCount == 1) + { + GL.DrawElements(_primitiveType, indexCount, _elementsType, indexBaseOffset); + } + else if (firstInstance == 0 && instanceCount == 1) + { + GL.DrawElementsBaseVertex( + _primitiveType, + indexCount, + _elementsType, + indexBaseOffset, + firstVertex); + } + else if (firstInstance == 0 && firstVertex == 0) + { + GL.DrawElementsInstanced( + _primitiveType, + indexCount, + _elementsType, + indexBaseOffset, + instanceCount); + } + else if (firstInstance == 0) + { + GL.DrawElementsInstancedBaseVertex( + _primitiveType, + indexCount, + _elementsType, + indexBaseOffset, + instanceCount, + firstVertex); + } + else if (firstVertex == 0) + { + GL.DrawElementsInstancedBaseInstance( + _primitiveType, + indexCount, + _elementsType, + indexBaseOffset, + instanceCount, + firstInstance); + } + else + { + GL.DrawElementsInstancedBaseVertexBaseInstance( + _primitiveType, + indexCount, + _elementsType, + indexBaseOffset, + instanceCount, + firstVertex, + firstInstance); + } + } + + public void SetBlendColor(ColorF color) + { + GL.BlendColor(color.Red, color.Green, color.Blue, color.Alpha); + } + + public void SetBlendState(int index, BlendDescriptor blend) + { + if (!blend.Enable) + { + GL.Disable(IndexedEnableCap.Blend, index); + + return; + } + + GL.BlendEquationSeparate( + index, + blend.ColorOp.Convert(), + blend.AlphaOp.Convert()); + + GL.BlendFuncSeparate( + index, + (BlendingFactorSrc)blend.ColorSrcFactor.Convert(), + (BlendingFactorDest)blend.ColorDstFactor.Convert(), + (BlendingFactorSrc)blend.AlphaSrcFactor.Convert(), + (BlendingFactorDest)blend.AlphaDstFactor.Convert()); + + GL.Enable(IndexedEnableCap.Blend, index); + } + + public void SetDepthBias(PolygonModeMask enables, float factor, float units, float clamp) + { + if ((enables & PolygonModeMask.Point) != 0) + { + GL.Enable(EnableCap.PolygonOffsetPoint); + } + else + { + GL.Disable(EnableCap.PolygonOffsetPoint); + } + + if ((enables & PolygonModeMask.Line) != 0) + { + GL.Enable(EnableCap.PolygonOffsetLine); + } + else + { + GL.Disable(EnableCap.PolygonOffsetLine); + } + + if ((enables & PolygonModeMask.Fill) != 0) + { + GL.Enable(EnableCap.PolygonOffsetFill); + } + else + { + GL.Disable(EnableCap.PolygonOffsetFill); + } + + if (enables == 0) + { + return; + } + + GL.PolygonOffset(factor, units); + // TODO: Enable when GL_EXT_polygon_offset_clamp is supported. + // GL.PolygonOffsetClamp(factor, units, clamp); + } + + public void SetDepthMode(DepthMode mode) + { + ClipDepthMode depthMode = mode.Convert(); + + if (_clipDepthMode != depthMode) + { + _clipDepthMode = depthMode; + + GL.ClipControl(_clipOrigin, depthMode); + } + } + + public void SetDepthTest(DepthTestDescriptor depthTest) + { + GL.DepthFunc((DepthFunction)depthTest.Func.Convert()); + + _depthMask = depthTest.WriteEnable; + _depthTest = depthTest.TestEnable; + + UpdateDepthTest(); + } + + public void SetFaceCulling(bool enable, Face face) + { + if (!enable) + { + GL.Disable(EnableCap.CullFace); + + return; + } + + GL.CullFace(face.Convert()); + + GL.Enable(EnableCap.CullFace); + } + + public void SetFrontFace(FrontFace frontFace) + { + GL.FrontFace(frontFace.Convert()); + } + + public void SetImage(int index, ShaderStage stage, ITexture texture) + { + int unit = _program.GetImageUnit(stage, index); + + if (unit != -1 && texture != null) + { + TextureView view = (TextureView)texture; + + FormatInfo formatInfo = FormatTable.GetFormatInfo(view.Format); + + SizedInternalFormat format = (SizedInternalFormat)formatInfo.PixelInternalFormat; + + GL.BindImageTexture(unit, view.Handle, 0, true, 0, TextureAccess.ReadWrite, format); + } + } + + public void SetIndexBuffer(BufferRange buffer, IndexType type) + { + _elementsType = type.Convert(); + + _indexBaseOffset = (IntPtr)buffer.Offset; + + EnsureVertexArray(); + + _vertexArray.SetIndexBuffer((Buffer)buffer.Buffer); + } + + public void SetPrimitiveRestart(bool enable, int index) + { + if (!enable) + { + GL.Disable(EnableCap.PrimitiveRestart); + + return; + } + + GL.PrimitiveRestartIndex(index); + + GL.Enable(EnableCap.PrimitiveRestart); + } + + public void SetPrimitiveTopology(PrimitiveTopology topology) + { + _primitiveType = topology.Convert(); + } + + public void SetProgram(IProgram program) + { + _program = (Program)program; + + _program.Bind(); + } + + public void SetRenderTargetColorMasks(uint[] componentMasks) + { + _componentMasks = (uint[])componentMasks.Clone(); + + for (int index = 0; index < componentMasks.Length; index++) + { + RestoreComponentMask(index); + } + } + + public void SetRenderTargets(ITexture[] colors, ITexture depthStencil) + { + EnsureFramebuffer(); + + for (int index = 0; index < colors.Length; index++) + { + TextureView color = (TextureView)colors[index]; + + _framebuffer.AttachColor(index, color); + } + + TextureView depthStencilView = (TextureView)depthStencil; + + _framebuffer.AttachDepthStencil(depthStencilView); + + _framebuffer.SetDrawBuffers(colors.Length); + + _hasDepthBuffer = depthStencil != null && depthStencilView.Format != Format.S8Uint; + + UpdateDepthTest(); + } + + public void SetSampler(int index, ShaderStage stage, ISampler sampler) + { + int unit = _program.GetTextureUnit(stage, index); + + if (unit != -1 && sampler != null) + { + ((Sampler)sampler).Bind(unit); + } + } + + public void SetStencilTest(StencilTestDescriptor stencilTest) + { + if (!stencilTest.TestEnable) + { + GL.Disable(EnableCap.StencilTest); + + return; + } + + GL.StencilOpSeparate( + StencilFace.Front, + stencilTest.FrontSFail.Convert(), + stencilTest.FrontDpFail.Convert(), + stencilTest.FrontDpPass.Convert()); + + GL.StencilFuncSeparate( + StencilFace.Front, + (StencilFunction)stencilTest.FrontFunc.Convert(), + stencilTest.FrontFuncRef, + stencilTest.FrontFuncMask); + + GL.StencilMaskSeparate(StencilFace.Front, stencilTest.FrontMask); + + GL.StencilOpSeparate( + StencilFace.Back, + stencilTest.BackSFail.Convert(), + stencilTest.BackDpFail.Convert(), + stencilTest.BackDpPass.Convert()); + + GL.StencilFuncSeparate( + StencilFace.Back, + (StencilFunction)stencilTest.BackFunc.Convert(), + stencilTest.BackFuncRef, + stencilTest.BackFuncMask); + + GL.StencilMaskSeparate(StencilFace.Back, stencilTest.BackMask); + + GL.Enable(EnableCap.StencilTest); + + _stencilFrontMask = stencilTest.FrontMask; + } + + public void SetStorageBuffer(int index, ShaderStage stage, BufferRange buffer) + { + SetBuffer(index, stage, buffer, isStorage: true); + } + + public void SetTexture(int index, ShaderStage stage, ITexture texture) + { + int unit = _program.GetTextureUnit(stage, index); + + if (unit != -1 && texture != null) + { + if (unit == 0) + { + _unit0Texture = ((TextureView)texture); + } + else + { + ((TextureView)texture).Bind(unit); + } + } + } + + public void SetUniformBuffer(int index, ShaderStage stage, BufferRange buffer) + { + SetBuffer(index, stage, buffer, isStorage: false); + } + + public void SetVertexAttribs(VertexAttribDescriptor[] vertexAttribs) + { + EnsureVertexArray(); + + _vertexArray.SetVertexAttributes(vertexAttribs); + } + + public void SetVertexBuffers(VertexBufferDescriptor[] vertexBuffers) + { + EnsureVertexArray(); + + _vertexArray.SetVertexBuffers(vertexBuffers); + } + + public void SetViewports(int first, Viewport[] viewports) + { + bool flipY = false; + + float[] viewportArray = new float[viewports.Length * 4]; + + double[] depthRangeArray = new double[viewports.Length * 2]; + + for (int index = 0; index < viewports.Length; index++) + { + int viewportElemIndex = index * 4; + + Viewport viewport = viewports[index]; + + viewportArray[viewportElemIndex + 0] = viewport.Region.X; + viewportArray[viewportElemIndex + 1] = viewport.Region.Y; + + // OpenGL does not support per-viewport flipping, so + // instead we decide that based on the viewport 0 value. + // It will apply to all viewports. + if (index == 0) + { + flipY = viewport.Region.Height < 0; + } + + if (viewport.SwizzleY == ViewportSwizzle.NegativeY) + { + flipY = !flipY; + } + + viewportArray[viewportElemIndex + 2] = MathF.Abs(viewport.Region.Width); + viewportArray[viewportElemIndex + 3] = MathF.Abs(viewport.Region.Height); + + depthRangeArray[index * 2 + 0] = viewport.DepthNear; + depthRangeArray[index * 2 + 1] = viewport.DepthFar; + } + + GL.ViewportArray(first, viewports.Length, viewportArray); + + GL.DepthRangeArray(first, viewports.Length, depthRangeArray); + + SetOrigin(flipY ? ClipOrigin.UpperLeft : ClipOrigin.LowerLeft); + } + + public void TextureBarrier() + { + GL.MemoryBarrier(MemoryBarrierFlags.TextureFetchBarrierBit); + } + + public void TextureBarrierTiled() + { + GL.MemoryBarrier(MemoryBarrierFlags.TextureFetchBarrierBit); + } + + private void SetBuffer(int index, ShaderStage stage, BufferRange buffer, bool isStorage) + { + int bindingPoint = isStorage + ? _program.GetStorageBufferBindingPoint(stage, index) + : _program.GetUniformBufferBindingPoint(stage, index); + + if (bindingPoint == -1) + { + return; + } + + BufferRangeTarget target = isStorage + ? BufferRangeTarget.ShaderStorageBuffer + : BufferRangeTarget.UniformBuffer; + + if (buffer.Buffer == null) + { + GL.BindBufferRange(target, bindingPoint, 0, IntPtr.Zero, 0); + + return; + } + + int bufferHandle = ((Buffer)buffer.Buffer).Handle; + + IntPtr bufferOffset = (IntPtr)buffer.Offset; + + GL.BindBufferRange(target, bindingPoint, bufferHandle, bufferOffset, buffer.Size); + } + + private void SetOrigin(ClipOrigin origin) + { + if (_clipOrigin != origin) + { + _clipOrigin = origin; + + GL.ClipControl(origin, _clipDepthMode); + } + } + + private void EnsureVertexArray() + { + if (_vertexArray == null) + { + _vertexArray = new VertexArray(); + + _vertexArray.Bind(); + } + } + + private void EnsureFramebuffer() + { + if (_framebuffer == null) + { + _framebuffer = new Framebuffer(); + + _framebuffer.Bind(); + + GL.Enable(EnableCap.FramebufferSrgb); + } + } + + private void UpdateDepthTest() + { + // Enabling depth operations is only valid when we have + // a depth buffer, otherwise it's not allowed. + if (_hasDepthBuffer) + { + if (_depthTest) + { + GL.Enable(EnableCap.DepthTest); + } + else + { + GL.Disable(EnableCap.DepthTest); + } + + GL.DepthMask(_depthMask); + } + else + { + GL.Disable(EnableCap.DepthTest); + + GL.DepthMask(false); + } + } + + private void PrepareForDispatch() + { + if (_unit0Texture != null) + { + _unit0Texture.Bind(0); + } + } + + private void PrepareForDraw() + { + _vertexArray.Validate(); + + if (_unit0Texture != null) + { + _unit0Texture.Bind(0); + } + } + + private void RestoreComponentMask(int index) + { + if (_componentMasks != null) + { + GL.ColorMask( + index, + (_componentMasks[index] & 1u) != 0, + (_componentMasks[index] & 2u) != 0, + (_componentMasks[index] & 4u) != 0, + (_componentMasks[index] & 8u) != 0); + } + } + + public void Dispose() + { + _framebuffer?.Dispose(); + _vertexArray?.Dispose(); + } + } +} diff --git a/Ryujinx.Graphics.OpenGL/Program.cs b/Ryujinx.Graphics.OpenGL/Program.cs new file mode 100644 index 0000000000..a8ee7ae895 --- /dev/null +++ b/Ryujinx.Graphics.OpenGL/Program.cs @@ -0,0 +1,224 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Shader; + +namespace Ryujinx.Graphics.OpenGL +{ + class Program : IProgram + { + private const int ShaderStages = 6; + + private const int UbStageShift = 5; + private const int SbStageShift = 4; + private const int TexStageShift = 5; + private const int ImgStageShift = 3; + + private const int UbsPerStage = 1 << UbStageShift; + private const int SbsPerStage = 1 << SbStageShift; + private const int TexsPerStage = 1 << TexStageShift; + private const int ImgsPerStage = 1 << ImgStageShift; + + public int Handle { get; private set; } + + public bool IsLinked { get; private set; } + + private int[] _ubBindingPoints; + private int[] _sbBindingPoints; + private int[] _textureUnits; + private int[] _imageUnits; + + public Program(IShader[] shaders) + { + _ubBindingPoints = new int[UbsPerStage * ShaderStages]; + _sbBindingPoints = new int[SbsPerStage * ShaderStages]; + _textureUnits = new int[TexsPerStage * ShaderStages]; + _imageUnits = new int[ImgsPerStage * ShaderStages]; + + for (int index = 0; index < _ubBindingPoints.Length; index++) + { + _ubBindingPoints[index] = -1; + } + + for (int index = 0; index < _sbBindingPoints.Length; index++) + { + _sbBindingPoints[index] = -1; + } + + for (int index = 0; index < _textureUnits.Length; index++) + { + _textureUnits[index] = -1; + } + + for (int index = 0; index < _imageUnits.Length; index++) + { + _imageUnits[index] = -1; + } + + Handle = GL.CreateProgram(); + + for (int index = 0; index < shaders.Length; index++) + { + int shaderHandle = ((Shader)shaders[index]).Handle; + + GL.AttachShader(Handle, shaderHandle); + } + + GL.LinkProgram(Handle); + + for (int index = 0; index < shaders.Length; index++) + { + int shaderHandle = ((Shader)shaders[index]).Handle; + + GL.DetachShader(Handle, shaderHandle); + } + + CheckProgramLink(); + + Bind(); + + int extraBlockindex = GL.GetUniformBlockIndex(Handle, "Extra"); + + if (extraBlockindex >= 0) + { + GL.UniformBlockBinding(Handle, extraBlockindex, 0); + } + + int ubBindingPoint = 1; + int sbBindingPoint = 0; + int textureUnit = 0; + int imageUnit = 0; + + for (int index = 0; index < shaders.Length; index++) + { + Shader shader = (Shader)shaders[index]; + + foreach (BufferDescriptor descriptor in shader.Info.CBuffers) + { + int location = GL.GetUniformBlockIndex(Handle, descriptor.Name); + + if (location < 0) + { + continue; + } + + GL.UniformBlockBinding(Handle, location, ubBindingPoint); + + int bpIndex = (int)shader.Stage << UbStageShift | descriptor.Slot; + + _ubBindingPoints[bpIndex] = ubBindingPoint; + + ubBindingPoint++; + } + + foreach (BufferDescriptor descriptor in shader.Info.SBuffers) + { + int location = GL.GetProgramResourceIndex(Handle, ProgramInterface.ShaderStorageBlock, descriptor.Name); + + if (location < 0) + { + continue; + } + + GL.ShaderStorageBlockBinding(Handle, location, sbBindingPoint); + + int bpIndex = (int)shader.Stage << SbStageShift | descriptor.Slot; + + _sbBindingPoints[bpIndex] = sbBindingPoint; + + sbBindingPoint++; + } + + int samplerIndex = 0; + + foreach (TextureDescriptor descriptor in shader.Info.Textures) + { + int location = GL.GetUniformLocation(Handle, descriptor.Name); + + if (location < 0) + { + continue; + } + + GL.Uniform1(location, textureUnit); + + int uIndex = (int)shader.Stage << TexStageShift | samplerIndex++; + + _textureUnits[uIndex] = textureUnit; + + textureUnit++; + } + + int imageIndex = 0; + + foreach (TextureDescriptor descriptor in shader.Info.Images) + { + int location = GL.GetUniformLocation(Handle, descriptor.Name); + + if (location < 0) + { + continue; + } + + GL.Uniform1(location, imageUnit); + + int uIndex = (int)shader.Stage << ImgStageShift | imageIndex++; + + _imageUnits[uIndex] = imageUnit; + + imageUnit++; + } + } + } + + public void Bind() + { + GL.UseProgram(Handle); + } + + public int GetUniformBufferBindingPoint(ShaderStage stage, int index) + { + return _ubBindingPoints[(int)stage << UbStageShift | index]; + } + + public int GetStorageBufferBindingPoint(ShaderStage stage, int index) + { + return _sbBindingPoints[(int)stage << SbStageShift | index]; + } + + public int GetTextureUnit(ShaderStage stage, int index) + { + return _textureUnits[(int)stage << TexStageShift | index]; + } + + public int GetImageUnit(ShaderStage stage, int index) + { + return _imageUnits[(int)stage << ImgStageShift | index]; + } + + private void CheckProgramLink() + { + GL.GetProgram(Handle, GetProgramParameterName.LinkStatus, out int status); + + if (status == 0) + { + // Use GL.GetProgramInfoLog(Handle), it may be too long to print on the log. + Logger.PrintDebug(LogClass.Gpu, "Shader linking failed."); + } + else + { + IsLinked = true; + } + } + + public void Dispose() + { + if (Handle != 0) + { + GL.DeleteProgram(Handle); + + Handle = 0; + } + } + } +} diff --git a/Ryujinx.Graphics.OpenGL/Renderer.cs b/Ryujinx.Graphics.OpenGL/Renderer.cs new file mode 100644 index 0000000000..ccb53397d1 --- /dev/null +++ b/Ryujinx.Graphics.OpenGL/Renderer.cs @@ -0,0 +1,100 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Shader; + +namespace Ryujinx.Graphics.OpenGL +{ + public sealed class Renderer : IRenderer + { + private Pipeline _pipeline; + + public IPipeline Pipeline => _pipeline; + + private readonly Counters _counters; + + private readonly Window _window; + + public IWindow Window => _window; + + internal TextureCopy TextureCopy { get; } + + public Renderer() + { + _pipeline = new Pipeline(); + + _counters = new Counters(); + + _window = new Window(); + + TextureCopy = new TextureCopy(); + } + + public IShader CompileShader(ShaderProgram shader) + { + return new Shader(shader); + } + + public IBuffer CreateBuffer(int size) + { + return new Buffer(size); + } + + public IProgram CreateProgram(IShader[] shaders) + { + return new Program(shaders); + } + + public ISampler CreateSampler(SamplerCreateInfo info) + { + return new Sampler(info); + } + + public ITexture CreateTexture(TextureCreateInfo info) + { + return new TextureStorage(this, info).CreateDefaultView(); + } + + public Capabilities GetCapabilities() + { + return new Capabilities( + HwCapabilities.SupportsAstcCompression, + HwCapabilities.SupportsNonConstantTextureOffset, + HwCapabilities.MaximumComputeSharedMemorySize, + HwCapabilities.StorageBufferOffsetAlignment); + } + + public ulong GetCounter(CounterType type) + { + return _counters.GetCounter(type); + } + + public void Initialize() + { + PrintGpuInformation(); + + _counters.Initialize(); + } + + private void PrintGpuInformation() + { + string gpuVendor = GL.GetString(StringName.Vendor); + string gpuRenderer = GL.GetString(StringName.Renderer); + string gpuVersion = GL.GetString(StringName.Version); + + Logger.PrintInfo(LogClass.Gpu, $"{gpuVendor} {gpuRenderer} ({gpuVersion})"); + } + + public void ResetCounter(CounterType type) + { + _counters.ResetCounter(type); + } + + public void Dispose() + { + TextureCopy.Dispose(); + _pipeline.Dispose(); + _window.Dispose(); + } + } +} diff --git a/Ryujinx.Graphics.OpenGL/Ryujinx.Graphics.OpenGL.csproj b/Ryujinx.Graphics.OpenGL/Ryujinx.Graphics.OpenGL.csproj new file mode 100644 index 0000000000..f2a9377737 --- /dev/null +++ b/Ryujinx.Graphics.OpenGL/Ryujinx.Graphics.OpenGL.csproj @@ -0,0 +1,19 @@ + + + + true + netcoreapp3.0 + win-x64;osx-x64;linux-x64 + + + + + + + + + + + + + diff --git a/Ryujinx.Graphics.OpenGL/Sampler.cs b/Ryujinx.Graphics.OpenGL/Sampler.cs new file mode 100644 index 0000000000..674fc7978b --- /dev/null +++ b/Ryujinx.Graphics.OpenGL/Sampler.cs @@ -0,0 +1,59 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Graphics.GAL; + +namespace Ryujinx.Graphics.OpenGL +{ + class Sampler : ISampler + { + public int Handle { get; private set; } + + public Sampler(SamplerCreateInfo info) + { + Handle = GL.GenSampler(); + + GL.SamplerParameter(Handle, SamplerParameterName.TextureMinFilter, (int)info.MinFilter.Convert()); + GL.SamplerParameter(Handle, SamplerParameterName.TextureMagFilter, (int)info.MagFilter.Convert()); + + GL.SamplerParameter(Handle, SamplerParameterName.TextureWrapS, (int)info.AddressU.Convert()); + GL.SamplerParameter(Handle, SamplerParameterName.TextureWrapT, (int)info.AddressV.Convert()); + GL.SamplerParameter(Handle, SamplerParameterName.TextureWrapR, (int)info.AddressP.Convert()); + + GL.SamplerParameter(Handle, SamplerParameterName.TextureCompareMode, (int)info.CompareMode.Convert()); + GL.SamplerParameter(Handle, SamplerParameterName.TextureCompareFunc, (int)info.CompareOp.Convert()); + + unsafe + { + float* borderColor = stackalloc float[4] + { + info.BorderColor.Red, + info.BorderColor.Green, + info.BorderColor.Blue, + info.BorderColor.Alpha + }; + + GL.SamplerParameter(Handle, SamplerParameterName.TextureBorderColor, borderColor); + } + + GL.SamplerParameter(Handle, SamplerParameterName.TextureMinLod, info.MinLod); + GL.SamplerParameter(Handle, SamplerParameterName.TextureMaxLod, info.MaxLod); + GL.SamplerParameter(Handle, SamplerParameterName.TextureLodBias, info.MipLodBias); + + GL.SamplerParameter(Handle, SamplerParameterName.TextureMaxAnisotropyExt, info.MaxAnisotropy); + } + + public void Bind(int unit) + { + GL.BindSampler(unit, Handle); + } + + public void Dispose() + { + if (Handle != 0) + { + GL.DeleteSampler(Handle); + + Handle = 0; + } + } + } +} diff --git a/Ryujinx.Graphics.OpenGL/Shader.cs b/Ryujinx.Graphics.OpenGL/Shader.cs new file mode 100644 index 0000000000..f25845cfd4 --- /dev/null +++ b/Ryujinx.Graphics.OpenGL/Shader.cs @@ -0,0 +1,49 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Shader; + +namespace Ryujinx.Graphics.OpenGL +{ + class Shader : IShader + { + public int Handle { get; private set; } + + private ShaderProgram _program; + + public ShaderProgramInfo Info => _program.Info; + + public ShaderStage Stage => _program.Stage; + + public Shader(ShaderProgram program) + { + _program = program; + + ShaderType type = ShaderType.VertexShader; + + switch (program.Stage) + { + case ShaderStage.Compute: type = ShaderType.ComputeShader; break; + case ShaderStage.Vertex: type = ShaderType.VertexShader; break; + case ShaderStage.TessellationControl: type = ShaderType.TessControlShader; break; + case ShaderStage.TessellationEvaluation: type = ShaderType.TessEvaluationShader; break; + case ShaderStage.Geometry: type = ShaderType.GeometryShader; break; + case ShaderStage.Fragment: type = ShaderType.FragmentShader; break; + } + + Handle = GL.CreateShader(type); + + GL.ShaderSource(Handle, program.Code); + GL.CompileShader(Handle); + } + + public void Dispose() + { + if (Handle != 0) + { + GL.DeleteShader(Handle); + + Handle = 0; + } + } + } +} diff --git a/Ryujinx.Graphics.OpenGL/TextureCopy.cs b/Ryujinx.Graphics.OpenGL/TextureCopy.cs new file mode 100644 index 0000000000..5566642696 --- /dev/null +++ b/Ryujinx.Graphics.OpenGL/TextureCopy.cs @@ -0,0 +1,141 @@ +using Ryujinx.Graphics.GAL; +using OpenTK.Graphics.OpenGL; +using System; + +namespace Ryujinx.Graphics.OpenGL +{ + class TextureCopy : IDisposable + { + private int _srcFramebuffer; + private int _dstFramebuffer; + + public void Copy( + TextureView src, + TextureView dst, + Extents2D srcRegion, + Extents2D dstRegion, + bool linearFilter) + { + GL.Disable(EnableCap.FramebufferSrgb); + + int oldReadFramebufferHandle = GL.GetInteger(GetPName.ReadFramebufferBinding); + int oldDrawFramebufferHandle = GL.GetInteger(GetPName.DrawFramebufferBinding); + + GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, GetSrcFramebufferLazy()); + GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, GetDstFramebufferLazy()); + + Attach(FramebufferTarget.ReadFramebuffer, src.Format, src.Handle); + Attach(FramebufferTarget.DrawFramebuffer, dst.Format, dst.Handle); + + ClearBufferMask mask = GetMask(src.Format); + + BlitFramebufferFilter filter = linearFilter + ? BlitFramebufferFilter.Linear + : BlitFramebufferFilter.Nearest; + + GL.ReadBuffer(ReadBufferMode.ColorAttachment0); + GL.DrawBuffer(DrawBufferMode.ColorAttachment0); + + GL.BlitFramebuffer( + srcRegion.X1, + srcRegion.Y1, + srcRegion.X2, + srcRegion.Y2, + dstRegion.X1, + dstRegion.Y1, + dstRegion.X2, + dstRegion.Y2, + mask, + filter); + + GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, oldReadFramebufferHandle); + GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, oldDrawFramebufferHandle); + + GL.Enable(EnableCap.FramebufferSrgb); + } + + private static void Attach(FramebufferTarget target, Format format, int handle) + { + if (format == Format.D24UnormS8Uint || format == Format.D32FloatS8Uint) + { + GL.FramebufferTexture(target, FramebufferAttachment.DepthStencilAttachment, handle, 0); + } + else if (IsDepthOnly(format)) + { + GL.FramebufferTexture(target, FramebufferAttachment.DepthAttachment, handle, 0); + } + else if (format == Format.S8Uint) + { + GL.FramebufferTexture(target, FramebufferAttachment.StencilAttachment, handle, 0); + } + else + { + GL.FramebufferTexture(target, FramebufferAttachment.ColorAttachment0, handle, 0); + } + } + + private static ClearBufferMask GetMask(Format format) + { + if (format == Format.D24UnormS8Uint || format == Format.D32FloatS8Uint) + { + return ClearBufferMask.DepthBufferBit | ClearBufferMask.StencilBufferBit; + } + else if (IsDepthOnly(format)) + { + return ClearBufferMask.DepthBufferBit; + } + else if (format == Format.S8Uint) + { + return ClearBufferMask.StencilBufferBit; + } + else + { + return ClearBufferMask.ColorBufferBit; + } + } + + private static bool IsDepthOnly(Format format) + { + return format == Format.D16Unorm || + format == Format.D24X8Unorm || + format == Format.D32Float; + } + + private int GetSrcFramebufferLazy() + { + if (_srcFramebuffer == 0) + { + _srcFramebuffer = GL.GenFramebuffer(); + } + + return _srcFramebuffer; + } + + private int GetDstFramebufferLazy() + { + if (_dstFramebuffer == 0) + { + _dstFramebuffer = GL.GenFramebuffer(); + } + + return _dstFramebuffer; + } + + public void Dispose() + { + if (_srcFramebuffer != 0) + { + GL.DeleteFramebuffer(_srcFramebuffer); + + _srcFramebuffer = 0; + } + + if (_dstFramebuffer != 0) + { + GL.DeleteFramebuffer(_dstFramebuffer); + + _dstFramebuffer = 0; + } + } + } +} diff --git a/Ryujinx.Graphics.OpenGL/TextureCopyUnscaled.cs b/Ryujinx.Graphics.OpenGL/TextureCopyUnscaled.cs new file mode 100644 index 0000000000..2597214a22 --- /dev/null +++ b/Ryujinx.Graphics.OpenGL/TextureCopyUnscaled.cs @@ -0,0 +1,85 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Common; +using Ryujinx.Graphics.GAL; +using System; + +namespace Ryujinx.Graphics.OpenGL +{ + static class TextureCopyUnscaled + { + public static void Copy(TextureView src, TextureView dst, int dstLayer, int dstLevel) + { + int srcWidth = src.Width; + int srcHeight = src.Height; + int srcDepth = src.DepthOrLayers; + int srcLevels = src.Levels; + + srcWidth = Math.Max(1, srcWidth >> dstLevel); + srcHeight = Math.Max(1, srcHeight >> dstLevel); + + if (src.Target == Target.Texture3D) + { + srcDepth = Math.Max(1, srcDepth >> dstLevel); + } + + int dstWidth = dst.Width; + int dstHeight = dst.Height; + int dstDepth = dst.DepthOrLayers; + int dstLevels = dst.Levels; + + // When copying from a compressed to a non-compressed format, + // the non-compressed texture will have the size of the texture + // in blocks (not in texels), so we must adjust that size to + // match the size in texels of the compressed texture. + if (!src.IsCompressed && dst.IsCompressed) + { + dstWidth = BitUtils.DivRoundUp(dstWidth, dst.BlockWidth); + dstHeight = BitUtils.DivRoundUp(dstHeight, dst.BlockHeight); + } + else if (src.IsCompressed && !dst.IsCompressed) + { + dstWidth *= dst.BlockWidth; + dstHeight *= dst.BlockHeight; + } + + int width = Math.Min(srcWidth, dstWidth); + int height = Math.Min(srcHeight, dstHeight); + int depth = Math.Min(srcDepth, dstDepth); + int levels = Math.Min(srcLevels, dstLevels); + + for (int level = 0; level < levels; level++) + { + // Stop copy if we are already out of the levels range. + if (level >= src.Levels || dstLevel + level >= dst.Levels) + { + break; + } + + GL.CopyImageSubData( + src.Handle, + src.Target.ConvertToImageTarget(), + level, + 0, + 0, + 0, + dst.Handle, + dst.Target.ConvertToImageTarget(), + dstLevel + level, + 0, + 0, + dstLayer, + width, + height, + depth); + + width = Math.Max(1, width >> 1); + height = Math.Max(1, height >> 1); + + if (src.Target == Target.Texture3D) + { + depth = Math.Max(1, depth >> 1); + } + } + } + } +} diff --git a/Ryujinx.Graphics.OpenGL/TextureStorage.cs b/Ryujinx.Graphics.OpenGL/TextureStorage.cs new file mode 100644 index 0000000000..241b4116ff --- /dev/null +++ b/Ryujinx.Graphics.OpenGL/TextureStorage.cs @@ -0,0 +1,180 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.GAL; + +namespace Ryujinx.Graphics.OpenGL +{ + class TextureStorage + { + public int Handle { get; private set; } + + private readonly Renderer _renderer; + + private readonly TextureCreateInfo _info; + + public Target Target => _info.Target; + + private int _viewsCount; + + public TextureStorage(Renderer renderer, TextureCreateInfo info) + { + _renderer = renderer; + _info = info; + + Handle = GL.GenTexture(); + + CreateImmutableStorage(); + } + + private void CreateImmutableStorage() + { + TextureTarget target = _info.Target.Convert(); + + GL.ActiveTexture(TextureUnit.Texture0); + + GL.BindTexture(target, Handle); + + FormatInfo format = FormatTable.GetFormatInfo(_info.Format); + + SizedInternalFormat internalFormat; + + if (format.IsCompressed) + { + internalFormat = (SizedInternalFormat)format.PixelFormat; + } + else + { + internalFormat = (SizedInternalFormat)format.PixelInternalFormat; + } + + switch (_info.Target) + { + case Target.Texture1D: + GL.TexStorage1D( + TextureTarget1d.Texture1D, + _info.Levels, + internalFormat, + _info.Width); + break; + + case Target.Texture1DArray: + GL.TexStorage2D( + TextureTarget2d.Texture1DArray, + _info.Levels, + internalFormat, + _info.Width, + _info.Height); + break; + + case Target.Texture2D: + GL.TexStorage2D( + TextureTarget2d.Texture2D, + _info.Levels, + internalFormat, + _info.Width, + _info.Height); + break; + + case Target.Texture2DArray: + GL.TexStorage3D( + TextureTarget3d.Texture2DArray, + _info.Levels, + internalFormat, + _info.Width, + _info.Height, + _info.Depth); + break; + + case Target.Texture2DMultisample: + GL.TexStorage2DMultisample( + TextureTargetMultisample2d.Texture2DMultisample, + _info.Samples, + internalFormat, + _info.Width, + _info.Height, + true); + break; + + case Target.Texture2DMultisampleArray: + GL.TexStorage3DMultisample( + TextureTargetMultisample3d.Texture2DMultisampleArray, + _info.Samples, + internalFormat, + _info.Width, + _info.Height, + _info.Depth, + true); + break; + + case Target.Texture3D: + GL.TexStorage3D( + TextureTarget3d.Texture3D, + _info.Levels, + internalFormat, + _info.Width, + _info.Height, + _info.Depth); + break; + + case Target.Cubemap: + GL.TexStorage2D( + TextureTarget2d.TextureCubeMap, + _info.Levels, + internalFormat, + _info.Width, + _info.Height); + break; + + case Target.CubemapArray: + GL.TexStorage3D( + (TextureTarget3d)All.TextureCubeMapArray, + _info.Levels, + internalFormat, + _info.Width, + _info.Height, + _info.Depth); + break; + + default: + Logger.PrintDebug(LogClass.Gpu, $"Invalid or unsupported texture target: {target}."); + break; + } + } + + public ITexture CreateDefaultView() + { + return CreateView(_info, 0, 0); + } + + public ITexture CreateView(TextureCreateInfo info, int firstLayer, int firstLevel) + { + IncrementViewsCount(); + + return new TextureView(_renderer, this, info, firstLayer, firstLevel); + } + + private void IncrementViewsCount() + { + _viewsCount++; + } + + public void DecrementViewsCount() + { + // If we don't have any views, then the storage is now useless, delete it. + if (--_viewsCount == 0) + { + Dispose(); + } + } + + public void Dispose() + { + if (Handle != 0) + { + GL.DeleteTexture(Handle); + + Handle = 0; + } + } + } +} diff --git a/Ryujinx.Graphics.OpenGL/TextureView.cs b/Ryujinx.Graphics.OpenGL/TextureView.cs new file mode 100644 index 0000000000..2efaf7c0a1 --- /dev/null +++ b/Ryujinx.Graphics.OpenGL/TextureView.cs @@ -0,0 +1,420 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Graphics.GAL; +using System; + +namespace Ryujinx.Graphics.OpenGL +{ + class TextureView : ITexture + { + public int Handle { get; private set; } + + private readonly Renderer _renderer; + + private readonly TextureStorage _parent; + + private TextureView _emulatedViewParent; + + private readonly TextureCreateInfo _info; + + private int _firstLayer; + private int _firstLevel; + + public int Width => _info.Width; + public int Height => _info.Height; + public int DepthOrLayers => _info.GetDepthOrLayers(); + public int Levels => _info.Levels; + + public Target Target => _info.Target; + public Format Format => _info.Format; + + public int BlockWidth => _info.BlockWidth; + public int BlockHeight => _info.BlockHeight; + + public bool IsCompressed => _info.IsCompressed; + + public TextureView( + Renderer renderer, + TextureStorage parent, + TextureCreateInfo info, + int firstLayer, + int firstLevel) + { + _renderer = renderer; + _parent = parent; + _info = info; + + _firstLayer = firstLayer; + _firstLevel = firstLevel; + + Handle = GL.GenTexture(); + + CreateView(); + } + + private void CreateView() + { + TextureTarget target = Target.Convert(); + + FormatInfo format = FormatTable.GetFormatInfo(_info.Format); + + PixelInternalFormat pixelInternalFormat; + + if (format.IsCompressed) + { + pixelInternalFormat = (PixelInternalFormat)format.PixelFormat; + } + else + { + pixelInternalFormat = format.PixelInternalFormat; + } + + GL.TextureView( + Handle, + target, + _parent.Handle, + pixelInternalFormat, + _firstLevel, + _info.Levels, + _firstLayer, + _info.GetLayers()); + + GL.ActiveTexture(TextureUnit.Texture0); + + GL.BindTexture(target, Handle); + + int[] swizzleRgba = new int[] + { + (int)_info.SwizzleR.Convert(), + (int)_info.SwizzleG.Convert(), + (int)_info.SwizzleB.Convert(), + (int)_info.SwizzleA.Convert() + }; + + GL.TexParameter(target, TextureParameterName.TextureSwizzleRgba, swizzleRgba); + + int maxLevel = _info.Levels - 1; + + if (maxLevel < 0) + { + maxLevel = 0; + } + + GL.TexParameter(target, TextureParameterName.TextureMaxLevel, maxLevel); + + // TODO: This requires ARB_stencil_texturing, we should uncomment and test this. + // GL.TexParameter(target, TextureParameterName.DepthStencilTextureMode, (int)_info.DepthStencilMode.Convert()); + } + + public ITexture CreateView(TextureCreateInfo info, int firstLayer, int firstLevel) + { + if (_info.IsCompressed == info.IsCompressed) + { + firstLayer += _firstLayer; + firstLevel += _firstLevel; + + return _parent.CreateView(info, firstLayer, firstLevel); + } + else + { + // TODO: Most graphics APIs doesn't support creating a texture view from a compressed format + // with a non-compressed format (or vice-versa), however NVN seems to support it. + // So we emulate that here with a texture copy (see the first CopyTo overload). + // However right now it only does a single copy right after the view is created, + // so it doesn't work for all cases. + TextureView emulatedView = (TextureView)_renderer.CreateTexture(info); + + emulatedView._emulatedViewParent = this; + + emulatedView._firstLayer = firstLayer; + emulatedView._firstLevel = firstLevel; + + return emulatedView; + } + } + + public void CopyTo(ITexture destination, int firstLayer, int firstLevel) + { + TextureView destinationView = (TextureView)destination; + + TextureCopyUnscaled.Copy(this, destinationView, firstLayer, firstLevel); + + if (destinationView._emulatedViewParent != null) + { + TextureCopyUnscaled.Copy( + this, + destinationView._emulatedViewParent, + destinationView._firstLayer, + destinationView._firstLevel); + } + } + + public void CopyTo(ITexture destination, Extents2D srcRegion, Extents2D dstRegion, bool linearFilter) + { + _renderer.TextureCopy.Copy(this, (TextureView)destination, srcRegion, dstRegion, linearFilter); + } + + public byte[] GetData() + { + int size = 0; + + for (int level = 0; level < _info.Levels; level++) + { + size += _info.GetMipSize(level); + } + + byte[] data = new byte[size]; + + unsafe + { + fixed (byte* ptr = data) + { + WriteTo((IntPtr)ptr); + } + } + + return data; + } + + private void WriteTo(IntPtr ptr) + { + TextureTarget target = Target.Convert(); + + Bind(target, 0); + + FormatInfo format = FormatTable.GetFormatInfo(_info.Format); + + int faces = 1; + + if (target == TextureTarget.TextureCubeMap) + { + target = TextureTarget.TextureCubeMapPositiveX; + + faces = 6; + } + + for (int level = 0; level < _info.Levels; level++) + { + for (int face = 0; face < faces; face++) + { + int faceOffset = face * _info.GetMipSize2D(level); + + if (format.IsCompressed) + { + GL.GetCompressedTexImage(target + face, level, ptr + faceOffset); + } + else + { + GL.GetTexImage( + target + face, + level, + format.PixelFormat, + format.PixelType, + ptr + faceOffset); + } + } + + ptr += _info.GetMipSize(level); + } + } + + public void SetData(ReadOnlySpan data) + { + unsafe + { + fixed (byte* ptr = data) + { + SetData((IntPtr)ptr, data.Length); + } + } + } + + private void SetData(IntPtr data, int size) + { + TextureTarget target = Target.Convert(); + + Bind(target, 0); + + FormatInfo format = FormatTable.GetFormatInfo(_info.Format); + + int width = _info.Width; + int height = _info.Height; + int depth = _info.Depth; + + int offset = 0; + + for (int level = 0; level < _info.Levels; level++) + { + int mipSize = _info.GetMipSize(level); + + int endOffset = offset + mipSize; + + if ((uint)endOffset > (uint)size) + { + return; + } + + switch (_info.Target) + { + case Target.Texture1D: + if (format.IsCompressed) + { + GL.CompressedTexSubImage1D( + target, + level, + 0, + width, + format.PixelFormat, + mipSize, + data); + } + else + { + GL.TexSubImage1D( + target, + level, + 0, + width, + format.PixelFormat, + format.PixelType, + data); + } + break; + + case Target.Texture1DArray: + case Target.Texture2D: + if (format.IsCompressed) + { + GL.CompressedTexSubImage2D( + target, + level, + 0, + 0, + width, + height, + format.PixelFormat, + mipSize, + data); + } + else + { + GL.TexSubImage2D( + target, + level, + 0, + 0, + width, + height, + format.PixelFormat, + format.PixelType, + data); + } + break; + + case Target.Texture2DArray: + case Target.Texture3D: + case Target.CubemapArray: + if (format.IsCompressed) + { + GL.CompressedTexSubImage3D( + target, + level, + 0, + 0, + 0, + width, + height, + depth, + format.PixelFormat, + mipSize, + data); + } + else + { + GL.TexSubImage3D( + target, + level, + 0, + 0, + 0, + width, + height, + depth, + format.PixelFormat, + format.PixelType, + data); + } + break; + + case Target.Cubemap: + int faceOffset = 0; + + for (int face = 0; face < 6; face++, faceOffset += mipSize / 6) + { + if (format.IsCompressed) + { + GL.CompressedTexSubImage2D( + TextureTarget.TextureCubeMapPositiveX + face, + level, + 0, + 0, + width, + height, + format.PixelFormat, + mipSize / 6, + data + faceOffset); + } + else + { + GL.TexSubImage2D( + TextureTarget.TextureCubeMapPositiveX + face, + level, + 0, + 0, + width, + height, + format.PixelFormat, + format.PixelType, + data + faceOffset); + } + } + break; + } + + data += mipSize; + offset += mipSize; + + width = Math.Max(1, width >> 1); + height = Math.Max(1, height >> 1); + + if (Target == Target.Texture3D) + { + depth = Math.Max(1, depth >> 1); + } + } + } + + public void Bind(int unit) + { + Bind(Target.Convert(), unit); + } + + private void Bind(TextureTarget target, int unit) + { + GL.ActiveTexture(TextureUnit.Texture0 + unit); + + GL.BindTexture(target, Handle); + } + + public void Dispose() + { + if (Handle != 0) + { + GL.DeleteTexture(Handle); + + _parent.DecrementViewsCount(); + + Handle = 0; + } + } + } +} diff --git a/Ryujinx.Graphics.OpenGL/VertexArray.cs b/Ryujinx.Graphics.OpenGL/VertexArray.cs new file mode 100644 index 0000000000..721a90f0c7 --- /dev/null +++ b/Ryujinx.Graphics.OpenGL/VertexArray.cs @@ -0,0 +1,139 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Graphics.GAL; +using System; + +namespace Ryujinx.Graphics.OpenGL +{ + class VertexArray : IDisposable + { + public int Handle { get; private set; } + + private bool _needsAttribsUpdate; + + private VertexBufferDescriptor[] _vertexBuffers; + private VertexAttribDescriptor[] _vertexAttribs; + + public VertexArray() + { + Handle = GL.GenVertexArray(); + } + + public void Bind() + { + GL.BindVertexArray(Handle); + } + + public void SetVertexBuffers(VertexBufferDescriptor[] vertexBuffers) + { + int bindingIndex = 0; + + foreach (VertexBufferDescriptor vb in vertexBuffers) + { + if (vb.Buffer.Buffer != null) + { + int bufferHandle = ((Buffer)vb.Buffer.Buffer).Handle; + + GL.BindVertexBuffer(bindingIndex, bufferHandle, (IntPtr)vb.Buffer.Offset, vb.Stride); + + GL.VertexBindingDivisor(bindingIndex, vb.Divisor); + } + else + { + GL.BindVertexBuffer(bindingIndex, 0, IntPtr.Zero, 0); + } + + bindingIndex++; + } + + _vertexBuffers = vertexBuffers; + + _needsAttribsUpdate = true; + } + + public void SetVertexAttributes(VertexAttribDescriptor[] vertexAttribs) + { + int attribIndex = 0; + + foreach (VertexAttribDescriptor attrib in vertexAttribs) + { + FormatInfo fmtInfo = FormatTable.GetFormatInfo(attrib.Format); + + GL.EnableVertexAttribArray(attribIndex); + + int offset = attrib.Offset; + int size = fmtInfo.Components; + + bool isFloat = fmtInfo.PixelType == PixelType.Float || + fmtInfo.PixelType == PixelType.HalfFloat; + + if (isFloat || fmtInfo.Normalized || fmtInfo.Scaled) + { + VertexAttribType type = (VertexAttribType)fmtInfo.PixelType; + + GL.VertexAttribFormat(attribIndex, size, type, fmtInfo.Normalized, offset); + } + else + { + VertexAttribIntegerType type = (VertexAttribIntegerType)fmtInfo.PixelType; + + GL.VertexAttribIFormat(attribIndex, size, type, offset); + } + + GL.VertexAttribBinding(attribIndex, attrib.BufferIndex); + + attribIndex++; + } + + for (; attribIndex < 16; attribIndex++) + { + GL.DisableVertexAttribArray(attribIndex); + } + + _vertexAttribs = vertexAttribs; + } + + public void SetIndexBuffer(Buffer indexBuffer) + { + GL.BindBuffer(BufferTarget.ElementArrayBuffer, indexBuffer?.Handle ?? 0); + } + + public void Validate() + { + for (int attribIndex = 0; attribIndex < _vertexAttribs.Length; attribIndex++) + { + VertexAttribDescriptor attrib = _vertexAttribs[attribIndex]; + + if ((uint)attrib.BufferIndex >= _vertexBuffers.Length) + { + GL.DisableVertexAttribArray(attribIndex); + + continue; + } + + if (_vertexBuffers[attrib.BufferIndex].Buffer.Buffer == null) + { + GL.DisableVertexAttribArray(attribIndex); + + continue; + } + + if (_needsAttribsUpdate) + { + GL.EnableVertexAttribArray(attribIndex); + } + } + + _needsAttribsUpdate = false; + } + + public void Dispose() + { + if (Handle != 0) + { + GL.DeleteVertexArray(Handle); + + Handle = 0; + } + } + } +} diff --git a/Ryujinx.Graphics.OpenGL/VertexBuffer.cs b/Ryujinx.Graphics.OpenGL/VertexBuffer.cs new file mode 100644 index 0000000000..19a58053c1 --- /dev/null +++ b/Ryujinx.Graphics.OpenGL/VertexBuffer.cs @@ -0,0 +1,19 @@ +using Ryujinx.Graphics.GAL; + +namespace Ryujinx.Graphics.OpenGL +{ + struct VertexBuffer + { + public BufferRange Range { get; } + + public int Divisor { get; } + public int Stride { get; } + + public VertexBuffer(BufferRange range, int divisor, int stride) + { + Range = range; + Divisor = divisor; + Stride = stride; + } + } +} diff --git a/Ryujinx.Graphics.OpenGL/Window.cs b/Ryujinx.Graphics.OpenGL/Window.cs new file mode 100644 index 0000000000..26fc6a64b8 --- /dev/null +++ b/Ryujinx.Graphics.OpenGL/Window.cs @@ -0,0 +1,132 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Graphics.GAL; +using System; + +namespace Ryujinx.Graphics.OpenGL +{ + class Window : IWindow, IDisposable + { + private const int NativeWidth = 1280; + private const int NativeHeight = 720; + + private int _width; + private int _height; + + private int _copyFramebufferHandle; + + public Window() + { + _width = NativeWidth; + _height = NativeHeight; + } + + public void Present(ITexture texture, ImageCrop crop) + { + TextureView view = (TextureView)texture; + + GL.Disable(EnableCap.FramebufferSrgb); + + int oldReadFramebufferHandle = GL.GetInteger(GetPName.ReadFramebufferBinding); + int oldDrawFramebufferHandle = GL.GetInteger(GetPName.DrawFramebufferBinding); + + GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, 0); + GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, GetCopyFramebufferHandleLazy()); + + GL.FramebufferTexture( + FramebufferTarget.ReadFramebuffer, + FramebufferAttachment.ColorAttachment0, + view.Handle, + 0); + + GL.ReadBuffer(ReadBufferMode.ColorAttachment0); + + GL.Clear(ClearBufferMask.ColorBufferBit); + + int srcX0, srcX1, srcY0, srcY1; + + if (crop.Left == 0 && crop.Right == 0) + { + srcX0 = 0; + srcX1 = view.Width; + } + else + { + srcX0 = crop.Left; + srcX1 = crop.Right; + } + + if (crop.Top == 0 && crop.Bottom == 0) + { + srcY0 = 0; + srcY1 = view.Height; + } + else + { + srcY0 = crop.Top; + srcY1 = crop.Bottom; + } + + float ratioX = MathF.Min(1f, (_height * (float)NativeWidth) / ((float)NativeHeight * _width)); + float ratioY = MathF.Min(1f, (_width * (float)NativeHeight) / ((float)NativeWidth * _height)); + + int dstWidth = (int)(_width * ratioX); + int dstHeight = (int)(_height * ratioY); + + int dstPaddingX = (_width - dstWidth) / 2; + int dstPaddingY = (_height - dstHeight) / 2; + + int dstX0 = crop.FlipX ? _width - dstPaddingX : dstPaddingX; + int dstX1 = crop.FlipX ? dstPaddingX : _width - dstPaddingX; + + int dstY0 = crop.FlipY ? dstPaddingY : _height - dstPaddingY; + int dstY1 = crop.FlipY ? _height - dstPaddingY : dstPaddingY; + + GL.BlitFramebuffer( + srcX0, + srcY0, + srcX1, + srcY1, + dstX0, + dstY0, + dstX1, + dstY1, + ClearBufferMask.ColorBufferBit, + BlitFramebufferFilter.Linear); + + GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, oldReadFramebufferHandle); + GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, oldDrawFramebufferHandle); + + GL.Enable(EnableCap.FramebufferSrgb); + } + + public void SetSize(int width, int height) + { + _width = width; + _height = height; + } + + private int GetCopyFramebufferHandleLazy() + { + int handle = _copyFramebufferHandle; + + if (handle == 0) + { + handle = GL.GenFramebuffer(); + + _copyFramebufferHandle = handle; + } + + return handle; + } + + public void Dispose() + { + if (_copyFramebufferHandle != 0) + { + GL.DeleteFramebuffer(_copyFramebufferHandle); + + _copyFramebufferHandle = 0; + } + } + } +} diff --git a/Ryujinx.Graphics.Shader/BufferDescriptor.cs b/Ryujinx.Graphics.Shader/BufferDescriptor.cs new file mode 100644 index 0000000000..4cc999989e --- /dev/null +++ b/Ryujinx.Graphics.Shader/BufferDescriptor.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.Graphics.Shader +{ + public struct BufferDescriptor + { + public string Name { get; } + + public int Slot { get; } + + public BufferDescriptor(string name, int slot) + { + Name = name; + Slot = slot; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/CodeGen/Constants.cs b/Ryujinx.Graphics.Shader/CodeGen/Constants.cs new file mode 100644 index 0000000000..59e9f14584 --- /dev/null +++ b/Ryujinx.Graphics.Shader/CodeGen/Constants.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Graphics.Shader.CodeGen +{ + static class Constants + { + public const int MaxShaderStorageBuffers = 16; + + public const int ConstantBufferSize = 0x10000; // In bytes + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/CodeGenContext.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/CodeGenContext.cs new file mode 100644 index 0000000000..6bef8e6c20 --- /dev/null +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/CodeGenContext.cs @@ -0,0 +1,95 @@ +using Ryujinx.Graphics.Shader.Translation; +using System.Collections.Generic; +using System.Text; + +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl +{ + class CodeGenContext + { + public const string Tab = " "; + + public ShaderConfig Config { get; } + + public List CBufferDescriptors { get; } + public List SBufferDescriptors { get; } + public List TextureDescriptors { get; } + public List ImageDescriptors { get; } + + public OperandManager OperandManager { get; } + + private StringBuilder _sb; + + private int _level; + + private string _indentation; + + public CodeGenContext(ShaderConfig config) + { + Config = config; + + CBufferDescriptors = new List(); + SBufferDescriptors = new List(); + TextureDescriptors = new List(); + ImageDescriptors = new List(); + + OperandManager = new OperandManager(); + + _sb = new StringBuilder(); + } + + public void AppendLine() + { + _sb.AppendLine(); + } + + public void AppendLine(string str) + { + _sb.AppendLine(_indentation + str); + } + + public string GetCode() + { + return _sb.ToString(); + } + + public void EnterScope() + { + AppendLine("{"); + + _level++; + + UpdateIndentation(); + } + + public void LeaveScope(string suffix = "") + { + if (_level == 0) + { + return; + } + + _level--; + + UpdateIndentation(); + + AppendLine("}" + suffix); + } + + private void UpdateIndentation() + { + _indentation = GetIndentation(_level); + } + + private static string GetIndentation(int level) + { + string indentation = string.Empty; + + for (int index = 0; index < level; index++) + { + indentation += Tab; + } + + return indentation; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs new file mode 100644 index 0000000000..200569c48e --- /dev/null +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs @@ -0,0 +1,469 @@ +using Ryujinx.Common; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.StructuredIr; +using Ryujinx.Graphics.Shader.Translation; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl +{ + static class Declarations + { + // At least 16 attributes are guaranteed by the spec. + public const int MaxAttributes = 16; + + public static void Declare(CodeGenContext context, StructuredProgramInfo info) + { + context.AppendLine("#version 420 core"); + context.AppendLine("#extension GL_ARB_gpu_shader_int64 : enable"); + context.AppendLine("#extension GL_ARB_shader_ballot : enable"); + context.AppendLine("#extension GL_ARB_shader_group_vote : enable"); + context.AppendLine("#extension GL_ARB_shader_storage_buffer_object : enable"); + + if (context.Config.Stage == ShaderStage.Compute) + { + context.AppendLine("#extension GL_ARB_compute_shader : enable"); + } + + context.AppendLine("#pragma optionNV(fastmath off)"); + + context.AppendLine(); + + context.AppendLine($"const int {DefaultNames.UndefinedName} = 0;"); + context.AppendLine(); + + if (context.Config.Stage == ShaderStage.Geometry) + { + string inPrimitive = ((InputTopology)context.Config.QueryInfo(QueryInfoName.PrimitiveTopology)).ToGlslString(); + + context.AppendLine($"layout ({inPrimitive}) in;"); + + string outPrimitive = context.Config.OutputTopology.ToGlslString(); + + int maxOutputVertices = context.Config.MaxOutputVertices; + + context.AppendLine($"layout ({outPrimitive}, max_vertices = {maxOutputVertices}) out;"); + context.AppendLine(); + } + + context.AppendLine("layout (std140) uniform Extra"); + + context.EnterScope(); + + context.AppendLine("vec2 flip;"); + context.AppendLine("int instance;"); + + context.LeaveScope(";"); + + context.AppendLine(); + + context.AppendLine($"uint {DefaultNames.LocalMemoryName}[0x100];"); + context.AppendLine(); + + if (context.Config.Stage == ShaderStage.Compute) + { + string size = NumberFormatter.FormatInt(BitUtils.DivRoundUp(context.Config.QueryInfo(QueryInfoName.ComputeSharedMemorySize), 4)); + + context.AppendLine($"shared uint {DefaultNames.SharedMemoryName}[{size}];"); + context.AppendLine(); + } + + if (info.CBuffers.Count != 0) + { + DeclareUniforms(context, info); + + context.AppendLine(); + } + + if (info.SBuffers.Count != 0) + { + DeclareStorages(context, info); + + context.AppendLine(); + } + + if (info.Samplers.Count != 0) + { + DeclareSamplers(context, info); + + context.AppendLine(); + } + + if (info.Images.Count != 0) + { + DeclareImages(context, info); + + context.AppendLine(); + } + + if (context.Config.Stage != ShaderStage.Compute) + { + if (info.IAttributes.Count != 0) + { + DeclareInputAttributes(context, info); + + context.AppendLine(); + } + + if (info.OAttributes.Count != 0 || context.Config.Stage != ShaderStage.Fragment) + { + DeclareOutputAttributes(context, info); + + context.AppendLine(); + } + } + else + { + string localSizeX = NumberFormatter.FormatInt(context.Config.QueryInfo(QueryInfoName.ComputeLocalSizeX)); + string localSizeY = NumberFormatter.FormatInt(context.Config.QueryInfo(QueryInfoName.ComputeLocalSizeY)); + string localSizeZ = NumberFormatter.FormatInt(context.Config.QueryInfo(QueryInfoName.ComputeLocalSizeZ)); + + context.AppendLine( + "layout (" + + $"local_size_x = {localSizeX}, " + + $"local_size_y = {localSizeY}, " + + $"local_size_z = {localSizeZ}) in;"); + context.AppendLine(); + } + + if ((info.HelperFunctionsMask & HelperFunctionsMask.MultiplyHighS32) != 0) + { + AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/MultiplyHighS32.glsl"); + } + + if ((info.HelperFunctionsMask & HelperFunctionsMask.MultiplyHighU32) != 0) + { + AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/MultiplyHighU32.glsl"); + } + + if ((info.HelperFunctionsMask & HelperFunctionsMask.Shuffle) != 0) + { + AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/Shuffle.glsl"); + } + + if ((info.HelperFunctionsMask & HelperFunctionsMask.ShuffleDown) != 0) + { + AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/ShuffleDown.glsl"); + } + + if ((info.HelperFunctionsMask & HelperFunctionsMask.ShuffleUp) != 0) + { + AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/ShuffleUp.glsl"); + } + + if ((info.HelperFunctionsMask & HelperFunctionsMask.ShuffleXor) != 0) + { + AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/ShuffleXor.glsl"); + } + + if ((info.HelperFunctionsMask & HelperFunctionsMask.SwizzleAdd) != 0) + { + AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/SwizzleAdd.glsl"); + } + } + + public static void DeclareLocals(CodeGenContext context, StructuredProgramInfo info) + { + foreach (AstOperand decl in info.Locals) + { + string name = context.OperandManager.DeclareLocal(decl); + + context.AppendLine(GetVarTypeName(decl.VarType) + " " + name + ";"); + } + } + + private static string GetVarTypeName(VariableType type) + { + switch (type) + { + case VariableType.Bool: return "bool"; + case VariableType.F32: return "precise float"; + case VariableType.S32: return "int"; + case VariableType.U32: return "uint"; + } + + throw new ArgumentException($"Invalid variable type \"{type}\"."); + } + + private static void DeclareUniforms(CodeGenContext context, StructuredProgramInfo info) + { + foreach (int cbufSlot in info.CBuffers.OrderBy(x => x)) + { + string ubName = OperandManager.GetShaderStagePrefix(context.Config.Stage); + + ubName += "_" + DefaultNames.UniformNamePrefix + cbufSlot; + + context.CBufferDescriptors.Add(new BufferDescriptor(ubName, cbufSlot)); + + context.AppendLine("layout (std140) uniform " + ubName); + + context.EnterScope(); + + string ubSize = "[" + NumberFormatter.FormatInt(Constants.ConstantBufferSize / 16) + "]"; + + context.AppendLine("vec4 " + OperandManager.GetUbName(context.Config.Stage, cbufSlot) + ubSize + ";"); + + context.LeaveScope(";"); + } + } + + private static void DeclareStorages(CodeGenContext context, StructuredProgramInfo info) + { + string sbName = OperandManager.GetShaderStagePrefix(context.Config.Stage); + + sbName += "_" + DefaultNames.StorageNamePrefix; + + string blockName = $"{sbName}_{DefaultNames.BlockSuffix}"; + + int maxSlot = 0; + + foreach (int sbufSlot in info.SBuffers) + { + context.SBufferDescriptors.Add(new BufferDescriptor($"{blockName}[{sbufSlot}]", sbufSlot)); + + if (maxSlot < sbufSlot) + { + maxSlot = sbufSlot; + } + } + + context.AppendLine("layout (std430) buffer " + blockName); + + context.EnterScope(); + + context.AppendLine("uint " + DefaultNames.DataName + "[];"); + + string arraySize = NumberFormatter.FormatInt(maxSlot + 1); + + context.LeaveScope($" {sbName}[{arraySize}];"); + } + + private static void DeclareSamplers(CodeGenContext context, StructuredProgramInfo info) + { + Dictionary samplers = new Dictionary(); + + foreach (AstTextureOperation texOp in info.Samplers.OrderBy(x => x.Handle)) + { + string indexExpr = NumberFormatter.FormatInt(texOp.ArraySize); + + string samplerName = OperandManager.GetSamplerName(context.Config.Stage, texOp, indexExpr); + + if (!samplers.TryAdd(samplerName, texOp)) + { + continue; + } + + string samplerTypeName = GetSamplerTypeName(texOp.Type); + + context.AppendLine("uniform " + samplerTypeName + " " + samplerName + ";"); + } + + foreach (KeyValuePair kv in samplers) + { + string samplerName = kv.Key; + + AstTextureOperation texOp = kv.Value; + + TextureDescriptor desc; + + if ((texOp.Flags & TextureFlags.Bindless) != 0) + { + AstOperand operand = texOp.GetSource(0) as AstOperand; + + desc = new TextureDescriptor(samplerName, texOp.Type, operand.CbufSlot, operand.CbufOffset); + + context.TextureDescriptors.Add(desc); + } + else if ((texOp.Type & SamplerType.Indexed) != 0) + { + for (int index = 0; index < texOp.ArraySize; index++) + { + string indexExpr = NumberFormatter.FormatInt(index); + + string indexedSamplerName = OperandManager.GetSamplerName(context.Config.Stage, texOp, indexExpr); + + desc = new TextureDescriptor(indexedSamplerName, texOp.Type, texOp.Handle + index * 2); + + context.TextureDescriptors.Add(desc); + } + } + else + { + desc = new TextureDescriptor(samplerName, texOp.Type, texOp.Handle); + + context.TextureDescriptors.Add(desc); + } + } + } + + private static void DeclareImages(CodeGenContext context, StructuredProgramInfo info) + { + Dictionary images = new Dictionary(); + + foreach (AstTextureOperation texOp in info.Images.OrderBy(x => x.Handle)) + { + string indexExpr = NumberFormatter.FormatInt(texOp.ArraySize); + + string imageName = OperandManager.GetImageName(context.Config.Stage, texOp, indexExpr); + + if (!images.TryAdd(imageName, texOp)) + { + continue; + } + + string imageTypeName = GetImageTypeName(texOp.Type); + + context.AppendLine("writeonly uniform " + imageTypeName + " " + imageName + ";"); + } + + foreach (KeyValuePair kv in images) + { + string imageName = kv.Key; + + AstTextureOperation texOp = kv.Value; + + if ((texOp.Type & SamplerType.Indexed) != 0) + { + for (int index = 0; index < texOp.ArraySize; index++) + { + string indexExpr = NumberFormatter.FormatInt(index); + + string indexedSamplerName = OperandManager.GetSamplerName(context.Config.Stage, texOp, indexExpr); + + var desc = new TextureDescriptor(indexedSamplerName, texOp.Type, texOp.Handle + index * 2); + + context.TextureDescriptors.Add(desc); + } + } + else + { + var desc = new TextureDescriptor(imageName, texOp.Type, texOp.Handle); + + context.ImageDescriptors.Add(desc); + } + } + } + + private static void DeclareInputAttributes(CodeGenContext context, StructuredProgramInfo info) + { + string suffix = context.Config.Stage == ShaderStage.Geometry ? "[]" : string.Empty; + + foreach (int attr in info.IAttributes.OrderBy(x => x)) + { + string iq = info.InterpolationQualifiers[attr].ToGlslQualifier(); + + if (iq != string.Empty) + { + iq += " "; + } + + context.AppendLine($"layout (location = {attr}) {iq}in vec4 {DefaultNames.IAttributePrefix}{attr}{suffix};"); + } + } + + private static void DeclareOutputAttributes(CodeGenContext context, StructuredProgramInfo info) + { + if (context.Config.Stage == ShaderStage.Fragment) + { + DeclareUsedOutputAttributes(context, info); + } + else + { + DeclareAllOutputAttributes(context, info); + } + } + + private static void DeclareUsedOutputAttributes(CodeGenContext context, StructuredProgramInfo info) + { + foreach (int attr in info.OAttributes.OrderBy(x => x)) + { + context.AppendLine($"layout (location = {attr}) out vec4 {DefaultNames.OAttributePrefix}{attr};"); + } + } + + private static void DeclareAllOutputAttributes(CodeGenContext context, StructuredProgramInfo info) + { + for (int attr = 0; attr < MaxAttributes; attr++) + { + string iq = $"{DefineNames.OutQualifierPrefixName}{attr} "; + + context.AppendLine($"layout (location = {attr}) {iq}out vec4 {DefaultNames.OAttributePrefix}{attr};"); + } + + foreach (int attr in info.OAttributes.OrderBy(x => x).Where(x => x >= MaxAttributes)) + { + context.AppendLine($"layout (location = {attr}) out vec4 {DefaultNames.OAttributePrefix}{attr};"); + } + } + + private static void AppendHelperFunction(CodeGenContext context, string filename) + { + string code = EmbeddedResources.ReadAllText(filename); + + context.AppendLine(code.Replace("\t", CodeGenContext.Tab)); + context.AppendLine(); + } + + private static string GetSamplerTypeName(SamplerType type) + { + string typeName; + + switch (type & SamplerType.Mask) + { + case SamplerType.Texture1D: typeName = "sampler1D"; break; + case SamplerType.TextureBuffer: typeName = "samplerBuffer"; break; + case SamplerType.Texture2D: typeName = "sampler2D"; break; + case SamplerType.Texture3D: typeName = "sampler3D"; break; + case SamplerType.TextureCube: typeName = "samplerCube"; break; + + default: throw new ArgumentException($"Invalid sampler type \"{type}\"."); + } + + if ((type & SamplerType.Multisample) != 0) + { + typeName += "MS"; + } + + if ((type & SamplerType.Array) != 0) + { + typeName += "Array"; + } + + if ((type & SamplerType.Shadow) != 0) + { + typeName += "Shadow"; + } + + return typeName; + } + + private static string GetImageTypeName(SamplerType type) + { + string typeName; + + switch (type & SamplerType.Mask) + { + case SamplerType.Texture1D: typeName = "image1D"; break; + case SamplerType.TextureBuffer: typeName = "imageBuffer"; break; + case SamplerType.Texture2D: typeName = "image2D"; break; + case SamplerType.Texture3D: typeName = "image3D"; break; + case SamplerType.TextureCube: typeName = "imageCube"; break; + + default: throw new ArgumentException($"Invalid sampler type \"{type}\"."); + } + + if ((type & SamplerType.Multisample) != 0) + { + typeName += "MS"; + } + + if ((type & SamplerType.Array) != 0) + { + typeName += "Array"; + } + + return typeName; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/DefaultNames.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/DefaultNames.cs new file mode 100644 index 0000000000..4da38b2de5 --- /dev/null +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/DefaultNames.cs @@ -0,0 +1,27 @@ +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl +{ + static class DefaultNames + { + public const string LocalNamePrefix = "temp"; + + public const string SamplerNamePrefix = "tex"; + public const string ImageNamePrefix = "img"; + + public const string IAttributePrefix = "in_attr"; + public const string OAttributePrefix = "out_attr"; + + public const string StorageNamePrefix = "s"; + + public const string DataName = "data"; + + public const string BlockSuffix = "block"; + + public const string UniformNamePrefix = "c"; + public const string UniformNameSuffix = "data"; + + public const string LocalMemoryName = "local_mem"; + public const string SharedMemoryName = "shared_mem"; + + public const string UndefinedName = "undef"; + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/GlslGenerator.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/GlslGenerator.cs new file mode 100644 index 0000000000..1465338e1e --- /dev/null +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/GlslGenerator.cs @@ -0,0 +1,152 @@ +using Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.StructuredIr; +using Ryujinx.Graphics.Shader.Translation; +using System; + +using static Ryujinx.Graphics.Shader.CodeGen.Glsl.TypeConversion; + +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl +{ + static class GlslGenerator + { + public static GlslProgram Generate(StructuredProgramInfo info, ShaderConfig config) + { + CodeGenContext context = new CodeGenContext(config); + + Declarations.Declare(context, info); + + PrintMainBlock(context, info); + + return new GlslProgram( + context.CBufferDescriptors.ToArray(), + context.SBufferDescriptors.ToArray(), + context.TextureDescriptors.ToArray(), + context.ImageDescriptors.ToArray(), + context.GetCode()); + } + + private static void PrintMainBlock(CodeGenContext context, StructuredProgramInfo info) + { + context.AppendLine("void main()"); + + context.EnterScope(); + + Declarations.DeclareLocals(context, info); + + // Some games will leave some elements of gl_Position uninitialized, + // in those cases, the elements will contain undefined values according + // to the spec, but on NVIDIA they seems to be always initialized to (0, 0, 0, 1), + // so we do explicit initialization to avoid UB on non-NVIDIA gpus. + if (context.Config.Stage == ShaderStage.Vertex) + { + context.AppendLine("gl_Position = vec4(0.0, 0.0, 0.0, 1.0);"); + } + + // Ensure that unused attributes are set, otherwise the downstream + // compiler may eliminate them. + // (Not needed for fragment shader as it is the last stage). + if (context.Config.Stage != ShaderStage.Compute && + context.Config.Stage != ShaderStage.Fragment) + { + for (int attr = 0; attr < Declarations.MaxAttributes; attr++) + { + if (info.OAttributes.Contains(attr)) + { + continue; + } + + context.AppendLine($"{DefaultNames.OAttributePrefix}{attr} = vec4(0);"); + } + } + + PrintBlock(context, info.MainBlock); + + context.LeaveScope(); + } + + private static void PrintBlock(CodeGenContext context, AstBlock block) + { + AstBlockVisitor visitor = new AstBlockVisitor(block); + + visitor.BlockEntered += (sender, e) => + { + switch (e.Block.Type) + { + case AstBlockType.DoWhile: + context.AppendLine("do"); + break; + + case AstBlockType.Else: + context.AppendLine("else"); + break; + + case AstBlockType.ElseIf: + context.AppendLine($"else if ({GetCondExpr(context, e.Block.Condition)})"); + break; + + case AstBlockType.If: + context.AppendLine($"if ({GetCondExpr(context, e.Block.Condition)})"); + break; + + default: throw new InvalidOperationException($"Found unexpected block type \"{e.Block.Type}\"."); + } + + context.EnterScope(); + }; + + visitor.BlockLeft += (sender, e) => + { + context.LeaveScope(); + + if (e.Block.Type == AstBlockType.DoWhile) + { + context.AppendLine($"while ({GetCondExpr(context, e.Block.Condition)});"); + } + }; + + foreach (IAstNode node in visitor.Visit()) + { + if (node is AstOperation operation) + { + context.AppendLine(InstGen.GetExpression(context, operation) + ";"); + } + else if (node is AstAssignment assignment) + { + VariableType srcType = OperandManager.GetNodeDestType(assignment.Source); + VariableType dstType = OperandManager.GetNodeDestType(assignment.Destination); + + string dest; + + if (assignment.Destination is AstOperand operand && operand.Type == OperandType.Attribute) + { + dest = OperandManager.GetOutAttributeName(operand, context.Config.Stage); + } + else + { + dest = InstGen.GetExpression(context, assignment.Destination); + } + + string src = ReinterpretCast(context, assignment.Source, srcType, dstType); + + context.AppendLine(dest + " = " + src + ";"); + } + else if (node is AstComment comment) + { + context.AppendLine("// " + comment.Comment); + } + else + { + throw new InvalidOperationException($"Found unexpected node type \"{node?.GetType().Name ?? "null"}\"."); + } + } + } + + private static string GetCondExpr(CodeGenContext context, IAstNode cond) + { + VariableType srcType = OperandManager.GetNodeDestType(cond); + + return ReinterpretCast(context, cond, srcType, VariableType.Bool); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/GlslProgram.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/GlslProgram.cs new file mode 100644 index 0000000000..31b7f31260 --- /dev/null +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/GlslProgram.cs @@ -0,0 +1,26 @@ +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl +{ + class GlslProgram + { + public BufferDescriptor[] CBufferDescriptors { get; } + public BufferDescriptor[] SBufferDescriptors { get; } + public TextureDescriptor[] TextureDescriptors { get; } + public TextureDescriptor[] ImageDescriptors { get; } + + public string Code { get; } + + public GlslProgram( + BufferDescriptor[] cBufferDescriptors, + BufferDescriptor[] sBufferDescriptors, + TextureDescriptor[] textureDescriptors, + TextureDescriptor[] imageDescriptors, + string code) + { + CBufferDescriptors = cBufferDescriptors; + SBufferDescriptors = sBufferDescriptors; + TextureDescriptors = textureDescriptors; + ImageDescriptors = imageDescriptors; + Code = code; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/HelperFunctionNames.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/HelperFunctionNames.cs new file mode 100644 index 0000000000..21c435475f --- /dev/null +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/HelperFunctionNames.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl +{ + static class HelperFunctionNames + { + public static string MultiplyHighS32 = "Helper_MultiplyHighS32"; + public static string MultiplyHighU32 = "Helper_MultiplyHighU32"; + + public static string Shuffle = "Helper_Shuffle"; + public static string ShuffleDown = "Helper_ShuffleDown"; + public static string ShuffleUp = "Helper_ShuffleUp"; + public static string ShuffleXor = "Helper_ShuffleXor"; + public static string SwizzleAdd = "Helper_SwizzleAdd"; + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/MultiplyHighS32.glsl b/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/MultiplyHighS32.glsl new file mode 100644 index 0000000000..caad6f5696 --- /dev/null +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/MultiplyHighS32.glsl @@ -0,0 +1,7 @@ +int Helper_MultiplyHighS32(int x, int y) +{ + int msb; + int lsb; + imulExtended(x, y, msb, lsb); + return msb; +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/MultiplyHighU32.glsl b/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/MultiplyHighU32.glsl new file mode 100644 index 0000000000..617a925f6b --- /dev/null +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/MultiplyHighU32.glsl @@ -0,0 +1,7 @@ +uint Helper_MultiplyHighU32(uint x, uint y) +{ + uint msb; + uint lsb; + umulExtended(x, y, msb, lsb); + return msb; +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/Shuffle.glsl b/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/Shuffle.glsl new file mode 100644 index 0000000000..380bc581f5 --- /dev/null +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/Shuffle.glsl @@ -0,0 +1,9 @@ +float Helper_Shuffle(float x, uint index, uint mask) +{ + uint clamp = mask & 0x1fu; + uint segMask = (mask >> 8) & 0x1fu; + uint minThreadId = gl_SubGroupInvocationARB & segMask; + uint maxThreadId = minThreadId | (clamp & ~segMask); + uint srcThreadId = (index & ~segMask) | minThreadId; + return (srcThreadId <= maxThreadId) ? readInvocationARB(x, srcThreadId) : x; +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/ShuffleDown.glsl b/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/ShuffleDown.glsl new file mode 100644 index 0000000000..46750f20de --- /dev/null +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/ShuffleDown.glsl @@ -0,0 +1,9 @@ +float Helper_ShuffleDown(float x, uint index, uint mask) +{ + uint clamp = mask & 0x1fu; + uint segMask = (mask >> 8) & 0x1fu; + uint minThreadId = gl_SubGroupInvocationARB & segMask; + uint maxThreadId = minThreadId | (clamp & ~segMask); + uint srcThreadId = gl_SubGroupInvocationARB + index; + return (srcThreadId <= maxThreadId) ? readInvocationARB(x, srcThreadId) : x; +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/ShuffleUp.glsl b/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/ShuffleUp.glsl new file mode 100644 index 0000000000..2bc8346972 --- /dev/null +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/ShuffleUp.glsl @@ -0,0 +1,8 @@ +float Helper_ShuffleUp(float x, uint index, uint mask) +{ + uint clamp = mask & 0x1fu; + uint segMask = (mask >> 8) & 0x1fu; + uint minThreadId = gl_SubGroupInvocationARB & segMask; + uint srcThreadId = gl_SubGroupInvocationARB - index; + return (srcThreadId >= minThreadId) ? readInvocationARB(x, srcThreadId) : x; +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/ShuffleXor.glsl b/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/ShuffleXor.glsl new file mode 100644 index 0000000000..1049e181fa --- /dev/null +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/ShuffleXor.glsl @@ -0,0 +1,9 @@ +float Helper_ShuffleXor(float x, uint index, uint mask) +{ + uint clamp = mask & 0x1fu; + uint segMask = (mask >> 8) & 0x1fu; + uint minThreadId = gl_SubGroupInvocationARB & segMask; + uint maxThreadId = minThreadId | (clamp & ~segMask); + uint srcThreadId = gl_SubGroupInvocationARB ^ index; + return (srcThreadId <= maxThreadId) ? readInvocationARB(x, srcThreadId) : x; +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/SwizzleAdd.glsl b/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/SwizzleAdd.glsl new file mode 100644 index 0000000000..7df3e57fd0 --- /dev/null +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/SwizzleAdd.glsl @@ -0,0 +1,7 @@ +float Helper_SwizzleAdd(float x, float y, int mask) +{ + vec4 xLut = vec4(1.0, -1.0, 1.0, 0.0); + vec4 yLut = vec4(1.0, 1.0, -1.0, 1.0); + int lutIdx = mask >> int(gl_SubGroupInvocationARB & 3u) * 2; + return x * xLut[lutIdx] + y * yLut[lutIdx]; +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGen.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGen.cs new file mode 100644 index 0000000000..73a71f9ee4 --- /dev/null +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGen.cs @@ -0,0 +1,165 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.StructuredIr; +using System; + +using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenHelper; +using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenMemory; +using static Ryujinx.Graphics.Shader.StructuredIr.InstructionInfo; + +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions +{ + static class InstGen + { + public static string GetExpression(CodeGenContext context, IAstNode node) + { + if (node is AstOperation operation) + { + return GetExpression(context, operation); + } + else if (node is AstOperand operand) + { + return context.OperandManager.GetExpression(operand, context.Config.Stage); + } + + throw new ArgumentException($"Invalid node type \"{node?.GetType().Name ?? "null"}\"."); + } + + private static string GetExpression(CodeGenContext context, AstOperation operation) + { + Instruction inst = operation.Inst; + + InstInfo info = GetInstructionInfo(inst); + + if ((info.Type & InstType.Call) != 0) + { + bool atomic = (info.Type & InstType.Atomic) != 0; + + int arity = (int)(info.Type & InstType.ArityMask); + + string args = string.Empty; + + for (int argIndex = 0; argIndex < arity; argIndex++) + { + if (argIndex != 0) + { + args += ", "; + } + + VariableType dstType = GetSrcVarType(inst, argIndex); + + if (argIndex == 0 && atomic) + { + Instruction memRegion = inst & Instruction.MrMask; + + switch (memRegion) + { + case Instruction.MrShared: args += LoadShared (context, operation); break; + case Instruction.MrStorage: args += LoadStorage(context, operation); break; + + default: throw new InvalidOperationException($"Invalid memory region \"{memRegion}\"."); + } + + // We use the first 2 operands above. + argIndex++; + } + else + { + args += GetSoureExpr(context, operation.GetSource(argIndex), dstType); + } + } + + if (inst == Instruction.Ballot) + { + return $"unpackUint2x32({info.OpName}({args})).x"; + } + else + { + return info.OpName + "(" + args + ")"; + } + } + else if ((info.Type & InstType.Op) != 0) + { + string op = info.OpName; + + int arity = (int)(info.Type & InstType.ArityMask); + + string[] expr = new string[arity]; + + for (int index = 0; index < arity; index++) + { + IAstNode src = operation.GetSource(index); + + string srcExpr = GetSoureExpr(context, src, GetSrcVarType(inst, index)); + + bool isLhs = arity == 2 && index == 0; + + expr[index] = Enclose(srcExpr, src, inst, info, isLhs); + } + + switch (arity) + { + case 0: + return op; + + case 1: + return op + expr[0]; + + case 2: + return $"{expr[0]} {op} {expr[1]}"; + + case 3: + return $"{expr[0]} {op[0]} {expr[1]} {op[1]} {expr[2]}"; + } + } + else if ((info.Type & InstType.Special) != 0) + { + switch (inst) + { + case Instruction.ImageStore: + return InstGenMemory.ImageStore(context, operation); + + case Instruction.LoadAttribute: + return InstGenMemory.LoadAttribute(context, operation); + + case Instruction.LoadConstant: + return InstGenMemory.LoadConstant(context, operation); + + case Instruction.LoadLocal: + return InstGenMemory.LoadLocal(context, operation); + + case Instruction.LoadShared: + return InstGenMemory.LoadShared(context, operation); + + case Instruction.LoadStorage: + return InstGenMemory.LoadStorage(context, operation); + + case Instruction.Lod: + return InstGenMemory.Lod(context, operation); + + case Instruction.PackHalf2x16: + return InstGenPacking.PackHalf2x16(context, operation); + + case Instruction.StoreLocal: + return InstGenMemory.StoreLocal(context, operation); + + case Instruction.StoreShared: + return InstGenMemory.StoreShared(context, operation); + + case Instruction.StoreStorage: + return InstGenMemory.StoreStorage(context, operation); + + case Instruction.TextureSample: + return InstGenMemory.TextureSample(context, operation); + + case Instruction.TextureSize: + return InstGenMemory.TextureSize(context, operation); + + case Instruction.UnpackHalf2x16: + return InstGenPacking.UnpackHalf2x16(context, operation); + } + } + + throw new InvalidOperationException($"Unexpected instruction type \"{info.Type}\"."); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs new file mode 100644 index 0000000000..8dec34997a --- /dev/null +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs @@ -0,0 +1,211 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.StructuredIr; + +using static Ryujinx.Graphics.Shader.CodeGen.Glsl.TypeConversion; + +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions +{ + static class InstGenHelper + { + private static InstInfo[] _infoTbl; + + static InstGenHelper() + { + _infoTbl = new InstInfo[(int)Instruction.Count]; + + Add(Instruction.AtomicAdd, InstType.AtomicBinary, "atomicAdd"); + Add(Instruction.AtomicAnd, InstType.AtomicBinary, "atomicAnd"); + Add(Instruction.AtomicCompareAndSwap, InstType.AtomicTernary, "atomicCompSwap"); + Add(Instruction.AtomicMaxS32, InstType.AtomicBinary, "atomicMax"); + Add(Instruction.AtomicMaxU32, InstType.AtomicBinary, "atomicMax"); + Add(Instruction.AtomicMinS32, InstType.AtomicBinary, "atomicMin"); + Add(Instruction.AtomicMinU32, InstType.AtomicBinary, "atomicMin"); + Add(Instruction.AtomicOr, InstType.AtomicBinary, "atomicOr"); + Add(Instruction.AtomicSwap, InstType.AtomicBinary, "atomicExchange"); + Add(Instruction.AtomicXor, InstType.AtomicBinary, "atomicXor"); + Add(Instruction.Absolute, InstType.CallUnary, "abs"); + Add(Instruction.Add, InstType.OpBinaryCom, "+", 2); + Add(Instruction.Ballot, InstType.CallUnary, "ballotARB"); + Add(Instruction.Barrier, InstType.CallNullary, "barrier"); + Add(Instruction.BitCount, InstType.CallUnary, "bitCount"); + Add(Instruction.BitfieldExtractS32, InstType.CallTernary, "bitfieldExtract"); + Add(Instruction.BitfieldExtractU32, InstType.CallTernary, "bitfieldExtract"); + Add(Instruction.BitfieldInsert, InstType.CallQuaternary, "bitfieldInsert"); + Add(Instruction.BitfieldReverse, InstType.CallUnary, "bitfieldReverse"); + Add(Instruction.BitwiseAnd, InstType.OpBinaryCom, "&", 6); + Add(Instruction.BitwiseExclusiveOr, InstType.OpBinaryCom, "^", 7); + Add(Instruction.BitwiseNot, InstType.OpUnary, "~", 0); + Add(Instruction.BitwiseOr, InstType.OpBinaryCom, "|", 8); + Add(Instruction.Ceiling, InstType.CallUnary, "ceil"); + Add(Instruction.Clamp, InstType.CallTernary, "clamp"); + Add(Instruction.ClampU32, InstType.CallTernary, "clamp"); + Add(Instruction.CompareEqual, InstType.OpBinaryCom, "==", 5); + Add(Instruction.CompareGreater, InstType.OpBinary, ">", 4); + Add(Instruction.CompareGreaterOrEqual, InstType.OpBinary, ">=", 4); + Add(Instruction.CompareGreaterOrEqualU32, InstType.OpBinary, ">=", 4); + Add(Instruction.CompareGreaterU32, InstType.OpBinary, ">", 4); + Add(Instruction.CompareLess, InstType.OpBinary, "<", 4); + Add(Instruction.CompareLessOrEqual, InstType.OpBinary, "<=", 4); + Add(Instruction.CompareLessOrEqualU32, InstType.OpBinary, "<=", 4); + Add(Instruction.CompareLessU32, InstType.OpBinary, "<", 4); + Add(Instruction.CompareNotEqual, InstType.OpBinaryCom, "!=", 5); + Add(Instruction.ConditionalSelect, InstType.OpTernary, "?:", 12); + Add(Instruction.ConvertFPToS32, InstType.CallUnary, "int"); + Add(Instruction.ConvertFPToU32, InstType.CallUnary, "uint"); + Add(Instruction.ConvertS32ToFP, InstType.CallUnary, "float"); + Add(Instruction.ConvertU32ToFP, InstType.CallUnary, "float"); + Add(Instruction.Cosine, InstType.CallUnary, "cos"); + Add(Instruction.Ddx, InstType.CallUnary, "dFdx"); + Add(Instruction.Ddy, InstType.CallUnary, "dFdy"); + Add(Instruction.Discard, InstType.OpNullary, "discard"); + Add(Instruction.Divide, InstType.OpBinary, "/", 1); + Add(Instruction.EmitVertex, InstType.CallNullary, "EmitVertex"); + Add(Instruction.EndPrimitive, InstType.CallNullary, "EndPrimitive"); + Add(Instruction.ExponentB2, InstType.CallUnary, "exp2"); + Add(Instruction.FindFirstSetS32, InstType.CallUnary, "findMSB"); + Add(Instruction.FindFirstSetU32, InstType.CallUnary, "findMSB"); + Add(Instruction.Floor, InstType.CallUnary, "floor"); + Add(Instruction.FusedMultiplyAdd, InstType.CallTernary, "fma"); + Add(Instruction.GroupMemoryBarrier, InstType.CallNullary, "groupMemoryBarrier"); + Add(Instruction.ImageLoad, InstType.Special); + Add(Instruction.ImageStore, InstType.Special); + Add(Instruction.IsNan, InstType.CallUnary, "isnan"); + Add(Instruction.LoadAttribute, InstType.Special); + Add(Instruction.LoadConstant, InstType.Special); + Add(Instruction.LoadLocal, InstType.Special); + Add(Instruction.LoadShared, InstType.Special); + Add(Instruction.LoadStorage, InstType.Special); + Add(Instruction.Lod, InstType.Special); + Add(Instruction.LogarithmB2, InstType.CallUnary, "log2"); + Add(Instruction.LogicalAnd, InstType.OpBinaryCom, "&&", 9); + Add(Instruction.LogicalExclusiveOr, InstType.OpBinaryCom, "^^", 10); + Add(Instruction.LogicalNot, InstType.OpUnary, "!", 0); + Add(Instruction.LogicalOr, InstType.OpBinaryCom, "||", 11); + Add(Instruction.LoopBreak, InstType.OpNullary, "break"); + Add(Instruction.LoopContinue, InstType.OpNullary, "continue"); + Add(Instruction.PackHalf2x16, InstType.Special); + Add(Instruction.ShiftLeft, InstType.OpBinary, "<<", 3); + Add(Instruction.ShiftRightS32, InstType.OpBinary, ">>", 3); + Add(Instruction.ShiftRightU32, InstType.OpBinary, ">>", 3); + Add(Instruction.Shuffle, InstType.CallTernary, HelperFunctionNames.Shuffle); + Add(Instruction.ShuffleDown, InstType.CallTernary, HelperFunctionNames.ShuffleDown); + Add(Instruction.ShuffleUp, InstType.CallTernary, HelperFunctionNames.ShuffleUp); + Add(Instruction.ShuffleXor, InstType.CallTernary, HelperFunctionNames.ShuffleXor); + Add(Instruction.Maximum, InstType.CallBinary, "max"); + Add(Instruction.MaximumU32, InstType.CallBinary, "max"); + Add(Instruction.MemoryBarrier, InstType.CallNullary, "memoryBarrier"); + Add(Instruction.Minimum, InstType.CallBinary, "min"); + Add(Instruction.MinimumU32, InstType.CallBinary, "min"); + Add(Instruction.Multiply, InstType.OpBinaryCom, "*", 1); + Add(Instruction.MultiplyHighS32, InstType.CallBinary, HelperFunctionNames.MultiplyHighS32); + Add(Instruction.MultiplyHighU32, InstType.CallBinary, HelperFunctionNames.MultiplyHighU32); + Add(Instruction.Negate, InstType.OpUnary, "-", 0); + Add(Instruction.ReciprocalSquareRoot, InstType.CallUnary, "inversesqrt"); + Add(Instruction.Return, InstType.OpNullary, "return"); + Add(Instruction.Round, InstType.CallUnary, "roundEven"); + Add(Instruction.Sine, InstType.CallUnary, "sin"); + Add(Instruction.SquareRoot, InstType.CallUnary, "sqrt"); + Add(Instruction.StoreLocal, InstType.Special); + Add(Instruction.StoreShared, InstType.Special); + Add(Instruction.StoreStorage, InstType.Special); + Add(Instruction.Subtract, InstType.OpBinary, "-", 2); + Add(Instruction.SwizzleAdd, InstType.CallTernary, HelperFunctionNames.SwizzleAdd); + Add(Instruction.TextureSample, InstType.Special); + Add(Instruction.TextureSize, InstType.Special); + Add(Instruction.Truncate, InstType.CallUnary, "trunc"); + Add(Instruction.UnpackHalf2x16, InstType.Special); + Add(Instruction.VoteAll, InstType.CallUnary, "allInvocationsARB"); + Add(Instruction.VoteAllEqual, InstType.CallUnary, "allInvocationsEqualARB"); + Add(Instruction.VoteAny, InstType.CallUnary, "anyInvocationARB"); + } + + private static void Add(Instruction inst, InstType flags, string opName = null, int precedence = 0) + { + _infoTbl[(int)inst] = new InstInfo(flags, opName, precedence); + } + + public static InstInfo GetInstructionInfo(Instruction inst) + { + return _infoTbl[(int)(inst & Instruction.Mask)]; + } + + public static string GetSoureExpr(CodeGenContext context, IAstNode node, VariableType dstType) + { + return ReinterpretCast(context, node, OperandManager.GetNodeDestType(node), dstType); + } + + public static string Enclose(string expr, IAstNode node, Instruction pInst, bool isLhs) + { + InstInfo pInfo = GetInstructionInfo(pInst); + + return Enclose(expr, node, pInst, pInfo, isLhs); + } + + public static string Enclose(string expr, IAstNode node, Instruction pInst, InstInfo pInfo, bool isLhs = false) + { + if (NeedsParenthesis(node, pInst, pInfo, isLhs)) + { + expr = "(" + expr + ")"; + } + + return expr; + } + + public static bool NeedsParenthesis(IAstNode node, Instruction pInst, InstInfo pInfo, bool isLhs) + { + // If the node isn't a operation, then it can only be a operand, + // and those never needs to be surrounded in parenthesis. + if (!(node is AstOperation operation)) + { + // This is sort of a special case, if this is a negative constant, + // and it is consumed by a unary operation, we need to put on the parenthesis, + // as in GLSL a sequence like --2 or ~-1 is not valid. + if (IsNegativeConst(node) && pInfo.Type == InstType.OpUnary) + { + return true; + } + + return false; + } + + if ((pInfo.Type & (InstType.Call | InstType.Special)) != 0) + { + return false; + } + + InstInfo info = _infoTbl[(int)(operation.Inst & Instruction.Mask)]; + + if ((info.Type & (InstType.Call | InstType.Special)) != 0) + { + return false; + } + + if (info.Precedence < pInfo.Precedence) + { + return false; + } + + if (info.Precedence == pInfo.Precedence && isLhs) + { + return false; + } + + if (pInst == operation.Inst && info.Type == InstType.OpBinaryCom) + { + return false; + } + + return true; + } + + private static bool IsNegativeConst(IAstNode node) + { + if (!(node is AstOperand operand)) + { + return false; + } + + return operand.Type == OperandType.Constant && operand.Value < 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs new file mode 100644 index 0000000000..5687ce7e16 --- /dev/null +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs @@ -0,0 +1,514 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.StructuredIr; +using System; + +using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenHelper; +using static Ryujinx.Graphics.Shader.StructuredIr.InstructionInfo; + +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions +{ + static class InstGenMemory + { + public static string ImageStore(CodeGenContext context, AstOperation operation) + { + AstTextureOperation texOp = (AstTextureOperation)operation; + + bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; + + bool isArray = (texOp.Type & SamplerType.Array) != 0; + bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; + + string texCall = "imageStore"; + + int srcIndex = isBindless ? 1 : 0; + + string Src(VariableType type) + { + return GetSoureExpr(context, texOp.GetSource(srcIndex++), type); + } + + string indexExpr = null; + + if (isIndexed) + { + indexExpr = Src(VariableType.S32); + } + + string imageName = OperandManager.GetImageName(context.Config.Stage, texOp, indexExpr); + + texCall += "(" + imageName; + + int coordsCount = texOp.Type.GetDimensions(); + + int pCount = coordsCount; + + int arrayIndexElem = -1; + + if (isArray) + { + arrayIndexElem = pCount++; + } + + void Append(string str) + { + texCall += ", " + str; + } + + if (pCount > 1) + { + string[] elems = new string[pCount]; + + for (int index = 0; index < pCount; index++) + { + elems[index] = Src(VariableType.S32); + } + + Append("ivec" + pCount + "(" + string.Join(", ", elems) + ")"); + } + else + { + Append(Src(VariableType.S32)); + } + + string[] cElems = new string[4]; + + for (int index = 0; index < 4; index++) + { + if (srcIndex < texOp.SourcesCount) + { + cElems[index] = Src(VariableType.F32); + } + else + { + cElems[index] = NumberFormatter.FormatFloat(0); + } + } + + Append("vec4(" + string.Join(", ", cElems) + ")"); + + texCall += ")"; + + return texCall; + } + + public static string LoadAttribute(CodeGenContext context, AstOperation operation) + { + IAstNode src1 = operation.GetSource(0); + IAstNode src2 = operation.GetSource(1); + + if (!(src1 is AstOperand attr) || attr.Type != OperandType.Attribute) + { + throw new InvalidOperationException("First source of LoadAttribute must be a attribute."); + } + + string indexExpr = GetSoureExpr(context, src2, GetSrcVarType(operation.Inst, 1)); + + return OperandManager.GetAttributeName(attr, context.Config.Stage, isOutAttr: false, indexExpr); + } + + public static string LoadConstant(CodeGenContext context, AstOperation operation) + { + IAstNode src1 = operation.GetSource(0); + IAstNode src2 = operation.GetSource(1); + + string offsetExpr = GetSoureExpr(context, src2, GetSrcVarType(operation.Inst, 1)); + + offsetExpr = Enclose(offsetExpr, src2, Instruction.ShiftRightS32, isLhs: true); + + return OperandManager.GetConstantBufferName(src1, offsetExpr, context.Config.Stage); + } + + public static string LoadLocal(CodeGenContext context, AstOperation operation) + { + return LoadLocalOrShared(context, operation, DefaultNames.LocalMemoryName); + } + + public static string LoadShared(CodeGenContext context, AstOperation operation) + { + return LoadLocalOrShared(context, operation, DefaultNames.SharedMemoryName); + } + + private static string LoadLocalOrShared(CodeGenContext context, AstOperation operation, string arrayName) + { + IAstNode src1 = operation.GetSource(0); + + string offsetExpr = GetSoureExpr(context, src1, GetSrcVarType(operation.Inst, 0)); + + return $"{arrayName}[{offsetExpr}]"; + } + + public static string LoadStorage(CodeGenContext context, AstOperation operation) + { + IAstNode src1 = operation.GetSource(0); + IAstNode src2 = operation.GetSource(1); + + string indexExpr = GetSoureExpr(context, src1, GetSrcVarType(operation.Inst, 0)); + string offsetExpr = GetSoureExpr(context, src2, GetSrcVarType(operation.Inst, 1)); + + return GetStorageBufferAccessor(indexExpr, offsetExpr, context.Config.Stage); + } + + public static string Lod(CodeGenContext context, AstOperation operation) + { + AstTextureOperation texOp = (AstTextureOperation)operation; + + int coordsCount = texOp.Type.GetDimensions(); + + bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; + + bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; + + string indexExpr = null; + + if (isIndexed) + { + indexExpr = GetSoureExpr(context, texOp.GetSource(0), VariableType.S32); + } + + string samplerName = OperandManager.GetSamplerName(context.Config.Stage, texOp, indexExpr); + + int coordsIndex = isBindless || isIndexed ? 1 : 0; + + string coordsExpr; + + if (coordsCount > 1) + { + string[] elems = new string[coordsCount]; + + for (int index = 0; index < coordsCount; index++) + { + elems[index] = GetSoureExpr(context, texOp.GetSource(coordsIndex + index), VariableType.F32); + } + + coordsExpr = "vec" + coordsCount + "(" + string.Join(", ", elems) + ")"; + } + else + { + coordsExpr = GetSoureExpr(context, texOp.GetSource(coordsIndex), VariableType.F32); + } + + return $"textureQueryLod({samplerName}, {coordsExpr}){GetMask(texOp.Index)}"; + } + + public static string StoreLocal(CodeGenContext context, AstOperation operation) + { + return StoreLocalOrShared(context, operation, DefaultNames.LocalMemoryName); + } + + public static string StoreShared(CodeGenContext context, AstOperation operation) + { + return StoreLocalOrShared(context, operation, DefaultNames.SharedMemoryName); + } + + private static string StoreLocalOrShared(CodeGenContext context, AstOperation operation, string arrayName) + { + IAstNode src1 = operation.GetSource(0); + IAstNode src2 = operation.GetSource(1); + + string offsetExpr = GetSoureExpr(context, src1, GetSrcVarType(operation.Inst, 0)); + + VariableType srcType = OperandManager.GetNodeDestType(src2); + + string src = TypeConversion.ReinterpretCast(context, src2, srcType, VariableType.U32); + + return $"{arrayName}[{offsetExpr}] = {src}"; + } + + public static string StoreStorage(CodeGenContext context, AstOperation operation) + { + IAstNode src1 = operation.GetSource(0); + IAstNode src2 = operation.GetSource(1); + IAstNode src3 = operation.GetSource(2); + + string indexExpr = GetSoureExpr(context, src1, GetSrcVarType(operation.Inst, 0)); + string offsetExpr = GetSoureExpr(context, src2, GetSrcVarType(operation.Inst, 1)); + + VariableType srcType = OperandManager.GetNodeDestType(src3); + + string src = TypeConversion.ReinterpretCast(context, src3, srcType, VariableType.U32); + + string sb = GetStorageBufferAccessor(indexExpr, offsetExpr, context.Config.Stage); + + return $"{sb} = {src}"; + } + + public static string TextureSample(CodeGenContext context, AstOperation operation) + { + AstTextureOperation texOp = (AstTextureOperation)operation; + + bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; + bool isGather = (texOp.Flags & TextureFlags.Gather) != 0; + bool hasDerivatives = (texOp.Flags & TextureFlags.Derivatives) != 0; + bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0; + bool hasLodBias = (texOp.Flags & TextureFlags.LodBias) != 0; + bool hasLodLevel = (texOp.Flags & TextureFlags.LodLevel) != 0; + bool hasOffset = (texOp.Flags & TextureFlags.Offset) != 0; + bool hasOffsets = (texOp.Flags & TextureFlags.Offsets) != 0; + + bool isArray = (texOp.Type & SamplerType.Array) != 0; + bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; + bool isMultisample = (texOp.Type & SamplerType.Multisample) != 0; + bool isShadow = (texOp.Type & SamplerType.Shadow) != 0; + + // This combination is valid, but not available on GLSL. + // For now, ignore the LOD level and do a normal sample. + // TODO: How to implement it properly? + if (hasLodLevel && isArray && isShadow) + { + hasLodLevel = false; + } + + string texCall = intCoords ? "texelFetch" : "texture"; + + if (isGather) + { + texCall += "Gather"; + } + else if (hasDerivatives) + { + texCall += "Grad"; + } + else if (hasLodLevel && !intCoords) + { + texCall += "Lod"; + } + + if (hasOffset) + { + texCall += "Offset"; + } + else if (hasOffsets) + { + texCall += "Offsets"; + } + + int srcIndex = isBindless ? 1 : 0; + + string Src(VariableType type) + { + return GetSoureExpr(context, texOp.GetSource(srcIndex++), type); + } + + string indexExpr = null; + + if (isIndexed) + { + indexExpr = Src(VariableType.S32); + } + + string samplerName = OperandManager.GetSamplerName(context.Config.Stage, texOp, indexExpr); + + texCall += "(" + samplerName; + + int coordsCount = texOp.Type.GetDimensions(); + + int pCount = coordsCount; + + int arrayIndexElem = -1; + + if (isArray) + { + arrayIndexElem = pCount++; + } + + // The sampler 1D shadow overload expects a + // dummy value on the middle of the vector, who knows why... + bool hasDummy1DShadowElem = texOp.Type == (SamplerType.Texture1D | SamplerType.Shadow); + + if (hasDummy1DShadowElem) + { + pCount++; + } + + if (isShadow && !isGather) + { + pCount++; + } + + // On textureGather*, the comparison value is + // always specified as an extra argument. + bool hasExtraCompareArg = isShadow && isGather; + + if (pCount == 5) + { + pCount = 4; + + hasExtraCompareArg = true; + } + + void Append(string str) + { + texCall += ", " + str; + } + + VariableType coordType = intCoords ? VariableType.S32 : VariableType.F32; + + string AssemblePVector(int count) + { + if (count > 1) + { + string[] elems = new string[count]; + + for (int index = 0; index < count; index++) + { + if (arrayIndexElem == index) + { + elems[index] = Src(VariableType.S32); + + if (!intCoords) + { + elems[index] = "float(" + elems[index] + ")"; + } + } + else if (index == 1 && hasDummy1DShadowElem) + { + elems[index] = NumberFormatter.FormatFloat(0); + } + else + { + elems[index] = Src(coordType); + } + } + + string prefix = intCoords ? "i" : string.Empty; + + return prefix + "vec" + count + "(" + string.Join(", ", elems) + ")"; + } + else + { + return Src(coordType); + } + } + + Append(AssemblePVector(pCount)); + + string AssembleDerivativesVector(int count) + { + if (count > 1) + { + string[] elems = new string[count]; + + for (int index = 0; index < count; index++) + { + elems[index] = Src(VariableType.F32); + } + + return "vec" + count + "(" + string.Join(", ", elems) + ")"; + } + else + { + return Src(VariableType.F32); + } + } + + if (hasExtraCompareArg) + { + Append(Src(VariableType.F32)); + } + + if (hasDerivatives) + { + Append(AssembleDerivativesVector(coordsCount)); // dPdx + Append(AssembleDerivativesVector(coordsCount)); // dPdy + } + + if (isMultisample) + { + Append(Src(VariableType.S32)); + } + else if (hasLodLevel) + { + Append(Src(coordType)); + } + + string AssembleOffsetVector(int count) + { + if (count > 1) + { + string[] elems = new string[count]; + + for (int index = 0; index < count; index++) + { + elems[index] = Src(VariableType.S32); + } + + return "ivec" + count + "(" + string.Join(", ", elems) + ")"; + } + else + { + return Src(VariableType.S32); + } + } + + if (hasOffset) + { + Append(AssembleOffsetVector(coordsCount)); + } + else if (hasOffsets) + { + texCall += $", ivec{coordsCount}[4]("; + + texCall += AssembleOffsetVector(coordsCount) + ", "; + texCall += AssembleOffsetVector(coordsCount) + ", "; + texCall += AssembleOffsetVector(coordsCount) + ", "; + texCall += AssembleOffsetVector(coordsCount) + ")"; + } + + if (hasLodBias) + { + Append(Src(VariableType.F32)); + } + + // textureGather* optional extra component index, + // not needed for shadow samplers. + if (isGather && !isShadow) + { + Append(Src(VariableType.S32)); + } + + texCall += ")" + (isGather || !isShadow ? GetMask(texOp.Index) : ""); + + return texCall; + } + + public static string TextureSize(CodeGenContext context, AstOperation operation) + { + AstTextureOperation texOp = (AstTextureOperation)operation; + + bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; + + bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; + + string indexExpr = null; + + if (isIndexed) + { + indexExpr = GetSoureExpr(context, texOp.GetSource(0), VariableType.S32); + } + + string samplerName = OperandManager.GetSamplerName(context.Config.Stage, texOp, indexExpr); + + int lodSrcIndex = isBindless || isIndexed ? 1 : 0; + + IAstNode lod = operation.GetSource(lodSrcIndex); + + string lodExpr = GetSoureExpr(context, lod, GetSrcVarType(operation.Inst, lodSrcIndex)); + + return $"textureSize({samplerName}, {lodExpr}){GetMask(texOp.Index)}"; + } + + private static string GetStorageBufferAccessor(string slotExpr, string offsetExpr, ShaderStage stage) + { + string sbName = OperandManager.GetShaderStagePrefix(stage); + + sbName += "_" + DefaultNames.StorageNamePrefix; + + return $"{sbName}[{slotExpr}].{DefaultNames.DataName}[{offsetExpr}]"; + } + + private static string GetMask(int index) + { + return '.' + "rgba".Substring(index, 1); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenPacking.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenPacking.cs new file mode 100644 index 0000000000..e5167f93f8 --- /dev/null +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenPacking.cs @@ -0,0 +1,35 @@ +using Ryujinx.Graphics.Shader.StructuredIr; + +using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenHelper; +using static Ryujinx.Graphics.Shader.StructuredIr.InstructionInfo; + +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions +{ + static class InstGenPacking + { + public static string PackHalf2x16(CodeGenContext context, AstOperation operation) + { + IAstNode src0 = operation.GetSource(0); + IAstNode src1 = operation.GetSource(1); + + string src0Expr = GetSoureExpr(context, src0, GetSrcVarType(operation.Inst, 0)); + string src1Expr = GetSoureExpr(context, src1, GetSrcVarType(operation.Inst, 1)); + + return $"packHalf2x16(vec2({src0Expr}, {src1Expr}))"; + } + + public static string UnpackHalf2x16(CodeGenContext context, AstOperation operation) + { + IAstNode src = operation.GetSource(0); + + string srcExpr = GetSoureExpr(context, src, GetSrcVarType(operation.Inst, 0)); + + return $"unpackHalf2x16({srcExpr}){GetMask(operation.Index)}"; + } + + private static string GetMask(int index) + { + return '.' + "xy".Substring(index, 1); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstInfo.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstInfo.cs new file mode 100644 index 0000000000..fc9aef7ec3 --- /dev/null +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstInfo.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions +{ + struct InstInfo + { + public InstType Type { get; } + + public string OpName { get; } + + public int Precedence { get; } + + public InstInfo(InstType type, string opName, int precedence) + { + Type = type; + OpName = opName; + Precedence = precedence; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstType.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstType.cs new file mode 100644 index 0000000000..84e36cdd62 --- /dev/null +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstType.cs @@ -0,0 +1,33 @@ +using System; + +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions +{ + [Flags] + enum InstType + { + OpNullary = Op | 0, + OpUnary = Op | 1, + OpBinary = Op | 2, + OpBinaryCom = Op | 2 | Commutative, + OpTernary = Op | 3, + + CallNullary = Call | 0, + CallUnary = Call | 1, + CallBinary = Call | 2, + CallTernary = Call | 3, + CallQuaternary = Call | 4, + + // The atomic instructions have one extra operand, + // for the storage slot and offset pair. + AtomicBinary = Call | Atomic | 3, + AtomicTernary = Call | Atomic | 4, + + Commutative = 1 << 8, + Op = 1 << 9, + Call = 1 << 10, + Atomic = 1 << 11, + Special = 1 << 12, + + ArityMask = 0xff + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/NumberFormatter.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/NumberFormatter.cs new file mode 100644 index 0000000000..2ec44277c3 --- /dev/null +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/NumberFormatter.cs @@ -0,0 +1,104 @@ +using Ryujinx.Graphics.Shader.StructuredIr; +using System; +using System.Globalization; + +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl +{ + static class NumberFormatter + { + private const int MaxDecimal = 256; + + public static bool TryFormat(int value, VariableType dstType, out string formatted) + { + if (dstType == VariableType.F32) + { + return TryFormatFloat(BitConverter.Int32BitsToSingle(value), out formatted); + } + else if (dstType == VariableType.S32) + { + formatted = FormatInt(value); + } + else if (dstType == VariableType.U32) + { + formatted = FormatUint((uint)value); + } + else if (dstType == VariableType.Bool) + { + formatted = value != 0 ? "true" : "false"; + } + else + { + throw new ArgumentException($"Invalid variable type \"{dstType}\"."); + } + + return true; + } + + public static string FormatFloat(float value) + { + if (!TryFormatFloat(value, out string formatted)) + { + throw new ArgumentException("Failed to convert float value to string."); + } + + return formatted; + } + + public static bool TryFormatFloat(float value, out string formatted) + { + if (float.IsNaN(value) || float.IsInfinity(value)) + { + formatted = null; + + return false; + } + + formatted = value.ToString("G9", CultureInfo.InvariantCulture); + + if (!(formatted.Contains('.') || + formatted.Contains('e') || + formatted.Contains('E'))) + { + formatted += ".0"; + } + + return true; + } + + public static string FormatInt(int value, VariableType dstType) + { + if (dstType == VariableType.S32) + { + return FormatInt(value); + } + else if (dstType == VariableType.U32) + { + return FormatUint((uint)value); + } + else + { + throw new ArgumentException($"Invalid variable type \"{dstType}\"."); + } + } + + public static string FormatInt(int value) + { + if (value <= MaxDecimal && value >= -MaxDecimal) + { + return value.ToString(CultureInfo.InvariantCulture); + } + + return "0x" + value.ToString("X", CultureInfo.InvariantCulture); + } + + public static string FormatUint(uint value) + { + if (value <= MaxDecimal && value >= 0) + { + return value.ToString(CultureInfo.InvariantCulture) + "u"; + } + + return "0x" + value.ToString("X", CultureInfo.InvariantCulture) + "u"; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs new file mode 100644 index 0000000000..4c9d5b5507 --- /dev/null +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs @@ -0,0 +1,300 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.StructuredIr; +using Ryujinx.Graphics.Shader.Translation; +using System; +using System.Collections.Generic; + +using static Ryujinx.Graphics.Shader.StructuredIr.InstructionInfo; + +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl +{ + class OperandManager + { + private static string[] _stagePrefixes = new string[] { "cp", "vp", "tcp", "tep", "gp", "fp" }; + + private struct BuiltInAttribute + { + public string Name { get; } + + public VariableType Type { get; } + + public BuiltInAttribute(string name, VariableType type) + { + Name = name; + Type = type; + } + } + + private static Dictionary _builtInAttributes = + new Dictionary() + { + { AttributeConsts.Layer, new BuiltInAttribute("gl_Layer", VariableType.S32) }, + { AttributeConsts.PointSize, new BuiltInAttribute("gl_PointSize", VariableType.F32) }, + { AttributeConsts.PositionX, new BuiltInAttribute("gl_Position.x", VariableType.F32) }, + { AttributeConsts.PositionY, new BuiltInAttribute("gl_Position.y", VariableType.F32) }, + { AttributeConsts.PositionZ, new BuiltInAttribute("gl_Position.z", VariableType.F32) }, + { AttributeConsts.PositionW, new BuiltInAttribute("gl_Position.w", VariableType.F32) }, + { AttributeConsts.ClipDistance0, new BuiltInAttribute("gl_ClipDistance[0]", VariableType.F32) }, + { AttributeConsts.ClipDistance1, new BuiltInAttribute("gl_ClipDistance[1]", VariableType.F32) }, + { AttributeConsts.ClipDistance2, new BuiltInAttribute("gl_ClipDistance[2]", VariableType.F32) }, + { AttributeConsts.ClipDistance3, new BuiltInAttribute("gl_ClipDistance[3]", VariableType.F32) }, + { AttributeConsts.ClipDistance4, new BuiltInAttribute("gl_ClipDistance[4]", VariableType.F32) }, + { AttributeConsts.ClipDistance5, new BuiltInAttribute("gl_ClipDistance[5]", VariableType.F32) }, + { AttributeConsts.ClipDistance6, new BuiltInAttribute("gl_ClipDistance[6]", VariableType.F32) }, + { AttributeConsts.ClipDistance7, new BuiltInAttribute("gl_ClipDistance[7]", VariableType.F32) }, + { AttributeConsts.PointCoordX, new BuiltInAttribute("gl_PointCoord.x", VariableType.F32) }, + { AttributeConsts.PointCoordY, new BuiltInAttribute("gl_PointCoord.y", VariableType.F32) }, + { AttributeConsts.TessCoordX, new BuiltInAttribute("gl_TessCoord.x", VariableType.F32) }, + { AttributeConsts.TessCoordY, new BuiltInAttribute("gl_TessCoord.y", VariableType.F32) }, + { AttributeConsts.InstanceId, new BuiltInAttribute("gl_InstanceID", VariableType.S32) }, + { AttributeConsts.VertexId, new BuiltInAttribute("gl_VertexID", VariableType.S32) }, + { AttributeConsts.FrontFacing, new BuiltInAttribute("gl_FrontFacing", VariableType.Bool) }, + + // Special. + { AttributeConsts.FragmentOutputDepth, new BuiltInAttribute("gl_FragDepth", VariableType.F32) }, + { AttributeConsts.ThreadIdX, new BuiltInAttribute("gl_LocalInvocationID.x", VariableType.U32) }, + { AttributeConsts.ThreadIdY, new BuiltInAttribute("gl_LocalInvocationID.y", VariableType.U32) }, + { AttributeConsts.ThreadIdZ, new BuiltInAttribute("gl_LocalInvocationID.z", VariableType.U32) }, + { AttributeConsts.CtaIdX, new BuiltInAttribute("gl_WorkGroupID.x", VariableType.U32) }, + { AttributeConsts.CtaIdY, new BuiltInAttribute("gl_WorkGroupID.y", VariableType.U32) }, + { AttributeConsts.CtaIdZ, new BuiltInAttribute("gl_WorkGroupID.z", VariableType.U32) }, + { AttributeConsts.LaneId, new BuiltInAttribute("gl_SubGroupInvocationARB", VariableType.U32) }, + { AttributeConsts.EqMask, new BuiltInAttribute("unpackUint2x32(gl_SubGroupEqMaskARB).x", VariableType.U32) }, + { AttributeConsts.GeMask, new BuiltInAttribute("unpackUint2x32(gl_SubGroupGeMaskARB).x", VariableType.U32) }, + { AttributeConsts.GtMask, new BuiltInAttribute("unpackUint2x32(gl_SubGroupGtMaskARB).x", VariableType.U32) }, + { AttributeConsts.LeMask, new BuiltInAttribute("unpackUint2x32(gl_SubGroupLeMaskARB).x", VariableType.U32) }, + { AttributeConsts.LtMask, new BuiltInAttribute("unpackUint2x32(gl_SubGroupLtMaskARB).x", VariableType.U32) }, + }; + + private Dictionary _locals; + + public OperandManager() + { + _locals = new Dictionary(); + } + + public string DeclareLocal(AstOperand operand) + { + string name = $"{DefaultNames.LocalNamePrefix}_{_locals.Count}"; + + _locals.Add(operand, name); + + return name; + } + + public string GetExpression(AstOperand operand, ShaderStage stage) + { + switch (operand.Type) + { + case OperandType.Attribute: + return GetAttributeName(operand, stage); + + case OperandType.Constant: + return NumberFormatter.FormatInt(operand.Value); + + case OperandType.ConstantBuffer: + return GetConstantBufferName(operand.CbufSlot, operand.CbufOffset, stage); + + case OperandType.LocalVariable: + return _locals[operand]; + + case OperandType.Undefined: + return DefaultNames.UndefinedName; + } + + throw new ArgumentException($"Invalid operand type \"{operand.Type}\"."); + } + + public static string GetConstantBufferName(int slot, int offset, ShaderStage stage) + { + string ubName = GetUbName(stage, slot); + + ubName += "[" + (offset >> 2) + "]"; + + return ubName + "." + GetSwizzleMask(offset & 3); + } + + public static string GetConstantBufferName(IAstNode slot, string offsetExpr, ShaderStage stage) + { + // Non-constant slots are not supported. + // It is expected that upstream stages are never going to generate non-constant + // slot access. + AstOperand operand = (AstOperand)slot; + + string ubName = GetUbName(stage, operand.Value); + + string index0 = "[" + offsetExpr + " >> 2]"; + string index1 = "[" + offsetExpr + " & 3]"; + + return ubName + index0 + index1; + } + + public static string GetOutAttributeName(AstOperand attr, ShaderStage stage) + { + return GetAttributeName(attr, stage, isOutAttr: true); + } + + public static string GetAttributeName(AstOperand attr, ShaderStage stage, bool isOutAttr = false, string indexExpr = "0") + { + int value = attr.Value; + + string swzMask = GetSwizzleMask((value >> 2) & 3); + + if (value >= AttributeConsts.UserAttributeBase && + value < AttributeConsts.UserAttributeEnd) + { + value -= AttributeConsts.UserAttributeBase; + + string prefix = isOutAttr + ? DefaultNames.OAttributePrefix + : DefaultNames.IAttributePrefix; + + string name = $"{prefix}{(value >> 4)}"; + + if (stage == ShaderStage.Geometry && !isOutAttr) + { + name += $"[{indexExpr}]"; + } + + name += "." + swzMask; + + return name; + } + else + { + if (value >= AttributeConsts.FragmentOutputColorBase && + value < AttributeConsts.FragmentOutputColorEnd) + { + value -= AttributeConsts.FragmentOutputColorBase; + + return $"{DefaultNames.OAttributePrefix}{(value >> 4)}.{swzMask}"; + } + else if (_builtInAttributes.TryGetValue(value & ~3, out BuiltInAttribute builtInAttr)) + { + // TODO: There must be a better way to handle this... + if (stage == ShaderStage.Fragment) + { + switch (value & ~3) + { + case AttributeConsts.PositionX: return "gl_FragCoord.x"; + case AttributeConsts.PositionY: return "gl_FragCoord.y"; + case AttributeConsts.PositionZ: return "gl_FragCoord.z"; + case AttributeConsts.PositionW: return "1.0"; + } + } + + string name = builtInAttr.Name; + + if (stage == ShaderStage.Geometry && !isOutAttr) + { + name = $"gl_in[{indexExpr}].{name}"; + } + + return name; + } + } + + // TODO: Warn about unknown built-in attribute. + + return isOutAttr ? "// bad_attr0x" + value.ToString("X") : "0.0"; + } + + public static string GetUbName(ShaderStage stage, int slot) + { + string ubName = GetShaderStagePrefix(stage); + + ubName += "_" + DefaultNames.UniformNamePrefix + slot; + + return ubName + "_" + DefaultNames.UniformNameSuffix; + } + + public static string GetSamplerName(ShaderStage stage, AstTextureOperation texOp, string indexExpr) + { + string suffix; + + if ((texOp.Flags & TextureFlags.Bindless) != 0) + { + AstOperand operand = texOp.GetSource(0) as AstOperand; + + suffix = "_cb" + operand.CbufSlot + "_" + operand.CbufOffset; + } + else + { + suffix = texOp.Handle.ToString(); + + if ((texOp.Type & SamplerType.Indexed) != 0) + { + suffix += $"a[{indexExpr}]"; + } + } + + return GetShaderStagePrefix(stage) + "_" + DefaultNames.SamplerNamePrefix + suffix; + } + + public static string GetImageName(ShaderStage stage, AstTextureOperation texOp, string indexExpr) + { + string suffix = texOp.Handle.ToString(); + + if ((texOp.Type & SamplerType.Indexed) != 0) + { + suffix += $"a[{indexExpr}]"; + } + + return GetShaderStagePrefix(stage) + "_" + DefaultNames.ImageNamePrefix + suffix; + } + + public static string GetShaderStagePrefix(ShaderStage stage) + { + int index = (int)stage; + + if ((uint)index >= _stagePrefixes.Length) + { + return "invalid"; + } + + return _stagePrefixes[index]; + } + + private static string GetSwizzleMask(int value) + { + return "xyzw".Substring(value, 1); + } + + public static VariableType GetNodeDestType(IAstNode node) + { + if (node is AstOperation operation) + { + // Load attribute basically just returns the attribute value. + // Some built-in attributes may have different types, so we need + // to return the type based on the attribute that is being read. + if (operation.Inst == Instruction.LoadAttribute) + { + return GetOperandVarType((AstOperand)operation.GetSource(0)); + } + + return GetDestVarType(operation.Inst); + } + else if (node is AstOperand operand) + { + return GetOperandVarType(operand); + } + else + { + throw new ArgumentException($"Invalid node type \"{node?.GetType().Name ?? "null"}\"."); + } + } + + private static VariableType GetOperandVarType(AstOperand operand) + { + if (operand.Type == OperandType.Attribute) + { + if (_builtInAttributes.TryGetValue(operand.Value & ~3, out BuiltInAttribute builtInAttr)) + { + return builtInAttr.Type; + } + } + + return OperandInfo.GetVarType(operand); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/TypeConversion.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/TypeConversion.cs new file mode 100644 index 0000000000..7adc5ad339 --- /dev/null +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/TypeConversion.cs @@ -0,0 +1,85 @@ +using Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.StructuredIr; +using System; + +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl +{ + static class TypeConversion + { + public static string ReinterpretCast( + CodeGenContext context, + IAstNode node, + VariableType srcType, + VariableType dstType) + { + if (node is AstOperand operand && operand.Type == OperandType.Constant) + { + if (NumberFormatter.TryFormat(operand.Value, dstType, out string formatted)) + { + return formatted; + } + } + + string expr = InstGen.GetExpression(context, node); + + return ReinterpretCast(expr, node, srcType, dstType); + } + + private static string ReinterpretCast(string expr, IAstNode node, VariableType srcType, VariableType dstType) + { + if (srcType == dstType) + { + return expr; + } + + if (srcType == VariableType.F32) + { + switch (dstType) + { + case VariableType.S32: return $"floatBitsToInt({expr})"; + case VariableType.U32: return $"floatBitsToUint({expr})"; + } + } + else if (dstType == VariableType.F32) + { + switch (srcType) + { + case VariableType.Bool: return $"intBitsToFloat({ReinterpretBoolToInt(expr, node, VariableType.S32)})"; + case VariableType.S32: return $"intBitsToFloat({expr})"; + case VariableType.U32: return $"uintBitsToFloat({expr})"; + } + } + else if (srcType == VariableType.Bool) + { + return ReinterpretBoolToInt(expr, node, dstType); + } + else if (dstType == VariableType.Bool) + { + expr = InstGenHelper.Enclose(expr, node, Instruction.CompareNotEqual, isLhs: true); + + return $"({expr} != 0)"; + } + else if (dstType == VariableType.S32) + { + return $"int({expr})"; + } + else if (dstType == VariableType.U32) + { + return $"uint({expr})"; + } + + throw new ArgumentException($"Invalid reinterpret cast from \"{srcType}\" to \"{dstType}\"."); + } + + private static string ReinterpretBoolToInt(string expr, IAstNode node, VariableType dstType) + { + string trueExpr = NumberFormatter.FormatInt(IrConsts.True, dstType); + string falseExpr = NumberFormatter.FormatInt(IrConsts.False, dstType); + + expr = InstGenHelper.Enclose(expr, node, Instruction.ConditionalSelect, isLhs: false); + + return $"({expr} ? {trueExpr} : {falseExpr})"; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/AtomicOp.cs b/Ryujinx.Graphics.Shader/Decoders/AtomicOp.cs new file mode 100644 index 0000000000..065a57c447 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/AtomicOp.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum AtomicOp + { + Add = 0, + Minimum = 1, + Maximum = 2, + Increment = 3, + Decrement = 4, + BitwiseAnd = 5, + BitwiseOr = 6, + BitwiseExclusiveOr = 7, + Swap = 8 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/BarrierLevel.cs b/Ryujinx.Graphics.Shader/Decoders/BarrierLevel.cs new file mode 100644 index 0000000000..95c71e0097 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/BarrierLevel.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum BarrierLevel + { + Cta = 0, + Gl = 1, + Sys = 2, + Vc = 3 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/BarrierMode.cs b/Ryujinx.Graphics.Shader/Decoders/BarrierMode.cs new file mode 100644 index 0000000000..a058cbbd71 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/BarrierMode.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum BarrierMode + { + ReductionPopCount = 2, + Scan = 3, + ReductionAnd = 0xa, + ReductionOr = 0x12, + Sync = 0x80, + Arrive = 0x81 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/BitfieldExtensions.cs b/Ryujinx.Graphics.Shader/Decoders/BitfieldExtensions.cs new file mode 100644 index 0000000000..3bb9bc1f4c --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/BitfieldExtensions.cs @@ -0,0 +1,25 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + static class BitfieldExtensions + { + public static bool Extract(this int value, int lsb) + { + return ((int)(value >> lsb) & 1) != 0; + } + + public static int Extract(this int value, int lsb, int length) + { + return (int)(value >> lsb) & (int)(uint.MaxValue >> (32 - length)); + } + + public static bool Extract(this long value, int lsb) + { + return ((int)(value >> lsb) & 1) != 0; + } + + public static int Extract(this long value, int lsb, int length) + { + return (int)(value >> lsb) & (int)(uint.MaxValue >> (32 - length)); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/Block.cs b/Ryujinx.Graphics.Shader/Decoders/Block.cs new file mode 100644 index 0000000000..e147023736 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/Block.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class Block + { + public ulong Address { get; set; } + public ulong EndAddress { get; set; } + + public Block Next { get; set; } + public Block Branch { get; set; } + + public OpCodeBranchIndir BrIndir { get; set; } + + public List OpCodes { get; } + public List PushOpCodes { get; } + + public Block(ulong address) + { + Address = address; + + OpCodes = new List(); + PushOpCodes = new List(); + } + + 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)); + + rightBlock.UpdatePushOps(); + + EndAddress = rightBlock.Address; + + Next = rightBlock; + Branch = null; + + OpCodes.RemoveRange(splitIndex, splitCount); + + UpdatePushOps(); + } + + 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 == opCode.Address) + { + break; + } + + if (address < 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; + } + + public void UpdatePushOps() + { + PushOpCodes.Clear(); + + for (int index = 0; index < OpCodes.Count; index++) + { + if (!(OpCodes[index] is OpCodePush op)) + { + continue; + } + + PushOpCodes.Add(op); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/Condition.cs b/Ryujinx.Graphics.Shader/Decoders/Condition.cs new file mode 100644 index 0000000000..10400f94ac --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/Condition.cs @@ -0,0 +1,45 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum Condition + { + Less = 1 << 0, + Equal = 1 << 1, + Greater = 1 << 2, + Nan = 1 << 3, + Unsigned = 1 << 4, + + Never = 0, + + LessOrEqual = Less | Equal, + NotEqual = Less | Greater, + GreaterOrEqual = Greater | Equal, + Number = Greater | Equal | Less, + + LessUnordered = Less | Nan, + EqualUnordered = Equal | Nan, + LessOrEqualUnordered = LessOrEqual | Nan, + GreaterUnordered = Greater | Nan, + NotEqualUnordered = NotEqual | Nan, + GreaterOrEqualUnordered = GreaterOrEqual | Nan, + + Always = 0xf, + + Off = Unsigned | Never, + Lower = Unsigned | Less, + Sff = Unsigned | Equal, + LowerOrSame = Unsigned | LessOrEqual, + Higher = Unsigned | Greater, + Sft = Unsigned | NotEqual, + HigherOrSame = Unsigned | GreaterOrEqual, + Oft = Unsigned | Always, + + CsmTa = 0x18, + CsmTr = 0x19, + CsmMx = 0x1a, + FcsmTa = 0x1b, + FcsmTr = 0x1c, + FcsmMx = 0x1d, + Rle = 0x1e, + Rgt = 0x1f + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/ConditionalOperation.cs b/Ryujinx.Graphics.Shader/Decoders/ConditionalOperation.cs new file mode 100644 index 0000000000..4fc31e842b --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/ConditionalOperation.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum ConditionalOperation + { + False = 0, + True = 1, + Zero = 2, + NotZero = 3 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/Decoder.cs b/Ryujinx.Graphics.Shader/Decoders/Decoder.cs new file mode 100644 index 0000000000..8a502e3c6c --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/Decoder.cs @@ -0,0 +1,463 @@ +using Ryujinx.Graphics.Shader.Instructions; +using System; +using System.Buffers.Binary; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Reflection.Emit; + +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + static class Decoder + { + private delegate object OpActivator(InstEmitter emitter, ulong address, long opCode); + + private static ConcurrentDictionary _opActivators; + + static Decoder() + { + _opActivators = new ConcurrentDictionary(); + } + + public static Block[] Decode(ReadOnlySpan code, ulong headerSize) + { + List blocks = new List(); + + Queue workQueue = new Queue(); + + Dictionary visited = new Dictionary(); + + ulong maxAddress = (ulong)code.Length - headerSize; + + Block GetBlock(ulong blkAddress) + { + if (!visited.TryGetValue(blkAddress, out Block block)) + { + block = new Block(blkAddress); + + workQueue.Enqueue(block); + + visited.Add(blkAddress, block); + } + + return block; + } + + GetBlock(0); + + 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."); + } + + nBlock.Split(currBlock); + + blocks.Insert(nBlkIndex + 1, currBlock); + + continue; + } + + // If we have a block after the current one, set the limit address. + ulong limitAddress = maxAddress; + + 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; + } + } + + FillBlock(code, currBlock, limitAddress, headerSize); + + if (currBlock.OpCodes.Count != 0) + { + // We should have blocks for all possible branch targets, + // including those from SSY/PBK instructions. + foreach (OpCodePush pushOp in currBlock.PushOpCodes) + { + if (pushOp.GetAbsoluteAddress() >= maxAddress) + { + return null; + } + + GetBlock(pushOp.GetAbsoluteAddress()); + } + + // 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 + // or end of program, Next is null. + OpCode lastOp = currBlock.GetLastOp(); + + if (lastOp is OpCodeBranch opBr) + { + if (opBr.GetAbsoluteAddress() >= maxAddress) + { + return null; + } + + currBlock.Branch = GetBlock(opBr.GetAbsoluteAddress()); + } + else if (lastOp is OpCodeBranchIndir opBrIndir) + { + // An indirect branch could go anywhere, we don't know the target. + // Those instructions are usually used on a switch to jump table + // compiler optimization, and in those cases the possible targets + // seems to be always right after the BRX itself. We can assume + // that the possible targets are all the blocks in-between the + // instruction right after the BRX, and the common target that + // all the "cases" should eventually jump to, acting as the + // switch break. + Block firstTarget = GetBlock(currBlock.EndAddress); + + firstTarget.BrIndir = opBrIndir; + + opBrIndir.PossibleTargets.Add(firstTarget); + } + + if (!IsUnconditionalBranch(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); + } + + // Do we have a block after the current one? + if (!IsExit(currBlock.GetLastOp()) && currBlock.BrIndir != null && currBlock.EndAddress < maxAddress) + { + bool targetVisited = visited.ContainsKey(currBlock.EndAddress); + + Block possibleTarget = GetBlock(currBlock.EndAddress); + + currBlock.BrIndir.PossibleTargets.Add(possibleTarget); + + if (!targetVisited) + { + possibleTarget.BrIndir = currBlock.BrIndir; + } + } + } + + foreach (Block block in blocks.Where(x => x.PushOpCodes.Count != 0)) + { + for (int pushOpIndex = 0; pushOpIndex < block.PushOpCodes.Count; pushOpIndex++) + { + PropagatePushOp(visited, block, pushOpIndex); + } + } + + return blocks.ToArray(); + } + + private 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( + ReadOnlySpan code, + Block block, + ulong limitAddress, + ulong startAddress) + { + ulong address = block.Address; + + do + { + if (address + 7 >= limitAddress) + { + break; + } + + // Ignore scheduling instructions, which are written every 32 bytes. + if ((address & 0x1f) == 0) + { + address += 8; + + continue; + } + + uint word0 = BinaryPrimitives.ReadUInt32LittleEndian(code.Slice((int)(startAddress + address))); + uint word1 = BinaryPrimitives.ReadUInt32LittleEndian(code.Slice((int)(startAddress + address + 4))); + + ulong opAddress = address; + + address += 8; + + long opCode = word0 | (long)word1 << 32; + + (InstEmitter emitter, Type opCodeType) = OpCodeTable.GetEmitter(opCode); + + if (emitter == null) + { + // TODO: Warning, illegal encoding. + + block.OpCodes.Add(new OpCode(null, opAddress, opCode)); + + continue; + } + + OpCode op = MakeOpCode(opCodeType, emitter, opAddress, opCode); + + block.OpCodes.Add(op); + } + while (!IsBranch(block.GetLastOp())); + + block.EndAddress = address; + + block.UpdatePushOps(); + } + + private static bool IsUnconditionalBranch(OpCode opCode) + { + return IsUnconditional(opCode) && IsBranch(opCode); + } + + private static bool IsUnconditional(OpCode opCode) + { + if (opCode is OpCodeExit op && op.Condition != Condition.Always) + { + return false; + } + + return opCode.Predicate.Index == RegisterConsts.PredicateTrueIndex && !opCode.InvertPredicate; + } + + private static bool IsBranch(OpCode opCode) + { + return (opCode is OpCodeBranch opBranch && !opBranch.PushTarget) || + opCode is OpCodeBranchIndir || + opCode is OpCodeBranchPop || + opCode is OpCodeExit; + } + + private static bool IsExit(OpCode opCode) + { + return opCode is OpCodeExit; + } + + private static OpCode MakeOpCode(Type type, InstEmitter emitter, ulong address, long opCode) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + OpActivator createInstance = _opActivators.GetOrAdd(type, CacheOpActivator); + + return (OpCode)createInstance(emitter, address, opCode); + } + + private static OpActivator CacheOpActivator(Type type) + { + Type[] argTypes = new Type[] { typeof(InstEmitter), typeof(ulong), typeof(long) }; + + DynamicMethod mthd = new DynamicMethod($"Make{type.Name}", type, argTypes); + + ILGenerator generator = mthd.GetILGenerator(); + + generator.Emit(OpCodes.Ldarg_0); + generator.Emit(OpCodes.Ldarg_1); + generator.Emit(OpCodes.Ldarg_2); + generator.Emit(OpCodes.Newobj, type.GetConstructor(argTypes)); + generator.Emit(OpCodes.Ret); + + return (OpActivator)mthd.CreateDelegate(typeof(OpActivator)); + } + + private struct PathBlockState + { + public Block Block { get; } + + private enum RestoreType + { + None, + PopPushOp, + PushBranchOp + } + + private RestoreType _restoreType; + + private ulong _restoreValue; + + public bool ReturningFromVisit => _restoreType != RestoreType.None; + + public PathBlockState(Block block) + { + Block = block; + _restoreType = RestoreType.None; + _restoreValue = 0; + } + + public PathBlockState(int oldStackSize) + { + Block = null; + _restoreType = RestoreType.PopPushOp; + _restoreValue = (ulong)oldStackSize; + } + + public PathBlockState(ulong syncAddress) + { + Block = null; + _restoreType = RestoreType.PushBranchOp; + _restoreValue = syncAddress; + } + + public void RestoreStackState(Stack branchStack) + { + if (_restoreType == RestoreType.PushBranchOp) + { + branchStack.Push(_restoreValue); + } + else if (_restoreType == RestoreType.PopPushOp) + { + while (branchStack.Count > (uint)_restoreValue) + { + branchStack.Pop(); + } + } + } + } + + private static void PropagatePushOp(Dictionary blocks, Block currBlock, int pushOpIndex) + { + OpCodePush pushOp = currBlock.PushOpCodes[pushOpIndex]; + + Stack workQueue = new Stack(); + + HashSet visited = new HashSet(); + + Stack branchStack = new Stack(); + + void Push(PathBlockState pbs) + { + if (pbs.Block == null || visited.Add(pbs.Block)) + { + workQueue.Push(pbs); + } + } + + Push(new PathBlockState(currBlock)); + + while (workQueue.TryPop(out PathBlockState pbs)) + { + if (pbs.ReturningFromVisit) + { + pbs.RestoreStackState(branchStack); + + continue; + } + + Block current = pbs.Block; + + int pushOpsCount = current.PushOpCodes.Count; + + if (pushOpsCount != 0) + { + Push(new PathBlockState(branchStack.Count)); + + for (int index = pushOpIndex; index < pushOpsCount; index++) + { + branchStack.Push(current.PushOpCodes[index].GetAbsoluteAddress()); + } + } + + pushOpIndex = 0; + + if (current.Next != null) + { + Push(new PathBlockState(current.Next)); + } + + if (current.Branch != null) + { + Push(new PathBlockState(current.Branch)); + } + else if (current.GetLastOp() is OpCodeBranchIndir brIndir) + { + foreach (Block possibleTarget in brIndir.PossibleTargets) + { + Push(new PathBlockState(possibleTarget)); + } + } + else if (current.GetLastOp() is OpCodeBranchPop op) + { + ulong targetAddress = branchStack.Pop(); + + if (branchStack.Count == 0) + { + branchStack.Push(targetAddress); + + op.Targets.Add(pushOp, op.Targets.Count); + + pushOp.PopOps.TryAdd(op, Local()); + } + else + { + Push(new PathBlockState(targetAddress)); + Push(new PathBlockState(blocks[targetAddress])); + } + } + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/DecoderHelper.cs b/Ryujinx.Graphics.Shader/Decoders/DecoderHelper.cs new file mode 100644 index 0000000000..77cd1bf728 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/DecoderHelper.cs @@ -0,0 +1,58 @@ +using System; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + static class DecoderHelper + { + public static int DecodeS20Immediate(long opCode) + { + int imm = opCode.Extract(20, 19); + + bool sign = opCode.Extract(56); + + if (sign) + { + imm = (imm << 13) >> 13; + } + + return imm; + } + + public static int Decode2xF10Immediate(long opCode) + { + int immH0 = opCode.Extract(20, 9); + int immH1 = opCode.Extract(30, 9); + + bool negateH0 = opCode.Extract(29); + bool negateH1 = opCode.Extract(56); + + if (negateH0) + { + immH0 |= 1 << 9; + } + + if (negateH1) + { + immH1 |= 1 << 9; + } + + return immH1 << 22 | immH0 << 6; + } + + public static float DecodeF20Immediate(long opCode) + { + int imm = opCode.Extract(20, 19); + + bool negate = opCode.Extract(56); + + imm <<= 12; + + if (negate) + { + imm |= 1 << 31; + } + + return BitConverter.Int32BitsToSingle(imm); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/FPHalfSwizzle.cs b/Ryujinx.Graphics.Shader/Decoders/FPHalfSwizzle.cs new file mode 100644 index 0000000000..3ddf17cfcc --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/FPHalfSwizzle.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum FPHalfSwizzle + { + FP16 = 0, + FP32 = 1, + DupH0 = 2, + DupH1 = 3 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/FPMultiplyScale.cs b/Ryujinx.Graphics.Shader/Decoders/FPMultiplyScale.cs new file mode 100644 index 0000000000..398c0e66f7 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/FPMultiplyScale.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum FPMultiplyScale + { + None = 0, + Divide2 = 1, + Divide4 = 2, + Divide8 = 3, + Multiply8 = 4, + Multiply4 = 5, + Multiply2 = 6 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/FPType.cs b/Ryujinx.Graphics.Shader/Decoders/FPType.cs new file mode 100644 index 0000000000..e602ad45fa --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/FPType.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum FPType + { + FP16 = 1, + FP32 = 2, + FP64 = 3 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/IOpCode.cs b/Ryujinx.Graphics.Shader/Decoders/IOpCode.cs new file mode 100644 index 0000000000..dd6ad79a2e --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/IOpCode.cs @@ -0,0 +1,16 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + interface IOpCode + { + InstEmitter Emitter { get; } + + ulong Address { get; } + long RawOpCode { get; } + + Register Predicate { get; } + + bool InvertPredicate { get; } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/IOpCodeAlu.cs b/Ryujinx.Graphics.Shader/Decoders/IOpCodeAlu.cs new file mode 100644 index 0000000000..6d1382a8a3 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/IOpCodeAlu.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + interface IOpCodeAlu : IOpCodeRd, IOpCodeRa, IOpCodePredicate39 + { + bool Extended { get; } + bool SetCondCode { get; } + bool Saturate { get; } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/IOpCodeCbuf.cs b/Ryujinx.Graphics.Shader/Decoders/IOpCodeCbuf.cs new file mode 100644 index 0000000000..42a174514c --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/IOpCodeCbuf.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + interface IOpCodeCbuf : IOpCode + { + int Offset { get; } + int Slot { get; } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/IOpCodeFArith.cs b/Ryujinx.Graphics.Shader/Decoders/IOpCodeFArith.cs new file mode 100644 index 0000000000..3d06eae0d0 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/IOpCodeFArith.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + interface IOpCodeFArith : IOpCodeAlu + { + RoundingMode RoundingMode { get; } + + FPMultiplyScale Scale { get; } + + bool FlushToZero { get; } + bool AbsoluteA { get; } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/IOpCodeHfma.cs b/Ryujinx.Graphics.Shader/Decoders/IOpCodeHfma.cs new file mode 100644 index 0000000000..4638f66086 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/IOpCodeHfma.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + interface IOpCodeHfma : IOpCode + { + bool NegateB { get; } + bool NegateC { get; } + bool Saturate { get; } + + FPHalfSwizzle SwizzleA { get; } + FPHalfSwizzle SwizzleB { get; } + FPHalfSwizzle SwizzleC { get; } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/IOpCodeImm.cs b/Ryujinx.Graphics.Shader/Decoders/IOpCodeImm.cs new file mode 100644 index 0000000000..9cfcd69b05 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/IOpCodeImm.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + interface IOpCodeImm : IOpCode + { + int Immediate { get; } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/IOpCodeImmF.cs b/Ryujinx.Graphics.Shader/Decoders/IOpCodeImmF.cs new file mode 100644 index 0000000000..629eff7973 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/IOpCodeImmF.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + interface IOpCodeImmF : IOpCode + { + float Immediate { get; } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/IOpCodeLop.cs b/Ryujinx.Graphics.Shader/Decoders/IOpCodeLop.cs new file mode 100644 index 0000000000..62c87bf435 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/IOpCodeLop.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + interface IOpCodeLop : IOpCodeAlu + { + LogicalOperation LogicalOp { get; } + + bool InvertA { get; } + bool InvertB { get; } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/IOpCodePredicate39.cs b/Ryujinx.Graphics.Shader/Decoders/IOpCodePredicate39.cs new file mode 100644 index 0000000000..74e7aff134 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/IOpCodePredicate39.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + interface IOpCodePredicate39 + { + Register Predicate39 { get; } + + bool InvertP { get; } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/IOpCodeRa.cs b/Ryujinx.Graphics.Shader/Decoders/IOpCodeRa.cs new file mode 100644 index 0000000000..e5902110e5 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/IOpCodeRa.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + interface IOpCodeRa : IOpCode + { + Register Ra { get; } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/IOpCodeRc.cs b/Ryujinx.Graphics.Shader/Decoders/IOpCodeRc.cs new file mode 100644 index 0000000000..bb806b95c9 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/IOpCodeRc.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + interface IOpCodeRc : IOpCode + { + Register Rc { get; } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/IOpCodeRd.cs b/Ryujinx.Graphics.Shader/Decoders/IOpCodeRd.cs new file mode 100644 index 0000000000..099c4061ab --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/IOpCodeRd.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + interface IOpCodeRd : IOpCode + { + Register Rd { get; } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/IOpCodeReg.cs b/Ryujinx.Graphics.Shader/Decoders/IOpCodeReg.cs new file mode 100644 index 0000000000..3ed157e821 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/IOpCodeReg.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + interface IOpCodeReg : IOpCode + { + Register Rb { get; } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/IOpCodeRegCbuf.cs b/Ryujinx.Graphics.Shader/Decoders/IOpCodeRegCbuf.cs new file mode 100644 index 0000000000..429f01bb7f --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/IOpCodeRegCbuf.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + interface IOpCodeRegCbuf : IOpCodeRc + { + int Offset { get; } + int Slot { get; } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/IOpCodeTexture.cs b/Ryujinx.Graphics.Shader/Decoders/IOpCodeTexture.cs new file mode 100644 index 0000000000..55d1225aeb --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/IOpCodeTexture.cs @@ -0,0 +1,23 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + interface IOpCodeTexture : IOpCode + { + Register Rd { get; } + Register Ra { get; } + Register Rb { get; } + + bool IsArray { get; } + + TextureDimensions Dimensions { get; } + + int ComponentMask { get; } + + int Immediate { get; } + + TextureLodMode LodMode { get; } + + bool HasOffset { get; } + bool HasDepthCompare { get; } + bool IsMultisample { get; } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/IOpCodeTld4.cs b/Ryujinx.Graphics.Shader/Decoders/IOpCodeTld4.cs new file mode 100644 index 0000000000..219d00cbf7 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/IOpCodeTld4.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + interface IOpCodeTld4 : IOpCodeTexture + { + TextureGatherOffset Offset { get; } + + int GatherCompIndex { get; } + + bool Bindless { get; } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/ImageComponents.cs b/Ryujinx.Graphics.Shader/Decoders/ImageComponents.cs new file mode 100644 index 0000000000..348a4768c9 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/ImageComponents.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum ImageComponents + { + Red = 1 << 0, + Green = 1 << 1, + Blue = 1 << 2, + Alpha = 1 << 3 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/ImageDimensions.cs b/Ryujinx.Graphics.Shader/Decoders/ImageDimensions.cs new file mode 100644 index 0000000000..ecf41a82fc --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/ImageDimensions.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum ImageDimensions + { + Image1D, + ImageBuffer, + Image1DArray, + Image2D, + Image2DArray, + Image3D + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/IntegerCondition.cs b/Ryujinx.Graphics.Shader/Decoders/IntegerCondition.cs new file mode 100644 index 0000000000..a1937c2f52 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/IntegerCondition.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum IntegerCondition + { + Less = 1 << 0, + Equal = 1 << 1, + Greater = 1 << 2, + + Never = 0, + + LessOrEqual = Less | Equal, + NotEqual = Less | Greater, + GreaterOrEqual = Greater | Equal, + Number = Greater | Equal | Less, + + Always = 7 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/IntegerHalfPart.cs b/Ryujinx.Graphics.Shader/Decoders/IntegerHalfPart.cs new file mode 100644 index 0000000000..b779f44d4b --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/IntegerHalfPart.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum IntegerHalfPart + { + B32 = 0, + H0 = 1, + H1 = 2 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/IntegerShift.cs b/Ryujinx.Graphics.Shader/Decoders/IntegerShift.cs new file mode 100644 index 0000000000..ce4d9f3bb6 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/IntegerShift.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum IntegerShift + { + NoShift = 0, + ShiftRight = 1, + ShiftLeft = 2 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/IntegerSize.cs b/Ryujinx.Graphics.Shader/Decoders/IntegerSize.cs new file mode 100644 index 0000000000..d39c2a9091 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/IntegerSize.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum IntegerSize + { + U8 = 0, + S8 = 1, + U16 = 2, + S16 = 3, + B32 = 4, + B64 = 5, + B128 = 6, + UB128 = 7 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/IntegerType.cs b/Ryujinx.Graphics.Shader/Decoders/IntegerType.cs new file mode 100644 index 0000000000..46734dbe4a --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/IntegerType.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum IntegerType + { + U8 = 0, + U16 = 1, + U32 = 2, + U64 = 3, + S8 = 4, + S16 = 5, + S32 = 6, + S64 = 7 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/InterpolationMode.cs b/Ryujinx.Graphics.Shader/Decoders/InterpolationMode.cs new file mode 100644 index 0000000000..98ee3b9703 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/InterpolationMode.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum InterpolationMode + { + Pass, + Default, + Constant, + Sc + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/LogicalOperation.cs b/Ryujinx.Graphics.Shader/Decoders/LogicalOperation.cs new file mode 100644 index 0000000000..5221442510 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/LogicalOperation.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum LogicalOperation + { + And = 0, + Or = 1, + ExclusiveOr = 2, + Passthrough = 3 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/MufuOperation.cs b/Ryujinx.Graphics.Shader/Decoders/MufuOperation.cs new file mode 100644 index 0000000000..88bd1f5cea --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/MufuOperation.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum MufuOperation + { + Cosine = 0, + Sine = 1, + ExponentB2 = 2, + LogarithmB2 = 3, + Reciprocal = 4, + ReciprocalSquareRoot = 5, + Reciprocal64H = 6, + ReciprocalSquareRoot64H = 7, + SquareRoot = 8 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCode.cs b/Ryujinx.Graphics.Shader/Decoders/OpCode.cs new file mode 100644 index 0000000000..94af49e05c --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCode.cs @@ -0,0 +1,30 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCode + { + public InstEmitter Emitter { get; } + + public ulong Address { get; } + public long RawOpCode { get; } + + public Register Predicate { get; protected set; } + + public bool InvertPredicate { get; protected set; } + + // When inverted, the always true predicate == always false. + public bool NeverExecute => Predicate.Index == RegisterConsts.PredicateTrueIndex && InvertPredicate; + + public OpCode(InstEmitter emitter, ulong address, long opCode) + { + Emitter = emitter; + Address = address; + RawOpCode = opCode; + + Predicate = new Register(opCode.Extract(16, 3), RegisterType.Predicate); + + InvertPredicate = opCode.Extract(19); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeAlu.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeAlu.cs new file mode 100644 index 0000000000..15fbb9af79 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeAlu.cs @@ -0,0 +1,34 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeAlu : OpCode, IOpCodeAlu, IOpCodeRc + { + public Register Rd { get; } + public Register Ra { get; } + public Register Rc { get; } + public Register Predicate39 { get; } + + public int ByteSelection { get; } + + public bool InvertP { get; } + public bool Extended { get; protected set; } + public bool SetCondCode { get; protected set; } + public bool Saturate { get; protected set; } + + public OpCodeAlu(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Rd = new Register(opCode.Extract(0, 8), RegisterType.Gpr); + Ra = new Register(opCode.Extract(8, 8), RegisterType.Gpr); + Rc = new Register(opCode.Extract(39, 8), RegisterType.Gpr); + Predicate39 = new Register(opCode.Extract(39, 3), RegisterType.Predicate); + + ByteSelection = opCode.Extract(41, 2); + + InvertP = opCode.Extract(42); + Extended = opCode.Extract(43); + SetCondCode = opCode.Extract(47); + Saturate = opCode.Extract(50); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeAluCbuf.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeAluCbuf.cs new file mode 100644 index 0000000000..9c12798947 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeAluCbuf.cs @@ -0,0 +1,16 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeAluCbuf : OpCodeAlu, IOpCodeCbuf + { + public int Offset { get; } + public int Slot { get; } + + public OpCodeAluCbuf(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Offset = opCode.Extract(20, 14); + Slot = opCode.Extract(34, 5); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeAluImm.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeAluImm.cs new file mode 100644 index 0000000000..a407fc6bf8 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeAluImm.cs @@ -0,0 +1,14 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeAluImm : OpCodeAlu, IOpCodeImm + { + public int Immediate { get; } + + public OpCodeAluImm(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Immediate = DecoderHelper.DecodeS20Immediate(opCode); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeAluImm2x10.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeAluImm2x10.cs new file mode 100644 index 0000000000..9aeb32bd4d --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeAluImm2x10.cs @@ -0,0 +1,14 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeAluImm2x10 : OpCodeAlu, IOpCodeImm + { + public int Immediate { get; } + + public OpCodeAluImm2x10(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Immediate = DecoderHelper.Decode2xF10Immediate(opCode); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeAluImm32.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeAluImm32.cs new file mode 100644 index 0000000000..5941e0b9a2 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeAluImm32.cs @@ -0,0 +1,18 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeAluImm32 : OpCodeAlu, IOpCodeImm + { + public int Immediate { get; } + + public OpCodeAluImm32(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Immediate = opCode.Extract(20, 32); + + SetCondCode = opCode.Extract(52); + Extended = opCode.Extract(53); + Saturate = opCode.Extract(54); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeAluReg.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeAluReg.cs new file mode 100644 index 0000000000..13b96a3ae8 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeAluReg.cs @@ -0,0 +1,14 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeAluReg : OpCodeAlu, IOpCodeReg + { + public Register Rb { get; protected set; } + + public OpCodeAluReg(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Rb = new Register(opCode.Extract(20, 8), RegisterType.Gpr); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeAluRegCbuf.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeAluRegCbuf.cs new file mode 100644 index 0000000000..6cf6bd2e22 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeAluRegCbuf.cs @@ -0,0 +1,18 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeAluRegCbuf : OpCodeAluReg, IOpCodeRegCbuf + { + public int Offset { get; } + public int Slot { get; } + + public OpCodeAluRegCbuf(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Offset = opCode.Extract(20, 14); + Slot = opCode.Extract(34, 5); + + Rb = new Register(opCode.Extract(39, 8), RegisterType.Gpr); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeAtom.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeAtom.cs new file mode 100644 index 0000000000..b572703ec8 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeAtom.cs @@ -0,0 +1,39 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeAtom : OpCode, IOpCodeRd, IOpCodeRa, IOpCodeReg + { + public Register Rd { get; } + public Register Ra { get; } + public Register Rb { get; } + + public ReductionType Type { get; } + + public int Offset { get; } + + public bool Extended { get; } + + public AtomicOp AtomicOp { get; } + + public OpCodeAtom(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Rd = new Register(opCode.Extract(0, 8), RegisterType.Gpr); + Ra = new Register(opCode.Extract(8, 8), RegisterType.Gpr); + Rb = new Register(opCode.Extract(20, 8), RegisterType.Gpr); + + Type = (ReductionType)opCode.Extract(28, 2); + + if (Type == ReductionType.FP32FtzRn) + { + Type = ReductionType.S64; + } + + Offset = opCode.Extract(30, 22); + + Extended = opCode.Extract(48); + + AtomicOp = (AtomicOp)opCode.Extract(52, 4); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeAttribute.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeAttribute.cs new file mode 100644 index 0000000000..fd8e63fcfb --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeAttribute.cs @@ -0,0 +1,16 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeAttribute : OpCodeAluReg + { + public int AttributeOffset { get; } + public int Count { get; } + + public OpCodeAttribute(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + AttributeOffset = opCode.Extract(20, 10); + Count = opCode.Extract(47, 2) + 1; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeBarrier.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeBarrier.cs new file mode 100644 index 0000000000..81e28aa143 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeBarrier.cs @@ -0,0 +1,14 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeBarrier : OpCode + { + public BarrierMode Mode { get; } + + public OpCodeBarrier(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Mode = (BarrierMode)((opCode >> 32) & 0x9b); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeBranch.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeBranch.cs new file mode 100644 index 0000000000..c4fa921265 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeBranch.cs @@ -0,0 +1,27 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeBranch : OpCode + { + public Condition Condition { get; } + + public int Offset { get; } + + public bool PushTarget { get; protected set; } + + public OpCodeBranch(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Condition = (Condition)(opCode & 0x1f); + + Offset = ((int)(opCode >> 20) << 8) >> 8; + + PushTarget = false; + } + + public ulong GetAbsoluteAddress() + { + return (ulong)((long)Address + (long)Offset + 8); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeBranchIndir.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeBranchIndir.cs new file mode 100644 index 0000000000..3e694e61c8 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeBranchIndir.cs @@ -0,0 +1,23 @@ +using Ryujinx.Graphics.Shader.Instructions; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeBranchIndir : OpCode + { + public HashSet PossibleTargets { get; } + + public Register Ra { get; } + + public int Offset { get; } + + public OpCodeBranchIndir(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + PossibleTargets = new HashSet(); + + Ra = new Register(opCode.Extract(8, 8), RegisterType.Gpr); + + Offset = ((int)(opCode >> 20) << 8) >> 8; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeBranchPop.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeBranchPop.cs new file mode 100644 index 0000000000..7ea66fe455 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeBranchPop.cs @@ -0,0 +1,15 @@ +using Ryujinx.Graphics.Shader.Instructions; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeBranchPop : OpCode + { + public Dictionary Targets { get; } + + public OpCodeBranchPop(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Targets = new Dictionary(); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeExit.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeExit.cs new file mode 100644 index 0000000000..d50903eb4f --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeExit.cs @@ -0,0 +1,14 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeExit : OpCode + { + public Condition Condition { get; } + + public OpCodeExit(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Condition = (Condition)opCode.Extract(0, 5); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeFArith.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeFArith.cs new file mode 100644 index 0000000000..cfbf65c3dd --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeFArith.cs @@ -0,0 +1,24 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeFArith : OpCodeAlu, IOpCodeFArith + { + public RoundingMode RoundingMode { get; } + + public FPMultiplyScale Scale { get; } + + public bool FlushToZero { get; } + public bool AbsoluteA { get; } + + public OpCodeFArith(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + RoundingMode = (RoundingMode)opCode.Extract(39, 2); + + Scale = (FPMultiplyScale)opCode.Extract(41, 3); + + FlushToZero = opCode.Extract(44); + AbsoluteA = opCode.Extract(46); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeFArithCbuf.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeFArithCbuf.cs new file mode 100644 index 0000000000..5486bb0b07 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeFArithCbuf.cs @@ -0,0 +1,16 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeFArithCbuf : OpCodeFArith, IOpCodeCbuf + { + public int Offset { get; } + public int Slot { get; } + + public OpCodeFArithCbuf(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Offset = opCode.Extract(20, 14); + Slot = opCode.Extract(34, 5); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeFArithImm.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeFArithImm.cs new file mode 100644 index 0000000000..1bb6f42552 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeFArithImm.cs @@ -0,0 +1,14 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeFArithImm : OpCodeFArith, IOpCodeImmF + { + public float Immediate { get; } + + public OpCodeFArithImm(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Immediate = DecoderHelper.DecodeF20Immediate(opCode); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeFArithImm32.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeFArithImm32.cs new file mode 100644 index 0000000000..aecc5143ca --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeFArithImm32.cs @@ -0,0 +1,30 @@ +using Ryujinx.Graphics.Shader.Instructions; +using System; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeFArithImm32 : OpCodeAlu, IOpCodeFArith, IOpCodeImmF + { + public RoundingMode RoundingMode => RoundingMode.ToNearest; + + public FPMultiplyScale Scale => FPMultiplyScale.None; + + public bool FlushToZero { get; } + public bool AbsoluteA { get; } + + public float Immediate { get; } + + public OpCodeFArithImm32(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + int imm = opCode.Extract(20, 32); + + Immediate = BitConverter.Int32BitsToSingle(imm); + + SetCondCode = opCode.Extract(52); + AbsoluteA = opCode.Extract(54); + FlushToZero = opCode.Extract(55); + + Saturate = false; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeFArithReg.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeFArithReg.cs new file mode 100644 index 0000000000..55cf448597 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeFArithReg.cs @@ -0,0 +1,14 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeFArithReg : OpCodeFArith, IOpCodeReg + { + public Register Rb { get; protected set; } + + public OpCodeFArithReg(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Rb = new Register(opCode.Extract(20, 8), RegisterType.Gpr); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeFArithRegCbuf.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeFArithRegCbuf.cs new file mode 100644 index 0000000000..315c2c8b15 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeFArithRegCbuf.cs @@ -0,0 +1,16 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeFArithRegCbuf : OpCodeFArith, IOpCodeRegCbuf + { + public int Offset { get; } + public int Slot { get; } + + public OpCodeFArithRegCbuf(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Offset = opCode.Extract(20, 14); + Slot = opCode.Extract(34, 5); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeFsetImm.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeFsetImm.cs new file mode 100644 index 0000000000..cb5f155e82 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeFsetImm.cs @@ -0,0 +1,14 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeFsetImm : OpCodeSet, IOpCodeImmF + { + public float Immediate { get; } + + public OpCodeFsetImm(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Immediate = DecoderHelper.DecodeF20Immediate(opCode); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeHfma.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeHfma.cs new file mode 100644 index 0000000000..32f3cd7ab8 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeHfma.cs @@ -0,0 +1,22 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeHfma : OpCode, IOpCodeRd, IOpCodeRa, IOpCodeRc + { + public Register Rd { get; } + public Register Ra { get; } + public Register Rc { get; protected set; } + + public FPHalfSwizzle SwizzleA { get; } + + public OpCodeHfma(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Rd = new Register(opCode.Extract(0, 8), RegisterType.Gpr); + Ra = new Register(opCode.Extract(8, 8), RegisterType.Gpr); + Rc = new Register(opCode.Extract(39, 8), RegisterType.Gpr); + + SwizzleA = (FPHalfSwizzle)opCode.Extract(47, 2); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeHfmaCbuf.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeHfmaCbuf.cs new file mode 100644 index 0000000000..33768c7d01 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeHfmaCbuf.cs @@ -0,0 +1,30 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeHfmaCbuf : OpCodeHfma, IOpCodeHfma, IOpCodeCbuf + { + public int Offset { get; } + public int Slot { get; } + + public bool NegateB { get; } + public bool NegateC { get; } + public bool Saturate { get; } + + public FPHalfSwizzle SwizzleB => FPHalfSwizzle.FP32; + public FPHalfSwizzle SwizzleC { get; } + + public OpCodeHfmaCbuf(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Offset = opCode.Extract(20, 14); + Slot = opCode.Extract(34, 5); + + NegateC = opCode.Extract(51); + Saturate = opCode.Extract(52); + + SwizzleC = (FPHalfSwizzle)opCode.Extract(53, 2); + + NegateB = opCode.Extract(56); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeHfmaImm2x10.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeHfmaImm2x10.cs new file mode 100644 index 0000000000..80a5a14089 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeHfmaImm2x10.cs @@ -0,0 +1,26 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeHfmaImm2x10 : OpCodeHfma, IOpCodeHfma, IOpCodeImm + { + public int Immediate { get; } + + public bool NegateB => false; + public bool NegateC { get; } + public bool Saturate { get; } + + public FPHalfSwizzle SwizzleB => FPHalfSwizzle.FP16; + public FPHalfSwizzle SwizzleC { get; } + + public OpCodeHfmaImm2x10(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Immediate = DecoderHelper.Decode2xF10Immediate(opCode); + + NegateC = opCode.Extract(51); + Saturate = opCode.Extract(52); + + SwizzleC = (FPHalfSwizzle)opCode.Extract(53, 2); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeHfmaImm32.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeHfmaImm32.cs new file mode 100644 index 0000000000..05eb9ffe0a --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeHfmaImm32.cs @@ -0,0 +1,25 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeHfmaImm32 : OpCodeHfma, IOpCodeHfma, IOpCodeImm + { + public int Immediate { get; } + + public bool NegateB => false; + public bool NegateC { get; } + public bool Saturate => false; + + public FPHalfSwizzle SwizzleB => FPHalfSwizzle.FP16; + public FPHalfSwizzle SwizzleC => FPHalfSwizzle.FP16; + + public OpCodeHfmaImm32(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Immediate = opCode.Extract(20, 32); + + NegateC = opCode.Extract(52); + + Rc = Rd; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeHfmaReg.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeHfmaReg.cs new file mode 100644 index 0000000000..714c89dead --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeHfmaReg.cs @@ -0,0 +1,29 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeHfmaReg : OpCodeHfma, IOpCodeHfma, IOpCodeReg + { + public Register Rb { get; } + + public bool NegateB { get; } + public bool NegateC { get; } + public bool Saturate { get; } + + public FPHalfSwizzle SwizzleB { get; } + public FPHalfSwizzle SwizzleC { get; } + + public OpCodeHfmaReg(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Rb = new Register(opCode.Extract(20, 8), RegisterType.Gpr); + + SwizzleB = (FPHalfSwizzle)opCode.Extract(28, 2); + + NegateC = opCode.Extract(30); + NegateB = opCode.Extract(31); + Saturate = opCode.Extract(32); + + SwizzleC = (FPHalfSwizzle)opCode.Extract(35, 2); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeHfmaRegCbuf.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeHfmaRegCbuf.cs new file mode 100644 index 0000000000..c0001908ce --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeHfmaRegCbuf.cs @@ -0,0 +1,30 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeHfmaRegCbuf : OpCodeHfma, IOpCodeHfma, IOpCodeRegCbuf + { + public int Offset { get; } + public int Slot { get; } + + public bool NegateB { get; } + public bool NegateC { get; } + public bool Saturate { get; } + + public FPHalfSwizzle SwizzleB { get; } + public FPHalfSwizzle SwizzleC => FPHalfSwizzle.FP32; + + public OpCodeHfmaRegCbuf(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Offset = opCode.Extract(20, 14); + Slot = opCode.Extract(34, 5); + + NegateC = opCode.Extract(51); + Saturate = opCode.Extract(52); + + SwizzleB = (FPHalfSwizzle)opCode.Extract(53, 2); + + NegateB = opCode.Extract(56); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeHsetImm2x10.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeHsetImm2x10.cs new file mode 100644 index 0000000000..03e1e44cec --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeHsetImm2x10.cs @@ -0,0 +1,14 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeHsetImm2x10 : OpCodeSet, IOpCodeImm + { + public int Immediate { get; } + + public OpCodeHsetImm2x10(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Immediate = DecoderHelper.Decode2xF10Immediate(opCode); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeImage.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeImage.cs new file mode 100644 index 0000000000..42fe677bdb --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeImage.cs @@ -0,0 +1,48 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeImage : OpCode + { + public Register Ra { get; } + public Register Rb { get; } + public Register Rc { get; } + + public ImageComponents Components { get; } + public IntegerSize Size { get; } + + public bool ByteAddress { get; } + + public ImageDimensions Dimensions { get; } + + public int Immediate { get; } + + public bool UseComponents { get; } + public bool IsBindless { get; } + + public OpCodeImage(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Ra = new Register(opCode.Extract(8, 8), RegisterType.Gpr); + Rb = new Register(opCode.Extract(0, 8), RegisterType.Gpr); + Rc = new Register(opCode.Extract(39, 8), RegisterType.Gpr); + + UseComponents = !opCode.Extract(52); + + if (UseComponents) + { + Components = (ImageComponents)opCode.Extract(20, 4); + } + else + { + Size = (IntegerSize)opCode.Extract(20, 4); + } + + ByteAddress = !opCode.Extract(23); + + Dimensions = (ImageDimensions)opCode.Extract(33, 3); + + Immediate = opCode.Extract(36, 13); + IsBindless = !opCode.Extract(51); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeIpa.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeIpa.cs new file mode 100644 index 0000000000..b475b6a16a --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeIpa.cs @@ -0,0 +1,20 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeIpa : OpCodeAluReg + { + public int AttributeOffset { get; } + + public InterpolationMode Mode { get; } + + public OpCodeIpa(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + AttributeOffset = opCode.Extract(28, 10); + + Saturate = opCode.Extract(51); + + Mode = (InterpolationMode)opCode.Extract(54, 2); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeLdc.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeLdc.cs new file mode 100644 index 0000000000..cc9f065828 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeLdc.cs @@ -0,0 +1,26 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeLdc : OpCode, IOpCodeRd, IOpCodeRa, IOpCodeCbuf + { + public Register Rd { get; } + public Register Ra { get; } + + public int Offset { get; } + public int Slot { get; } + + public IntegerSize Size { get; } + + public OpCodeLdc(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Rd = new Register(opCode.Extract(0, 8), RegisterType.Gpr); + Ra = new Register(opCode.Extract(8, 8), RegisterType.Gpr); + + Offset = opCode.Extract(22, 14); + Slot = opCode.Extract(36, 5); + + Size = (IntegerSize)opCode.Extract(48, 3); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeLop.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeLop.cs new file mode 100644 index 0000000000..c5f903451b --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeLop.cs @@ -0,0 +1,28 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeLop : OpCodeAlu, IOpCodeLop + { + public bool InvertA { get; protected set; } + public bool InvertB { get; protected set; } + + public LogicalOperation LogicalOp { get; } + + public ConditionalOperation CondOp { get; } + + public Register Predicate48 { get; } + + public OpCodeLop(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + InvertA = opCode.Extract(39); + InvertB = opCode.Extract(40); + + LogicalOp = (LogicalOperation)opCode.Extract(41, 2); + + CondOp = (ConditionalOperation)opCode.Extract(44, 2); + + Predicate48 = new Register(opCode.Extract(48, 3), RegisterType.Predicate); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeLopCbuf.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeLopCbuf.cs new file mode 100644 index 0000000000..f174733c42 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeLopCbuf.cs @@ -0,0 +1,16 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeLopCbuf : OpCodeLop, IOpCodeCbuf + { + public int Offset { get; } + public int Slot { get; } + + public OpCodeLopCbuf(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Offset = opCode.Extract(20, 14); + Slot = opCode.Extract(34, 5); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeLopImm.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeLopImm.cs new file mode 100644 index 0000000000..a2f091a2c4 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeLopImm.cs @@ -0,0 +1,14 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeLopImm : OpCodeLop, IOpCodeImm + { + public int Immediate { get; } + + public OpCodeLopImm(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Immediate = DecoderHelper.DecodeS20Immediate(opCode); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeLopImm32.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeLopImm32.cs new file mode 100644 index 0000000000..cb48f3a615 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeLopImm32.cs @@ -0,0 +1,22 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeLopImm32 : OpCodeAluImm32, IOpCodeLop, IOpCodeImm + { + public LogicalOperation LogicalOp { get; } + + public bool InvertA { get; } + public bool InvertB { get; } + + public OpCodeLopImm32(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + LogicalOp = (LogicalOperation)opCode.Extract(53, 2); + + InvertA = opCode.Extract(55); + InvertB = opCode.Extract(56); + + Extended = opCode.Extract(57); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeLopReg.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeLopReg.cs new file mode 100644 index 0000000000..5f43db72b5 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeLopReg.cs @@ -0,0 +1,14 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeLopReg : OpCodeLop, IOpCodeReg + { + public Register Rb { get; } + + public OpCodeLopReg(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Rb = new Register(opCode.Extract(20, 8), RegisterType.Gpr); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeMemory.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeMemory.cs new file mode 100644 index 0000000000..bece456223 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeMemory.cs @@ -0,0 +1,28 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeMemory : OpCode, IOpCodeRd, IOpCodeRa + { + public Register Rd { get; } + public Register Ra { get; } + + public int Offset { get; } + + public bool Extended { get; } + + public IntegerSize Size { get; } + + public OpCodeMemory(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Rd = new Register(opCode.Extract(0, 8), RegisterType.Gpr); + Ra = new Register(opCode.Extract(8, 8), RegisterType.Gpr); + + Offset = opCode.Extract(20, 24); + + Extended = opCode.Extract(45); + + Size = (IntegerSize)opCode.Extract(48, 3); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeMemoryBarrier.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeMemoryBarrier.cs new file mode 100644 index 0000000000..c31fe87b97 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeMemoryBarrier.cs @@ -0,0 +1,14 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeMemoryBarrier : OpCode + { + public BarrierLevel Level { get; } + + public OpCodeMemoryBarrier(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Level = (BarrierLevel)opCode.Extract(8, 2); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodePset.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodePset.cs new file mode 100644 index 0000000000..df508442d3 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodePset.cs @@ -0,0 +1,26 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodePset : OpCodeSet + { + public Register Predicate12 { get; } + public Register Predicate29 { get; } + + public bool InvertA { get; } + public bool InvertB { get; } + + public LogicalOperation LogicalOpAB { get; } + + public OpCodePset(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Predicate12 = new Register(opCode.Extract(12, 3), RegisterType.Predicate); + Predicate29 = new Register(opCode.Extract(29, 3), RegisterType.Predicate); + + InvertA = opCode.Extract(15); + InvertB = opCode.Extract(32); + + LogicalOpAB = (LogicalOperation)opCode.Extract(24, 2); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodePush.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodePush.cs new file mode 100644 index 0000000000..a7657bcf8e --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodePush.cs @@ -0,0 +1,22 @@ +using Ryujinx.Graphics.Shader.Instructions; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodePush : OpCodeBranch + { + public Dictionary PopOps { get; } + + public OpCodePush(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + PopOps = new Dictionary(); + + Predicate = new Register(RegisterConsts.PredicateTrueIndex, RegisterType.Predicate); + + InvertPredicate = false; + + PushTarget = true; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeRed.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeRed.cs new file mode 100644 index 0000000000..8fde82a260 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeRed.cs @@ -0,0 +1,32 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeRed : OpCode, IOpCodeRd, IOpCodeRa + { + public Register Rd { get; } + public Register Ra { get; } + + public AtomicOp AtomicOp { get; } + + public ReductionType Type { get; } + + public int Offset { get; } + + public bool Extended { get; } + + public OpCodeRed(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Rd = new Register(opCode.Extract(0, 8), RegisterType.Gpr); + Ra = new Register(opCode.Extract(8, 8), RegisterType.Gpr); + + Type = (ReductionType)opCode.Extract(20, 3); + + AtomicOp = (AtomicOp)opCode.Extract(23, 3); + + Offset = opCode.Extract(28, 20); + + Extended = opCode.Extract(48); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeSet.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeSet.cs new file mode 100644 index 0000000000..b4ee10fb3d --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeSet.cs @@ -0,0 +1,26 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeSet : OpCodeAlu + { + public Register Predicate0 { get; } + public Register Predicate3 { get; } + + public bool NegateP { get; } + + public LogicalOperation LogicalOp { get; } + + public bool FlushToZero { get; } + + public OpCodeSet(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Predicate0 = new Register(opCode.Extract(0, 3), RegisterType.Predicate); + Predicate3 = new Register(opCode.Extract(3, 3), RegisterType.Predicate); + + LogicalOp = (LogicalOperation)opCode.Extract(45, 2); + + FlushToZero = opCode.Extract(47); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeSetCbuf.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeSetCbuf.cs new file mode 100644 index 0000000000..4f3dbd7412 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeSetCbuf.cs @@ -0,0 +1,16 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeSetCbuf : OpCodeSet, IOpCodeCbuf + { + public int Offset { get; } + public int Slot { get; } + + public OpCodeSetCbuf(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Offset = opCode.Extract(20, 14); + Slot = opCode.Extract(34, 5); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeSetImm.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeSetImm.cs new file mode 100644 index 0000000000..bc63b9f476 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeSetImm.cs @@ -0,0 +1,14 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeSetImm : OpCodeSet, IOpCodeImm + { + public int Immediate { get; } + + public OpCodeSetImm(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Immediate = DecoderHelper.DecodeS20Immediate(opCode); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeSetReg.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeSetReg.cs new file mode 100644 index 0000000000..bbdee19622 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeSetReg.cs @@ -0,0 +1,14 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeSetReg : OpCodeSet, IOpCodeReg + { + public Register Rb { get; protected set; } + + public OpCodeSetReg(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Rb = new Register(opCode.Extract(20, 8), RegisterType.Gpr); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeShuffle.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeShuffle.cs new file mode 100644 index 0000000000..43693cf490 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeShuffle.cs @@ -0,0 +1,40 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeShuffle : OpCode, IOpCodeRd, IOpCodeRa + { + public Register Rd { get; } + public Register Ra { get; } + public Register Rb { get; } + public Register Rc { get; } + + public int ImmediateB { get; } + public int ImmediateC { get; } + + public bool IsBImmediate { get; } + public bool IsCImmediate { get; } + + public ShuffleType ShuffleType { get; } + + public Register Predicate48 { get; } + + public OpCodeShuffle(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Rd = new Register(opCode.Extract(0, 8), RegisterType.Gpr); + Ra = new Register(opCode.Extract(8, 8), RegisterType.Gpr); + Rb = new Register(opCode.Extract(20, 8), RegisterType.Gpr); + Rc = new Register(opCode.Extract(39, 8), RegisterType.Gpr); + + ImmediateB = opCode.Extract(20, 5); + ImmediateC = opCode.Extract(34, 13); + + IsBImmediate = opCode.Extract(28); + IsCImmediate = opCode.Extract(29); + + ShuffleType = (ShuffleType)opCode.Extract(30, 2); + + Predicate48 = new Register(opCode.Extract(48, 3), RegisterType.Predicate); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeTable.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeTable.cs new file mode 100644 index 0000000000..87f1de0c48 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeTable.cs @@ -0,0 +1,264 @@ +using Ryujinx.Graphics.Shader.Instructions; +using System; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + static class OpCodeTable + { + private const int EncodingBits = 14; + + private class TableEntry + { + public InstEmitter Emitter { get; } + + public Type OpCodeType { get; } + + public int XBits { get; } + + public TableEntry(InstEmitter emitter, Type opCodeType, int xBits) + { + Emitter = emitter; + OpCodeType = opCodeType; + XBits = xBits; + } + } + + private static TableEntry[] _opCodes; + + static OpCodeTable() + { + _opCodes = new TableEntry[1 << EncodingBits]; + +#region Instructions + Set("1110111111011x", InstEmit.Ald, typeof(OpCodeAttribute)); + Set("1110111111110x", InstEmit.Ast, typeof(OpCodeAttribute)); + Set("11101100xxxxxx", InstEmit.Atoms, typeof(OpCodeAtom)); + Set("1111000010101x", InstEmit.Bar, typeof(OpCodeBarrier)); + Set("0100110000000x", InstEmit.Bfe, typeof(OpCodeAluCbuf)); + Set("0011100x00000x", InstEmit.Bfe, typeof(OpCodeAluImm)); + Set("0101110000000x", InstEmit.Bfe, typeof(OpCodeAluReg)); + Set("0100101111110x", InstEmit.Bfi, typeof(OpCodeAluCbuf)); + Set("0011011x11110x", InstEmit.Bfi, typeof(OpCodeAluImm)); + Set("0101001111110x", InstEmit.Bfi, typeof(OpCodeAluRegCbuf)); + Set("0101101111110x", InstEmit.Bfi, typeof(OpCodeAluReg)); + Set("111000100100xx", InstEmit.Bra, typeof(OpCodeBranch)); + Set("111000110100xx", InstEmit.Brk, typeof(OpCodeBranchPop)); + Set("111000100101xx", InstEmit.Brx, typeof(OpCodeBranchIndir)); + Set("0101000010100x", InstEmit.Csetp, typeof(OpCodePset)); + Set("111000110000xx", InstEmit.Exit, typeof(OpCodeExit)); + Set("0100110010101x", InstEmit.F2F, typeof(OpCodeFArithCbuf)); + Set("0011100x10101x", InstEmit.F2F, typeof(OpCodeFArithImm)); + Set("0101110010101x", InstEmit.F2F, typeof(OpCodeFArithReg)); + Set("0100110010110x", InstEmit.F2I, typeof(OpCodeFArithCbuf)); + Set("0011100x10110x", InstEmit.F2I, typeof(OpCodeFArithImm)); + Set("0101110010110x", InstEmit.F2I, typeof(OpCodeFArithReg)); + Set("0100110001011x", InstEmit.Fadd, typeof(OpCodeFArithCbuf)); + Set("0011100x01011x", InstEmit.Fadd, typeof(OpCodeFArithImm)); + Set("000010xxxxxxxx", InstEmit.Fadd, typeof(OpCodeFArithImm32)); + Set("0101110001011x", InstEmit.Fadd, typeof(OpCodeFArithReg)); + Set("010010011xxxxx", InstEmit.Ffma, typeof(OpCodeFArithCbuf)); + Set("0011001x1xxxxx", InstEmit.Ffma, typeof(OpCodeFArithImm)); + Set("000011xxxxxxxx", InstEmit.Ffma32i, typeof(OpCodeFArithImm32)); + Set("010100011xxxxx", InstEmit.Ffma, typeof(OpCodeFArithRegCbuf)); + Set("010110011xxxxx", InstEmit.Ffma, typeof(OpCodeFArithReg)); + Set("0100110000110x", InstEmit.Flo, typeof(OpCodeAluCbuf)); + Set("0011100x00110x", InstEmit.Flo, typeof(OpCodeAluImm)); + Set("0101110000110x", InstEmit.Flo, typeof(OpCodeAluReg)); + Set("0100110001100x", InstEmit.Fmnmx, typeof(OpCodeFArithCbuf)); + Set("0011100x01100x", InstEmit.Fmnmx, typeof(OpCodeFArithImm)); + Set("0101110001100x", InstEmit.Fmnmx, typeof(OpCodeFArithReg)); + Set("0100110001101x", InstEmit.Fmul, typeof(OpCodeFArithCbuf)); + Set("0011100x01101x", InstEmit.Fmul, typeof(OpCodeFArithImm)); + Set("00011110xxxxxx", InstEmit.Fmul, typeof(OpCodeFArithImm32)); + Set("0101110001101x", InstEmit.Fmul, typeof(OpCodeFArithReg)); + Set("0100100xxxxxxx", InstEmit.Fset, typeof(OpCodeSetCbuf)); + Set("0011000xxxxxxx", InstEmit.Fset, typeof(OpCodeFsetImm)); + Set("01011000xxxxxx", InstEmit.Fset, typeof(OpCodeSetReg)); + Set("010010111011xx", InstEmit.Fsetp, typeof(OpCodeSetCbuf)); + Set("0011011x1011xx", InstEmit.Fsetp, typeof(OpCodeFsetImm)); + Set("010110111011xx", InstEmit.Fsetp, typeof(OpCodeSetReg)); + Set("0101000011111x", InstEmit.Fswzadd, typeof(OpCodeAluReg)); + Set("0111101x1xxxxx", InstEmit.Hadd2, typeof(OpCodeAluCbuf)); + Set("0111101x0xxxxx", InstEmit.Hadd2, typeof(OpCodeAluImm2x10)); + Set("0010110xxxxxxx", InstEmit.Hadd2, typeof(OpCodeAluImm32)); + Set("0101110100010x", InstEmit.Hadd2, typeof(OpCodeAluReg)); + Set("01110xxx1xxxxx", InstEmit.Hfma2, typeof(OpCodeHfmaCbuf)); + Set("01110xxx0xxxxx", InstEmit.Hfma2, typeof(OpCodeHfmaImm2x10)); + Set("0010100xxxxxxx", InstEmit.Hfma2, typeof(OpCodeHfmaImm32)); + Set("0101110100000x", InstEmit.Hfma2, typeof(OpCodeHfmaReg)); + Set("01100xxx1xxxxx", InstEmit.Hfma2, typeof(OpCodeHfmaRegCbuf)); + Set("0111100x1xxxxx", InstEmit.Hmul2, typeof(OpCodeAluCbuf)); + Set("0111100x0xxxxx", InstEmit.Hmul2, typeof(OpCodeAluImm2x10)); + Set("0010101xxxxxxx", InstEmit.Hmul2, typeof(OpCodeAluImm32)); + Set("0101110100001x", InstEmit.Hmul2, typeof(OpCodeAluReg)); + Set("0111110x1xxxxx", InstEmit.Hset2, typeof(OpCodeSetCbuf)); + Set("0111110x0xxxxx", InstEmit.Hset2, typeof(OpCodeHsetImm2x10)); + Set("0101110100011x", InstEmit.Hset2, typeof(OpCodeSetReg)); + Set("0111111x1xxxxx", InstEmit.Hsetp2, typeof(OpCodeSetCbuf)); + Set("0111111x0xxxxx", InstEmit.Hsetp2, typeof(OpCodeHsetImm2x10)); + Set("0101110100100x", InstEmit.Hsetp2, typeof(OpCodeSetReg)); + Set("0100110010111x", InstEmit.I2F, typeof(OpCodeAluCbuf)); + Set("0011100x10111x", InstEmit.I2F, typeof(OpCodeAluImm)); + Set("0101110010111x", InstEmit.I2F, typeof(OpCodeAluReg)); + Set("0100110011100x", InstEmit.I2I, typeof(OpCodeAluCbuf)); + Set("0011100x11100x", InstEmit.I2I, typeof(OpCodeAluImm)); + Set("0101110011100x", InstEmit.I2I, typeof(OpCodeAluReg)); + Set("0100110000010x", InstEmit.Iadd, typeof(OpCodeAluCbuf)); + Set("0011100x00010x", InstEmit.Iadd, typeof(OpCodeAluImm)); + Set("0001110x0xxxxx", InstEmit.Iadd, typeof(OpCodeAluImm32)); + Set("0101110000010x", InstEmit.Iadd, typeof(OpCodeAluReg)); + Set("010011001100xx", InstEmit.Iadd3, typeof(OpCodeAluCbuf)); + Set("0011100x1100xx", InstEmit.Iadd3, typeof(OpCodeAluImm)); + Set("010111001100xx", InstEmit.Iadd3, typeof(OpCodeAluReg)); + Set("010010100xxxxx", InstEmit.Imad, typeof(OpCodeAluCbuf)); + Set("0011010x0xxxxx", InstEmit.Imad, typeof(OpCodeAluImm)); + Set("010110100xxxxx", InstEmit.Imad, typeof(OpCodeAluReg)); + Set("010100100xxxxx", InstEmit.Imad, typeof(OpCodeAluRegCbuf)); + Set("0100110000100x", InstEmit.Imnmx, typeof(OpCodeAluCbuf)); + Set("0011100x00100x", InstEmit.Imnmx, typeof(OpCodeAluImm)); + Set("0101110000100x", InstEmit.Imnmx, typeof(OpCodeAluReg)); + Set("11100000xxxxxx", InstEmit.Ipa, typeof(OpCodeIpa)); + Set("1110111111010x", InstEmit.Isberd, typeof(OpCodeAlu)); + Set("0100110000011x", InstEmit.Iscadd, typeof(OpCodeAluCbuf)); + Set("0011100x00011x", InstEmit.Iscadd, typeof(OpCodeAluImm)); + Set("000101xxxxxxxx", InstEmit.Iscadd, typeof(OpCodeAluImm32)); + Set("0101110000011x", InstEmit.Iscadd, typeof(OpCodeAluReg)); + Set("010010110101xx", InstEmit.Iset, typeof(OpCodeSetCbuf)); + Set("001101100101xx", InstEmit.Iset, typeof(OpCodeSetImm)); + Set("010110110101xx", InstEmit.Iset, typeof(OpCodeSetReg)); + Set("010010110110xx", InstEmit.Isetp, typeof(OpCodeSetCbuf)); + Set("0011011x0110xx", InstEmit.Isetp, typeof(OpCodeSetImm)); + Set("010110110110xx", InstEmit.Isetp, typeof(OpCodeSetReg)); + Set("111000110011xx", InstEmit.Kil, typeof(OpCodeExit)); + Set("1110111101000x", InstEmit.Ld, typeof(OpCodeMemory)); + Set("1110111110010x", InstEmit.Ldc, typeof(OpCodeLdc)); + Set("1110111011010x", InstEmit.Ldg, typeof(OpCodeMemory)); + Set("1110111101001x", InstEmit.Lds, typeof(OpCodeMemory)); + Set("0100110001000x", InstEmit.Lop, typeof(OpCodeLopCbuf)); + Set("0011100001000x", InstEmit.Lop, typeof(OpCodeLopImm)); + Set("000001xxxxxxxx", InstEmit.Lop, typeof(OpCodeLopImm32)); + Set("0101110001000x", InstEmit.Lop, typeof(OpCodeLopReg)); + Set("0010000xxxxxxx", InstEmit.Lop3, typeof(OpCodeLopCbuf)); + Set("001111xxxxxxxx", InstEmit.Lop3, typeof(OpCodeLopImm)); + Set("0101101111100x", InstEmit.Lop3, typeof(OpCodeLopReg)); + Set("1110111110011x", InstEmit.Membar, typeof(OpCodeMemoryBarrier)); + Set("0100110010011x", InstEmit.Mov, typeof(OpCodeAluCbuf)); + Set("0011100x10011x", InstEmit.Mov, typeof(OpCodeAluImm)); + Set("000000010000xx", InstEmit.Mov, typeof(OpCodeAluImm32)); + Set("0101110010011x", InstEmit.Mov, typeof(OpCodeAluReg)); + Set("0101000010000x", InstEmit.Mufu, typeof(OpCodeFArith)); + Set("1111101111100x", InstEmit.Out, typeof(OpCode)); + Set("111000101010xx", InstEmit.Pbk, typeof(OpCodePush)); + Set("0100110000001x", InstEmit.Popc, typeof(OpCodeAluCbuf)); + Set("0011100x00001x", InstEmit.Popc, typeof(OpCodeAluImm)); + Set("0101110000001x", InstEmit.Popc, typeof(OpCodeAluReg)); + Set("0101000010001x", InstEmit.Pset, typeof(OpCodePset)); + Set("0101000010010x", InstEmit.Psetp, typeof(OpCodePset)); + Set("0100110011110x", InstEmit.R2p, typeof(OpCodeAluCbuf)); + Set("0011100x11110x", InstEmit.R2p, typeof(OpCodeAluImm)); + Set("0101110011110x", InstEmit.R2p, typeof(OpCodeAluReg)); + Set("1110101111111x", InstEmit.Red, typeof(OpCodeRed)); + Set("0100110010010x", InstEmit.Rro, typeof(OpCodeFArithCbuf)); + Set("0011100x10010x", InstEmit.Rro, typeof(OpCodeFArithImm)); + Set("0101110010010x", InstEmit.Rro, typeof(OpCodeFArithReg)); + Set("1111000011001x", InstEmit.S2r, typeof(OpCodeAlu)); + Set("0100110010100x", InstEmit.Sel, typeof(OpCodeAluCbuf)); + Set("0011100x10100x", InstEmit.Sel, typeof(OpCodeAluImm)); + Set("0101110010100x", InstEmit.Sel, typeof(OpCodeAluReg)); + Set("1110111100010x", InstEmit.Shfl, typeof(OpCodeShuffle)); + Set("0100110001001x", InstEmit.Shl, typeof(OpCodeAluCbuf)); + Set("0011100x01001x", InstEmit.Shl, typeof(OpCodeAluImm)); + Set("0101110001001x", InstEmit.Shl, typeof(OpCodeAluReg)); + Set("0100110000101x", InstEmit.Shr, typeof(OpCodeAluCbuf)); + Set("0011100x00101x", InstEmit.Shr, typeof(OpCodeAluImm)); + Set("0101110000101x", InstEmit.Shr, typeof(OpCodeAluReg)); + Set("111000101001xx", InstEmit.Ssy, typeof(OpCodePush)); + Set("1110111101010x", InstEmit.St, typeof(OpCodeMemory)); + Set("1110111011011x", InstEmit.Stg, typeof(OpCodeMemory)); + Set("1110111101011x", InstEmit.Sts, typeof(OpCodeMemory)); + Set("11101011001xxx", InstEmit.Sust, typeof(OpCodeImage)); + Set("1111000011111x", InstEmit.Sync, typeof(OpCodeBranchPop)); + Set("110000xxxx111x", InstEmit.Tex, typeof(OpCodeTex)); + Set("1101111010111x", InstEmit.TexB, typeof(OpCodeTexB)); + Set("1101x00xxxxxxx", InstEmit.Texs, typeof(OpCodeTexs)); + Set("1101x01xxxxxxx", InstEmit.Texs, typeof(OpCodeTlds)); + Set("11011111x0xxxx", InstEmit.Texs, typeof(OpCodeTld4s)); + Set("11011100xx111x", InstEmit.Tld, typeof(OpCodeTld)); + Set("11011101xx111x", InstEmit.TldB, typeof(OpCodeTld)); + Set("110010xxxx111x", InstEmit.Tld4, typeof(OpCodeTld4)); + Set("1101111011111x", InstEmit.Tld4, typeof(OpCodeTld4B)); + Set("110111100x1110", InstEmit.Txd, typeof(OpCodeTxd)); + Set("1101111101001x", InstEmit.Txq, typeof(OpCodeTex)); + Set("1101111101010x", InstEmit.TxqB, typeof(OpCodeTex)); + Set("01011111xxxxxx", InstEmit.Vmad, typeof(OpCodeVideo)); + Set("0101000011011x", InstEmit.Vote, typeof(OpCodeVote)); + Set("0100111xxxxxxx", InstEmit.Xmad, typeof(OpCodeAluCbuf)); + Set("0011011x00xxxx", InstEmit.Xmad, typeof(OpCodeAluImm)); + Set("010100010xxxxx", InstEmit.Xmad, typeof(OpCodeAluRegCbuf)); + Set("0101101100xxxx", InstEmit.Xmad, typeof(OpCodeAluReg)); +#endregion + } + + private static void Set(string encoding, InstEmitter emitter, Type opCodeType) + { + if (encoding.Length != EncodingBits) + { + throw new ArgumentException(nameof(encoding)); + } + + int bit = encoding.Length - 1; + int value = 0; + int xMask = 0; + int xBits = 0; + + int[] xPos = new int[encoding.Length]; + + for (int index = 0; index < encoding.Length; index++, bit--) + { + char chr = encoding[index]; + + if (chr == '1') + { + value |= 1 << bit; + } + else if (chr == 'x') + { + xMask |= 1 << bit; + + xPos[xBits++] = bit; + } + } + + xMask = ~xMask; + + TableEntry entry = new TableEntry(emitter, opCodeType, xBits); + + for (int index = 0; index < (1 << xBits); index++) + { + value &= xMask; + + for (int x = 0; x < xBits; x++) + { + value |= ((index >> x) & 1) << xPos[x]; + } + + if (_opCodes[value] == null || _opCodes[value].XBits > xBits) + { + _opCodes[value] = entry; + } + } + } + + public static (InstEmitter emitter, Type opCodeType) GetEmitter(long opCode) + { + TableEntry entry = _opCodes[(ulong)opCode >> (64 - EncodingBits)]; + + if (entry != null) + { + return (entry.Emitter, entry.OpCodeType); + } + + return (null, null); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeTex.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeTex.cs new file mode 100644 index 0000000000..da8756b91c --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeTex.cs @@ -0,0 +1,14 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeTex : OpCodeTexture + { + public OpCodeTex(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + HasDepthCompare = opCode.Extract(50); + + HasOffset = opCode.Extract(54); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeTexB.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeTexB.cs new file mode 100644 index 0000000000..b18bf3befa --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeTexB.cs @@ -0,0 +1,20 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeTexB : OpCodeTex + { + public OpCodeTexB(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + switch (opCode.Extract(37, 3)) + { + case 0: LodMode = TextureLodMode.None; break; + case 1: LodMode = TextureLodMode.LodZero; break; + case 2: LodMode = TextureLodMode.LodBias; break; + case 3: LodMode = TextureLodMode.LodLevel; break; + case 6: LodMode = TextureLodMode.LodBiasA; break; + case 7: LodMode = TextureLodMode.LodLevelA; break; + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeTexs.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeTexs.cs new file mode 100644 index 0000000000..fb90ccf60a --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeTexs.cs @@ -0,0 +1,11 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeTexs : OpCodeTextureScalar + { + public TextureTarget Target => (TextureTarget)RawType; + + public OpCodeTexs(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) { } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeTexture.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeTexture.cs new file mode 100644 index 0000000000..76e95118f7 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeTexture.cs @@ -0,0 +1,42 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeTexture : OpCode, IOpCodeTexture + { + public Register Rd { get; } + public Register Ra { get; } + public Register Rb { get; } + + public bool IsArray { get; } + + public TextureDimensions Dimensions { get; } + + public int ComponentMask { get; } + + public int Immediate { get; } + + public TextureLodMode LodMode { get; protected set; } + + public bool HasOffset { get; protected set; } + public bool HasDepthCompare { get; protected set; } + public bool IsMultisample { get; protected set; } + + public OpCodeTexture(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Rd = new Register(opCode.Extract(0, 8), RegisterType.Gpr); + Ra = new Register(opCode.Extract(8, 8), RegisterType.Gpr); + Rb = new Register(opCode.Extract(20, 8), RegisterType.Gpr); + + IsArray = opCode.Extract(28); + + Dimensions = (TextureDimensions)opCode.Extract(29, 2); + + ComponentMask = opCode.Extract(31, 4); + + Immediate = opCode.Extract(36, 13); + + LodMode = (TextureLodMode)opCode.Extract(55, 3); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeTextureScalar.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeTextureScalar.cs new file mode 100644 index 0000000000..543f8d1367 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeTextureScalar.cs @@ -0,0 +1,62 @@ +// ReSharper disable InconsistentNaming +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeTextureScalar : OpCode + { +#region "Component mask LUT" + private const int ____ = 0x0; + private const int R___ = 0x1; + private const int _G__ = 0x2; + private const int RG__ = 0x3; + private const int __B_ = 0x4; + private const int RGB_ = 0x7; + private const int ___A = 0x8; + private const int R__A = 0x9; + private const int _G_A = 0xa; + private const int RG_A = 0xb; + private const int __BA = 0xc; + private const int R_BA = 0xd; + private const int _GBA = 0xe; + private const int RGBA = 0xf; + + private static int[,] _maskLut = new int[,] + { + { R___, _G__, __B_, ___A, RG__, R__A, _G_A, __BA }, + { RGB_, RG_A, R_BA, _GBA, RGBA, ____, ____, ____ } + }; +#endregion + + public Register Rd0 { get; } + public Register Ra { get; } + public Register Rb { get; } + public Register Rd1 { get; } + + public int Immediate { get; } + + public int ComponentMask { get; protected set; } + + protected int RawType; + + public bool IsFp16 { get; protected set; } + + public OpCodeTextureScalar(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Rd0 = new Register(opCode.Extract(0, 8), RegisterType.Gpr); + Ra = new Register(opCode.Extract(8, 8), RegisterType.Gpr); + Rb = new Register(opCode.Extract(20, 8), RegisterType.Gpr); + Rd1 = new Register(opCode.Extract(28, 8), RegisterType.Gpr); + + Immediate = opCode.Extract(36, 13); + + int compSel = opCode.Extract(50, 3); + + RawType = opCode.Extract(53, 4); + + IsFp16 = !opCode.Extract(59); + + ComponentMask = _maskLut[Rd1.IsRZ ? 0 : 1, compSel]; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeTld.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeTld.cs new file mode 100644 index 0000000000..61bd900b2d --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeTld.cs @@ -0,0 +1,20 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeTld : OpCodeTexture + { + public OpCodeTld(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + HasOffset = opCode.Extract(35); + + IsMultisample = opCode.Extract(50); + + bool isLL = opCode.Extract(55); + + LodMode = isLL + ? TextureLodMode.LodLevel + : TextureLodMode.LodZero; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeTld4.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeTld4.cs new file mode 100644 index 0000000000..0ffafbe1a4 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeTld4.cs @@ -0,0 +1,22 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeTld4 : OpCodeTexture, IOpCodeTld4 + { + public TextureGatherOffset Offset { get; } + + public int GatherCompIndex { get; } + + public bool Bindless => false; + + public OpCodeTld4(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + HasDepthCompare = opCode.Extract(50); + + Offset = (TextureGatherOffset)opCode.Extract(54, 2); + + GatherCompIndex = opCode.Extract(56, 2); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeTld4B.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeTld4B.cs new file mode 100644 index 0000000000..dc274d1415 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeTld4B.cs @@ -0,0 +1,22 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeTld4B : OpCodeTexture, IOpCodeTld4 + { + public TextureGatherOffset Offset { get; } + + public int GatherCompIndex { get; } + + public bool Bindless => true; + + public OpCodeTld4B(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + HasDepthCompare = opCode.Extract(50); + + Offset = (TextureGatherOffset)opCode.Extract(36, 2); + + GatherCompIndex = opCode.Extract(38, 2); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeTld4s.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeTld4s.cs new file mode 100644 index 0000000000..fd3240a0ee --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeTld4s.cs @@ -0,0 +1,24 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeTld4s : OpCodeTextureScalar + { + public bool HasDepthCompare { get; } + public bool HasOffset { get; } + + public int GatherCompIndex { get; } + + public OpCodeTld4s(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + HasDepthCompare = opCode.Extract(50); + HasOffset = opCode.Extract(51); + + GatherCompIndex = opCode.Extract(52, 2); + + IsFp16 = opCode.Extract(55); + + ComponentMask = Rd1.IsRZ ? 3 : 0xf; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeTlds.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeTlds.cs new file mode 100644 index 0000000000..1e4e943ffa --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeTlds.cs @@ -0,0 +1,11 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeTlds : OpCodeTextureScalar + { + public TexelLoadTarget Target => (TexelLoadTarget)RawType; + + public OpCodeTlds(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) { } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeTxd.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeTxd.cs new file mode 100644 index 0000000000..25df1f81f9 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeTxd.cs @@ -0,0 +1,18 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeTxd : OpCodeTexture + { + public bool IsBindless { get; } + + public OpCodeTxd(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + HasOffset = opCode.Extract(35); + + IsBindless = opCode.Extract(54); + + LodMode = TextureLodMode.None; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeVideo.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeVideo.cs new file mode 100644 index 0000000000..15dcfa981f --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeVideo.cs @@ -0,0 +1,24 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeVideo : OpCode, IOpCodeRd, IOpCodeRa, IOpCodeRc + { + public Register Rd { get; } + public Register Ra { get; } + public Register Rc { get; } + + public bool SetCondCode { get; protected set; } + public bool Saturate { get; protected set; } + + public OpCodeVideo(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Rd = new Register(opCode.Extract(0, 8), RegisterType.Gpr); + Ra = new Register(opCode.Extract(8, 8), RegisterType.Gpr); + Rc = new Register(opCode.Extract(39, 8), RegisterType.Gpr); + + SetCondCode = opCode.Extract(47); + Saturate = opCode.Extract(55); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeVote.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeVote.cs new file mode 100644 index 0000000000..374767bd2d --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeVote.cs @@ -0,0 +1,26 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class OpCodeVote : OpCode, IOpCodeRd, IOpCodePredicate39 + { + public Register Rd { get; } + public Register Predicate39 { get; } + public Register Predicate45 { get; } + + public VoteOp VoteOp { get; } + + public bool InvertP { get; } + + public OpCodeVote(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode) + { + Rd = new Register(opCode.Extract(0, 8), RegisterType.Gpr); + Predicate39 = new Register(opCode.Extract(39, 3), RegisterType.Predicate); + Predicate45 = new Register(opCode.Extract(45, 3), RegisterType.Predicate); + + InvertP = opCode.Extract(42); + + VoteOp = (VoteOp)opCode.Extract(48, 2); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/ReductionType.cs b/Ryujinx.Graphics.Shader/Decoders/ReductionType.cs new file mode 100644 index 0000000000..aaa2186e73 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/ReductionType.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum ReductionType + { + U32 = 0, + S32 = 1, + U64 = 2, + FP32FtzRn = 3, + U128 = 4, + S64 = 5 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/Register.cs b/Ryujinx.Graphics.Shader/Decoders/Register.cs new file mode 100644 index 0000000000..30840d8c03 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/Register.cs @@ -0,0 +1,36 @@ +using System; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + struct Register : IEquatable + { + public int Index { get; } + + public RegisterType Type { get; } + + public bool IsRZ => Type == RegisterType.Gpr && Index == RegisterConsts.RegisterZeroIndex; + public bool IsPT => Type == RegisterType.Predicate && Index == RegisterConsts.PredicateTrueIndex; + + public Register(int index, RegisterType type) + { + Index = index; + Type = type; + } + + public override int GetHashCode() + { + return (ushort)Index | ((ushort)Type << 16); + } + + 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/Ryujinx.Graphics.Shader/Decoders/RegisterConsts.cs b/Ryujinx.Graphics.Shader/Decoders/RegisterConsts.cs new file mode 100644 index 0000000000..d381f9543e --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/RegisterConsts.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + static class RegisterConsts + { + public const int GprsCount = 255; + public const int PredsCount = 7; + public const int FlagsCount = 4; + public const int TotalCount = GprsCount + PredsCount + FlagsCount; + + public const int RegisterZeroIndex = GprsCount; + public const int PredicateTrueIndex = PredsCount; + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/RegisterType.cs b/Ryujinx.Graphics.Shader/Decoders/RegisterType.cs new file mode 100644 index 0000000000..648f816a24 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/RegisterType.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum RegisterType + { + Flag, + Gpr, + Predicate, + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/RoundingMode.cs b/Ryujinx.Graphics.Shader/Decoders/RoundingMode.cs new file mode 100644 index 0000000000..13bb08dc82 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/RoundingMode.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum RoundingMode + { + ToNearest = 0, + TowardsNegativeInfinity = 1, + TowardsPositiveInfinity = 2, + TowardsZero = 3 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/ShuffleType.cs b/Ryujinx.Graphics.Shader/Decoders/ShuffleType.cs new file mode 100644 index 0000000000..2892c8dd1c --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/ShuffleType.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum ShuffleType + { + Indexed = 0, + Up = 1, + Down = 2, + Butterfly = 3 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/SystemRegister.cs b/Ryujinx.Graphics.Shader/Decoders/SystemRegister.cs new file mode 100644 index 0000000000..45ef378267 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/SystemRegister.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum SystemRegister + { + LaneId = 0, + YDirection = 0x12, + ThreadId = 0x20, + ThreadIdX = 0x21, + ThreadIdY = 0x22, + ThreadIdZ = 0x23, + CtaIdX = 0x25, + CtaIdY = 0x26, + CtaIdZ = 0x27, + EqMask = 0x38, + LtMask = 0x39, + LeMask = 0x3a, + GtMask = 0x3b, + GeMask = 0x3c + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/TexelLoadTarget.cs b/Ryujinx.Graphics.Shader/Decoders/TexelLoadTarget.cs new file mode 100644 index 0000000000..e5a0c004b5 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/TexelLoadTarget.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum TexelLoadTarget + { + Texture1DLodZero = 0x0, + Texture1DLodLevel = 0x1, + Texture2DLodZero = 0x2, + Texture2DLodZeroOffset = 0x4, + Texture2DLodLevel = 0x5, + Texture2DLodZeroMultisample = 0x6, + Texture3DLodZero = 0x7, + Texture2DArrayLodZero = 0x8, + Texture2DLodLevelOffset = 0xc + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/TextureDimensions.cs b/Ryujinx.Graphics.Shader/Decoders/TextureDimensions.cs new file mode 100644 index 0000000000..dbdf1927fb --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/TextureDimensions.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum TextureDimensions + { + Texture1D = 0, + Texture2D = 1, + Texture3D = 2, + TextureCube = 3 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/TextureGatherOffset.cs b/Ryujinx.Graphics.Shader/Decoders/TextureGatherOffset.cs new file mode 100644 index 0000000000..4e9ade26a4 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/TextureGatherOffset.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum TextureGatherOffset + { + None = 0, + Offset = 1, + Offsets = 2 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/TextureLodMode.cs b/Ryujinx.Graphics.Shader/Decoders/TextureLodMode.cs new file mode 100644 index 0000000000..0cc6f71432 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/TextureLodMode.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum TextureLodMode + { + None = 0, + LodZero = 1, + LodBias = 2, + LodLevel = 3, + LodBiasA = 4, //? + LodLevelA = 5 //? + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/TextureProperty.cs b/Ryujinx.Graphics.Shader/Decoders/TextureProperty.cs new file mode 100644 index 0000000000..ea35b1d1cb --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/TextureProperty.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum TextureProperty + { + Dimensions = 0x1, + Type = 0x2, + SamplePos = 0x5, + Filter = 0xa, + Lod = 0xc, + Wrap = 0xe, + BorderColor = 0x10 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/TextureTarget.cs b/Ryujinx.Graphics.Shader/Decoders/TextureTarget.cs new file mode 100644 index 0000000000..181a0a0d62 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/TextureTarget.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum TextureTarget + { + Texture1DLodZero = 0x0, + Texture2D = 0x1, + Texture2DLodZero = 0x2, + Texture2DLodLevel = 0x3, + Texture2DDepthCompare = 0x4, + Texture2DLodLevelDepthCompare = 0x5, + Texture2DLodZeroDepthCompare = 0x6, + Texture2DArray = 0x7, + Texture2DArrayLodZero = 0x8, + Texture2DArrayLodZeroDepthCompare = 0x9, + Texture3D = 0xa, + Texture3DLodZero = 0xb, + TextureCube = 0xc, + TextureCubeLodLevel = 0xd + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/VoteOp.cs b/Ryujinx.Graphics.Shader/Decoders/VoteOp.cs new file mode 100644 index 0000000000..2fe937c8c4 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/VoteOp.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum VoteOp + { + All = 0, + Any = 1, + AllEqual = 2 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Decoders/XmadCMode.cs b/Ryujinx.Graphics.Shader/Decoders/XmadCMode.cs new file mode 100644 index 0000000000..949a2ef70a --- /dev/null +++ b/Ryujinx.Graphics.Shader/Decoders/XmadCMode.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum XmadCMode + { + Cfull = 0, + Clo = 1, + Chi = 2, + Csfu = 3, + Cbcc = 4 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/DefineNames.cs b/Ryujinx.Graphics.Shader/DefineNames.cs new file mode 100644 index 0000000000..b043049919 --- /dev/null +++ b/Ryujinx.Graphics.Shader/DefineNames.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.Graphics.Shader +{ + public static class DefineNames + { + public const string OutQualifierPrefixName = "S_OUT_QUALIFIER"; + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/InputTopology.cs b/Ryujinx.Graphics.Shader/InputTopology.cs new file mode 100644 index 0000000000..3b0dda45f1 --- /dev/null +++ b/Ryujinx.Graphics.Shader/InputTopology.cs @@ -0,0 +1,28 @@ +namespace Ryujinx.Graphics.Shader +{ + public enum InputTopology + { + Points, + Lines, + LinesAdjacency, + Triangles, + TrianglesAdjacency + } + + static class InputTopologyExtensions + { + public static string ToGlslString(this InputTopology topology) + { + switch (topology) + { + case InputTopology.Points: return "points"; + case InputTopology.Lines: return "lines"; + case InputTopology.LinesAdjacency: return "lines_adjacency"; + case InputTopology.Triangles: return "triangles"; + case InputTopology.TrianglesAdjacency: return "triangles_adjacency"; + } + + return "points"; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Instructions/InstEmitAlu.cs b/Ryujinx.Graphics.Shader/Instructions/InstEmitAlu.cs new file mode 100644 index 0000000000..92354cece3 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Instructions/InstEmitAlu.cs @@ -0,0 +1,835 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; +using System; + +using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper; +using static Ryujinx.Graphics.Shader.Instructions.InstEmitAluHelper; +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static partial class InstEmit + { + public static void Bfe(EmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + bool isReverse = op.RawOpCode.Extract(40); + bool isSigned = op.RawOpCode.Extract(48); + + Operand srcA = GetSrcA(context); + Operand srcB = GetSrcB(context); + + if (isReverse) + { + srcA = context.BitfieldReverse(srcA); + } + + Operand position = context.BitwiseAnd(srcB, Const(0xff)); + + Operand size = context.BitfieldExtractU32(srcB, Const(8), Const(8)); + + Operand res = isSigned + ? context.BitfieldExtractS32(srcA, position, size) + : context.BitfieldExtractU32(srcA, position, size); + + context.Copy(GetDest(context), res); + + // TODO: CC, X, corner cases + } + + public static void Bfi(EmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + Operand srcA = GetSrcA(context); + Operand srcB = GetSrcB(context); + Operand srcC = GetSrcC(context); + + Operand position = context.BitwiseAnd(srcB, Const(0xff)); + + Operand size = context.BitfieldExtractU32(srcB, Const(8), Const(8)); + + Operand res = context.BitfieldInsert(srcC, srcA, position, size); + + context.Copy(GetDest(context), res); + } + + public static void Csetp(EmitterContext context) + { + OpCodePset op = (OpCodePset)context.CurrOp; + + // TODO: Implement that properly + + Operand p0Res = Const(IrConsts.True); + + Operand p1Res = context.BitwiseNot(p0Res); + + Operand pred = GetPredicate39(context); + + p0Res = GetPredLogicalOp(context, op.LogicalOp, p0Res, pred); + p1Res = GetPredLogicalOp(context, op.LogicalOp, p1Res, pred); + + context.Copy(Register(op.Predicate3), p0Res); + context.Copy(Register(op.Predicate0), p1Res); + } + + public static void Flo(EmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + bool invert = op.RawOpCode.Extract(40); + bool countZeros = op.RawOpCode.Extract(41); + bool isSigned = op.RawOpCode.Extract(48); + + Operand srcB = context.BitwiseNot(GetSrcB(context), invert); + + Operand res = isSigned + ? context.FindFirstSetS32(srcB) + : context.FindFirstSetU32(srcB); + + if (countZeros) + { + res = context.BitwiseExclusiveOr(res, Const(31)); + } + + context.Copy(GetDest(context), res); + } + + public static void Iadd(EmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + bool negateA = false, negateB = false; + + if (!(op is OpCodeAluImm32)) + { + negateB = op.RawOpCode.Extract(48); + negateA = op.RawOpCode.Extract(49); + } + else + { + // TODO: Other IADD32I variant without the negate. + negateA = op.RawOpCode.Extract(56); + } + + Operand srcA = context.INegate(GetSrcA(context), negateA); + Operand srcB = context.INegate(GetSrcB(context), negateB); + + Operand res = context.IAdd(srcA, srcB); + + bool isSubtraction = negateA || negateB; + + if (op.Extended) + { + // Add carry, or subtract borrow. + res = context.IAdd(res, isSubtraction + ? context.BitwiseNot(GetCF()) + : context.BitwiseAnd(GetCF(), Const(1))); + } + + SetIaddFlags(context, res, srcA, srcB, op.SetCondCode, op.Extended, isSubtraction); + + context.Copy(GetDest(context), res); + } + + public static void Iadd3(EmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + IntegerHalfPart partC = (IntegerHalfPart)op.RawOpCode.Extract(31, 2); + IntegerHalfPart partB = (IntegerHalfPart)op.RawOpCode.Extract(33, 2); + IntegerHalfPart partA = (IntegerHalfPart)op.RawOpCode.Extract(35, 2); + + IntegerShift mode = (IntegerShift)op.RawOpCode.Extract(37, 2); + + bool negateC = op.RawOpCode.Extract(49); + bool negateB = op.RawOpCode.Extract(50); + bool negateA = op.RawOpCode.Extract(51); + + Operand Extend(Operand src, IntegerHalfPart part) + { + if (!(op is OpCodeAluReg) || part == IntegerHalfPart.B32) + { + return src; + } + + if (part == IntegerHalfPart.H0) + { + return context.BitwiseAnd(src, Const(0xffff)); + } + else if (part == IntegerHalfPart.H1) + { + return context.ShiftRightU32(src, Const(16)); + } + else + { + // TODO: Warning. + } + + return src; + } + + Operand srcA = context.INegate(Extend(GetSrcA(context), partA), negateA); + Operand srcB = context.INegate(Extend(GetSrcB(context), partB), negateB); + Operand srcC = context.INegate(Extend(GetSrcC(context), partC), negateC); + + Operand res = context.IAdd(srcA, srcB); + + if (op is OpCodeAluReg && mode != IntegerShift.NoShift) + { + if (mode == IntegerShift.ShiftLeft) + { + res = context.ShiftLeft(res, Const(16)); + } + else if (mode == IntegerShift.ShiftRight) + { + res = context.ShiftRightU32(res, Const(16)); + } + else + { + // TODO: Warning. + } + } + + res = context.IAdd(res, srcC); + + context.Copy(GetDest(context), res); + + // TODO: CC, X, corner cases + } + + public static void Imad(EmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + bool signedA = context.CurrOp.RawOpCode.Extract(48); + bool signedB = context.CurrOp.RawOpCode.Extract(53); + bool high = context.CurrOp.RawOpCode.Extract(54); + + Operand srcA = GetSrcA(context); + Operand srcB = GetSrcB(context); + Operand srcC = GetSrcC(context); + + Operand res; + + if (high) + { + if (signedA && signedB) + { + res = context.MultiplyHighS32(srcA, srcB); + } + else + { + res = context.MultiplyHighU32(srcA, srcB); + + if (signedA) + { + res = context.IAdd(res, context.IMultiply(srcB, context.ShiftRightS32(srcA, Const(31)))); + } + else if (signedB) + { + res = context.IAdd(res, context.IMultiply(srcA, context.ShiftRightS32(srcB, Const(31)))); + } + } + } + else + { + res = context.IMultiply(srcA, srcB); + } + + res = context.IAdd(res, srcC); + + // TODO: CC, X, SAT, and more? + + context.Copy(GetDest(context), res); + } + + public static void Imnmx(EmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + bool isSignedInt = op.RawOpCode.Extract(48); + + Operand srcA = GetSrcA(context); + Operand srcB = GetSrcB(context); + + Operand resMin = isSignedInt + ? context.IMinimumS32(srcA, srcB) + : context.IMinimumU32(srcA, srcB); + + Operand resMax = isSignedInt + ? context.IMaximumS32(srcA, srcB) + : context.IMaximumU32(srcA, srcB); + + Operand pred = GetPredicate39(context); + + Operand dest = GetDest(context); + + context.Copy(dest, context.ConditionalSelect(pred, resMin, resMax)); + + SetZnFlags(context, dest, op.SetCondCode); + + // TODO: X flags. + } + + public static void Iscadd(EmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + bool negateA = false, negateB = false; + + if (!(op is OpCodeAluImm32)) + { + negateB = op.RawOpCode.Extract(48); + negateA = op.RawOpCode.Extract(49); + } + + int shift = op is OpCodeAluImm32 + ? op.RawOpCode.Extract(53, 5) + : op.RawOpCode.Extract(39, 5); + + Operand srcA = GetSrcA(context); + Operand srcB = GetSrcB(context); + + srcA = context.ShiftLeft(srcA, Const(shift)); + + srcA = context.INegate(srcA, negateA); + srcB = context.INegate(srcB, negateB); + + Operand res = context.IAdd(srcA, srcB); + + context.Copy(GetDest(context), res); + + // TODO: CC, X + } + + public static void Iset(EmitterContext context) + { + OpCodeSet op = (OpCodeSet)context.CurrOp; + + bool boolFloat = op.RawOpCode.Extract(44); + bool isSigned = op.RawOpCode.Extract(48); + + IntegerCondition cmpOp = (IntegerCondition)op.RawOpCode.Extract(49, 3); + + Operand srcA = GetSrcA(context); + Operand srcB = GetSrcB(context); + + Operand res = GetIntComparison(context, cmpOp, srcA, srcB, isSigned); + + Operand pred = GetPredicate39(context); + + res = GetPredLogicalOp(context, op.LogicalOp, res, pred); + + Operand dest = GetDest(context); + + if (boolFloat) + { + res = context.ConditionalSelect(res, ConstF(1), Const(0)); + + context.Copy(dest, res); + + SetFPZnFlags(context, res, op.SetCondCode); + } + else + { + context.Copy(dest, res); + + SetZnFlags(context, res, op.SetCondCode, op.Extended); + } + + // TODO: X + } + + public static void Isetp(EmitterContext context) + { + OpCodeSet op = (OpCodeSet)context.CurrOp; + + bool isSigned = op.RawOpCode.Extract(48); + + IntegerCondition cmpOp = (IntegerCondition)op.RawOpCode.Extract(49, 3); + + Operand srcA = GetSrcA(context); + Operand srcB = GetSrcB(context); + + Operand p0Res = GetIntComparison(context, cmpOp, srcA, srcB, isSigned); + + Operand p1Res = context.BitwiseNot(p0Res); + + Operand pred = GetPredicate39(context); + + p0Res = GetPredLogicalOp(context, op.LogicalOp, p0Res, pred); + p1Res = GetPredLogicalOp(context, op.LogicalOp, p1Res, pred); + + context.Copy(Register(op.Predicate3), p0Res); + context.Copy(Register(op.Predicate0), p1Res); + } + + public static void Lop(EmitterContext context) + { + IOpCodeLop op = (IOpCodeLop)context.CurrOp; + + Operand srcA = context.BitwiseNot(GetSrcA(context), op.InvertA); + Operand srcB = context.BitwiseNot(GetSrcB(context), op.InvertB); + + Operand res = srcB; + + switch (op.LogicalOp) + { + case LogicalOperation.And: res = context.BitwiseAnd (srcA, srcB); break; + case LogicalOperation.Or: res = context.BitwiseOr (srcA, srcB); break; + case LogicalOperation.ExclusiveOr: res = context.BitwiseExclusiveOr(srcA, srcB); break; + } + + EmitLopPredWrite(context, op, res); + + Operand dest = GetDest(context); + + context.Copy(dest, res); + + SetZnFlags(context, dest, op.SetCondCode, op.Extended); + } + + public static void Lop3(EmitterContext context) + { + IOpCodeLop op = (IOpCodeLop)context.CurrOp; + + Operand srcA = GetSrcA(context); + Operand srcB = GetSrcB(context); + Operand srcC = GetSrcC(context); + + bool regVariant = op is OpCodeLopReg; + + int truthTable = regVariant + ? op.RawOpCode.Extract(28, 8) + : op.RawOpCode.Extract(48, 8); + + Operand res = Lop3Expression.GetFromTruthTable(context, srcA, srcB, srcC, truthTable); + + if (regVariant) + { + EmitLopPredWrite(context, op, res); + } + + Operand dest = GetDest(context); + + context.Copy(dest, res); + + SetZnFlags(context, dest, op.SetCondCode, op.Extended); + } + + public static void Popc(EmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + bool invert = op.RawOpCode.Extract(40); + + Operand srcB = context.BitwiseNot(GetSrcB(context), invert); + + Operand res = context.BitCount(srcB); + + context.Copy(GetDest(context), res); + } + + public static void Pset(EmitterContext context) + { + OpCodePset op = (OpCodePset)context.CurrOp; + + bool boolFloat = op.RawOpCode.Extract(44); + + Operand srcA = context.BitwiseNot(Register(op.Predicate12), op.InvertA); + Operand srcB = context.BitwiseNot(Register(op.Predicate29), op.InvertB); + Operand srcC = context.BitwiseNot(Register(op.Predicate39), op.InvertP); + + Operand res = GetPredLogicalOp(context, op.LogicalOpAB, srcA, srcB); + + res = GetPredLogicalOp(context, op.LogicalOp, res, srcC); + + Operand dest = GetDest(context); + + if (boolFloat) + { + context.Copy(dest, context.ConditionalSelect(res, ConstF(1), Const(0))); + } + else + { + context.Copy(dest, res); + } + } + + public static void Psetp(EmitterContext context) + { + OpCodePset op = (OpCodePset)context.CurrOp; + + Operand srcA = context.BitwiseNot(Register(op.Predicate12), op.InvertA); + Operand srcB = context.BitwiseNot(Register(op.Predicate29), op.InvertB); + + Operand p0Res = GetPredLogicalOp(context, op.LogicalOpAB, srcA, srcB); + + Operand p1Res = context.BitwiseNot(p0Res); + + Operand pred = GetPredicate39(context); + + p0Res = GetPredLogicalOp(context, op.LogicalOp, p0Res, pred); + p1Res = GetPredLogicalOp(context, op.LogicalOp, p1Res, pred); + + context.Copy(Register(op.Predicate3), p0Res); + context.Copy(Register(op.Predicate0), p1Res); + } + + public static void Rro(EmitterContext context) + { + // This is the range reduction operator, + // we translate it as a simple move, as it + // should be always followed by a matching + // MUFU instruction. + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + bool negateB = op.RawOpCode.Extract(45); + bool absoluteB = op.RawOpCode.Extract(49); + + Operand srcB = GetSrcB(context); + + srcB = context.FPAbsNeg(srcB, absoluteB, negateB); + + context.Copy(GetDest(context), srcB); + } + + public static void Shl(EmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + bool isMasked = op.RawOpCode.Extract(39); + + Operand srcB = GetSrcB(context); + + if (isMasked) + { + srcB = context.BitwiseAnd(srcB, Const(0x1f)); + } + + Operand res = context.ShiftLeft(GetSrcA(context), srcB); + + if (!isMasked) + { + // Clamped shift value. + Operand isLessThan32 = context.ICompareLessUnsigned(srcB, Const(32)); + + res = context.ConditionalSelect(isLessThan32, res, Const(0)); + } + + // TODO: X, CC + + context.Copy(GetDest(context), res); + } + + public static void Shr(EmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + bool isMasked = op.RawOpCode.Extract(39); + bool isReverse = op.RawOpCode.Extract(40); + bool isSigned = op.RawOpCode.Extract(48); + + Operand srcA = GetSrcA(context); + Operand srcB = GetSrcB(context); + + if (isReverse) + { + srcA = context.BitfieldReverse(srcA); + } + + if (isMasked) + { + srcB = context.BitwiseAnd(srcB, Const(0x1f)); + } + + Operand res = isSigned + ? context.ShiftRightS32(srcA, srcB) + : context.ShiftRightU32(srcA, srcB); + + if (!isMasked) + { + // Clamped shift value. + Operand resShiftBy32; + + if (isSigned) + { + resShiftBy32 = context.ShiftRightS32(srcA, Const(31)); + } + else + { + resShiftBy32 = Const(0); + } + + Operand isLessThan32 = context.ICompareLessUnsigned(srcB, Const(32)); + + res = context.ConditionalSelect(isLessThan32, res, resShiftBy32); + } + + // TODO: X, CC + + context.Copy(GetDest(context), res); + } + + public static void Xmad(EmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + bool signedA = context.CurrOp.RawOpCode.Extract(48); + bool signedB = context.CurrOp.RawOpCode.Extract(49); + bool highA = context.CurrOp.RawOpCode.Extract(53); + bool highB = false; + + XmadCMode mode; + + if (op is OpCodeAluReg) + { + highB = context.CurrOp.RawOpCode.Extract(35); + + mode = (XmadCMode)context.CurrOp.RawOpCode.Extract(50, 3); + } + else + { + mode = (XmadCMode)context.CurrOp.RawOpCode.Extract(50, 2); + + if (!(op is OpCodeAluImm)) + { + highB = context.CurrOp.RawOpCode.Extract(52); + } + } + + Operand srcA = GetSrcA(context); + Operand srcB = GetSrcB(context); + Operand srcC = GetSrcC(context); + + // XMAD immediates are 16-bits unsigned integers. + if (srcB.Type == OperandType.Constant) + { + srcB = Const(srcB.Value & 0xffff); + } + + Operand Extend16To32(Operand src, bool high, bool signed) + { + if (signed && high) + { + return context.ShiftRightS32(src, Const(16)); + } + else if (signed) + { + return context.BitfieldExtractS32(src, Const(0), Const(16)); + } + else if (high) + { + return context.ShiftRightU32(src, Const(16)); + } + else + { + return context.BitwiseAnd(src, Const(0xffff)); + } + } + + srcA = Extend16To32(srcA, highA, signedA); + srcB = Extend16To32(srcB, highB, signedB); + + bool productShiftLeft = false; + bool merge = false; + + if (!(op is OpCodeAluRegCbuf)) + { + productShiftLeft = context.CurrOp.RawOpCode.Extract(36); + merge = context.CurrOp.RawOpCode.Extract(37); + } + + bool extended; + + if ((op is OpCodeAluReg) || (op is OpCodeAluImm)) + { + extended = context.CurrOp.RawOpCode.Extract(38); + } + else + { + extended = context.CurrOp.RawOpCode.Extract(54); + } + + Operand res = context.IMultiply(srcA, srcB); + + if (productShiftLeft) + { + res = context.ShiftLeft(res, Const(16)); + } + + switch (mode) + { + case XmadCMode.Cfull: break; + + case XmadCMode.Clo: srcC = Extend16To32(srcC, high: false, signed: false); break; + case XmadCMode.Chi: srcC = Extend16To32(srcC, high: true, signed: false); break; + + case XmadCMode.Cbcc: + { + srcC = context.IAdd(srcC, context.ShiftLeft(GetSrcB(context), Const(16))); + + break; + } + + case XmadCMode.Csfu: + { + Operand signAdjustA = context.ShiftLeft(context.ShiftRightU32(srcA, Const(31)), Const(16)); + Operand signAdjustB = context.ShiftLeft(context.ShiftRightU32(srcB, Const(31)), Const(16)); + + srcC = context.ISubtract(srcC, context.IAdd(signAdjustA, signAdjustB)); + + break; + } + + default: /* TODO: Warning */ break; + } + + Operand product = res; + + if (extended) + { + // Add with carry. + res = context.IAdd(res, context.BitwiseAnd(GetCF(), Const(1))); + } + else + { + // Add (no carry in). + res = context.IAdd(res, srcC); + } + + SetIaddFlags(context, res, product, srcC, op.SetCondCode, extended); + + if (merge) + { + res = context.BitwiseAnd(res, Const(0xffff)); + res = context.BitwiseOr(res, context.ShiftLeft(GetSrcB(context), Const(16))); + } + + context.Copy(GetDest(context), res); + } + + private static Operand GetIntComparison( + EmitterContext context, + IntegerCondition cond, + Operand srcA, + Operand srcB, + bool isSigned) + { + Operand res; + + if (cond == IntegerCondition.Always) + { + res = Const(IrConsts.True); + } + else if (cond == IntegerCondition.Never) + { + res = Const(IrConsts.False); + } + else + { + Instruction inst; + + switch (cond) + { + case IntegerCondition.Less: inst = Instruction.CompareLessU32; break; + case IntegerCondition.Equal: inst = Instruction.CompareEqual; break; + case IntegerCondition.LessOrEqual: inst = Instruction.CompareLessOrEqualU32; break; + case IntegerCondition.Greater: inst = Instruction.CompareGreaterU32; break; + case IntegerCondition.NotEqual: inst = Instruction.CompareNotEqual; break; + case IntegerCondition.GreaterOrEqual: inst = Instruction.CompareGreaterOrEqualU32; break; + + default: throw new InvalidOperationException($"Unexpected condition \"{cond}\"."); + } + + if (isSigned) + { + switch (cond) + { + case IntegerCondition.Less: inst = Instruction.CompareLess; break; + case IntegerCondition.LessOrEqual: inst = Instruction.CompareLessOrEqual; break; + case IntegerCondition.Greater: inst = Instruction.CompareGreater; break; + case IntegerCondition.GreaterOrEqual: inst = Instruction.CompareGreaterOrEqual; break; + } + } + + res = context.Add(inst, Local(), srcA, srcB); + } + + return res; + } + + private static void EmitLopPredWrite(EmitterContext context, IOpCodeLop op, Operand result) + { + if (op is OpCodeLop opLop && !opLop.Predicate48.IsPT) + { + Operand pRes; + + if (opLop.CondOp == ConditionalOperation.False) + { + pRes = Const(IrConsts.False); + } + else if (opLop.CondOp == ConditionalOperation.True) + { + pRes = Const(IrConsts.True); + } + else if (opLop.CondOp == ConditionalOperation.Zero) + { + pRes = context.ICompareEqual(result, Const(0)); + } + else /* if (opLop.CondOp == ConditionalOperation.NotZero) */ + { + pRes = context.ICompareNotEqual(result, Const(0)); + } + + context.Copy(Register(opLop.Predicate48), pRes); + } + } + + private static void SetIaddFlags( + EmitterContext context, + Operand res, + Operand srcA, + Operand srcB, + bool setCC, + bool extended, + bool isSubtraction = false) + { + if (!setCC) + { + return; + } + + if (!extended || isSubtraction) + { + // C = d < a + context.Copy(GetCF(), context.ICompareLessUnsigned(res, srcA)); + } + else + { + // C = (d == a && CIn) || d < a + Operand tempC0 = context.ICompareEqual (res, srcA); + Operand tempC1 = context.ICompareLessUnsigned(res, srcA); + + tempC0 = context.BitwiseAnd(tempC0, GetCF()); + + context.Copy(GetCF(), context.BitwiseOr(tempC0, tempC1)); + } + + // V = (d ^ a) & ~(a ^ b) < 0 + Operand tempV0 = context.BitwiseExclusiveOr(res, srcA); + Operand tempV1 = context.BitwiseExclusiveOr(srcA, srcB); + + tempV1 = context.BitwiseNot(tempV1); + + Operand tempV = context.BitwiseAnd(tempV0, tempV1); + + context.Copy(GetVF(), context.ICompareLess(tempV, Const(0))); + + SetZnFlags(context, res, setCC: true, extended: extended); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Instructions/InstEmitAluHelper.cs b/Ryujinx.Graphics.Shader/Instructions/InstEmitAluHelper.cs new file mode 100644 index 0000000000..572068dad3 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Instructions/InstEmitAluHelper.cs @@ -0,0 +1,97 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; +using System; + +using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper; +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static class InstEmitAluHelper + { + public static long GetIntMin(IntegerType type) + { + switch (type) + { + case IntegerType.U8: return byte.MinValue; + case IntegerType.S8: return sbyte.MinValue; + case IntegerType.U16: return ushort.MinValue; + case IntegerType.S16: return short.MinValue; + case IntegerType.U32: return uint.MinValue; + case IntegerType.S32: return int.MinValue; + } + + throw new ArgumentException($"The type \"{type}\" is not a supported int type."); + } + + public static long GetIntMax(IntegerType type) + { + switch (type) + { + case IntegerType.U8: return byte.MaxValue; + case IntegerType.S8: return sbyte.MaxValue; + case IntegerType.U16: return ushort.MaxValue; + case IntegerType.S16: return short.MaxValue; + case IntegerType.U32: return uint.MaxValue; + case IntegerType.S32: return int.MaxValue; + } + + throw new ArgumentException($"The type \"{type}\" is not a supported int type."); + } + + public static Operand GetPredLogicalOp( + EmitterContext context, + LogicalOperation logicalOp, + Operand input, + Operand pred) + { + switch (logicalOp) + { + case LogicalOperation.And: return context.BitwiseAnd (input, pred); + case LogicalOperation.Or: return context.BitwiseOr (input, pred); + case LogicalOperation.ExclusiveOr: return context.BitwiseExclusiveOr(input, pred); + } + + return input; + } + + public static void SetZnFlags(EmitterContext context, Operand dest, bool setCC, bool extended = false) + { + if (!setCC) + { + return; + } + + if (extended) + { + // When the operation is extended, it means we are doing + // the operation on a long word with any number of bits, + // so we need to AND the zero flag from result with the + // previous result when extended is specified, to ensure + // we have ZF set only if all words are zero, and not just + // the last one. + Operand oldZF = GetZF(); + + Operand res = context.BitwiseAnd(context.ICompareEqual(dest, Const(0)), oldZF); + + context.Copy(GetZF(), res); + } + else + { + context.Copy(GetZF(), context.ICompareEqual(dest, Const(0))); + } + + context.Copy(GetNF(), context.ICompareLess(dest, Const(0))); + } + + public static void SetFPZnFlags(EmitterContext context, Operand dest, bool setCC) + { + if (setCC) + { + context.Copy(GetZF(), context.FPCompareEqual(dest, ConstF(0))); + context.Copy(GetNF(), context.FPCompareLess (dest, ConstF(0))); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Instructions/InstEmitConversion.cs b/Ryujinx.Graphics.Shader/Instructions/InstEmitConversion.cs new file mode 100644 index 0000000000..afec77616b --- /dev/null +++ b/Ryujinx.Graphics.Shader/Instructions/InstEmitConversion.cs @@ -0,0 +1,238 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; + +using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper; +using static Ryujinx.Graphics.Shader.Instructions.InstEmitAluHelper; +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static partial class InstEmit + { + public static void F2F(EmitterContext context) + { + OpCodeFArith op = (OpCodeFArith)context.CurrOp; + + FPType dstType = (FPType)op.RawOpCode.Extract(8, 2); + FPType srcType = (FPType)op.RawOpCode.Extract(10, 2); + + bool round = op.RawOpCode.Extract(42); + bool negateB = op.RawOpCode.Extract(45); + bool absoluteB = op.RawOpCode.Extract(49); + + Operand srcB = context.FPAbsNeg(GetSrcB(context, srcType), absoluteB, negateB); + + if (round) + { + switch (op.RoundingMode) + { + case RoundingMode.ToNearest: + srcB = context.FPRound(srcB); + break; + + case RoundingMode.TowardsNegativeInfinity: + srcB = context.FPFloor(srcB); + break; + + case RoundingMode.TowardsPositiveInfinity: + srcB = context.FPCeiling(srcB); + break; + + case RoundingMode.TowardsZero: + srcB = context.FPTruncate(srcB); + break; + } + } + + srcB = context.FPSaturate(srcB, op.Saturate); + + WriteFP(context, dstType, srcB); + + // TODO: CC. + } + + public static void F2I(EmitterContext context) + { + OpCodeFArith op = (OpCodeFArith)context.CurrOp; + + IntegerType intType = (IntegerType)op.RawOpCode.Extract(8, 2); + + if (intType == IntegerType.U64) + { + context.Config.PrintLog("Unimplemented 64-bits F2I."); + + return; + } + + bool isSmallInt = intType <= IntegerType.U16; + + FPType floatType = (FPType)op.RawOpCode.Extract(10, 2); + + bool isSignedInt = op.RawOpCode.Extract(12); + bool negateB = op.RawOpCode.Extract(45); + bool absoluteB = op.RawOpCode.Extract(49); + + if (isSignedInt) + { + intType |= IntegerType.S8; + } + + Operand srcB = context.FPAbsNeg(GetSrcB(context, floatType), absoluteB, negateB); + + switch (op.RoundingMode) + { + case RoundingMode.ToNearest: + srcB = context.FPRound(srcB); + break; + + case RoundingMode.TowardsNegativeInfinity: + srcB = context.FPFloor(srcB); + break; + + case RoundingMode.TowardsPositiveInfinity: + srcB = context.FPCeiling(srcB); + break; + + case RoundingMode.TowardsZero: + srcB = context.FPTruncate(srcB); + break; + } + + if (!isSignedInt) + { + // Negative float to uint cast is undefined, so we clamp + // the value before conversion. + srcB = context.FPMaximum(srcB, ConstF(0)); + } + + srcB = isSignedInt + ? context.FPConvertToS32(srcB) + : context.FPConvertToU32(srcB); + + if (isSmallInt) + { + int min = (int)GetIntMin(intType); + int max = (int)GetIntMax(intType); + + srcB = isSignedInt + ? context.IClampS32(srcB, Const(min), Const(max)) + : context.IClampU32(srcB, Const(min), Const(max)); + } + + Operand dest = GetDest(context); + + context.Copy(dest, srcB); + + // TODO: CC. + } + + public static void I2F(EmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + FPType dstType = (FPType)op.RawOpCode.Extract(8, 2); + + IntegerType srcType = (IntegerType)op.RawOpCode.Extract(10, 2); + + // TODO: Handle S/U64. + + bool isSmallInt = srcType <= IntegerType.U16; + + bool isSignedInt = op.RawOpCode.Extract(13); + bool negateB = op.RawOpCode.Extract(45); + bool absoluteB = op.RawOpCode.Extract(49); + + Operand srcB = context.IAbsNeg(GetSrcB(context), absoluteB, negateB); + + if (isSmallInt) + { + int size = srcType == IntegerType.U16 ? 16 : 8; + + srcB = isSignedInt + ? context.BitfieldExtractS32(srcB, Const(op.ByteSelection * 8), Const(size)) + : context.BitfieldExtractU32(srcB, Const(op.ByteSelection * 8), Const(size)); + } + + srcB = isSignedInt + ? context.IConvertS32ToFP(srcB) + : context.IConvertU32ToFP(srcB); + + WriteFP(context, dstType, srcB); + + // TODO: CC. + } + + public static void I2I(EmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + IntegerType dstType = (IntegerType)op.RawOpCode.Extract(8, 2); + IntegerType srcType = (IntegerType)op.RawOpCode.Extract(10, 2); + + if (srcType == IntegerType.U64 || dstType == IntegerType.U64) + { + context.Config.PrintLog("Invalid I2I encoding."); + + return; + } + + bool srcIsSmallInt = srcType <= IntegerType.U16; + + bool dstIsSignedInt = op.RawOpCode.Extract(12); + bool srcIsSignedInt = op.RawOpCode.Extract(13); + bool negateB = op.RawOpCode.Extract(45); + bool absoluteB = op.RawOpCode.Extract(49); + + Operand srcB = GetSrcB(context); + + if (srcIsSmallInt) + { + int size = srcType == IntegerType.U16 ? 16 : 8; + + srcB = srcIsSignedInt + ? context.BitfieldExtractS32(srcB, Const(op.ByteSelection * 8), Const(size)) + : context.BitfieldExtractU32(srcB, Const(op.ByteSelection * 8), Const(size)); + } + + srcB = context.IAbsNeg(srcB, absoluteB, negateB); + + if (op.Saturate) + { + if (dstIsSignedInt) + { + dstType |= IntegerType.S8; + } + + int min = (int)GetIntMin(dstType); + int max = (int)GetIntMax(dstType); + + srcB = dstIsSignedInt + ? context.IClampS32(srcB, Const(min), Const(max)) + : context.IClampU32(srcB, Const(min), Const(max)); + } + + context.Copy(GetDest(context), srcB); + + // TODO: CC. + } + + private static void WriteFP(EmitterContext context, FPType type, Operand srcB) + { + Operand dest = GetDest(context); + + if (type == FPType.FP32) + { + context.Copy(dest, srcB); + } + else if (type == FPType.FP16) + { + context.Copy(dest, context.PackHalf2x16(srcB, ConstF(0))); + } + else + { + // TODO. + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Instructions/InstEmitFArith.cs b/Ryujinx.Graphics.Shader/Instructions/InstEmitFArith.cs new file mode 100644 index 0000000000..23f40d4692 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Instructions/InstEmitFArith.cs @@ -0,0 +1,487 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; +using System; + +using static Ryujinx.Graphics.Shader.Instructions.InstEmitAluHelper; +using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper; +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static partial class InstEmit + { + public static void Fadd(EmitterContext context) + { + IOpCodeFArith op = (IOpCodeFArith)context.CurrOp; + + bool absoluteA = op.AbsoluteA, absoluteB, negateA, negateB; + + if (op is OpCodeFArithImm32) + { + negateB = op.RawOpCode.Extract(53); + negateA = op.RawOpCode.Extract(56); + absoluteB = op.RawOpCode.Extract(57); + } + else + { + negateB = op.RawOpCode.Extract(45); + negateA = op.RawOpCode.Extract(48); + absoluteB = op.RawOpCode.Extract(49); + } + + Operand srcA = context.FPAbsNeg(GetSrcA(context), absoluteA, negateA); + Operand srcB = context.FPAbsNeg(GetSrcB(context), absoluteB, negateB); + + Operand dest = GetDest(context); + + context.Copy(dest, context.FPSaturate(context.FPAdd(srcA, srcB), op.Saturate)); + + SetFPZnFlags(context, dest, op.SetCondCode); + } + + public static void Ffma(EmitterContext context) + { + IOpCodeFArith op = (IOpCodeFArith)context.CurrOp; + + bool negateB = op.RawOpCode.Extract(48); + bool negateC = op.RawOpCode.Extract(49); + + Operand srcA = GetSrcA(context); + + Operand srcB = context.FPNegate(GetSrcB(context), negateB); + Operand srcC = context.FPNegate(GetSrcC(context), negateC); + + Operand dest = GetDest(context); + + context.Copy(dest, context.FPSaturate(context.FPFusedMultiplyAdd(srcA, srcB, srcC), op.Saturate)); + + SetFPZnFlags(context, dest, op.SetCondCode); + } + + public static void Ffma32i(EmitterContext context) + { + IOpCodeFArith op = (IOpCodeFArith)context.CurrOp; + + bool saturate = op.RawOpCode.Extract(55); + bool negateA = op.RawOpCode.Extract(56); + bool negateC = op.RawOpCode.Extract(57); + + Operand srcA = context.FPNegate(GetSrcA(context), negateA); + Operand srcC = context.FPNegate(GetDest(context), negateC); + + Operand srcB = GetSrcB(context); + + Operand dest = GetDest(context); + + context.Copy(dest, context.FPSaturate(context.FPFusedMultiplyAdd(srcA, srcB, srcC), saturate)); + + SetFPZnFlags(context, dest, op.SetCondCode); + } + + public static void Fmnmx(EmitterContext context) + { + IOpCodeFArith op = (IOpCodeFArith)context.CurrOp; + + bool absoluteA = op.AbsoluteA; + bool negateB = op.RawOpCode.Extract(45); + bool negateA = op.RawOpCode.Extract(48); + bool absoluteB = op.RawOpCode.Extract(49); + + Operand srcA = context.FPAbsNeg(GetSrcA(context), absoluteA, negateA); + Operand srcB = context.FPAbsNeg(GetSrcB(context), absoluteB, negateB); + + Operand resMin = context.FPMinimum(srcA, srcB); + Operand resMax = context.FPMaximum(srcA, srcB); + + Operand pred = GetPredicate39(context); + + Operand dest = GetDest(context); + + context.Copy(dest, context.ConditionalSelect(pred, resMin, resMax)); + + SetFPZnFlags(context, dest, op.SetCondCode); + } + + public static void Fmul(EmitterContext context) + { + IOpCodeFArith op = (IOpCodeFArith)context.CurrOp; + + bool isImm32 = op is OpCodeFArithImm32; + + bool negateB = !isImm32 && op.RawOpCode.Extract(48); + + Operand srcA = GetSrcA(context); + + Operand srcB = context.FPNegate(GetSrcB(context), negateB); + + switch (op.Scale) + { + case FPMultiplyScale.None: break; + + case FPMultiplyScale.Divide2: srcA = context.FPDivide (srcA, ConstF(2)); break; + case FPMultiplyScale.Divide4: srcA = context.FPDivide (srcA, ConstF(4)); break; + case FPMultiplyScale.Divide8: srcA = context.FPDivide (srcA, ConstF(8)); break; + case FPMultiplyScale.Multiply2: srcA = context.FPMultiply(srcA, ConstF(2)); break; + case FPMultiplyScale.Multiply4: srcA = context.FPMultiply(srcA, ConstF(4)); break; + case FPMultiplyScale.Multiply8: srcA = context.FPMultiply(srcA, ConstF(8)); break; + + default: break; //TODO: Warning. + } + + Operand dest = GetDest(context); + + bool saturate = isImm32 ? op.RawOpCode.Extract(55) : op.Saturate; + + context.Copy(dest, context.FPSaturate(context.FPMultiply(srcA, srcB), saturate)); + + SetFPZnFlags(context, dest, op.SetCondCode); + } + + public static void Fset(EmitterContext context) + { + OpCodeSet op = (OpCodeSet)context.CurrOp; + + Condition cmpOp = (Condition)op.RawOpCode.Extract(48, 4); + + bool negateA = op.RawOpCode.Extract(43); + bool absoluteB = op.RawOpCode.Extract(44); + bool boolFloat = op.RawOpCode.Extract(52); + bool negateB = op.RawOpCode.Extract(53); + bool absoluteA = op.RawOpCode.Extract(54); + + Operand srcA = context.FPAbsNeg(GetSrcA(context), absoluteA, negateA); + Operand srcB = context.FPAbsNeg(GetSrcB(context), absoluteB, negateB); + + Operand res = GetFPComparison(context, cmpOp, srcA, srcB); + + Operand pred = GetPredicate39(context); + + res = GetPredLogicalOp(context, op.LogicalOp, res, pred); + + Operand dest = GetDest(context); + + if (boolFloat) + { + res = context.ConditionalSelect(res, ConstF(1), Const(0)); + + context.Copy(dest, res); + + SetFPZnFlags(context, res, op.SetCondCode); + } + else + { + context.Copy(dest, res); + + SetZnFlags(context, res, op.SetCondCode, op.Extended); + } + + // TODO: X + } + + public static void Fsetp(EmitterContext context) + { + OpCodeSet op = (OpCodeSet)context.CurrOp; + + Condition cmpOp = (Condition)op.RawOpCode.Extract(48, 4); + + bool negateB = op.RawOpCode.Extract(6); + bool absoluteA = op.RawOpCode.Extract(7); + bool negateA = op.RawOpCode.Extract(43); + bool absoluteB = op.RawOpCode.Extract(44); + + Operand srcA = context.FPAbsNeg(GetSrcA(context), absoluteA, negateA); + Operand srcB = context.FPAbsNeg(GetSrcB(context), absoluteB, negateB); + + Operand p0Res = GetFPComparison(context, cmpOp, srcA, srcB); + + Operand p1Res = context.BitwiseNot(p0Res); + + Operand pred = GetPredicate39(context); + + p0Res = GetPredLogicalOp(context, op.LogicalOp, p0Res, pred); + p1Res = GetPredLogicalOp(context, op.LogicalOp, p1Res, pred); + + context.Copy(Register(op.Predicate3), p0Res); + context.Copy(Register(op.Predicate0), p1Res); + } + + public static void Fswzadd(EmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + int mask = op.RawOpCode.Extract(28, 8); + + Operand srcA = GetSrcA(context); + Operand srcB = GetSrcB(context); + + Operand dest = GetDest(context); + + context.Copy(dest, context.FPSwizzleAdd(srcA, srcB, mask)); + + SetFPZnFlags(context, dest, op.SetCondCode); + } + + public static void Hadd2(EmitterContext context) + { + Hadd2Hmul2Impl(context, isAdd: true); + } + + public static void Hfma2(EmitterContext context) + { + IOpCodeHfma op = (IOpCodeHfma)context.CurrOp; + + Operand[] srcA = GetHfmaSrcA(context); + Operand[] srcB = GetHfmaSrcB(context); + Operand[] srcC = GetHfmaSrcC(context); + + Operand[] res = new Operand[2]; + + for (int index = 0; index < res.Length; index++) + { + res[index] = context.FPFusedMultiplyAdd(srcA[index], srcB[index], srcC[index]); + + res[index] = context.FPSaturate(res[index], op.Saturate); + } + + context.Copy(GetDest(context), GetHalfPacked(context, res)); + } + + public static void Hmul2(EmitterContext context) + { + Hadd2Hmul2Impl(context, isAdd: false); + } + + private static void Hadd2Hmul2Impl(EmitterContext context, bool isAdd) + { + OpCode op = context.CurrOp; + + bool saturate = op.RawOpCode.Extract(op is IOpCodeReg ? 32 : 52); + + Operand[] srcA = GetHalfSrcA(context, isAdd); + Operand[] srcB = GetHalfSrcB(context); + + Operand[] res = new Operand[2]; + + for (int index = 0; index < res.Length; index++) + { + if (isAdd) + { + res[index] = context.FPAdd(srcA[index], srcB[index]); + } + else + { + res[index] = context.FPMultiply(srcA[index], srcB[index]); + } + + res[index] = context.FPSaturate(res[index], saturate); + } + + context.Copy(GetDest(context), GetHalfPacked(context, res)); + } + + public static void Hset2(EmitterContext context) + { + OpCodeSet op = (OpCodeSet)context.CurrOp; + + bool isRegVariant = op is IOpCodeReg; + + bool boolFloat = isRegVariant + ? op.RawOpCode.Extract(49) + : op.RawOpCode.Extract(53); + + Condition cmpOp = isRegVariant + ? (Condition)op.RawOpCode.Extract(35, 4) + : (Condition)op.RawOpCode.Extract(49, 4); + + Operand[] srcA = GetHalfSrcA(context); + Operand[] srcB = GetHalfSrcB(context); + + Operand[] res = new Operand[2]; + + res[0] = GetFPComparison(context, cmpOp, srcA[0], srcB[0]); + res[1] = GetFPComparison(context, cmpOp, srcA[1], srcB[1]); + + Operand pred = GetPredicate39(context); + + res[0] = GetPredLogicalOp(context, op.LogicalOp, res[0], pred); + res[1] = GetPredLogicalOp(context, op.LogicalOp, res[1], pred); + + if (boolFloat) + { + res[0] = context.ConditionalSelect(res[0], ConstF(1), Const(0)); + res[1] = context.ConditionalSelect(res[1], ConstF(1), Const(0)); + + context.Copy(GetDest(context), context.PackHalf2x16(res[0], res[1])); + } + else + { + Operand low = context.BitwiseAnd(res[0], Const(0xffff)); + Operand high = context.ShiftLeft (res[1], Const(16)); + + Operand packed = context.BitwiseOr(low, high); + + context.Copy(GetDest(context), packed); + } + } + + public static void Hsetp2(EmitterContext context) + { + OpCodeSet op = (OpCodeSet)context.CurrOp; + + bool isRegVariant = op is IOpCodeReg; + + bool hAnd = isRegVariant + ? op.RawOpCode.Extract(49) + : op.RawOpCode.Extract(53); + + Condition cmpOp = isRegVariant + ? (Condition)op.RawOpCode.Extract(35, 4) + : (Condition)op.RawOpCode.Extract(49, 4); + + Operand[] srcA = GetHalfSrcA(context); + Operand[] srcB = GetHalfSrcB(context); + + Operand p0Res = GetFPComparison(context, cmpOp, srcA[0], srcB[0]); + Operand p1Res = GetFPComparison(context, cmpOp, srcA[1], srcB[1]); + + if (hAnd) + { + p0Res = context.BitwiseAnd(p0Res, p1Res); + p1Res = context.BitwiseNot(p0Res); + } + + Operand pred = GetPredicate39(context); + + p0Res = GetPredLogicalOp(context, op.LogicalOp, p0Res, pred); + p1Res = GetPredLogicalOp(context, op.LogicalOp, p1Res, pred); + + context.Copy(Register(op.Predicate3), p0Res); + context.Copy(Register(op.Predicate0), p1Res); + } + + public static void Mufu(EmitterContext context) + { + IOpCodeFArith op = (IOpCodeFArith)context.CurrOp; + + bool negateB = op.RawOpCode.Extract(48); + + Operand res = context.FPAbsNeg(GetSrcA(context), op.AbsoluteA, negateB); + + MufuOperation subOp = (MufuOperation)context.CurrOp.RawOpCode.Extract(20, 4); + + switch (subOp) + { + case MufuOperation.Cosine: + res = context.FPCosine(res); + break; + + case MufuOperation.Sine: + res = context.FPSine(res); + break; + + case MufuOperation.ExponentB2: + res = context.FPExponentB2(res); + break; + + case MufuOperation.LogarithmB2: + res = context.FPLogarithmB2(res); + break; + + case MufuOperation.Reciprocal: + res = context.FPReciprocal(res); + break; + + case MufuOperation.ReciprocalSquareRoot: + res = context.FPReciprocalSquareRoot(res); + break; + + case MufuOperation.SquareRoot: + res = context.FPSquareRoot(res); + break; + + default: /* TODO */ break; + } + + context.Copy(GetDest(context), context.FPSaturate(res, op.Saturate)); + } + + private static Operand GetFPComparison( + EmitterContext context, + Condition cond, + Operand srcA, + Operand srcB) + { + Operand res; + + if (cond == Condition.Always) + { + res = Const(IrConsts.True); + } + else if (cond == Condition.Never) + { + res = Const(IrConsts.False); + } + else if (cond == Condition.Nan || cond == Condition.Number) + { + res = context.BitwiseOr(context.IsNan(srcA), context.IsNan(srcB)); + + if (cond == Condition.Number) + { + res = context.BitwiseNot(res); + } + } + else + { + Instruction inst; + + switch (cond & ~Condition.Nan) + { + case Condition.Less: inst = Instruction.CompareLess; break; + case Condition.Equal: inst = Instruction.CompareEqual; break; + case Condition.LessOrEqual: inst = Instruction.CompareLessOrEqual; break; + case Condition.Greater: inst = Instruction.CompareGreater; break; + case Condition.NotEqual: inst = Instruction.CompareNotEqual; break; + case Condition.GreaterOrEqual: inst = Instruction.CompareGreaterOrEqual; break; + + default: throw new InvalidOperationException($"Unexpected condition \"{cond}\"."); + } + + res = context.Add(inst | Instruction.FP, Local(), srcA, srcB); + + if ((cond & Condition.Nan) != 0) + { + res = context.BitwiseOr(res, context.IsNan(srcA)); + res = context.BitwiseOr(res, context.IsNan(srcB)); + } + } + + return res; + } + + private static Operand[] GetHfmaSrcA(EmitterContext context) + { + IOpCodeHfma op = (IOpCodeHfma)context.CurrOp; + + return GetHalfUnpacked(context, GetSrcA(context), op.SwizzleA); + } + + private static Operand[] GetHfmaSrcB(EmitterContext context) + { + IOpCodeHfma op = (IOpCodeHfma)context.CurrOp; + + Operand[] operands = GetHalfUnpacked(context, GetSrcB(context), op.SwizzleB); + + return FPAbsNeg(context, operands, false, op.NegateB); + } + + private static Operand[] GetHfmaSrcC(EmitterContext context) + { + IOpCodeHfma op = (IOpCodeHfma)context.CurrOp; + + Operand[] operands = GetHalfUnpacked(context, GetSrcC(context), op.SwizzleC); + + return FPAbsNeg(context, operands, false, op.NegateC); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Instructions/InstEmitFlow.cs b/Ryujinx.Graphics.Shader/Instructions/InstEmitFlow.cs new file mode 100644 index 0000000000..0b96d7518e --- /dev/null +++ b/Ryujinx.Graphics.Shader/Instructions/InstEmitFlow.cs @@ -0,0 +1,181 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; +using System.Collections.Generic; +using System.Linq; + +using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper; +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static partial class InstEmit + { + public static void Bra(EmitterContext context) + { + EmitBranch(context, context.CurrBlock.Branch.Address); + } + + public static void Brk(EmitterContext context) + { + EmitBrkOrSync(context); + } + + public static void Brx(EmitterContext context) + { + OpCodeBranchIndir op = (OpCodeBranchIndir)context.CurrOp; + + int offset = (int)op.Address + 8 + op.Offset; + + Operand address = context.IAdd(Register(op.Ra), Const(offset)); + + // Sorting the target addresses in descending order improves the code, + // since it will always check the most distant targets first, then the + // near ones. This can be easily transformed into if/else statements. + IOrderedEnumerable sortedTargets = op.PossibleTargets.OrderByDescending(x => x.Address); + + Block lastTarget = sortedTargets.LastOrDefault(); + + foreach (Block possibleTarget in sortedTargets) + { + Operand label = context.GetLabel(possibleTarget.Address); + + if (possibleTarget != lastTarget) + { + context.BranchIfTrue(label, context.ICompareEqual(address, Const((int)possibleTarget.Address))); + } + else + { + context.Branch(label); + } + } + } + + public static void Exit(EmitterContext context) + { + OpCodeExit op = (OpCodeExit)context.CurrOp; + + // TODO: Figure out how this is supposed to work in the + // presence of other condition codes. + if (op.Condition == Condition.Always) + { + context.Return(); + } + } + + public static void Kil(EmitterContext context) + { + context.Discard(); + } + + public static void Pbk(EmitterContext context) + { + EmitPbkOrSsy(context); + } + + public static void Ssy(EmitterContext context) + { + EmitPbkOrSsy(context); + } + + public static void Sync(EmitterContext context) + { + EmitBrkOrSync(context); + } + + private static void EmitPbkOrSsy(EmitterContext context) + { + OpCodePush op = (OpCodePush)context.CurrOp; + + foreach (KeyValuePair kv in op.PopOps) + { + OpCodeBranchPop opSync = kv.Key; + + Operand local = kv.Value; + + int pushOpIndex = opSync.Targets[op]; + + context.Copy(local, Const(pushOpIndex)); + } + } + + private static void EmitBrkOrSync(EmitterContext context) + { + OpCodeBranchPop op = (OpCodeBranchPop)context.CurrOp; + + if (op.Targets.Count == 1) + { + // If we have only one target, then the SSY/PBK is basically + // a branch, we can produce better codegen for this case. + OpCodePush pushOp = op.Targets.Keys.First(); + + EmitBranch(context, pushOp.GetAbsoluteAddress()); + } + else + { + foreach (KeyValuePair kv in op.Targets) + { + OpCodePush pushOp = kv.Key; + + Operand label = context.GetLabel(pushOp.GetAbsoluteAddress()); + + Operand local = pushOp.PopOps[op]; + + int pushOpIndex = kv.Value; + + context.BranchIfTrue(label, context.ICompareEqual(local, Const(pushOpIndex))); + } + } + } + + private static void EmitBranch(EmitterContext context, ulong address) + { + OpCode op = context.CurrOp; + + // If we're branching to the next instruction, then the branch + // is useless and we can ignore it. + if (address == op.Address + 8) + { + return; + } + + Operand label = context.GetLabel(address); + + Operand pred = Register(op.Predicate); + + if (op is OpCodeBranch opBranch && opBranch.Condition != Condition.Always) + { + pred = context.BitwiseAnd(pred, GetCondition(context, opBranch.Condition)); + } + + if (op.Predicate.IsPT) + { + context.Branch(label); + } + else if (op.InvertPredicate) + { + context.BranchIfFalse(label, pred); + } + else + { + context.BranchIfTrue(label, pred); + } + } + + private static Operand GetCondition(EmitterContext context, Condition cond) + { + // TODO: More condition codes, figure out how they work. + switch (cond) + { + case Condition.Equal: + case Condition.EqualUnordered: + return GetZF(); + case Condition.NotEqual: + case Condition.NotEqualUnordered: + return context.BitwiseNot(GetZF()); + } + + return Const(IrConsts.True); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Instructions/InstEmitHelper.cs b/Ryujinx.Graphics.Shader/Instructions/InstEmitHelper.cs new file mode 100644 index 0000000000..5123a6e2af --- /dev/null +++ b/Ryujinx.Graphics.Shader/Instructions/InstEmitHelper.cs @@ -0,0 +1,269 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; +using System; + +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static class InstEmitHelper + { + public static Operand GetZF() + { + return Register(0, RegisterType.Flag); + } + + public static Operand GetNF() + { + return Register(1, RegisterType.Flag); + } + + public static Operand GetCF() + { + return Register(2, RegisterType.Flag); + } + + public static Operand GetVF() + { + return Register(3, RegisterType.Flag); + } + + public static Operand GetDest(EmitterContext context) + { + return Register(((IOpCodeRd)context.CurrOp).Rd); + } + + public static Operand GetSrcA(EmitterContext context) + { + return Register(((IOpCodeRa)context.CurrOp).Ra); + } + + public static Operand GetSrcB(EmitterContext context, FPType floatType) + { + if (floatType == FPType.FP32) + { + return GetSrcB(context); + } + else if (floatType == FPType.FP16) + { + int h = context.CurrOp.RawOpCode.Extract(41, 1); + + return GetHalfUnpacked(context, GetSrcB(context), FPHalfSwizzle.FP16)[h]; + } + else if (floatType == FPType.FP64) + { + // TODO: Double floating-point type support. + } + + context.Config.PrintLog($"Invalid floating point type: {floatType}."); + + return ConstF(0); + } + + public static Operand GetSrcB(EmitterContext context) + { + switch (context.CurrOp) + { + case IOpCodeCbuf op: + return Cbuf(op.Slot, op.Offset); + + case IOpCodeImm op: + return Const(op.Immediate); + + case IOpCodeImmF op: + return ConstF(op.Immediate); + + case IOpCodeReg op: + return Register(op.Rb); + + case IOpCodeRegCbuf op: + return Register(op.Rc); + } + + throw new InvalidOperationException($"Unexpected opcode type \"{context.CurrOp.GetType().Name}\"."); + } + + public static Operand GetSrcC(EmitterContext context) + { + switch (context.CurrOp) + { + case IOpCodeRegCbuf op: + return Cbuf(op.Slot, op.Offset); + + case IOpCodeRc op: + return Register(op.Rc); + } + + throw new InvalidOperationException($"Unexpected opcode type \"{context.CurrOp.GetType().Name}\"."); + } + + public static Operand[] GetHalfSrcA(EmitterContext context, bool isAdd = false) + { + OpCode op = context.CurrOp; + + bool absoluteA = false, negateA = false; + + if (op is OpCodeAluImm32 && isAdd) + { + negateA = op.RawOpCode.Extract(56); + } + else if (isAdd || op is IOpCodeCbuf || op is IOpCodeImm) + { + negateA = op.RawOpCode.Extract(43); + absoluteA = op.RawOpCode.Extract(44); + } + else if (op is IOpCodeReg) + { + absoluteA = op.RawOpCode.Extract(44); + } + + FPHalfSwizzle swizzle = (FPHalfSwizzle)op.RawOpCode.Extract(47, 2); + + Operand[] operands = GetHalfUnpacked(context, GetSrcA(context), swizzle); + + return FPAbsNeg(context, operands, absoluteA, negateA); + } + + public static Operand[] GetHalfSrcB(EmitterContext context) + { + OpCode op = context.CurrOp; + + FPHalfSwizzle swizzle = FPHalfSwizzle.FP16; + + bool absoluteB = false, negateB = false; + + if (op is IOpCodeReg) + { + swizzle = (FPHalfSwizzle)op.RawOpCode.Extract(28, 2); + + absoluteB = op.RawOpCode.Extract(30); + negateB = op.RawOpCode.Extract(31); + } + else if (op is IOpCodeCbuf) + { + swizzle = FPHalfSwizzle.FP32; + + absoluteB = op.RawOpCode.Extract(54); + } + + Operand[] operands = GetHalfUnpacked(context, GetSrcB(context), swizzle); + + return FPAbsNeg(context, operands, absoluteB, negateB); + } + + public static Operand[] FPAbsNeg(EmitterContext context, Operand[] operands, bool abs, bool neg) + { + for (int index = 0; index < operands.Length; index++) + { + operands[index] = context.FPAbsNeg(operands[index], abs, neg); + } + + return operands; + } + + public static Operand[] GetHalfUnpacked(EmitterContext context, Operand src, FPHalfSwizzle swizzle) + { + switch (swizzle) + { + case FPHalfSwizzle.FP16: + return new Operand[] + { + context.UnpackHalf2x16Low (src), + context.UnpackHalf2x16High(src) + }; + + case FPHalfSwizzle.FP32: return new Operand[] { src, src }; + + case FPHalfSwizzle.DupH0: + return new Operand[] + { + context.UnpackHalf2x16Low(src), + context.UnpackHalf2x16Low(src) + }; + + case FPHalfSwizzle.DupH1: + return new Operand[] + { + context.UnpackHalf2x16High(src), + context.UnpackHalf2x16High(src) + }; + } + + throw new ArgumentException($"Invalid swizzle \"{swizzle}\"."); + } + + public static Operand GetHalfPacked(EmitterContext context, Operand[] results) + { + OpCode op = context.CurrOp; + + FPHalfSwizzle swizzle = FPHalfSwizzle.FP16; + + if (!(op is OpCodeAluImm32)) + { + swizzle = (FPHalfSwizzle)context.CurrOp.RawOpCode.Extract(49, 2); + } + + switch (swizzle) + { + case FPHalfSwizzle.FP16: return context.PackHalf2x16(results[0], results[1]); + + case FPHalfSwizzle.FP32: return results[0]; + + case FPHalfSwizzle.DupH0: + { + Operand h1 = GetHalfDest(context, isHigh: true); + + return context.PackHalf2x16(results[0], h1); + } + + case FPHalfSwizzle.DupH1: + { + Operand h0 = GetHalfDest(context, isHigh: false); + + return context.PackHalf2x16(h0, results[1]); + } + } + + throw new ArgumentException($"Invalid swizzle \"{swizzle}\"."); + } + + public static Operand GetHalfDest(EmitterContext context, bool isHigh) + { + if (isHigh) + { + return context.UnpackHalf2x16High(GetDest(context)); + } + else + { + return context.UnpackHalf2x16Low(GetDest(context)); + } + } + + public static Operand GetPredicate39(EmitterContext context) + { + IOpCodePredicate39 op = (IOpCodePredicate39)context.CurrOp; + + Operand local = Register(op.Predicate39); + + if (op.InvertP) + { + local = context.BitwiseNot(local); + } + + return local; + } + + public static Operand SignExtendTo32(EmitterContext context, Operand src, int srcBits) + { + return context.BitfieldExtractS32(src, Const(0), Const(srcBits)); + } + + public static Operand ZeroExtendTo32(EmitterContext context, Operand src, int srcBits) + { + int mask = (int)(0xffffffffu >> (32 - srcBits)); + + return context.BitwiseAnd(src, Const(mask)); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Instructions/InstEmitMemory.cs b/Ryujinx.Graphics.Shader/Instructions/InstEmitMemory.cs new file mode 100644 index 0000000000..25bf259255 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Instructions/InstEmitMemory.cs @@ -0,0 +1,609 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; + +using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper; +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static partial class InstEmit + { + private enum MemoryRegion + { + Local, + Shared + } + + public static void Ald(EmitterContext context) + { + OpCodeAttribute op = (OpCodeAttribute)context.CurrOp; + + Operand primVertex = context.Copy(GetSrcC(context)); + + for (int index = 0; index < op.Count; index++) + { + Register rd = new Register(op.Rd.Index + index, RegisterType.Gpr); + + if (rd.IsRZ) + { + break; + } + + Operand src = Attribute(op.AttributeOffset + index * 4); + + context.Copy(Register(rd), context.LoadAttribute(src, primVertex)); + } + } + + public static void Ast(EmitterContext context) + { + OpCodeAttribute op = (OpCodeAttribute)context.CurrOp; + + for (int index = 0; index < op.Count; index++) + { + if (op.Rd.Index + index > RegisterConsts.RegisterZeroIndex) + { + break; + } + + Register rd = new Register(op.Rd.Index + index, RegisterType.Gpr); + + Operand dest = Attribute(op.AttributeOffset + index * 4); + + context.Copy(dest, Register(rd)); + } + } + + public static void Atoms(EmitterContext context) + { + OpCodeAtom op = (OpCodeAtom)context.CurrOp; + + Operand offset = context.ShiftRightU32(GetSrcA(context), Const(2)); + + offset = context.IAdd(offset, Const(op.Offset)); + + Operand value = GetSrcB(context); + + Operand res = EmitAtomicOp( + context, + Instruction.MrShared, + op.AtomicOp, + op.Type, + offset, + Const(0), + value); + + context.Copy(GetDest(context), res); + } + + public static void Bar(EmitterContext context) + { + OpCodeBarrier op = (OpCodeBarrier)context.CurrOp; + + // TODO: Support other modes. + if (op.Mode == BarrierMode.Sync) + { + context.Barrier(); + } + else + { + context.Config.PrintLog($"Invalid barrier mode: {op.Mode}."); + } + } + + public static void Ipa(EmitterContext context) + { + OpCodeIpa op = (OpCodeIpa)context.CurrOp; + + InterpolationQualifier iq = InterpolationQualifier.None; + + switch (op.Mode) + { + case InterpolationMode.Pass: iq = InterpolationQualifier.NoPerspective; break; + } + + Operand srcA = Attribute(op.AttributeOffset, iq); + + Operand res = context.FPSaturate(srcA, op.Saturate); + + context.Copy(GetDest(context), res); + } + + public static void Isberd(EmitterContext context) + { + // This instruction performs a load from ISBE memory, + // however it seems to be only used to get some vertex + // input data, so we instead propagate the offset so that + // it can be used on the attribute load. + context.Copy(GetDest(context), GetSrcA(context)); + } + + public static void Ld(EmitterContext context) + { + EmitLoad(context, MemoryRegion.Local); + } + + public static void Ldc(EmitterContext context) + { + OpCodeLdc op = (OpCodeLdc)context.CurrOp; + + if (op.Size > IntegerSize.B64) + { + context.Config.PrintLog($"Invalid LDC size: {op.Size}."); + } + + bool isSmallInt = op.Size < IntegerSize.B32; + + int count = op.Size == IntegerSize.B64 ? 2 : 1; + + Operand wordOffset = context.ShiftRightU32(GetSrcA(context), Const(2)); + + wordOffset = context.IAdd(wordOffset, Const(op.Offset)); + + Operand bitOffset = GetBitOffset(context, GetSrcA(context)); + + for (int index = 0; index < count; index++) + { + Register rd = new Register(op.Rd.Index + index, RegisterType.Gpr); + + if (rd.IsRZ) + { + break; + } + + Operand offset = context.IAdd(wordOffset, Const(index)); + + Operand value = context.LoadConstant(Const(op.Slot), offset); + + if (isSmallInt) + { + value = ExtractSmallInt(context, op.Size, bitOffset, value); + } + + context.Copy(Register(rd), value); + } + } + + public static void Ldg(EmitterContext context) + { + EmitLoadGlobal(context); + } + + public static void Lds(EmitterContext context) + { + EmitLoad(context, MemoryRegion.Shared); + } + + public static void Membar(EmitterContext context) + { + OpCodeMemoryBarrier op = (OpCodeMemoryBarrier)context.CurrOp; + + if (op.Level == BarrierLevel.Cta) + { + context.GroupMemoryBarrier(); + } + else + { + context.MemoryBarrier(); + } + } + + public static void Out(EmitterContext context) + { + OpCode op = context.CurrOp; + + bool emit = op.RawOpCode.Extract(39); + bool cut = op.RawOpCode.Extract(40); + + if (!(emit || cut)) + { + context.Config.PrintLog("Invalid OUT encoding."); + } + + if (emit) + { + context.EmitVertex(); + } + + if (cut) + { + context.EndPrimitive(); + } + } + + public static void Red(EmitterContext context) + { + OpCodeRed op = (OpCodeRed)context.CurrOp; + + (Operand addrLow, Operand addrHigh) = Get40BitsAddress(context, op.Ra, op.Extended, op.Offset); + + EmitAtomicOp( + context, + Instruction.MrGlobal, + op.AtomicOp, + op.Type, + addrLow, + addrHigh, + GetDest(context)); + } + + public static void St(EmitterContext context) + { + EmitStore(context, MemoryRegion.Local); + } + + public static void Stg(EmitterContext context) + { + EmitStoreGlobal(context); + } + + public static void Sts(EmitterContext context) + { + EmitStore(context, MemoryRegion.Shared); + } + + private static Operand EmitAtomicOp( + EmitterContext context, + Instruction mr, + AtomicOp op, + ReductionType type, + Operand addrLow, + Operand addrHigh, + Operand value) + { + Operand res = Const(0); + + switch (op) + { + case AtomicOp.Add: + if (type == ReductionType.S32 || type == ReductionType.U32) + { + res = context.AtomicAdd(mr, addrLow, addrHigh, value); + } + else + { + context.Config.PrintLog($"Invalid reduction type: {type}."); + } + break; + case AtomicOp.BitwiseAnd: + if (type == ReductionType.S32 || type == ReductionType.U32) + { + res = context.AtomicAnd(mr, addrLow, addrHigh, value); + } + else + { + context.Config.PrintLog($"Invalid reduction type: {type}."); + } + break; + case AtomicOp.BitwiseExclusiveOr: + if (type == ReductionType.S32 || type == ReductionType.U32) + { + res = context.AtomicXor(mr, addrLow, addrHigh, value); + } + else + { + context.Config.PrintLog($"Invalid reduction type: {type}."); + } + break; + case AtomicOp.BitwiseOr: + if (type == ReductionType.S32 || type == ReductionType.U32) + { + res = context.AtomicOr(mr, addrLow, addrHigh, value); + } + else + { + context.Config.PrintLog($"Invalid reduction type: {type}."); + } + break; + case AtomicOp.Maximum: + if (type == ReductionType.S32) + { + res = context.AtomicMaxS32(mr, addrLow, addrHigh, value); + } + else if (type == ReductionType.U32) + { + res = context.AtomicMaxU32(mr, addrLow, addrHigh, value); + } + else + { + context.Config.PrintLog($"Invalid reduction type: {type}."); + } + break; + case AtomicOp.Minimum: + if (type == ReductionType.S32) + { + res = context.AtomicMinS32(mr, addrLow, addrHigh, value); + } + else if (type == ReductionType.U32) + { + res = context.AtomicMinU32(mr, addrLow, addrHigh, value); + } + else + { + context.Config.PrintLog($"Invalid reduction type: {type}."); + } + break; + } + + return res; + } + + private static void EmitLoad(EmitterContext context, MemoryRegion region) + { + OpCodeMemory op = (OpCodeMemory)context.CurrOp; + + if (op.Size > IntegerSize.B128) + { + context.Config.PrintLog($"Invalid load size: {op.Size}."); + } + + bool isSmallInt = op.Size < IntegerSize.B32; + + int count = 1; + + switch (op.Size) + { + case IntegerSize.B64: count = 2; break; + case IntegerSize.B128: count = 4; break; + } + + Operand baseOffset = context.IAdd(GetSrcA(context), Const(op.Offset)); + + // Word offset = byte offset / 4 (one word = 4 bytes). + Operand wordOffset = context.ShiftRightU32(baseOffset, Const(2)); + + Operand bitOffset = GetBitOffset(context, baseOffset); + + for (int index = 0; index < count; index++) + { + Register rd = new Register(op.Rd.Index + index, RegisterType.Gpr); + + if (rd.IsRZ) + { + break; + } + + Operand offset = context.IAdd(wordOffset, Const(index)); + + Operand value = null; + + switch (region) + { + case MemoryRegion.Local: value = context.LoadLocal (offset); break; + case MemoryRegion.Shared: value = context.LoadShared(offset); break; + } + + if (isSmallInt) + { + value = ExtractSmallInt(context, op.Size, bitOffset, value); + } + + context.Copy(Register(rd), value); + } + } + + private static void EmitLoadGlobal(EmitterContext context) + { + OpCodeMemory op = (OpCodeMemory)context.CurrOp; + + bool isSmallInt = op.Size < IntegerSize.B32; + + int count = GetVectorCount(op.Size); + + (Operand addrLow, Operand addrHigh) = Get40BitsAddress(context, op.Ra, op.Extended, op.Offset); + + Operand bitOffset = GetBitOffset(context, addrLow); + + for (int index = 0; index < count; index++) + { + Register rd = new Register(op.Rd.Index + index, RegisterType.Gpr); + + if (rd.IsRZ) + { + break; + } + + Operand value = context.LoadGlobal(context.IAdd(addrLow, Const(index * 4)), addrHigh); + + if (isSmallInt) + { + value = ExtractSmallInt(context, op.Size, bitOffset, value); + } + + context.Copy(Register(rd), value); + } + } + + private static void EmitStore(EmitterContext context, MemoryRegion region) + { + OpCodeMemory op = (OpCodeMemory)context.CurrOp; + + if (op.Size > IntegerSize.B128) + { + context.Config.PrintLog($"Invalid store size: {op.Size}."); + } + + bool isSmallInt = op.Size < IntegerSize.B32; + + int count = 1; + + switch (op.Size) + { + case IntegerSize.B64: count = 2; break; + case IntegerSize.B128: count = 4; break; + } + + Operand baseOffset = context.IAdd(GetSrcA(context), Const(op.Offset)); + + Operand wordOffset = context.ShiftRightU32(baseOffset, Const(2)); + + Operand bitOffset = GetBitOffset(context, baseOffset); + + for (int index = 0; index < count; index++) + { + Register rd = new Register(op.Rd.Index + index, RegisterType.Gpr); + + Operand value = Register(rd); + + Operand offset = context.IAdd(wordOffset, Const(index)); + + if (isSmallInt) + { + Operand word = null; + + switch (region) + { + case MemoryRegion.Local: word = context.LoadLocal (offset); break; + case MemoryRegion.Shared: word = context.LoadShared(offset); break; + } + + value = InsertSmallInt(context, op.Size, bitOffset, word, value); + } + + switch (region) + { + case MemoryRegion.Local: context.StoreLocal (offset, value); break; + case MemoryRegion.Shared: context.StoreShared(offset, value); break; + } + + if (rd.IsRZ) + { + break; + } + } + } + + private static void EmitStoreGlobal(EmitterContext context) + { + OpCodeMemory op = (OpCodeMemory)context.CurrOp; + + bool isSmallInt = op.Size < IntegerSize.B32; + + int count = GetVectorCount(op.Size); + + (Operand addrLow, Operand addrHigh) = Get40BitsAddress(context, op.Ra, op.Extended, op.Offset); + + Operand bitOffset = GetBitOffset(context, addrLow); + + for (int index = 0; index < count; index++) + { + Register rd = new Register(op.Rd.Index + index, RegisterType.Gpr); + + Operand value = Register(rd); + + if (isSmallInt) + { + Operand word = context.LoadGlobal(addrLow, addrHigh); + + value = InsertSmallInt(context, op.Size, bitOffset, word, value); + } + + context.StoreGlobal(context.IAdd(addrLow, Const(index * 4)), addrHigh, value); + + if (rd.IsRZ) + { + break; + } + } + } + + private static int GetVectorCount(IntegerSize size) + { + switch (size) + { + case IntegerSize.B64: + return 2; + case IntegerSize.B128: + case IntegerSize.UB128: + return 4; + } + + return 1; + } + + private static (Operand, Operand) Get40BitsAddress( + EmitterContext context, + Register ra, + bool extended, + int offset) + { + Operand addrLow = GetSrcA(context); + Operand addrHigh; + + if (extended && !ra.IsRZ) + { + addrHigh = Register(ra.Index + 1, RegisterType.Gpr); + } + else + { + addrHigh = Const(0); + } + + Operand offs = Const(offset); + + addrLow = context.IAdd(addrLow, offs); + + if (extended) + { + Operand carry = context.ICompareLessUnsigned(addrLow, offs); + + addrHigh = context.IAdd(addrHigh, context.ConditionalSelect(carry, Const(1), Const(0))); + } + + return (addrLow, addrHigh); + } + + private static Operand GetBitOffset(EmitterContext context, Operand baseOffset) + { + // Note: bit offset = (baseOffset & 0b11) * 8. + // Addresses should be always aligned to the integer type, + // so we don't need to take unaligned addresses into account. + return context.ShiftLeft(context.BitwiseAnd(baseOffset, Const(3)), Const(3)); + } + + private static Operand ExtractSmallInt( + EmitterContext context, + IntegerSize size, + Operand bitOffset, + Operand value) + { + value = context.ShiftRightU32(value, bitOffset); + + switch (size) + { + case IntegerSize.U8: value = ZeroExtendTo32(context, value, 8); break; + case IntegerSize.U16: value = ZeroExtendTo32(context, value, 16); break; + case IntegerSize.S8: value = SignExtendTo32(context, value, 8); break; + case IntegerSize.S16: value = SignExtendTo32(context, value, 16); break; + } + + return value; + } + + private static Operand InsertSmallInt( + EmitterContext context, + IntegerSize size, + Operand bitOffset, + Operand word, + Operand value) + { + switch (size) + { + case IntegerSize.U8: + case IntegerSize.S8: + value = context.BitwiseAnd(value, Const(0xff)); + value = context.BitfieldInsert(word, value, bitOffset, Const(8)); + break; + + case IntegerSize.U16: + case IntegerSize.S16: + value = context.BitwiseAnd(value, Const(0xffff)); + value = context.BitfieldInsert(word, value, bitOffset, Const(16)); + break; + } + + return value; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Instructions/InstEmitMove.cs b/Ryujinx.Graphics.Shader/Instructions/InstEmitMove.cs new file mode 100644 index 0000000000..ffc4c430f5 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Instructions/InstEmitMove.cs @@ -0,0 +1,144 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; + +using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper; +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static partial class InstEmit + { + public static void Mov(EmitterContext context) + { + context.Copy(GetDest(context), GetSrcB(context)); + } + + public static void R2p(EmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + bool isCC = op.RawOpCode.Extract(40); + int shift = op.RawOpCode.Extract(41, 2) * 8; + + Operand value = GetSrcA(context); + Operand mask = GetSrcB(context); + + Operand Test(Operand value, int bit) + { + return context.ICompareNotEqual(context.BitwiseAnd(value, Const(1 << bit)), Const(0)); + } + + if (isCC) + { + // TODO: Support Register to condition code flags copy. + context.Config.PrintLog("R2P.CC not implemented."); + } + else + { + for (int bit = 0; bit < 7; bit++) + { + Operand pred = Register(bit, RegisterType.Predicate); + + Operand res = context.ConditionalSelect(Test(mask, bit), Test(value, bit + shift), pred); + + context.Copy(pred, res); + } + } + } + + public static void S2r(EmitterContext context) + { + // TODO: Better impl. + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + SystemRegister sysReg = (SystemRegister)op.RawOpCode.Extract(20, 8); + + Operand src; + + switch (sysReg) + { + case SystemRegister.LaneId: src = Attribute(AttributeConsts.LaneId); break; + + // TODO: Use value from Y direction GPU register. + case SystemRegister.YDirection: src = ConstF(1); break; + + case SystemRegister.ThreadId: + { + Operand tidX = Attribute(AttributeConsts.ThreadIdX); + Operand tidY = Attribute(AttributeConsts.ThreadIdY); + Operand tidZ = Attribute(AttributeConsts.ThreadIdZ); + + tidY = context.ShiftLeft(tidY, Const(16)); + tidZ = context.ShiftLeft(tidZ, Const(26)); + + src = context.BitwiseOr(tidX, context.BitwiseOr(tidY, tidZ)); + + break; + } + + case SystemRegister.ThreadIdX: src = Attribute(AttributeConsts.ThreadIdX); break; + case SystemRegister.ThreadIdY: src = Attribute(AttributeConsts.ThreadIdY); break; + case SystemRegister.ThreadIdZ: src = Attribute(AttributeConsts.ThreadIdZ); break; + case SystemRegister.CtaIdX: src = Attribute(AttributeConsts.CtaIdX); break; + case SystemRegister.CtaIdY: src = Attribute(AttributeConsts.CtaIdY); break; + case SystemRegister.CtaIdZ: src = Attribute(AttributeConsts.CtaIdZ); break; + case SystemRegister.EqMask: src = Attribute(AttributeConsts.EqMask); break; + case SystemRegister.LtMask: src = Attribute(AttributeConsts.LtMask); break; + case SystemRegister.LeMask: src = Attribute(AttributeConsts.LeMask); break; + case SystemRegister.GtMask: src = Attribute(AttributeConsts.GtMask); break; + case SystemRegister.GeMask: src = Attribute(AttributeConsts.GeMask); break; + + default: src = Const(0); break; + } + + context.Copy(GetDest(context), src); + } + + public static void Sel(EmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + Operand pred = GetPredicate39(context); + + Operand srcA = GetSrcA(context); + Operand srcB = GetSrcB(context); + + Operand res = context.ConditionalSelect(pred, srcA, srcB); + + context.Copy(GetDest(context), res); + } + + public static void Shfl(EmitterContext context) + { + OpCodeShuffle op = (OpCodeShuffle)context.CurrOp; + + Operand pred = Register(op.Predicate48); + + Operand srcA = GetSrcA(context); + + Operand srcB = op.IsBImmediate ? Const(op.ImmediateB) : Register(op.Rb); + Operand srcC = op.IsCImmediate ? Const(op.ImmediateC) : Register(op.Rc); + + Operand res = null; + + switch (op.ShuffleType) + { + case ShuffleType.Indexed: + res = context.Shuffle(srcA, srcB, srcC); + break; + case ShuffleType.Up: + res = context.ShuffleUp(srcA, srcB, srcC); + break; + case ShuffleType.Down: + res = context.ShuffleDown(srcA, srcB, srcC); + break; + case ShuffleType.Butterfly: + res = context.ShuffleXor(srcA, srcB, srcC); + break; + } + + context.Copy(GetDest(context), res); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Instructions/InstEmitTexture.cs b/Ryujinx.Graphics.Shader/Instructions/InstEmitTexture.cs new file mode 100644 index 0000000000..7b9794eaf1 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Instructions/InstEmitTexture.cs @@ -0,0 +1,1032 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; +using System; +using System.Collections.Generic; + +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static partial class InstEmit + { + public static void Sust(EmitterContext context) + { + OpCodeImage op = (OpCodeImage)context.CurrOp; + + SamplerType type = ConvertSamplerType(op.Dimensions); + + if (type == SamplerType.None) + { + context.Config.PrintLog("Invalid image store sampler type."); + + return; + } + + int raIndex = op.Ra.Index; + int rbIndex = op.Rb.Index; + + Operand Ra() + { + if (raIndex > RegisterConsts.RegisterZeroIndex) + { + return Const(0); + } + + return context.Copy(Register(raIndex++, RegisterType.Gpr)); + } + + Operand Rb() + { + if (rbIndex > RegisterConsts.RegisterZeroIndex) + { + return Const(0); + } + + return context.Copy(Register(rbIndex++, RegisterType.Gpr)); + } + + bool isArray = op.Dimensions == ImageDimensions.Image1DArray || + op.Dimensions == ImageDimensions.Image2DArray; + + Operand arrayIndex = isArray ? Ra() : null; + + List sourcesList = new List(); + + if (op.IsBindless) + { + sourcesList.Add(context.Copy(Register(op.Rc))); + } + + int coordsCount = type.GetDimensions(); + + for (int index = 0; index < coordsCount; index++) + { + sourcesList.Add(Ra()); + } + + if (isArray) + { + sourcesList.Add(arrayIndex); + + type |= SamplerType.Array; + } + + if (op.UseComponents) + { + int componentMask = (int)op.Components; + + for (int compMask = componentMask, compIndex = 0; compMask != 0; compMask >>= 1, compIndex++) + { + if ((compMask & 1) != 0) + { + sourcesList.Add(Rb()); + } + } + } + else + { + context.Config.PrintLog("Unsized image store not supported."); + } + + Operand[] sources = sourcesList.ToArray(); + + int handle = !op.IsBindless ? op.Immediate : 0; + + TextureFlags flags = op.IsBindless ? TextureFlags.Bindless : TextureFlags.None; + + TextureOperation operation = new TextureOperation( + Instruction.ImageStore, + type, + flags, + handle, + 0, + null, + sources); + + context.Add(operation); + } + + public static void Tex(EmitterContext context) + { + EmitTextureSample(context, TextureFlags.None); + } + + public static void TexB(EmitterContext context) + { + EmitTextureSample(context, TextureFlags.Bindless); + } + + public static void Tld(EmitterContext context) + { + EmitTextureSample(context, TextureFlags.IntCoords); + } + + public static void TldB(EmitterContext context) + { + EmitTextureSample(context, TextureFlags.IntCoords | TextureFlags.Bindless); + } + + public static void Texs(EmitterContext context) + { + OpCodeTextureScalar op = (OpCodeTextureScalar)context.CurrOp; + + if (op.Rd0.IsRZ && op.Rd1.IsRZ) + { + return; + } + + List sourcesList = new List(); + + int raIndex = op.Ra.Index; + int rbIndex = op.Rb.Index; + + Operand Ra() + { + if (raIndex > RegisterConsts.RegisterZeroIndex) + { + return Const(0); + } + + return context.Copy(Register(raIndex++, RegisterType.Gpr)); + } + + Operand Rb() + { + if (rbIndex > RegisterConsts.RegisterZeroIndex) + { + return Const(0); + } + + return context.Copy(Register(rbIndex++, RegisterType.Gpr)); + } + + void AddTextureOffset(int coordsCount, int stride, int size) + { + Operand packedOffs = Rb(); + + for (int index = 0; index < coordsCount; index++) + { + sourcesList.Add(context.BitfieldExtractS32(packedOffs, Const(index * stride), Const(size))); + } + } + + SamplerType type; + TextureFlags flags; + + if (op is OpCodeTexs texsOp) + { + type = ConvertSamplerType(texsOp.Target); + + if (type == SamplerType.None) + { + context.Config.PrintLog("Invalid texture sampler type."); + + return; + } + + flags = ConvertTextureFlags(texsOp.Target); + + if ((type & SamplerType.Array) != 0) + { + Operand arrayIndex = Ra(); + + sourcesList.Add(Ra()); + sourcesList.Add(Rb()); + + sourcesList.Add(arrayIndex); + + if ((type & SamplerType.Shadow) != 0) + { + sourcesList.Add(Rb()); + } + + if ((flags & TextureFlags.LodLevel) != 0) + { + sourcesList.Add(ConstF(0)); + } + } + else + { + switch (texsOp.Target) + { + case TextureTarget.Texture1DLodZero: + sourcesList.Add(Ra()); + break; + + case TextureTarget.Texture2D: + sourcesList.Add(Ra()); + sourcesList.Add(Rb()); + break; + + case TextureTarget.Texture2DLodZero: + sourcesList.Add(Ra()); + sourcesList.Add(Rb()); + sourcesList.Add(ConstF(0)); + break; + + case TextureTarget.Texture2DLodLevel: + case TextureTarget.Texture2DDepthCompare: + case TextureTarget.Texture3D: + case TextureTarget.TextureCube: + sourcesList.Add(Ra()); + sourcesList.Add(Ra()); + sourcesList.Add(Rb()); + break; + + case TextureTarget.Texture2DLodZeroDepthCompare: + case TextureTarget.Texture3DLodZero: + sourcesList.Add(Ra()); + sourcesList.Add(Ra()); + sourcesList.Add(Rb()); + sourcesList.Add(ConstF(0)); + break; + + case TextureTarget.Texture2DLodLevelDepthCompare: + case TextureTarget.TextureCubeLodLevel: + sourcesList.Add(Ra()); + sourcesList.Add(Ra()); + sourcesList.Add(Rb()); + sourcesList.Add(Rb()); + break; + } + } + } + else if (op is OpCodeTlds tldsOp) + { + type = ConvertSamplerType (tldsOp.Target); + + if (type == SamplerType.None) + { + context.Config.PrintLog("Invalid texel fetch sampler type."); + + return; + } + + flags = ConvertTextureFlags(tldsOp.Target) | TextureFlags.IntCoords; + + switch (tldsOp.Target) + { + case TexelLoadTarget.Texture1DLodZero: + sourcesList.Add(Ra()); + sourcesList.Add(Const(0)); + break; + + case TexelLoadTarget.Texture1DLodLevel: + sourcesList.Add(Ra()); + sourcesList.Add(Rb()); + break; + + case TexelLoadTarget.Texture2DLodZero: + sourcesList.Add(Ra()); + sourcesList.Add(Rb()); + sourcesList.Add(Const(0)); + break; + + case TexelLoadTarget.Texture2DLodZeroOffset: + sourcesList.Add(Ra()); + sourcesList.Add(Ra()); + sourcesList.Add(Const(0)); + break; + + case TexelLoadTarget.Texture2DLodZeroMultisample: + case TexelLoadTarget.Texture2DLodLevel: + case TexelLoadTarget.Texture2DLodLevelOffset: + sourcesList.Add(Ra()); + sourcesList.Add(Ra()); + sourcesList.Add(Rb()); + break; + + case TexelLoadTarget.Texture3DLodZero: + sourcesList.Add(Ra()); + sourcesList.Add(Ra()); + sourcesList.Add(Rb()); + sourcesList.Add(Const(0)); + break; + + case TexelLoadTarget.Texture2DArrayLodZero: + sourcesList.Add(Rb()); + sourcesList.Add(Rb()); + sourcesList.Add(Ra()); + sourcesList.Add(Const(0)); + break; + } + + if ((flags & TextureFlags.Offset) != 0) + { + AddTextureOffset(type.GetDimensions(), 4, 4); + } + } + else if (op is OpCodeTld4s tld4sOp) + { + if (!(tld4sOp.HasDepthCompare || tld4sOp.HasOffset)) + { + sourcesList.Add(Ra()); + sourcesList.Add(Rb()); + } + else + { + sourcesList.Add(Ra()); + sourcesList.Add(Ra()); + } + + type = SamplerType.Texture2D; + flags = TextureFlags.Gather; + + if (tld4sOp.HasDepthCompare) + { + sourcesList.Add(Rb()); + + type |= SamplerType.Shadow; + } + + if (tld4sOp.HasOffset) + { + AddTextureOffset(type.GetDimensions(), 8, 6); + + flags |= TextureFlags.Offset; + } + + sourcesList.Add(Const(tld4sOp.GatherCompIndex)); + } + else + { + throw new InvalidOperationException($"Invalid opcode type \"{op.GetType().Name}\"."); + } + + Operand[] sources = sourcesList.ToArray(); + + Operand[] rd0 = new Operand[2] { ConstF(0), ConstF(0) }; + Operand[] rd1 = new Operand[2] { ConstF(0), ConstF(0) }; + + int destIncrement = 0; + + Operand GetDest() + { + int high = destIncrement >> 1; + int low = destIncrement & 1; + + destIncrement++; + + if (op.IsFp16) + { + return high != 0 + ? (rd1[low] = Local()) + : (rd0[low] = Local()); + } + else + { + int rdIndex = high != 0 ? op.Rd1.Index : op.Rd0.Index; + + if (rdIndex < RegisterConsts.RegisterZeroIndex) + { + rdIndex += low; + } + + return Register(rdIndex, RegisterType.Gpr); + } + } + + int handle = op.Immediate; + + for (int compMask = op.ComponentMask, compIndex = 0; compMask != 0; compMask >>= 1, compIndex++) + { + if ((compMask & 1) != 0) + { + Operand dest = GetDest(); + + TextureOperation operation = new TextureOperation( + Instruction.TextureSample, + type, + flags, + handle, + compIndex, + dest, + sources); + + context.Add(operation); + } + } + + if (op.IsFp16) + { + context.Copy(Register(op.Rd0), context.PackHalf2x16(rd0[0], rd0[1])); + context.Copy(Register(op.Rd1), context.PackHalf2x16(rd1[0], rd1[1])); + } + } + + public static void Tld4(EmitterContext context) + { + IOpCodeTld4 op = (IOpCodeTld4)context.CurrOp; + + if (op.Rd.IsRZ) + { + return; + } + + int raIndex = op.Ra.Index; + int rbIndex = op.Rb.Index; + + Operand Ra() + { + if (raIndex > RegisterConsts.RegisterZeroIndex) + { + return Const(0); + } + + return context.Copy(Register(raIndex++, RegisterType.Gpr)); + } + + Operand Rb() + { + if (rbIndex > RegisterConsts.RegisterZeroIndex) + { + return Const(0); + } + + return context.Copy(Register(rbIndex++, RegisterType.Gpr)); + } + + Operand arrayIndex = op.IsArray ? Ra() : null; + + List sourcesList = new List(); + + SamplerType type = ConvertSamplerType(op.Dimensions); + + TextureFlags flags = TextureFlags.Gather; + + if (op.Bindless) + { + sourcesList.Add(Rb()); + + flags |= TextureFlags.Bindless; + } + + int coordsCount = type.GetDimensions(); + + for (int index = 0; index < coordsCount; index++) + { + sourcesList.Add(Ra()); + } + + if (op.IsArray) + { + sourcesList.Add(arrayIndex); + + type |= SamplerType.Array; + } + + Operand[] packedOffs = new Operand[2]; + + packedOffs[0] = op.Offset != TextureGatherOffset.None ? Rb() : null; + packedOffs[1] = op.Offset == TextureGatherOffset.Offsets ? Rb() : null; + + if (op.HasDepthCompare) + { + sourcesList.Add(Rb()); + + type |= SamplerType.Shadow; + } + + if (op.Offset != TextureGatherOffset.None) + { + int offsetTexelsCount = op.Offset == TextureGatherOffset.Offsets ? 4 : 1; + + for (int index = 0; index < coordsCount * offsetTexelsCount; index++) + { + Operand packed = packedOffs[(index >> 2) & 1]; + + sourcesList.Add(context.BitfieldExtractS32(packed, Const((index & 3) * 8), Const(6))); + } + + flags |= op.Offset == TextureGatherOffset.Offsets + ? TextureFlags.Offsets + : TextureFlags.Offset; + } + + sourcesList.Add(Const(op.GatherCompIndex)); + + Operand[] sources = sourcesList.ToArray(); + + int rdIndex = op.Rd.Index; + + Operand GetDest() + { + if (rdIndex > RegisterConsts.RegisterZeroIndex) + { + return Const(0); + } + + return Register(rdIndex++, RegisterType.Gpr); + } + + int handle = op.Immediate; + + for (int compMask = op.ComponentMask, compIndex = 0; compMask != 0; compMask >>= 1, compIndex++) + { + if ((compMask & 1) != 0) + { + Operand dest = GetDest(); + + TextureOperation operation = new TextureOperation( + Instruction.TextureSample, + type, + flags, + handle, + compIndex, + dest, + sources); + + context.Add(operation); + } + } + } + + public static void Txd(EmitterContext context) + { + OpCodeTxd op = (OpCodeTxd)context.CurrOp; + + if (op.Rd.IsRZ) + { + return; + } + + int raIndex = op.Ra.Index; + int rbIndex = op.Rb.Index; + + Operand Ra() + { + if (raIndex > RegisterConsts.RegisterZeroIndex) + { + return Const(0); + } + + return context.Copy(Register(raIndex++, RegisterType.Gpr)); + } + + Operand Rb() + { + if (rbIndex > RegisterConsts.RegisterZeroIndex) + { + return Const(0); + } + + return context.Copy(Register(rbIndex++, RegisterType.Gpr)); + } + + TextureFlags flags = TextureFlags.Derivatives; + + List sourcesList = new List(); + + if (op.IsBindless) + { + sourcesList.Add(Ra()); + } + + SamplerType type = ConvertSamplerType(op.Dimensions); + + int coordsCount = type.GetDimensions(); + + for (int index = 0; index < coordsCount; index++) + { + sourcesList.Add(Ra()); + } + + Operand packedParams = Ra(); + + if (op.IsArray) + { + sourcesList.Add(context.BitwiseAnd(packedParams, Const(0xffff))); + + type |= SamplerType.Array; + } + + // Derivatives (X and Y). + for (int dIndex = 0; dIndex < 2 * coordsCount; dIndex++) + { + sourcesList.Add(Rb()); + } + + if (op.HasOffset) + { + for (int index = 0; index < coordsCount; index++) + { + sourcesList.Add(context.BitfieldExtractS32(packedParams, Const(16 + index * 4), Const(4))); + } + + flags |= TextureFlags.Offset; + } + + Operand[] sources = sourcesList.ToArray(); + + int rdIndex = op.Rd.Index; + + Operand GetDest() + { + if (rdIndex > RegisterConsts.RegisterZeroIndex) + { + return Const(0); + } + + return Register(rdIndex++, RegisterType.Gpr); + } + + int handle = !op.IsBindless ? op.Immediate : 0; + + for (int compMask = op.ComponentMask, compIndex = 0; compMask != 0; compMask >>= 1, compIndex++) + { + if ((compMask & 1) != 0) + { + Operand dest = GetDest(); + + TextureOperation operation = new TextureOperation( + Instruction.TextureSample, + type, + flags, + handle, + compIndex, + dest, + sources); + + context.Add(operation); + } + } + } + + public static void Txq(EmitterContext context) + { + EmitTextureQuery(context, bindless: false); + } + + public static void TxqB(EmitterContext context) + { + EmitTextureQuery(context, bindless: true); + } + + private static void EmitTextureQuery(EmitterContext context, bool bindless) + { + OpCodeTex op = (OpCodeTex)context.CurrOp; + + if (op.Rd.IsRZ) + { + return; + } + + TextureProperty property = (TextureProperty)op.RawOpCode.Extract(22, 6); + + // TODO: Validate and use property. + Instruction inst = Instruction.TextureSize; + + SamplerType type = SamplerType.Texture2D; + + TextureFlags flags = bindless ? TextureFlags.Bindless : TextureFlags.None; + + int raIndex = op.Ra.Index; + + Operand Ra() + { + if (raIndex > RegisterConsts.RegisterZeroIndex) + { + return Const(0); + } + + return context.Copy(Register(raIndex++, RegisterType.Gpr)); + } + + List sourcesList = new List(); + + if (bindless) + { + sourcesList.Add(Ra()); + } + + sourcesList.Add(Ra()); + + Operand[] sources = sourcesList.ToArray(); + + int rdIndex = op.Rd.Index; + + Operand GetDest() + { + if (rdIndex > RegisterConsts.RegisterZeroIndex) + { + return Const(0); + } + + return Register(rdIndex++, RegisterType.Gpr); + } + + int handle = !bindless ? op.Immediate : 0; + + for (int compMask = op.ComponentMask, compIndex = 0; compMask != 0; compMask >>= 1, compIndex++) + { + if ((compMask & 1) != 0) + { + Operand dest = GetDest(); + + TextureOperation operation = new TextureOperation( + inst, + type, + flags, + handle, + compIndex, + dest, + sources); + + context.Add(operation); + } + } + } + + private static void EmitTextureSample(EmitterContext context, TextureFlags flags) + { + OpCodeTexture op = (OpCodeTexture)context.CurrOp; + + bool isBindless = (flags & TextureFlags.Bindless) != 0; + + if (op.Rd.IsRZ) + { + return; + } + + int raIndex = op.Ra.Index; + int rbIndex = op.Rb.Index; + + Operand Ra() + { + if (raIndex > RegisterConsts.RegisterZeroIndex) + { + return Const(0); + } + + return context.Copy(Register(raIndex++, RegisterType.Gpr)); + } + + Operand Rb() + { + if (rbIndex > RegisterConsts.RegisterZeroIndex) + { + return Const(0); + } + + return context.Copy(Register(rbIndex++, RegisterType.Gpr)); + } + + Operand arrayIndex = op.IsArray ? Ra() : null; + + List sourcesList = new List(); + + if (isBindless) + { + sourcesList.Add(Rb()); + } + + SamplerType type = ConvertSamplerType(op.Dimensions); + + int coordsCount = type.GetDimensions(); + + for (int index = 0; index < coordsCount; index++) + { + sourcesList.Add(Ra()); + } + + if (op.IsArray) + { + sourcesList.Add(arrayIndex); + + type |= SamplerType.Array; + } + + bool hasLod = op.LodMode > TextureLodMode.LodZero; + + Operand lodValue = hasLod ? Rb() : ConstF(0); + + Operand packedOffs = op.HasOffset ? Rb() : null; + + if (op.HasDepthCompare) + { + sourcesList.Add(Rb()); + + type |= SamplerType.Shadow; + } + + if ((op.LodMode == TextureLodMode.LodZero || + op.LodMode == TextureLodMode.LodLevel || + op.LodMode == TextureLodMode.LodLevelA) && !op.IsMultisample) + { + sourcesList.Add(lodValue); + + flags |= TextureFlags.LodLevel; + } + + if (op.HasOffset) + { + for (int index = 0; index < coordsCount; index++) + { + sourcesList.Add(context.BitfieldExtractS32(packedOffs, Const(index * 4), Const(4))); + } + + flags |= TextureFlags.Offset; + } + + if (op.LodMode == TextureLodMode.LodBias || + op.LodMode == TextureLodMode.LodBiasA) + { + sourcesList.Add(lodValue); + + flags |= TextureFlags.LodBias; + } + + if (op.IsMultisample) + { + sourcesList.Add(Rb()); + + type |= SamplerType.Multisample; + } + + Operand[] sources = sourcesList.ToArray(); + + int rdIndex = op.Rd.Index; + + Operand GetDest() + { + if (rdIndex > RegisterConsts.RegisterZeroIndex) + { + return Const(0); + } + + return Register(rdIndex++, RegisterType.Gpr); + } + + int handle = !isBindless ? op.Immediate : 0; + + for (int compMask = op.ComponentMask, compIndex = 0; compMask != 0; compMask >>= 1, compIndex++) + { + if ((compMask & 1) != 0) + { + Operand dest = GetDest(); + + TextureOperation operation = new TextureOperation( + Instruction.TextureSample, + type, + flags, + handle, + compIndex, + dest, + sources); + + context.Add(operation); + } + } + } + + private static SamplerType ConvertSamplerType(ImageDimensions target) + { + switch (target) + { + case ImageDimensions.Image1D: + return SamplerType.Texture1D; + + case ImageDimensions.ImageBuffer: + return SamplerType.TextureBuffer; + + case ImageDimensions.Image1DArray: + return SamplerType.Texture1D | SamplerType.Array; + + case ImageDimensions.Image2D: + return SamplerType.Texture2D; + + case ImageDimensions.Image2DArray: + return SamplerType.Texture2D | SamplerType.Array; + + case ImageDimensions.Image3D: + return SamplerType.Texture3D; + } + + return SamplerType.None; + } + + private static SamplerType ConvertSamplerType(TextureDimensions dimensions) + { + switch (dimensions) + { + case TextureDimensions.Texture1D: return SamplerType.Texture1D; + case TextureDimensions.Texture2D: return SamplerType.Texture2D; + case TextureDimensions.Texture3D: return SamplerType.Texture3D; + case TextureDimensions.TextureCube: return SamplerType.TextureCube; + } + + throw new ArgumentException($"Invalid texture dimensions \"{dimensions}\"."); + } + + private static SamplerType ConvertSamplerType(TextureTarget type) + { + switch (type) + { + case TextureTarget.Texture1DLodZero: + return SamplerType.Texture1D; + + case TextureTarget.Texture2D: + case TextureTarget.Texture2DLodZero: + case TextureTarget.Texture2DLodLevel: + return SamplerType.Texture2D; + + case TextureTarget.Texture2DDepthCompare: + case TextureTarget.Texture2DLodLevelDepthCompare: + case TextureTarget.Texture2DLodZeroDepthCompare: + return SamplerType.Texture2D | SamplerType.Shadow; + + case TextureTarget.Texture2DArray: + case TextureTarget.Texture2DArrayLodZero: + return SamplerType.Texture2D | SamplerType.Array; + + case TextureTarget.Texture2DArrayLodZeroDepthCompare: + return SamplerType.Texture2D | SamplerType.Array | SamplerType.Shadow; + + case TextureTarget.Texture3D: + case TextureTarget.Texture3DLodZero: + return SamplerType.Texture3D; + + case TextureTarget.TextureCube: + case TextureTarget.TextureCubeLodLevel: + return SamplerType.TextureCube; + } + + return SamplerType.None; + } + + private static SamplerType ConvertSamplerType(TexelLoadTarget type) + { + switch (type) + { + case TexelLoadTarget.Texture1DLodZero: + case TexelLoadTarget.Texture1DLodLevel: + return SamplerType.Texture1D; + + case TexelLoadTarget.Texture2DLodZero: + case TexelLoadTarget.Texture2DLodZeroOffset: + case TexelLoadTarget.Texture2DLodLevel: + case TexelLoadTarget.Texture2DLodLevelOffset: + return SamplerType.Texture2D; + + case TexelLoadTarget.Texture2DLodZeroMultisample: + return SamplerType.Texture2D | SamplerType.Multisample; + + case TexelLoadTarget.Texture3DLodZero: + return SamplerType.Texture3D; + + case TexelLoadTarget.Texture2DArrayLodZero: + return SamplerType.Texture2D | SamplerType.Array; + } + + return SamplerType.None; + } + + private static TextureFlags ConvertTextureFlags(Decoders.TextureTarget type) + { + switch (type) + { + case TextureTarget.Texture1DLodZero: + case TextureTarget.Texture2DLodZero: + case TextureTarget.Texture2DLodLevel: + case TextureTarget.Texture2DLodLevelDepthCompare: + case TextureTarget.Texture2DLodZeroDepthCompare: + case TextureTarget.Texture2DArrayLodZero: + case TextureTarget.Texture2DArrayLodZeroDepthCompare: + case TextureTarget.Texture3DLodZero: + case TextureTarget.TextureCubeLodLevel: + return TextureFlags.LodLevel; + + case TextureTarget.Texture2D: + case TextureTarget.Texture2DDepthCompare: + case TextureTarget.Texture2DArray: + case TextureTarget.Texture3D: + case TextureTarget.TextureCube: + return TextureFlags.None; + } + + return TextureFlags.None; + } + + private static TextureFlags ConvertTextureFlags(TexelLoadTarget type) + { + switch (type) + { + case TexelLoadTarget.Texture1DLodZero: + case TexelLoadTarget.Texture1DLodLevel: + case TexelLoadTarget.Texture2DLodZero: + case TexelLoadTarget.Texture2DLodLevel: + case TexelLoadTarget.Texture2DLodZeroMultisample: + case TexelLoadTarget.Texture3DLodZero: + case TexelLoadTarget.Texture2DArrayLodZero: + return TextureFlags.LodLevel; + + case TexelLoadTarget.Texture2DLodZeroOffset: + case TexelLoadTarget.Texture2DLodLevelOffset: + return TextureFlags.LodLevel | TextureFlags.Offset; + } + + return TextureFlags.None; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Instructions/InstEmitVideo.cs b/Ryujinx.Graphics.Shader/Instructions/InstEmitVideo.cs new file mode 100644 index 0000000000..aac12c781c --- /dev/null +++ b/Ryujinx.Graphics.Shader/Instructions/InstEmitVideo.cs @@ -0,0 +1,17 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.Translation; + +using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static partial class InstEmit + { + public static void Vmad(EmitterContext context) + { + OpCodeVideo op = (OpCodeVideo)context.CurrOp; + + context.Copy(GetDest(context), GetSrcC(context)); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Instructions/InstEmitVote.cs b/Ryujinx.Graphics.Shader/Instructions/InstEmitVote.cs new file mode 100644 index 0000000000..8f81ecb4df --- /dev/null +++ b/Ryujinx.Graphics.Shader/Instructions/InstEmitVote.cs @@ -0,0 +1,48 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; + +using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper; +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static partial class InstEmit + { + public static void Vote(EmitterContext context) + { + OpCodeVote op = (OpCodeVote)context.CurrOp; + + Operand pred = GetPredicate39(context); + + Operand res = null; + + switch (op.VoteOp) + { + case VoteOp.All: + res = context.VoteAll(pred); + break; + case VoteOp.Any: + res = context.VoteAny(pred); + break; + case VoteOp.AllEqual: + res = context.VoteAllEqual(pred); + break; + } + + if (res != null) + { + context.Copy(Register(op.Predicate45), res); + } + else + { + context.Config.PrintLog($"Invalid vote operation: {op.VoteOp}."); + } + + if (!op.Rd.IsRZ) + { + context.Copy(Register(op.Rd), context.Ballot(pred)); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Instructions/InstEmitter.cs b/Ryujinx.Graphics.Shader/Instructions/InstEmitter.cs new file mode 100644 index 0000000000..91c740b684 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Instructions/InstEmitter.cs @@ -0,0 +1,6 @@ +using Ryujinx.Graphics.Shader.Translation; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + delegate void InstEmitter(EmitterContext context); +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Instructions/Lop3Expression.cs b/Ryujinx.Graphics.Shader/Instructions/Lop3Expression.cs new file mode 100644 index 0000000000..67e2495742 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Instructions/Lop3Expression.cs @@ -0,0 +1,149 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; + +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static class Lop3Expression + { + public static Operand GetFromTruthTable( + EmitterContext context, + Operand srcA, + Operand srcB, + Operand srcC, + int imm) + { + Operand expr = null; + + // Handle some simple cases, or cases where + // the KMap would yield poor results (like XORs). + if (imm == 0x96 || imm == 0x69) + { + // XOR (0x96) and XNOR (0x69). + if (imm == 0x69) + { + srcA = context.BitwiseNot(srcA); + } + + expr = context.BitwiseExclusiveOr(srcA, srcB); + expr = context.BitwiseExclusiveOr(expr, srcC); + + return expr; + } + else if (imm == 0) + { + // Always false. + return Const(IrConsts.False); + } + else if (imm == 0xff) + { + // Always true. + return Const(IrConsts.True); + } + + int map; + + // Encode into gray code. + map = ((imm >> 0) & 1) << 0; + map |= ((imm >> 1) & 1) << 4; + map |= ((imm >> 2) & 1) << 1; + map |= ((imm >> 3) & 1) << 5; + map |= ((imm >> 4) & 1) << 3; + map |= ((imm >> 5) & 1) << 7; + map |= ((imm >> 6) & 1) << 2; + map |= ((imm >> 7) & 1) << 6; + + // Solve KMap, get sum of products. + int visited = 0; + + for (int index = 0; index < 8 && visited != 0xff; index++) + { + if ((map & (1 << index)) == 0) + { + continue; + } + + int mask = 0; + + for (int mSize = 4; mSize != 0; mSize >>= 1) + { + mask = RotateLeft4((1 << mSize) - 1, index & 3) << (index & 4); + + if ((map & mask) == mask) + { + break; + } + } + + // The mask should wrap, if we are on the high row, shift to low etc. + int mask2 = (index & 4) != 0 ? mask >> 4 : mask << 4; + + if ((map & mask2) == mask2) + { + mask |= mask2; + } + + if ((mask & visited) == mask) + { + continue; + } + + bool notA = (mask & 0x33) != 0; + bool notB = (mask & 0x99) != 0; + bool notC = (mask & 0x0f) != 0; + + bool aChanges = (mask & 0xcc) != 0 && notA; + bool bChanges = (mask & 0x66) != 0 && notB; + bool cChanges = (mask & 0xf0) != 0 && notC; + + Operand localExpr = null; + + void And(Operand source) + { + if (localExpr != null) + { + localExpr = context.BitwiseAnd(localExpr, source); + } + else + { + localExpr = source; + } + } + + if (!aChanges) + { + And(context.BitwiseNot(srcA, notA)); + } + + if (!bChanges) + { + And(context.BitwiseNot(srcB, notB)); + } + + if (!cChanges) + { + And(context.BitwiseNot(srcC, notC)); + } + + if (expr != null) + { + expr = context.BitwiseOr(expr, localExpr); + } + else + { + expr = localExpr; + } + + visited |= mask; + } + + return expr; + } + + private static int RotateLeft4(int value, int shift) + { + return ((value << shift) | (value >> (4 - shift))) & 0xf; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/IntermediateRepresentation/BasicBlock.cs b/Ryujinx.Graphics.Shader/IntermediateRepresentation/BasicBlock.cs new file mode 100644 index 0000000000..949753377e --- /dev/null +++ b/Ryujinx.Graphics.Shader/IntermediateRepresentation/BasicBlock.cs @@ -0,0 +1,61 @@ +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.IntermediateRepresentation +{ + class BasicBlock + { + public int Index { get; set; } + + public LinkedList Operations { get; } + + private BasicBlock _next; + private BasicBlock _branch; + + public BasicBlock Next + { + get => _next; + set => _next = AddSuccessor(_next, value); + } + + public BasicBlock Branch + { + get => _branch; + set => _branch = AddSuccessor(_branch, value); + } + + public bool HasBranch => _branch != null; + + public List Predecessors { get; } + + public HashSet DominanceFrontiers { get; } + + public BasicBlock ImmediateDominator { get; set; } + + public BasicBlock() + { + Operations = new LinkedList(); + + Predecessors = new List(); + + DominanceFrontiers = new HashSet(); + } + + public BasicBlock(int index) : this() + { + Index = index; + } + + private BasicBlock AddSuccessor(BasicBlock oldBlock, BasicBlock newBlock) + { + oldBlock?.Predecessors.Remove(this); + newBlock?.Predecessors.Add(this); + + return newBlock; + } + + public INode GetLastOp() + { + return Operations.Last?.Value; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/IntermediateRepresentation/CommentNode.cs b/Ryujinx.Graphics.Shader/IntermediateRepresentation/CommentNode.cs new file mode 100644 index 0000000000..d4d87b0676 --- /dev/null +++ b/Ryujinx.Graphics.Shader/IntermediateRepresentation/CommentNode.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.Shader.IntermediateRepresentation +{ + class CommentNode : Operation + { + public string Comment { get; } + + public CommentNode(string comment) : base(Instruction.Comment, null) + { + Comment = comment; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/IntermediateRepresentation/INode.cs b/Ryujinx.Graphics.Shader/IntermediateRepresentation/INode.cs new file mode 100644 index 0000000000..48dda24b1e --- /dev/null +++ b/Ryujinx.Graphics.Shader/IntermediateRepresentation/INode.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.Graphics.Shader.IntermediateRepresentation +{ + interface INode + { + Operand Dest { get; set; } + + int SourcesCount { get; } + + Operand GetSource(int index); + + void SetSource(int index, Operand operand); + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/IntermediateRepresentation/Instruction.cs b/Ryujinx.Graphics.Shader/IntermediateRepresentation/Instruction.cs new file mode 100644 index 0000000000..7108112c97 --- /dev/null +++ b/Ryujinx.Graphics.Shader/IntermediateRepresentation/Instruction.cs @@ -0,0 +1,159 @@ +using System; + +namespace Ryujinx.Graphics.Shader.IntermediateRepresentation +{ + [Flags] + enum Instruction + { + Absolute = 1, + Add, + AtomicAdd, + AtomicAnd, + AtomicCompareAndSwap, + AtomicMinS32, + AtomicMinU32, + AtomicMaxS32, + AtomicMaxU32, + AtomicOr, + AtomicSwap, + AtomicXor, + Ballot, + Barrier, + BitCount, + BitfieldExtractS32, + BitfieldExtractU32, + BitfieldInsert, + BitfieldReverse, + BitwiseAnd, + BitwiseExclusiveOr, + BitwiseNot, + BitwiseOr, + Branch, + BranchIfFalse, + BranchIfTrue, + Ceiling, + Clamp, + ClampU32, + Comment, + CompareEqual, + CompareGreater, + CompareGreaterOrEqual, + CompareGreaterOrEqualU32, + CompareGreaterU32, + CompareLess, + CompareLessOrEqual, + CompareLessOrEqualU32, + CompareLessU32, + CompareNotEqual, + ConditionalSelect, + ConvertFPToS32, + ConvertFPToU32, + ConvertS32ToFP, + ConvertU32ToFP, + Copy, + Cosine, + Ddx, + Ddy, + Discard, + Divide, + EmitVertex, + EndPrimitive, + ExponentB2, + FindFirstSetS32, + FindFirstSetU32, + Floor, + FusedMultiplyAdd, + GroupMemoryBarrier, + ImageLoad, + ImageStore, + IsNan, + LoadAttribute, + LoadConstant, + LoadGlobal, + LoadLocal, + LoadShared, + LoadStorage, + Lod, + LogarithmB2, + LogicalAnd, + LogicalExclusiveOr, + LogicalNot, + LogicalOr, + LoopBreak, + LoopContinue, + MarkLabel, + Maximum, + MaximumU32, + MemoryBarrier, + Minimum, + MinimumU32, + Multiply, + MultiplyHighS32, + MultiplyHighU32, + Negate, + PackDouble2x32, + PackHalf2x16, + ReciprocalSquareRoot, + Return, + Round, + ShiftLeft, + ShiftRightS32, + ShiftRightU32, + Shuffle, + ShuffleDown, + ShuffleUp, + ShuffleXor, + Sine, + SquareRoot, + StoreGlobal, + StoreLocal, + StoreShared, + StoreStorage, + Subtract, + SwizzleAdd, + TextureSample, + TextureSize, + Truncate, + UnpackDouble2x32, + UnpackHalf2x16, + VoteAll, + VoteAllEqual, + VoteAny, + + Count, + + FP = 1 << 16, + + MrShift = 17, + + MrGlobal = 0 << MrShift, + MrShared = 1 << MrShift, + MrStorage = 2 << MrShift, + MrMask = 3 << MrShift, + + Mask = 0xffff + } + + static class InstructionExtensions + { + public static bool IsAtomic(this Instruction inst) + { + switch (inst & Instruction.Mask) + { + case Instruction.AtomicAdd: + case Instruction.AtomicAnd: + case Instruction.AtomicCompareAndSwap: + case Instruction.AtomicMaxS32: + case Instruction.AtomicMaxU32: + case Instruction.AtomicMinS32: + case Instruction.AtomicMinU32: + case Instruction.AtomicOr: + case Instruction.AtomicSwap: + case Instruction.AtomicXor: + return true; + } + + return false; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/IntermediateRepresentation/IrConsts.cs b/Ryujinx.Graphics.Shader/IntermediateRepresentation/IrConsts.cs new file mode 100644 index 0000000000..c264e47d1a --- /dev/null +++ b/Ryujinx.Graphics.Shader/IntermediateRepresentation/IrConsts.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Graphics.Shader.IntermediateRepresentation +{ + static class IrConsts + { + public const int False = 0; + public const int True = -1; + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/IntermediateRepresentation/Operand.cs b/Ryujinx.Graphics.Shader/IntermediateRepresentation/Operand.cs new file mode 100644 index 0000000000..567277a75b --- /dev/null +++ b/Ryujinx.Graphics.Shader/IntermediateRepresentation/Operand.cs @@ -0,0 +1,82 @@ +using Ryujinx.Graphics.Shader.Decoders; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.IntermediateRepresentation +{ + class Operand + { + private const int CbufSlotBits = 5; + private const int CbufSlotLsb = 32 - CbufSlotBits; + private const int CbufSlotMask = (1 << CbufSlotBits) - 1; + + public OperandType Type { get; } + + public int Value { get; } + + public InterpolationQualifier Interpolation { get; } + + public INode AsgOp { get; set; } + + public HashSet UseOps { get; } + + private Operand() + { + UseOps = new HashSet(); + } + + public Operand(OperandType type) : this() + { + Type = type; + } + + public Operand(OperandType type, int value, InterpolationQualifier iq = InterpolationQualifier.None) : this() + { + Type = type; + Value = value; + Interpolation = iq; + } + + public Operand(Register reg) : this() + { + Type = OperandType.Register; + Value = PackRegInfo(reg.Index, reg.Type); + } + + public Operand(int slot, int offset) : this() + { + Type = OperandType.ConstantBuffer; + Value = PackCbufInfo(slot, offset); + } + + private static int PackCbufInfo(int slot, int offset) + { + return (slot << CbufSlotLsb) | offset; + } + + private static int PackRegInfo(int index, RegisterType type) + { + return ((int)type << 24) | index; + } + + public int GetCbufSlot() + { + return (Value >> CbufSlotLsb) & CbufSlotMask; + } + + public int GetCbufOffset() + { + return Value & ~(CbufSlotMask << CbufSlotLsb); + } + + public Register GetRegister() + { + return new Register(Value & 0xffffff, (RegisterType)(Value >> 24)); + } + + public float AsFloat() + { + return BitConverter.Int32BitsToSingle(Value); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/IntermediateRepresentation/OperandHelper.cs b/Ryujinx.Graphics.Shader/IntermediateRepresentation/OperandHelper.cs new file mode 100644 index 0000000000..45c9ba1e55 --- /dev/null +++ b/Ryujinx.Graphics.Shader/IntermediateRepresentation/OperandHelper.cs @@ -0,0 +1,62 @@ +using Ryujinx.Graphics.Shader.Decoders; +using System; + +namespace Ryujinx.Graphics.Shader.IntermediateRepresentation +{ + static class OperandHelper + { + public static Operand Attribute(int value, InterpolationQualifier iq = InterpolationQualifier.None) + { + return new Operand(OperandType.Attribute, value, iq); + } + + public static Operand Cbuf(int slot, int offset) + { + return new Operand(slot, offset); + } + + public static Operand Const(int value) + { + return new Operand(OperandType.Constant, value); + } + + public static Operand ConstF(float value) + { + return new Operand(OperandType.Constant, BitConverter.SingleToInt32Bits(value)); + } + + public static Operand Label() + { + return new Operand(OperandType.Label); + } + + public static Operand Local() + { + return new Operand(OperandType.LocalVariable); + } + + public static Operand Register(int index, RegisterType type) + { + return Register(new Register(index, type)); + } + + public static Operand Register(Register reg) + { + if (reg.IsRZ) + { + return Const(0); + } + else if (reg.IsPT) + { + return Const(IrConsts.True); + } + + return new Operand(reg); + } + + public static Operand Undef() + { + return new Operand(OperandType.Undefined); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/IntermediateRepresentation/OperandType.cs b/Ryujinx.Graphics.Shader/IntermediateRepresentation/OperandType.cs new file mode 100644 index 0000000000..8f8df9e4f0 --- /dev/null +++ b/Ryujinx.Graphics.Shader/IntermediateRepresentation/OperandType.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.Graphics.Shader.IntermediateRepresentation +{ + enum OperandType + { + Attribute, + Constant, + ConstantBuffer, + Label, + LocalVariable, + Register, + Undefined + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/IntermediateRepresentation/Operation.cs b/Ryujinx.Graphics.Shader/IntermediateRepresentation/Operation.cs new file mode 100644 index 0000000000..6b7fb82fe6 --- /dev/null +++ b/Ryujinx.Graphics.Shader/IntermediateRepresentation/Operation.cs @@ -0,0 +1,106 @@ +namespace Ryujinx.Graphics.Shader.IntermediateRepresentation +{ + class Operation : INode + { + public Instruction Inst { get; private set; } + + private Operand _dest; + + public Operand Dest + { + get => _dest; + set => _dest = AssignDest(value); + } + + private Operand[] _sources; + + public int SourcesCount => _sources.Length; + + public int Index { get; } + + public Operation(Instruction inst, Operand dest, params Operand[] sources) + { + Inst = inst; + Dest = dest; + + // The array may be modified externally, so we store a copy. + _sources = (Operand[])sources.Clone(); + + for (int index = 0; index < _sources.Length; index++) + { + Operand source = _sources[index]; + + if (source.Type == OperandType.LocalVariable) + { + source.UseOps.Add(this); + } + } + } + + public Operation( + Instruction inst, + int index, + Operand dest, + params Operand[] sources) : this(inst, dest, sources) + { + Index = index; + } + + private Operand AssignDest(Operand dest) + { + if (dest != null && dest.Type == OperandType.LocalVariable) + { + dest.AsgOp = this; + } + + return dest; + } + + public Operand GetSource(int index) + { + return _sources[index]; + } + + public void SetSource(int index, Operand source) + { + Operand oldSrc = _sources[index]; + + if (oldSrc != null && oldSrc.Type == OperandType.LocalVariable) + { + oldSrc.UseOps.Remove(this); + } + + if (source != null && source.Type == OperandType.LocalVariable) + { + source.UseOps.Add(this); + } + + _sources[index] = source; + } + + public void TurnIntoCopy(Operand source) + { + TurnInto(Instruction.Copy, source); + } + + public void TurnInto(Instruction newInst, Operand source) + { + Inst = newInst; + + foreach (Operand oldSrc in _sources) + { + if (oldSrc != null && oldSrc.Type == OperandType.LocalVariable) + { + oldSrc.UseOps.Remove(this); + } + } + + if (source.Type == OperandType.LocalVariable) + { + source.UseOps.Add(this); + } + + _sources = new Operand[] { source }; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/IntermediateRepresentation/PhiNode.cs b/Ryujinx.Graphics.Shader/IntermediateRepresentation/PhiNode.cs new file mode 100644 index 0000000000..13ff41bd14 --- /dev/null +++ b/Ryujinx.Graphics.Shader/IntermediateRepresentation/PhiNode.cs @@ -0,0 +1,94 @@ +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.IntermediateRepresentation +{ + class PhiNode : INode + { + private Operand _dest; + + public Operand Dest + { + get => _dest; + set => _dest = AssignDest(value); + } + + private HashSet _blocks; + + private class PhiSource + { + public BasicBlock Block { get; } + public Operand Operand { get; set; } + + public PhiSource(BasicBlock block, Operand operand) + { + Block = block; + Operand = operand; + } + } + + private List _sources; + + public int SourcesCount => _sources.Count; + + public PhiNode(Operand dest) + { + _blocks = new HashSet(); + + _sources = new List(); + + dest.AsgOp = this; + + Dest = dest; + } + + private Operand AssignDest(Operand dest) + { + if (dest != null && dest.Type == OperandType.LocalVariable) + { + dest.AsgOp = this; + } + + return dest; + } + + public void AddSource(BasicBlock block, Operand operand) + { + if (_blocks.Add(block)) + { + if (operand.Type == OperandType.LocalVariable) + { + operand.UseOps.Add(this); + } + + _sources.Add(new PhiSource(block, operand)); + } + } + + public Operand GetSource(int index) + { + return _sources[index].Operand; + } + + public BasicBlock GetBlock(int index) + { + return _sources[index].Block; + } + + public void SetSource(int index, Operand source) + { + Operand oldSrc = _sources[index].Operand; + + if (oldSrc != null && oldSrc.Type == OperandType.LocalVariable) + { + oldSrc.UseOps.Remove(this); + } + + if (source.Type == OperandType.LocalVariable) + { + source.UseOps.Add(this); + } + + _sources[index].Operand = source; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureFlags.cs b/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureFlags.cs new file mode 100644 index 0000000000..5334afacca --- /dev/null +++ b/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureFlags.cs @@ -0,0 +1,18 @@ +using System; + +namespace Ryujinx.Graphics.Shader.IntermediateRepresentation +{ + [Flags] + enum TextureFlags + { + None = 0, + Bindless = 1 << 0, + Gather = 1 << 1, + Derivatives = 1 << 2, + IntCoords = 1 << 3, + LodBias = 1 << 4, + LodLevel = 1 << 5, + Offset = 1 << 6, + Offsets = 1 << 7 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureOperation.cs b/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureOperation.cs new file mode 100644 index 0000000000..718d2c2ec1 --- /dev/null +++ b/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureOperation.cs @@ -0,0 +1,33 @@ +namespace Ryujinx.Graphics.Shader.IntermediateRepresentation +{ + class TextureOperation : Operation + { + public SamplerType Type { get; private set; } + public TextureFlags Flags { get; private set; } + + public int Handle { get; private set; } + + public TextureOperation( + Instruction inst, + SamplerType type, + TextureFlags flags, + int handle, + int compIndex, + Operand dest, + params Operand[] sources) : base(inst, compIndex, dest, sources) + { + Type = type; + Flags = flags; + Handle = handle; + } + + public void TurnIntoIndexed(int handle) + { + Type |= SamplerType.Indexed; + + Flags &= ~TextureFlags.Bindless; + + Handle = handle; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/InterpolationQualifier.cs b/Ryujinx.Graphics.Shader/InterpolationQualifier.cs new file mode 100644 index 0000000000..e710427dde --- /dev/null +++ b/Ryujinx.Graphics.Shader/InterpolationQualifier.cs @@ -0,0 +1,45 @@ +using System; + +namespace Ryujinx.Graphics.Shader +{ + [Flags] + public enum InterpolationQualifier + { + None = 0, + + Flat = 1, + NoPerspective = 2, + Smooth = 3, + + Centroid = 1 << 16, + Sample = 1 << 17, + + FlagsMask = Centroid | Sample + } + + public static class InterpolationQualifierExtensions + { + public static string ToGlslQualifier(this InterpolationQualifier iq) + { + string output = string.Empty; + + switch (iq & ~InterpolationQualifier.FlagsMask) + { + case InterpolationQualifier.Flat: output = "flat"; break; + case InterpolationQualifier.NoPerspective: output = "noperspective"; break; + case InterpolationQualifier.Smooth: output = "smooth"; break; + } + + if ((iq & InterpolationQualifier.Centroid) != 0) + { + output = "centroid " + output; + } + else if ((iq & InterpolationQualifier.Sample) != 0) + { + output = "sample " + output; + } + + return output; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/OutputTopology.cs b/Ryujinx.Graphics.Shader/OutputTopology.cs new file mode 100644 index 0000000000..6f977becb4 --- /dev/null +++ b/Ryujinx.Graphics.Shader/OutputTopology.cs @@ -0,0 +1,24 @@ +namespace Ryujinx.Graphics.Shader +{ + enum OutputTopology + { + PointList = 1, + LineStrip = 6, + TriangleStrip = 7 + } + + static class OutputTopologyExtensions + { + public static string ToGlslString(this OutputTopology topology) + { + switch (topology) + { + case OutputTopology.LineStrip: return "line_strip"; + case OutputTopology.PointList: return "points"; + case OutputTopology.TriangleStrip: return "triangle_strip"; + } + + return "points"; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/QueryInfoName.cs b/Ryujinx.Graphics.Shader/QueryInfoName.cs new file mode 100644 index 0000000000..c4f2cb6cc2 --- /dev/null +++ b/Ryujinx.Graphics.Shader/QueryInfoName.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.Graphics.Shader +{ + public enum QueryInfoName + { + ComputeLocalSizeX, + ComputeLocalSizeY, + ComputeLocalSizeZ, + ComputeSharedMemorySize, + IsTextureBuffer, + IsTextureRectangle, + PrimitiveTopology, + StorageBufferOffsetAlignment, + SupportsNonConstantTextureOffset + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Ryujinx.Graphics.Shader.csproj b/Ryujinx.Graphics.Shader/Ryujinx.Graphics.Shader.csproj new file mode 100644 index 0000000000..55864c6dd8 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Ryujinx.Graphics.Shader.csproj @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + netcoreapp3.0 + win-x64;osx-x64;linux-x64 + + + diff --git a/Ryujinx.Graphics.Shader/SamplerType.cs b/Ryujinx.Graphics.Shader/SamplerType.cs new file mode 100644 index 0000000000..9546efe494 --- /dev/null +++ b/Ryujinx.Graphics.Shader/SamplerType.cs @@ -0,0 +1,39 @@ +using System; + +namespace Ryujinx.Graphics.Shader +{ + [Flags] + public enum SamplerType + { + None = 0, + Texture1D, + TextureBuffer, + Texture2D, + Texture3D, + TextureCube, + + Mask = 0xff, + + Array = 1 << 8, + Indexed = 1 << 9, + Multisample = 1 << 10, + Shadow = 1 << 11 + } + + static class SamplerTypeExtensions + { + public static int GetDimensions(this SamplerType type) + { + switch (type & SamplerType.Mask) + { + case SamplerType.Texture1D: return 1; + case SamplerType.TextureBuffer: return 1; + case SamplerType.Texture2D: return 2; + case SamplerType.Texture3D: return 3; + case SamplerType.TextureCube: return 3; + } + + throw new ArgumentException($"Invalid sampler type \"{type}\"."); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/ShaderProgram.cs b/Ryujinx.Graphics.Shader/ShaderProgram.cs new file mode 100644 index 0000000000..4d0c6e5bd6 --- /dev/null +++ b/Ryujinx.Graphics.Shader/ShaderProgram.cs @@ -0,0 +1,33 @@ +using System; + +namespace Ryujinx.Graphics.Shader +{ + public class ShaderProgram + { + public ShaderProgramInfo Info { get; } + + public ShaderStage Stage { get; } + + public string Code { get; private set; } + + public int Size { get; } + + internal ShaderProgram(ShaderProgramInfo info, ShaderStage stage, string code, int size) + { + Info = info; + Stage = stage; + Code = code; + Size = size; + } + + public void Prepend(string line) + { + Code = line + Environment.NewLine + Code; + } + + public void Replace(string name, string value) + { + Code = Code.Replace(name, value); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/ShaderProgramInfo.cs b/Ryujinx.Graphics.Shader/ShaderProgramInfo.cs new file mode 100644 index 0000000000..1ff602a240 --- /dev/null +++ b/Ryujinx.Graphics.Shader/ShaderProgramInfo.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.ObjectModel; + +namespace Ryujinx.Graphics.Shader +{ + public class ShaderProgramInfo + { + public ReadOnlyCollection CBuffers { get; } + public ReadOnlyCollection SBuffers { get; } + public ReadOnlyCollection Textures { get; } + public ReadOnlyCollection Images { get; } + + public ReadOnlyCollection InterpolationQualifiers { get; } + + public bool UsesInstanceId { get; } + + internal ShaderProgramInfo( + BufferDescriptor[] cBuffers, + BufferDescriptor[] sBuffers, + TextureDescriptor[] textures, + TextureDescriptor[] images, + InterpolationQualifier[] interpolationQualifiers, + bool usesInstanceId) + { + CBuffers = Array.AsReadOnly(cBuffers); + SBuffers = Array.AsReadOnly(sBuffers); + Textures = Array.AsReadOnly(textures); + Images = Array.AsReadOnly(images); + + InterpolationQualifiers = Array.AsReadOnly(interpolationQualifiers); + + UsesInstanceId = usesInstanceId; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/ShaderStage.cs b/Ryujinx.Graphics.Shader/ShaderStage.cs new file mode 100644 index 0000000000..30b65348e6 --- /dev/null +++ b/Ryujinx.Graphics.Shader/ShaderStage.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.Shader +{ + public enum ShaderStage + { + Compute, + Vertex, + TessellationControl, + TessellationEvaluation, + Geometry, + Fragment + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/StructuredIr/AstAssignment.cs b/Ryujinx.Graphics.Shader/StructuredIr/AstAssignment.cs new file mode 100644 index 0000000000..bb3fe7af4b --- /dev/null +++ b/Ryujinx.Graphics.Shader/StructuredIr/AstAssignment.cs @@ -0,0 +1,35 @@ +using static Ryujinx.Graphics.Shader.StructuredIr.AstHelper; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + class AstAssignment : AstNode + { + public IAstNode Destination { get; } + + private IAstNode _source; + + public IAstNode Source + { + get + { + return _source; + } + set + { + RemoveUse(_source, this); + + AddUse(value, this); + + _source = value; + } + } + + public AstAssignment(IAstNode destination, IAstNode source) + { + Destination = destination; + Source = source; + + AddDef(destination, this); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/StructuredIr/AstBlock.cs b/Ryujinx.Graphics.Shader/StructuredIr/AstBlock.cs new file mode 100644 index 0000000000..fdef87de56 --- /dev/null +++ b/Ryujinx.Graphics.Shader/StructuredIr/AstBlock.cs @@ -0,0 +1,116 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System; +using System.Collections; +using System.Collections.Generic; + +using static Ryujinx.Graphics.Shader.StructuredIr.AstHelper; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + class AstBlock : AstNode, IEnumerable + { + public AstBlockType Type { get; private set; } + + private IAstNode _condition; + + public IAstNode Condition + { + get + { + return _condition; + } + set + { + RemoveUse(_condition, this); + + AddUse(value, this); + + _condition = value; + } + } + + private LinkedList _nodes; + + public IAstNode First => _nodes.First?.Value; + + public int Count => _nodes.Count; + + public AstBlock(AstBlockType type, IAstNode condition = null) + { + Type = type; + Condition = condition; + + _nodes = new LinkedList(); + } + + public void Add(IAstNode node) + { + Add(node, _nodes.AddLast(node)); + } + + public void AddFirst(IAstNode node) + { + Add(node, _nodes.AddFirst(node)); + } + + public void AddBefore(IAstNode next, IAstNode node) + { + Add(node, _nodes.AddBefore(next.LLNode, node)); + } + + public void AddAfter(IAstNode prev, IAstNode node) + { + Add(node, _nodes.AddAfter(prev.LLNode, node)); + } + + private void Add(IAstNode node, LinkedListNode newNode) + { + if (node.Parent != null) + { + throw new ArgumentException("Node already belongs to a block."); + } + + node.Parent = this; + node.LLNode = newNode; + } + + public void Remove(IAstNode node) + { + _nodes.Remove(node.LLNode); + + node.Parent = null; + node.LLNode = null; + } + + public void AndCondition(IAstNode cond) + { + Condition = new AstOperation(Instruction.LogicalAnd, Condition, cond); + } + + public void OrCondition(IAstNode cond) + { + Condition = new AstOperation(Instruction.LogicalOr, Condition, cond); + } + public void TurnIntoIf(IAstNode cond) + { + Condition = cond; + + Type = AstBlockType.If; + } + + public void TurnIntoElseIf() + { + Type = AstBlockType.ElseIf; + } + + public IEnumerator GetEnumerator() + { + return _nodes.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/StructuredIr/AstBlockType.cs b/Ryujinx.Graphics.Shader/StructuredIr/AstBlockType.cs new file mode 100644 index 0000000000..c12efda909 --- /dev/null +++ b/Ryujinx.Graphics.Shader/StructuredIr/AstBlockType.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + enum AstBlockType + { + DoWhile, + If, + Else, + ElseIf, + Main, + While + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/StructuredIr/AstBlockVisitor.cs b/Ryujinx.Graphics.Shader/StructuredIr/AstBlockVisitor.cs new file mode 100644 index 0000000000..10d5dce0a9 --- /dev/null +++ b/Ryujinx.Graphics.Shader/StructuredIr/AstBlockVisitor.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; + +using static Ryujinx.Graphics.Shader.StructuredIr.AstHelper; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + class AstBlockVisitor + { + public AstBlock Block { get; private set; } + + public class BlockVisitationEventArgs : EventArgs + { + public AstBlock Block { get; } + + public BlockVisitationEventArgs(AstBlock block) + { + Block = block; + } + } + + public event EventHandler BlockEntered; + public event EventHandler BlockLeft; + + public AstBlockVisitor(AstBlock mainBlock) + { + Block = mainBlock; + } + + public IEnumerable Visit() + { + IAstNode node = Block.First; + + while (node != null) + { + // We reached a child block, visit the nodes inside. + while (node is AstBlock childBlock) + { + Block = childBlock; + + node = childBlock.First; + + BlockEntered?.Invoke(this, new BlockVisitationEventArgs(Block)); + } + + // Node may be null, if the block is empty. + if (node != null) + { + IAstNode next = Next(node); + + yield return node; + + node = next; + } + + // We reached the end of the list, go up on tree to the parent blocks. + while (node == null && Block.Type != AstBlockType.Main) + { + BlockLeft?.Invoke(this, new BlockVisitationEventArgs(Block)); + + node = Next(Block); + + Block = Block.Parent; + } + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/StructuredIr/AstComment.cs b/Ryujinx.Graphics.Shader/StructuredIr/AstComment.cs new file mode 100644 index 0000000000..dabe623fd8 --- /dev/null +++ b/Ryujinx.Graphics.Shader/StructuredIr/AstComment.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + class AstComment : AstNode + { + public string Comment { get; } + + public AstComment(string comment) + { + Comment = comment; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/StructuredIr/AstHelper.cs b/Ryujinx.Graphics.Shader/StructuredIr/AstHelper.cs new file mode 100644 index 0000000000..9d3148e1bb --- /dev/null +++ b/Ryujinx.Graphics.Shader/StructuredIr/AstHelper.cs @@ -0,0 +1,73 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + static class AstHelper + { + public static void AddUse(IAstNode node, IAstNode parent) + { + if (node is AstOperand operand && operand.Type == OperandType.LocalVariable) + { + operand.Uses.Add(parent); + } + } + + public static void AddDef(IAstNode node, IAstNode parent) + { + if (node is AstOperand operand && operand.Type == OperandType.LocalVariable) + { + operand.Defs.Add(parent); + } + } + + public static void RemoveUse(IAstNode node, IAstNode parent) + { + if (node is AstOperand operand && operand.Type == OperandType.LocalVariable) + { + operand.Uses.Remove(parent); + } + } + + public static void RemoveDef(IAstNode node, IAstNode parent) + { + if (node is AstOperand operand && operand.Type == OperandType.LocalVariable) + { + operand.Defs.Remove(parent); + } + } + + public static AstAssignment Assign(IAstNode destination, IAstNode source) + { + return new AstAssignment(destination, source); + } + + public static AstOperand Const(int value) + { + return new AstOperand(OperandType.Constant, value); + } + + public static AstOperand Local(VariableType type) + { + AstOperand local = new AstOperand(OperandType.LocalVariable); + + local.VarType = type; + + return local; + } + + public static IAstNode InverseCond(IAstNode cond) + { + return new AstOperation(Instruction.LogicalNot, cond); + } + + public static IAstNode Next(IAstNode node) + { + return node.LLNode.Next?.Value; + } + + public static IAstNode Previous(IAstNode node) + { + return node.LLNode.Previous?.Value; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/StructuredIr/AstNode.cs b/Ryujinx.Graphics.Shader/StructuredIr/AstNode.cs new file mode 100644 index 0000000000..c667aac988 --- /dev/null +++ b/Ryujinx.Graphics.Shader/StructuredIr/AstNode.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + class AstNode : IAstNode + { + public AstBlock Parent { get; set; } + + public LinkedListNode LLNode { get; set; } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/StructuredIr/AstOperand.cs b/Ryujinx.Graphics.Shader/StructuredIr/AstOperand.cs new file mode 100644 index 0000000000..25b09636fe --- /dev/null +++ b/Ryujinx.Graphics.Shader/StructuredIr/AstOperand.cs @@ -0,0 +1,52 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + class AstOperand : AstNode + { + public HashSet Defs { get; } + public HashSet Uses { get; } + + public OperandType Type { get; } + + public VariableType VarType { get; set; } + + public InterpolationQualifier Interpolation { get; } + + public int Value { get; } + + public int CbufSlot { get; } + public int CbufOffset { get; } + + private AstOperand() + { + Defs = new HashSet(); + Uses = new HashSet(); + + VarType = VariableType.S32; + } + + public AstOperand(Operand operand) : this() + { + Type = operand.Type; + Interpolation = operand.Interpolation; + + if (Type == OperandType.ConstantBuffer) + { + CbufSlot = operand.GetCbufSlot(); + CbufOffset = operand.GetCbufOffset(); + } + else + { + Value = operand.Value; + } + } + + public AstOperand(OperandType type, int value = 0) : this() + { + Type = type; + Value = value; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/StructuredIr/AstOperation.cs b/Ryujinx.Graphics.Shader/StructuredIr/AstOperation.cs new file mode 100644 index 0000000000..76eee71e82 --- /dev/null +++ b/Ryujinx.Graphics.Shader/StructuredIr/AstOperation.cs @@ -0,0 +1,49 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; + +using static Ryujinx.Graphics.Shader.StructuredIr.AstHelper; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + class AstOperation : AstNode + { + public Instruction Inst { get; } + + public int Index { get; } + + private IAstNode[] _sources; + + public int SourcesCount => _sources.Length; + + public AstOperation(Instruction inst, params IAstNode[] sources) + { + Inst = inst; + _sources = sources; + + foreach (IAstNode source in sources) + { + AddUse(source, this); + } + + Index = 0; + } + + public AstOperation(Instruction inst, int index, params IAstNode[] sources) : this(inst, sources) + { + Index = index; + } + + public IAstNode GetSource(int index) + { + return _sources[index]; + } + + public void SetSource(int index, IAstNode source) + { + RemoveUse(_sources[index], this); + + AddUse(source, this); + + _sources[index] = source; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/StructuredIr/AstOptimizer.cs b/Ryujinx.Graphics.Shader/StructuredIr/AstOptimizer.cs new file mode 100644 index 0000000000..a37e1a3e85 --- /dev/null +++ b/Ryujinx.Graphics.Shader/StructuredIr/AstOptimizer.cs @@ -0,0 +1,155 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; +using System.Collections.Generic; +using System.Linq; + +using static Ryujinx.Graphics.Shader.StructuredIr.AstHelper; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + static class AstOptimizer + { + public static void Optimize(StructuredProgramContext context) + { + AstBlock mainBlock = context.Info.MainBlock; + + // When debug mode is enabled, we disable expression propagation + // (this makes comparison with the disassembly easier). + if ((context.Config.Flags & TranslationFlags.DebugMode) == 0) + { + AstBlockVisitor visitor = new AstBlockVisitor(mainBlock); + + foreach (IAstNode node in visitor.Visit()) + { + if (node is AstAssignment assignment && assignment.Destination is AstOperand propVar) + { + bool isWorthPropagating = propVar.Uses.Count == 1 || IsWorthPropagating(assignment.Source); + + if (propVar.Defs.Count == 1 && isWorthPropagating) + { + PropagateExpression(propVar, assignment.Source); + } + + if (propVar.Type == OperandType.LocalVariable && propVar.Uses.Count == 0) + { + visitor.Block.Remove(assignment); + + context.Info.Locals.Remove(propVar); + } + } + } + } + + RemoveEmptyBlocks(mainBlock); + } + + private static bool IsWorthPropagating(IAstNode source) + { + if (!(source is AstOperation srcOp)) + { + return false; + } + + if (!InstructionInfo.IsUnary(srcOp.Inst)) + { + return false; + } + + return srcOp.GetSource(0) is AstOperand || srcOp.Inst == Instruction.Copy; + } + + private static void PropagateExpression(AstOperand propVar, IAstNode source) + { + IAstNode[] uses = propVar.Uses.ToArray(); + + foreach (IAstNode useNode in uses) + { + if (useNode is AstBlock useBlock) + { + useBlock.Condition = source; + } + else if (useNode is AstOperation useOperation) + { + for (int srcIndex = 0; srcIndex < useOperation.SourcesCount; srcIndex++) + { + if (useOperation.GetSource(srcIndex) == propVar) + { + useOperation.SetSource(srcIndex, source); + } + } + } + else if (useNode is AstAssignment useAssignment) + { + useAssignment.Source = source; + } + } + } + + private static void RemoveEmptyBlocks(AstBlock mainBlock) + { + Queue pending = new Queue(); + + pending.Enqueue(mainBlock); + + while (pending.TryDequeue(out AstBlock block)) + { + foreach (IAstNode node in block) + { + if (node is AstBlock childBlock) + { + pending.Enqueue(childBlock); + } + } + + AstBlock parent = block.Parent; + + if (parent == null) + { + continue; + } + + AstBlock nextBlock = Next(block) as AstBlock; + + bool hasElse = nextBlock != null && nextBlock.Type == AstBlockType.Else; + + bool isIf = block.Type == AstBlockType.If; + + if (block.Count == 0) + { + if (isIf) + { + if (hasElse) + { + nextBlock.TurnIntoIf(InverseCond(block.Condition)); + } + + parent.Remove(block); + } + else if (block.Type == AstBlockType.Else) + { + parent.Remove(block); + } + } + else if (isIf && parent.Type == AstBlockType.Else && parent.Count == (hasElse ? 2 : 1)) + { + AstBlock parentOfParent = parent.Parent; + + parent.Remove(block); + + parentOfParent.AddAfter(parent, block); + + if (hasElse) + { + parent.Remove(nextBlock); + + parentOfParent.AddAfter(block, nextBlock); + } + + parentOfParent.Remove(parent); + + block.TurnIntoElseIf(); + } + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/StructuredIr/AstTextureOperation.cs b/Ryujinx.Graphics.Shader/StructuredIr/AstTextureOperation.cs new file mode 100644 index 0000000000..5473978e2b --- /dev/null +++ b/Ryujinx.Graphics.Shader/StructuredIr/AstTextureOperation.cs @@ -0,0 +1,28 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + class AstTextureOperation : AstOperation + { + public SamplerType Type { get; } + public TextureFlags Flags { get; } + + public int Handle { get; } + public int ArraySize { get; } + + public AstTextureOperation( + Instruction inst, + SamplerType type, + TextureFlags flags, + int handle, + int arraySize, + int index, + params IAstNode[] sources) : base(inst, index, sources) + { + Type = type; + Flags = flags; + Handle = handle; + ArraySize = arraySize; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/StructuredIr/GotoElimination.cs b/Ryujinx.Graphics.Shader/StructuredIr/GotoElimination.cs new file mode 100644 index 0000000000..8bcf9d9c92 --- /dev/null +++ b/Ryujinx.Graphics.Shader/StructuredIr/GotoElimination.cs @@ -0,0 +1,459 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System; +using System.Collections.Generic; + +using static Ryujinx.Graphics.Shader.StructuredIr.AstHelper; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + static class GotoElimination + { + // This is a modified version of the algorithm presented on the paper + // "Taming Control Flow: A Structured Approach to Eliminating Goto Statements". + public static void Eliminate(GotoStatement[] gotos) + { + for (int index = gotos.Length - 1; index >= 0; index--) + { + GotoStatement stmt = gotos[index]; + + AstBlock gBlock = ParentBlock(stmt.Goto); + AstBlock lBlock = ParentBlock(stmt.Label); + + int gLevel = Level(gBlock); + int lLevel = Level(lBlock); + + if (IndirectlyRelated(gBlock, lBlock, gLevel, lLevel)) + { + AstBlock drBlock = gBlock; + + int drLevel = gLevel; + + do + { + drBlock = drBlock.Parent; + + drLevel--; + } + while (!DirectlyRelated(drBlock, lBlock, drLevel, lLevel)); + + MoveOutward(stmt, gLevel, drLevel); + + gBlock = drBlock; + gLevel = drLevel; + + if (Previous(stmt.Goto) is AstBlock elseBlock && elseBlock.Type == AstBlockType.Else) + { + // It's possible that the label was enclosed inside an else block, + // in this case we need to update the block and level. + // We also need to set the IsLoop for the case when the label is + // now before the goto, due to the newly introduced else block. + lBlock = ParentBlock(stmt.Label); + + lLevel = Level(lBlock); + + if (!IndirectlyRelated(elseBlock, lBlock, gLevel + 1, lLevel)) + { + stmt.IsLoop = true; + } + } + } + + if (DirectlyRelated(gBlock, lBlock, gLevel, lLevel)) + { + if (gLevel > lLevel) + { + MoveOutward(stmt, gLevel, lLevel); + } + else + { + if (stmt.IsLoop) + { + Lift(stmt); + } + + MoveInward(stmt); + } + } + + gBlock = ParentBlock(stmt.Goto); + + if (stmt.IsLoop) + { + EncloseDoWhile(stmt, gBlock, stmt.Label); + } + else + { + Enclose(gBlock, AstBlockType.If, stmt.Condition, Next(stmt.Goto), stmt.Label); + } + + gBlock.Remove(stmt.Goto); + } + } + + private static bool IndirectlyRelated(AstBlock lBlock, AstBlock rBlock, int lLevel, int rlevel) + { + return !(lBlock == rBlock || DirectlyRelated(lBlock, rBlock, lLevel, rlevel)); + } + + private static bool DirectlyRelated(AstBlock lBlock, AstBlock rBlock, int lLevel, int rLevel) + { + // If the levels are equal, they can be either siblings or indirectly related. + if (lLevel == rLevel) + { + return false; + } + + IAstNode block; + IAstNode other; + + int blockLvl, otherLvl; + + if (lLevel > rLevel) + { + block = lBlock; + blockLvl = lLevel; + other = rBlock; + otherLvl = rLevel; + } + else /* if (rLevel > lLevel) */ + { + block = rBlock; + blockLvl = rLevel; + other = lBlock; + otherLvl = lLevel; + } + + while (blockLvl >= otherLvl) + { + if (block == other) + { + return true; + } + + block = block.Parent; + + blockLvl--; + } + + return false; + } + + private static void Lift(GotoStatement stmt) + { + AstBlock block = ParentBlock(stmt.Goto); + + AstBlock[] path = BackwardsPath(block, ParentBlock(stmt.Label)); + + AstBlock loopFirstStmt = path[path.Length - 1]; + + if (loopFirstStmt.Type == AstBlockType.Else) + { + loopFirstStmt = Previous(loopFirstStmt) as AstBlock; + + if (loopFirstStmt == null || loopFirstStmt.Type != AstBlockType.If) + { + throw new InvalidOperationException("Found an else without a matching if."); + } + } + + AstBlock newBlock = EncloseDoWhile(stmt, block, loopFirstStmt); + + block.Remove(stmt.Goto); + + newBlock.AddFirst(stmt.Goto); + + stmt.IsLoop = false; + } + + private static void MoveOutward(GotoStatement stmt, int gLevel, int lLevel) + { + AstBlock origin = ParentBlock(stmt.Goto); + + AstBlock block = origin; + + // Check if a loop is enclosing the goto, and the block that is + // directly related to the label is above the loop block. + // In that case, we need to introduce a break to get out of the loop. + AstBlock loopBlock = origin; + + int loopLevel = gLevel; + + while (loopLevel > lLevel) + { + AstBlock child = loopBlock; + + loopBlock = loopBlock.Parent; + + loopLevel--; + + if (child.Type == AstBlockType.DoWhile) + { + EncloseSingleInst(stmt, Instruction.LoopBreak); + + block.Remove(stmt.Goto); + + loopBlock.AddAfter(child, stmt.Goto); + + block = loopBlock; + gLevel = loopLevel; + } + } + + // Insert ifs to skip the parts that shouldn't be executed due to the goto. + bool tryInsertElse = stmt.IsUnconditional && origin.Type == AstBlockType.If; + + while (gLevel > lLevel) + { + Enclose(block, AstBlockType.If, stmt.Condition, Next(stmt.Goto)); + + block.Remove(stmt.Goto); + + AstBlock child = block; + + // We can't move the goto in the middle of a if and a else block, in + // this case we need to move it after the else. + // IsLoop may need to be updated if the label is inside the else, as + // introducing a loop is the only way to ensure the else will be executed. + if (Next(child) is AstBlock elseBlock && elseBlock.Type == AstBlockType.Else) + { + child = elseBlock; + } + + block = block.Parent; + + block.AddAfter(child, stmt.Goto); + + gLevel--; + + if (tryInsertElse && child == origin) + { + AstBlock lBlock = ParentBlock(stmt.Label); + + IAstNode last = block == lBlock && !stmt.IsLoop ? stmt.Label : null; + + AstBlock newBlock = Enclose(block, AstBlockType.Else, null, Next(stmt.Goto), last); + + if (newBlock != null) + { + block.Remove(stmt.Goto); + + block.AddAfter(newBlock, stmt.Goto); + } + } + } + } + + private static void MoveInward(GotoStatement stmt) + { + AstBlock block = ParentBlock(stmt.Goto); + + AstBlock[] path = BackwardsPath(block, ParentBlock(stmt.Label)); + + for (int index = path.Length - 1; index >= 0; index--) + { + AstBlock child = path[index]; + AstBlock last = child; + + if (child.Type == AstBlockType.If) + { + // Modify the if condition to allow it to be entered by the goto. + if (!ContainsCondComb(child.Condition, Instruction.LogicalOr, stmt.Condition)) + { + child.OrCondition(stmt.Condition); + } + } + else if (child.Type == AstBlockType.Else) + { + // Modify the matching if condition to force the else to be entered by the goto. + if (!(Previous(child) is AstBlock ifBlock) || ifBlock.Type != AstBlockType.If) + { + throw new InvalidOperationException("Found an else without a matching if."); + } + + IAstNode cond = InverseCond(stmt.Condition); + + if (!ContainsCondComb(ifBlock.Condition, Instruction.LogicalAnd, cond)) + { + ifBlock.AndCondition(cond); + } + + last = ifBlock; + } + + Enclose(block, AstBlockType.If, stmt.Condition, Next(stmt.Goto), last); + + block.Remove(stmt.Goto); + + child.AddFirst(stmt.Goto); + + block = child; + } + } + + private static bool ContainsCondComb(IAstNode node, Instruction inst, IAstNode newCond) + { + while (node is AstOperation operation && operation.SourcesCount == 2) + { + if (operation.Inst == inst && IsSameCond(operation.GetSource(1), newCond)) + { + return true; + } + + node = operation.GetSource(0); + } + + return false; + } + + private static AstBlock EncloseDoWhile(GotoStatement stmt, AstBlock block, IAstNode first) + { + if (block.Type == AstBlockType.DoWhile && first == block.First) + { + // We only need to insert the continue if we're not at the end of the loop, + // or if our condition is different from the loop condition. + if (Next(stmt.Goto) != null || block.Condition != stmt.Condition) + { + EncloseSingleInst(stmt, Instruction.LoopContinue); + } + + // Modify the do-while condition to allow it to continue. + if (!ContainsCondComb(block.Condition, Instruction.LogicalOr, stmt.Condition)) + { + block.OrCondition(stmt.Condition); + } + + return block; + } + + return Enclose(block, AstBlockType.DoWhile, stmt.Condition, first, stmt.Goto); + } + + private static void EncloseSingleInst(GotoStatement stmt, Instruction inst) + { + AstBlock block = ParentBlock(stmt.Goto); + + AstBlock newBlock = new AstBlock(AstBlockType.If, stmt.Condition); + + block.AddAfter(stmt.Goto, newBlock); + + newBlock.AddFirst(new AstOperation(inst)); + } + + private static AstBlock Enclose( + AstBlock block, + AstBlockType type, + IAstNode cond, + IAstNode first, + IAstNode last = null) + { + if (first == last) + { + return null; + } + + if (type == AstBlockType.If) + { + cond = InverseCond(cond); + } + + // Do a quick check, if we are enclosing a single block, + // and the block type/condition matches the one we're going + // to create, then we don't need a new block, we can just + // return the old one. + bool hasSingleNode = Next(first) == last; + + if (hasSingleNode && BlockMatches(first, type, cond)) + { + return first as AstBlock; + } + + AstBlock newBlock = new AstBlock(type, cond); + + block.AddBefore(first, newBlock); + + while (first != last) + { + IAstNode next = Next(first); + + block.Remove(first); + + newBlock.Add(first); + + first = next; + } + + return newBlock; + } + + private static bool BlockMatches(IAstNode node, AstBlockType type, IAstNode cond) + { + if (!(node is AstBlock block)) + { + return false; + } + + return block.Type == type && IsSameCond(block.Condition, cond); + } + + private static bool IsSameCond(IAstNode lCond, IAstNode rCond) + { + if (lCond is AstOperation lCondOp && lCondOp.Inst == Instruction.LogicalNot) + { + if (!(rCond is AstOperation rCondOp) || rCondOp.Inst != lCondOp.Inst) + { + return false; + } + + lCond = lCondOp.GetSource(0); + rCond = rCondOp.GetSource(0); + } + + return lCond == rCond; + } + + private static AstBlock ParentBlock(IAstNode node) + { + if (node is AstBlock block) + { + return block.Parent; + } + + while (!(node is AstBlock)) + { + node = node.Parent; + } + + return node as AstBlock; + } + + private static AstBlock[] BackwardsPath(AstBlock top, AstBlock bottom) + { + AstBlock block = bottom; + + List path = new List(); + + while (block != top) + { + path.Add(block); + + block = block.Parent; + } + + return path.ToArray(); + } + + private static int Level(IAstNode node) + { + int level = 0; + + while (node != null) + { + level++; + + node = node.Parent; + } + + return level; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/StructuredIr/GotoStatement.cs b/Ryujinx.Graphics.Shader/StructuredIr/GotoStatement.cs new file mode 100644 index 0000000000..25216e55fb --- /dev/null +++ b/Ryujinx.Graphics.Shader/StructuredIr/GotoStatement.cs @@ -0,0 +1,23 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + class GotoStatement + { + public AstOperation Goto { get; } + public AstAssignment Label { get; } + + public IAstNode Condition => Label.Destination; + + public bool IsLoop { get; set; } + + public bool IsUnconditional => Goto.Inst == Instruction.Branch; + + public GotoStatement(AstOperation branch, AstAssignment label, bool isLoop) + { + Goto = branch; + Label = label; + IsLoop = isLoop; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/StructuredIr/HelperFunctionsMask.cs b/Ryujinx.Graphics.Shader/StructuredIr/HelperFunctionsMask.cs new file mode 100644 index 0000000000..53367fce14 --- /dev/null +++ b/Ryujinx.Graphics.Shader/StructuredIr/HelperFunctionsMask.cs @@ -0,0 +1,16 @@ +using System; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + [Flags] + enum HelperFunctionsMask + { + MultiplyHighS32 = 1 << 0, + MultiplyHighU32 = 1 << 1, + Shuffle = 1 << 2, + ShuffleDown = 1 << 3, + ShuffleUp = 1 << 4, + ShuffleXor = 1 << 5, + SwizzleAdd = 1 << 6 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/StructuredIr/IAstNode.cs b/Ryujinx.Graphics.Shader/StructuredIr/IAstNode.cs new file mode 100644 index 0000000000..5ececbb5e4 --- /dev/null +++ b/Ryujinx.Graphics.Shader/StructuredIr/IAstNode.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + interface IAstNode + { + AstBlock Parent { get; set; } + + LinkedListNode LLNode { get; set; } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/StructuredIr/InstructionInfo.cs b/Ryujinx.Graphics.Shader/StructuredIr/InstructionInfo.cs new file mode 100644 index 0000000000..0482c35eea --- /dev/null +++ b/Ryujinx.Graphics.Shader/StructuredIr/InstructionInfo.cs @@ -0,0 +1,188 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + static class InstructionInfo + { + private struct InstInfo + { + public VariableType DestType { get; } + + public VariableType[] SrcTypes { get; } + + public InstInfo(VariableType destType, params VariableType[] srcTypes) + { + DestType = destType; + SrcTypes = srcTypes; + } + } + + private static InstInfo[] _infoTbl; + + static InstructionInfo() + { + _infoTbl = new InstInfo[(int)Instruction.Count]; + + // Inst Destination type Source 1 type Source 2 type Source 3 type Source 4 type + Add(Instruction.AtomicAdd, VariableType.U32, VariableType.S32, VariableType.S32, VariableType.U32); + Add(Instruction.AtomicAnd, VariableType.U32, VariableType.S32, VariableType.S32, VariableType.U32); + Add(Instruction.AtomicCompareAndSwap, VariableType.U32, VariableType.S32, VariableType.S32, VariableType.U32, VariableType.U32); + Add(Instruction.AtomicMaxS32, VariableType.S32, VariableType.S32, VariableType.S32, VariableType.S32); + Add(Instruction.AtomicMaxU32, VariableType.U32, VariableType.S32, VariableType.S32, VariableType.U32); + Add(Instruction.AtomicMinS32, VariableType.S32, VariableType.S32, VariableType.S32, VariableType.S32); + Add(Instruction.AtomicMinU32, VariableType.U32, VariableType.S32, VariableType.S32, VariableType.U32); + Add(Instruction.AtomicOr, VariableType.U32, VariableType.S32, VariableType.S32, VariableType.U32); + Add(Instruction.AtomicSwap, VariableType.U32, VariableType.S32, VariableType.S32, VariableType.U32); + Add(Instruction.AtomicXor, VariableType.U32, VariableType.S32, VariableType.S32, VariableType.U32); + Add(Instruction.Absolute, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.Add, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.Ballot, VariableType.U32, VariableType.Bool); + Add(Instruction.BitCount, VariableType.Int, VariableType.Int); + Add(Instruction.BitfieldExtractS32, VariableType.S32, VariableType.S32, VariableType.S32, VariableType.S32); + Add(Instruction.BitfieldExtractU32, VariableType.U32, VariableType.U32, VariableType.S32, VariableType.S32); + Add(Instruction.BitfieldInsert, VariableType.Int, VariableType.Int, VariableType.Int, VariableType.S32, VariableType.S32); + Add(Instruction.BitfieldReverse, VariableType.Int, VariableType.Int); + Add(Instruction.BitwiseAnd, VariableType.Int, VariableType.Int, VariableType.Int); + Add(Instruction.BitwiseExclusiveOr, VariableType.Int, VariableType.Int, VariableType.Int); + Add(Instruction.BitwiseNot, VariableType.Int, VariableType.Int); + Add(Instruction.BitwiseOr, VariableType.Int, VariableType.Int, VariableType.Int); + Add(Instruction.BranchIfTrue, VariableType.None, VariableType.Bool); + Add(Instruction.BranchIfFalse, VariableType.None, VariableType.Bool); + Add(Instruction.Ceiling, VariableType.F32, VariableType.F32, VariableType.F32); + Add(Instruction.Clamp, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.ClampU32, VariableType.U32, VariableType.U32, VariableType.U32, VariableType.U32); + Add(Instruction.CompareEqual, VariableType.Bool, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.CompareGreater, VariableType.Bool, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.CompareGreaterOrEqual, VariableType.Bool, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.CompareGreaterOrEqualU32, VariableType.Bool, VariableType.U32, VariableType.U32); + Add(Instruction.CompareGreaterU32, VariableType.Bool, VariableType.U32, VariableType.U32); + Add(Instruction.CompareLess, VariableType.Bool, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.CompareLessOrEqual, VariableType.Bool, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.CompareLessOrEqualU32, VariableType.Bool, VariableType.U32, VariableType.U32); + Add(Instruction.CompareLessU32, VariableType.Bool, VariableType.U32, VariableType.U32); + Add(Instruction.CompareNotEqual, VariableType.Bool, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.ConditionalSelect, VariableType.Scalar, VariableType.Bool, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.ConvertFPToS32, VariableType.S32, VariableType.F32); + Add(Instruction.ConvertFPToU32, VariableType.U32, VariableType.F32); + Add(Instruction.ConvertS32ToFP, VariableType.F32, VariableType.S32); + Add(Instruction.ConvertU32ToFP, VariableType.F32, VariableType.U32); + Add(Instruction.Cosine, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.Ddx, VariableType.F32, VariableType.F32); + Add(Instruction.Ddy, VariableType.F32, VariableType.F32); + Add(Instruction.Divide, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.ExponentB2, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.FindFirstSetS32, VariableType.S32, VariableType.S32); + Add(Instruction.FindFirstSetU32, VariableType.S32, VariableType.U32); + Add(Instruction.Floor, VariableType.F32, VariableType.F32); + Add(Instruction.FusedMultiplyAdd, VariableType.F32, VariableType.F32, VariableType.F32, VariableType.F32); + Add(Instruction.ImageLoad, VariableType.F32); + Add(Instruction.ImageStore, VariableType.None); + Add(Instruction.IsNan, VariableType.Bool, VariableType.F32); + Add(Instruction.LoadAttribute, VariableType.F32, VariableType.S32, VariableType.S32); + Add(Instruction.LoadConstant, VariableType.F32, VariableType.S32, VariableType.S32); + Add(Instruction.LoadGlobal, VariableType.U32, VariableType.S32, VariableType.S32); + Add(Instruction.LoadLocal, VariableType.U32, VariableType.S32); + Add(Instruction.LoadShared, VariableType.U32, VariableType.S32); + Add(Instruction.LoadStorage, VariableType.U32, VariableType.S32, VariableType.S32); + Add(Instruction.Lod, VariableType.F32); + Add(Instruction.LogarithmB2, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.LogicalAnd, VariableType.Bool, VariableType.Bool, VariableType.Bool); + Add(Instruction.LogicalExclusiveOr, VariableType.Bool, VariableType.Bool, VariableType.Bool); + Add(Instruction.LogicalNot, VariableType.Bool, VariableType.Bool); + Add(Instruction.LogicalOr, VariableType.Bool, VariableType.Bool, VariableType.Bool); + Add(Instruction.ShiftLeft, VariableType.Int, VariableType.Int, VariableType.Int); + Add(Instruction.ShiftRightS32, VariableType.S32, VariableType.S32, VariableType.Int); + Add(Instruction.ShiftRightU32, VariableType.U32, VariableType.U32, VariableType.Int); + Add(Instruction.Shuffle, VariableType.F32, VariableType.F32, VariableType.U32, VariableType.U32); + Add(Instruction.ShuffleDown, VariableType.F32, VariableType.F32, VariableType.U32, VariableType.U32); + Add(Instruction.ShuffleUp, VariableType.F32, VariableType.F32, VariableType.U32, VariableType.U32); + Add(Instruction.ShuffleXor, VariableType.F32, VariableType.F32, VariableType.U32, VariableType.U32); + Add(Instruction.Maximum, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.MaximumU32, VariableType.U32, VariableType.U32, VariableType.U32); + Add(Instruction.Minimum, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.MinimumU32, VariableType.U32, VariableType.U32, VariableType.U32); + Add(Instruction.Multiply, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.MultiplyHighS32, VariableType.S32, VariableType.S32, VariableType.S32); + Add(Instruction.MultiplyHighU32, VariableType.U32, VariableType.U32, VariableType.U32); + Add(Instruction.Negate, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.PackHalf2x16, VariableType.U32, VariableType.F32, VariableType.F32); + Add(Instruction.ReciprocalSquareRoot, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.Round, VariableType.F32, VariableType.F32); + Add(Instruction.Sine, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.SquareRoot, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.StoreGlobal, VariableType.None, VariableType.S32, VariableType.S32, VariableType.U32); + Add(Instruction.StoreLocal, VariableType.None, VariableType.S32, VariableType.U32); + Add(Instruction.StoreShared, VariableType.None, VariableType.S32, VariableType.U32); + Add(Instruction.StoreStorage, VariableType.None, VariableType.S32, VariableType.S32, VariableType.U32); + Add(Instruction.Subtract, VariableType.Scalar, VariableType.Scalar, VariableType.Scalar); + Add(Instruction.SwizzleAdd, VariableType.F32, VariableType.F32, VariableType.F32, VariableType.S32); + Add(Instruction.TextureSample, VariableType.F32); + Add(Instruction.TextureSize, VariableType.S32, VariableType.S32, VariableType.S32); + Add(Instruction.Truncate, VariableType.F32, VariableType.F32); + Add(Instruction.UnpackHalf2x16, VariableType.F32, VariableType.U32); + Add(Instruction.VoteAll, VariableType.Bool, VariableType.Bool); + Add(Instruction.VoteAllEqual, VariableType.Bool, VariableType.Bool); + Add(Instruction.VoteAny, VariableType.Bool, VariableType.Bool); + } + + private static void Add(Instruction inst, VariableType destType, params VariableType[] srcTypes) + { + _infoTbl[(int)inst] = new InstInfo(destType, srcTypes); + } + + public static VariableType GetDestVarType(Instruction inst) + { + return GetFinalVarType(_infoTbl[(int)(inst & Instruction.Mask)].DestType, inst); + } + + public static VariableType GetSrcVarType(Instruction inst, int index) + { + // TODO: Return correct type depending on source index, + // that can improve the decompiler output. + if (inst == Instruction.ImageLoad || + inst == Instruction.ImageStore || + inst == Instruction.Lod || + inst == Instruction.TextureSample) + { + return VariableType.F32; + } + + return GetFinalVarType(_infoTbl[(int)(inst & Instruction.Mask)].SrcTypes[index], inst); + } + + private static VariableType GetFinalVarType(VariableType type, Instruction inst) + { + if (type == VariableType.Scalar) + { + return (inst & Instruction.FP) != 0 + ? VariableType.F32 + : VariableType.S32; + } + else if (type == VariableType.Int) + { + return VariableType.S32; + } + else if (type == VariableType.None) + { + throw new ArgumentException($"Invalid operand for instruction \"{inst}\"."); + } + + return type; + } + + public static bool IsUnary(Instruction inst) + { + if (inst == Instruction.Copy) + { + return true; + } + else if (inst == Instruction.TextureSample) + { + return false; + } + + return _infoTbl[(int)(inst & Instruction.Mask)].SrcTypes.Length == 1; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/StructuredIr/OperandInfo.cs b/Ryujinx.Graphics.Shader/StructuredIr/OperandInfo.cs new file mode 100644 index 0000000000..95c5731a9f --- /dev/null +++ b/Ryujinx.Graphics.Shader/StructuredIr/OperandInfo.cs @@ -0,0 +1,33 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + static class OperandInfo + { + public static VariableType GetVarType(AstOperand operand) + { + if (operand.Type == OperandType.LocalVariable) + { + return operand.VarType; + } + else + { + return GetVarType(operand.Type); + } + } + + public static VariableType GetVarType(OperandType type) + { + switch (type) + { + case OperandType.Attribute: return VariableType.F32; + case OperandType.Constant: return VariableType.S32; + case OperandType.ConstantBuffer: return VariableType.F32; + case OperandType.Undefined: return VariableType.S32; + } + + throw new ArgumentException($"Invalid operand type \"{type}\"."); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/StructuredIr/PhiFunctions.cs b/Ryujinx.Graphics.Shader/StructuredIr/PhiFunctions.cs new file mode 100644 index 0000000000..53391b6268 --- /dev/null +++ b/Ryujinx.Graphics.Shader/StructuredIr/PhiFunctions.cs @@ -0,0 +1,74 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + static class PhiFunctions + { + public static void Remove(BasicBlock[] blocks) + { + for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++) + { + BasicBlock block = blocks[blkIndex]; + + LinkedListNode node = block.Operations.First; + + while (node != null) + { + LinkedListNode nextNode = node.Next; + + if (!(node.Value is PhiNode phi)) + { + node = nextNode; + + continue; + } + + for (int index = 0; index < phi.SourcesCount; index++) + { + Operand src = phi.GetSource(index); + + BasicBlock srcBlock = phi.GetBlock(index); + + Operation copyOp = new Operation(Instruction.Copy, phi.Dest, src); + + AddBeforeBranch(srcBlock, copyOp); + } + + block.Operations.Remove(node); + + node = nextNode; + } + } + } + + private static void AddBeforeBranch(BasicBlock block, INode node) + { + INode lastOp = block.GetLastOp(); + + if (lastOp is Operation operation && IsControlFlowInst(operation.Inst)) + { + block.Operations.AddBefore(block.Operations.Last, node); + } + else + { + block.Operations.AddLast(node); + } + } + + private static bool IsControlFlowInst(Instruction inst) + { + switch (inst) + { + case Instruction.Branch: + case Instruction.BranchIfFalse: + case Instruction.BranchIfTrue: + case Instruction.Discard: + case Instruction.Return: + return true; + } + + return false; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs b/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs new file mode 100644 index 0000000000..504dc38676 --- /dev/null +++ b/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs @@ -0,0 +1,345 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + static class StructuredProgram + { + public static StructuredProgramInfo MakeStructuredProgram(BasicBlock[] blocks, ShaderConfig config) + { + PhiFunctions.Remove(blocks); + + StructuredProgramContext context = new StructuredProgramContext(blocks.Length, config); + + for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++) + { + BasicBlock block = blocks[blkIndex]; + + context.EnterBlock(block); + + foreach (INode node in block.Operations) + { + Operation operation = (Operation)node; + + if (IsBranchInst(operation.Inst)) + { + context.LeaveBlock(block, operation); + } + else + { + AddOperation(context, operation); + } + } + } + + GotoElimination.Eliminate(context.GetGotos()); + + AstOptimizer.Optimize(context); + + return context.Info; + } + + private static void AddOperation(StructuredProgramContext context, Operation operation) + { + Instruction inst = operation.Inst; + + IAstNode[] sources = new IAstNode[operation.SourcesCount]; + + for (int index = 0; index < sources.Length; index++) + { + sources[index] = context.GetOperandUse(operation.GetSource(index)); + } + + AstTextureOperation GetAstTextureOperation(TextureOperation texOp) + { + return new AstTextureOperation( + inst, + texOp.Type, + texOp.Flags, + texOp.Handle, + 4, // TODO: Non-hardcoded array size. + texOp.Index, + sources); + } + + if (operation.Dest != null) + { + AstOperand dest = context.GetOperandDef(operation.Dest); + + if (inst == Instruction.LoadConstant) + { + Operand slot = operation.GetSource(0); + + if (slot.Type != OperandType.Constant) + { + throw new InvalidOperationException("Found load with non-constant constant buffer slot."); + } + + context.Info.CBuffers.Add(slot.Value); + } + else if (UsesStorage(inst)) + { + AddSBufferUse(context.Info.SBuffers, operation); + } + + AstAssignment assignment; + + // If all the sources are bool, it's better to use short-circuiting + // logical operations, rather than forcing a cast to int and doing + // a bitwise operation with the value, as it is likely to be used as + // a bool in the end. + if (IsBitwiseInst(inst) && AreAllSourceTypesEqual(sources, VariableType.Bool)) + { + inst = GetLogicalFromBitwiseInst(inst); + } + + bool isCondSel = inst == Instruction.ConditionalSelect; + bool isCopy = inst == Instruction.Copy; + + if (isCondSel || isCopy) + { + VariableType type = GetVarTypeFromUses(operation.Dest); + + if (isCondSel && type == VariableType.F32) + { + inst |= Instruction.FP; + } + + dest.VarType = type; + } + else + { + dest.VarType = InstructionInfo.GetDestVarType(inst); + } + + IAstNode source; + + if (operation is TextureOperation texOp) + { + AstTextureOperation astTexOp = GetAstTextureOperation(texOp); + + if (texOp.Inst == Instruction.ImageLoad) + { + context.Info.Images.Add(astTexOp); + } + else + { + context.Info.Samplers.Add(astTexOp); + } + + source = astTexOp; + } + else if (!isCopy) + { + source = new AstOperation(inst, operation.Index, sources); + } + else + { + source = sources[0]; + } + + assignment = new AstAssignment(dest, source); + + context.AddNode(assignment); + } + else if (operation.Inst == Instruction.Comment) + { + context.AddNode(new AstComment(((CommentNode)operation).Comment)); + } + else if (operation is TextureOperation texOp) + { + AstTextureOperation astTexOp = GetAstTextureOperation(texOp); + + context.Info.Images.Add(astTexOp); + + context.AddNode(astTexOp); + } + else + { + if (UsesStorage(inst)) + { + AddSBufferUse(context.Info.SBuffers, operation); + } + + context.AddNode(new AstOperation(inst, operation.Index, sources)); + } + + // Those instructions needs to be emulated by using helper functions, + // because they are NVIDIA specific. Those flags helps the backend to + // decide which helper functions are needed on the final generated code. + switch (operation.Inst) + { + case Instruction.MultiplyHighS32: + context.Info.HelperFunctionsMask |= HelperFunctionsMask.MultiplyHighS32; + break; + case Instruction.MultiplyHighU32: + context.Info.HelperFunctionsMask |= HelperFunctionsMask.MultiplyHighU32; + break; + case Instruction.Shuffle: + context.Info.HelperFunctionsMask |= HelperFunctionsMask.Shuffle; + break; + case Instruction.ShuffleDown: + context.Info.HelperFunctionsMask |= HelperFunctionsMask.ShuffleDown; + break; + case Instruction.ShuffleUp: + context.Info.HelperFunctionsMask |= HelperFunctionsMask.ShuffleUp; + break; + case Instruction.ShuffleXor: + context.Info.HelperFunctionsMask |= HelperFunctionsMask.ShuffleXor; + break; + case Instruction.SwizzleAdd: + context.Info.HelperFunctionsMask |= HelperFunctionsMask.SwizzleAdd; + break; + } + } + + private static void AddSBufferUse(HashSet sBuffers, Operation operation) + { + Operand slot = operation.GetSource(0); + + if (slot.Type == OperandType.Constant) + { + sBuffers.Add(slot.Value); + } + else + { + // If the value is not constant, then we don't know + // how many storage buffers are used, so we assume + // all of them are used. + for (int index = 0; index < GlobalMemory.StorageMaxCount; index++) + { + sBuffers.Add(index); + } + } + } + + private static VariableType GetVarTypeFromUses(Operand dest) + { + HashSet visited = new HashSet(); + + Queue pending = new Queue(); + + bool Enqueue(Operand operand) + { + if (visited.Add(operand)) + { + pending.Enqueue(operand); + + return true; + } + + return false; + } + + Enqueue(dest); + + while (pending.TryDequeue(out Operand operand)) + { + foreach (INode useNode in operand.UseOps) + { + if (!(useNode is Operation operation)) + { + continue; + } + + if (operation.Inst == Instruction.Copy) + { + if (operation.Dest.Type == OperandType.LocalVariable) + { + if (Enqueue(operation.Dest)) + { + break; + } + } + else + { + return OperandInfo.GetVarType(operation.Dest.Type); + } + } + else + { + for (int index = 0; index < operation.SourcesCount; index++) + { + if (operation.GetSource(index) == operand) + { + return InstructionInfo.GetSrcVarType(operation.Inst, index); + } + } + } + } + } + + return VariableType.S32; + } + + private static bool AreAllSourceTypesEqual(IAstNode[] sources, VariableType type) + { + foreach (IAstNode node in sources) + { + if (!(node is AstOperand operand)) + { + return false; + } + + if (operand.VarType != type) + { + return false; + } + } + + return true; + } + + private static bool IsBranchInst(Instruction inst) + { + switch (inst) + { + case Instruction.Branch: + case Instruction.BranchIfFalse: + case Instruction.BranchIfTrue: + return true; + } + + return false; + } + + private static bool IsBitwiseInst(Instruction inst) + { + switch (inst) + { + case Instruction.BitwiseAnd: + case Instruction.BitwiseExclusiveOr: + case Instruction.BitwiseNot: + case Instruction.BitwiseOr: + return true; + } + + return false; + } + + private static Instruction GetLogicalFromBitwiseInst(Instruction inst) + { + switch (inst) + { + case Instruction.BitwiseAnd: return Instruction.LogicalAnd; + case Instruction.BitwiseExclusiveOr: return Instruction.LogicalExclusiveOr; + case Instruction.BitwiseNot: return Instruction.LogicalNot; + case Instruction.BitwiseOr: return Instruction.LogicalOr; + } + + throw new ArgumentException($"Unexpected instruction \"{inst}\"."); + } + + private static bool UsesStorage(Instruction inst) + { + if (inst == Instruction.LoadStorage || inst == Instruction.StoreStorage) + { + return true; + } + + return inst.IsAtomic() && (inst & Instruction.MrMask) == Instruction.MrStorage; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramContext.cs b/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramContext.cs new file mode 100644 index 0000000000..f2af84f3b5 --- /dev/null +++ b/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramContext.cs @@ -0,0 +1,340 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; +using System.Collections.Generic; +using System.Linq; + +using static Ryujinx.Graphics.Shader.StructuredIr.AstHelper; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + class StructuredProgramContext + { + private HashSet _loopTails; + + private Stack<(AstBlock Block, int CurrEndIndex, int LoopEndIndex)> _blockStack; + + private Dictionary _localsMap; + + private Dictionary _gotoTempAsgs; + + private List _gotos; + + private AstBlock _currBlock; + + private int _currEndIndex; + private int _loopEndIndex; + + public StructuredProgramInfo Info { get; } + + public ShaderConfig Config { get; } + + public StructuredProgramContext(int blocksCount, ShaderConfig config) + { + _loopTails = new HashSet(); + + _blockStack = new Stack<(AstBlock, int, int)>(); + + _localsMap = new Dictionary(); + + _gotoTempAsgs = new Dictionary(); + + _gotos = new List(); + + _currBlock = new AstBlock(AstBlockType.Main); + + _currEndIndex = blocksCount; + _loopEndIndex = blocksCount; + + Info = new StructuredProgramInfo(_currBlock); + + Config = config; + } + + public void EnterBlock(BasicBlock block) + { + while (_currEndIndex == block.Index) + { + (_currBlock, _currEndIndex, _loopEndIndex) = _blockStack.Pop(); + } + + if (_gotoTempAsgs.TryGetValue(block.Index, out AstAssignment gotoTempAsg)) + { + AddGotoTempReset(block, gotoTempAsg); + } + + LookForDoWhileStatements(block); + } + + public void LeaveBlock(BasicBlock block, Operation branchOp) + { + LookForIfStatements(block, branchOp); + } + + private void LookForDoWhileStatements(BasicBlock block) + { + // Check if we have any predecessor whose index is greater than the + // current block, this indicates a loop. + bool done = false; + + foreach (BasicBlock predecessor in block.Predecessors.OrderByDescending(x => x.Index)) + { + // If not a loop, break. + if (predecessor.Index < block.Index) + { + break; + } + + // Check if we can create a do-while loop here (only possible if the loop end + // falls inside the current scope), if not add a goto instead. + if (predecessor.Index < _currEndIndex && !done) + { + // Create do-while loop block. We must avoid inserting a goto at the end + // of the loop later, when the tail block is processed. So we add the predecessor + // to a list of loop tails to prevent it from being processed later. + Operation branchOp = (Operation)predecessor.GetLastOp(); + + NewBlock(AstBlockType.DoWhile, branchOp, predecessor.Index + 1); + + _loopTails.Add(predecessor); + + done = true; + } + else + { + // Failed to create loop. Since this block is the loop head, we reset the + // goto condition variable here. The variable is always reset on the jump + // target, and this block is the jump target for some loop. + AddGotoTempReset(block, GetGotoTempAsg(block.Index)); + + break; + } + } + } + + private void LookForIfStatements(BasicBlock block, Operation branchOp) + { + if (block.Branch == null) + { + return; + } + + // We can only enclose the "if" when the branch lands before + // the end of the current block. If the current enclosing block + // is not a loop, then we can also do so if the branch lands + // right at the end of the current block. When it is a loop, + // this is not valid as the loop condition would be evaluated, + // and it could erroneously jump back to the start of the loop. + bool inRange = + block.Branch.Index < _currEndIndex || + (block.Branch.Index == _currEndIndex && block.Branch.Index < _loopEndIndex); + + bool isLoop = block.Branch.Index <= block.Index; + + if (inRange && !isLoop) + { + NewBlock(AstBlockType.If, branchOp, block.Branch.Index); + } + else if (!_loopTails.Contains(block)) + { + AstAssignment gotoTempAsg = GetGotoTempAsg(block.Branch.Index); + + // We use DoWhile type here, as the condition should be true for + // unconditional branches, or it should jump if the condition is true otherwise. + IAstNode cond = GetBranchCond(AstBlockType.DoWhile, branchOp); + + AddNode(Assign(gotoTempAsg.Destination, cond)); + + AstOperation branch = new AstOperation(branchOp.Inst); + + AddNode(branch); + + GotoStatement gotoStmt = new GotoStatement(branch, gotoTempAsg, isLoop); + + _gotos.Add(gotoStmt); + } + } + + private AstAssignment GetGotoTempAsg(int index) + { + if (_gotoTempAsgs.TryGetValue(index, out AstAssignment gotoTempAsg)) + { + return gotoTempAsg; + } + + AstOperand gotoTemp = NewTemp(VariableType.Bool); + + gotoTempAsg = Assign(gotoTemp, Const(IrConsts.False)); + + _gotoTempAsgs.Add(index, gotoTempAsg); + + return gotoTempAsg; + } + + private void AddGotoTempReset(BasicBlock block, AstAssignment gotoTempAsg) + { + // If it was already added, we don't need to add it again. + if (gotoTempAsg.Parent != null) + { + return; + } + + AddNode(gotoTempAsg); + + // For block 0, we don't need to add the extra "reset" at the beginning, + // because it is already the first node to be executed on the shader, + // so it is reset to false by the "local" assignment anyway. + if (block.Index != 0) + { + Info.MainBlock.AddFirst(Assign(gotoTempAsg.Destination, Const(IrConsts.False))); + } + } + + private void NewBlock(AstBlockType type, Operation branchOp, int endIndex) + { + NewBlock(type, GetBranchCond(type, branchOp), endIndex); + } + + private void NewBlock(AstBlockType type, IAstNode cond, int endIndex) + { + AstBlock childBlock = new AstBlock(type, cond); + + AddNode(childBlock); + + _blockStack.Push((_currBlock, _currEndIndex, _loopEndIndex)); + + _currBlock = childBlock; + _currEndIndex = endIndex; + + if (type == AstBlockType.DoWhile) + { + _loopEndIndex = endIndex; + } + } + + private IAstNode GetBranchCond(AstBlockType type, Operation branchOp) + { + IAstNode cond; + + if (branchOp.Inst == Instruction.Branch) + { + // If the branch is not conditional, the condition is a constant. + // For if it's false (always jump over, if block never executed). + // For loops it's always true (always loop). + cond = Const(type == AstBlockType.If ? IrConsts.False : IrConsts.True); + } + else + { + cond = GetOperandUse(branchOp.GetSource(0)); + + Instruction invInst = type == AstBlockType.If + ? Instruction.BranchIfTrue + : Instruction.BranchIfFalse; + + if (branchOp.Inst == invInst) + { + cond = new AstOperation(Instruction.LogicalNot, cond); + } + } + + return cond; + } + + public void AddNode(IAstNode node) + { + _currBlock.Add(node); + } + + public GotoStatement[] GetGotos() + { + return _gotos.ToArray(); + } + + private AstOperand NewTemp(VariableType type) + { + AstOperand newTemp = Local(type); + + Info.Locals.Add(newTemp); + + return newTemp; + } + + public AstOperand GetOperandDef(Operand operand) + { + if (TryGetUserAttributeIndex(operand, out int attrIndex)) + { + Info.OAttributes.Add(attrIndex); + } + + return GetOperand(operand); + } + + public AstOperand GetOperandUse(Operand operand) + { + if (TryGetUserAttributeIndex(operand, out int attrIndex)) + { + Info.IAttributes.Add(attrIndex); + + Info.InterpolationQualifiers[attrIndex] = operand.Interpolation; + } + else if (operand.Type == OperandType.Attribute && operand.Value == AttributeConsts.InstanceId) + { + Info.UsesInstanceId = true; + } + else if (operand.Type == OperandType.ConstantBuffer) + { + Info.CBuffers.Add(operand.GetCbufSlot()); + } + + return GetOperand(operand); + } + + private AstOperand GetOperand(Operand operand) + { + if (operand == null) + { + return null; + } + + if (operand.Type != OperandType.LocalVariable) + { + return new AstOperand(operand); + } + + if (!_localsMap.TryGetValue(operand, out AstOperand astOperand)) + { + astOperand = new AstOperand(operand); + + _localsMap.Add(operand, astOperand); + + Info.Locals.Add(astOperand); + } + + return astOperand; + } + + private static bool TryGetUserAttributeIndex(Operand operand, out int attrIndex) + { + if (operand.Type == OperandType.Attribute) + { + if (operand.Value >= AttributeConsts.UserAttributeBase && + operand.Value < AttributeConsts.UserAttributeEnd) + { + attrIndex = (operand.Value - AttributeConsts.UserAttributeBase) >> 4; + + return true; + } + else if (operand.Value >= AttributeConsts.FragmentOutputColorBase && + operand.Value < AttributeConsts.FragmentOutputColorEnd) + { + attrIndex = (operand.Value - AttributeConsts.FragmentOutputColorBase) >> 4; + + return true; + } + } + + attrIndex = 0; + + return false; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramInfo.cs b/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramInfo.cs new file mode 100644 index 0000000000..0ef4bde340 --- /dev/null +++ b/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramInfo.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + class StructuredProgramInfo + { + public AstBlock MainBlock { get; } + + public HashSet Locals { get; } + + public HashSet CBuffers { get; } + public HashSet SBuffers { get; } + + public HashSet IAttributes { get; } + public HashSet OAttributes { get; } + + public InterpolationQualifier[] InterpolationQualifiers { get; } + + public bool UsesInstanceId { get; set; } + + public HelperFunctionsMask HelperFunctionsMask { get; set; } + + public HashSet Samplers { get; } + public HashSet Images { get; } + + public StructuredProgramInfo(AstBlock mainBlock) + { + MainBlock = mainBlock; + + Locals = new HashSet(); + + CBuffers = new HashSet(); + SBuffers = new HashSet(); + + IAttributes = new HashSet(); + OAttributes = new HashSet(); + + InterpolationQualifiers = new InterpolationQualifier[32]; + + Samplers = new HashSet(); + Images = new HashSet(); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/StructuredIr/VariableType.cs b/Ryujinx.Graphics.Shader/StructuredIr/VariableType.cs new file mode 100644 index 0000000000..4c7f384978 --- /dev/null +++ b/Ryujinx.Graphics.Shader/StructuredIr/VariableType.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + enum VariableType + { + None, + Bool, + Scalar, + Int, + F32, + S32, + U32 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/TextureDescriptor.cs b/Ryujinx.Graphics.Shader/TextureDescriptor.cs new file mode 100644 index 0000000000..fae9b58c33 --- /dev/null +++ b/Ryujinx.Graphics.Shader/TextureDescriptor.cs @@ -0,0 +1,40 @@ +namespace Ryujinx.Graphics.Shader +{ + public struct TextureDescriptor + { + public string Name { get; } + + public SamplerType Type { get; } + + public int HandleIndex { get; } + + public bool IsBindless { get; } + + public int CbufSlot { get; } + public int CbufOffset { get; } + + public TextureDescriptor(string name, SamplerType type, int handleIndex) + { + Name = name; + Type = type; + HandleIndex = handleIndex; + + IsBindless = false; + + CbufSlot = 0; + CbufOffset = 0; + } + + public TextureDescriptor(string name, SamplerType type, int cbufSlot, int cbufOffset) + { + Name = name; + Type = type; + HandleIndex = 0; + + IsBindless = true; + + CbufSlot = cbufSlot; + CbufOffset = cbufOffset; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Translation/AttributeConsts.cs b/Ryujinx.Graphics.Shader/Translation/AttributeConsts.cs new file mode 100644 index 0000000000..8ff37429ae --- /dev/null +++ b/Ryujinx.Graphics.Shader/Translation/AttributeConsts.cs @@ -0,0 +1,54 @@ +namespace Ryujinx.Graphics.Shader.Translation +{ + static class AttributeConsts + { + public const int Layer = 0x064; + public const int PointSize = 0x06c; + public const int PositionX = 0x070; + public const int PositionY = 0x074; + public const int PositionZ = 0x078; + public const int PositionW = 0x07c; + public const int ClipDistance0 = 0x2c0; + public const int ClipDistance1 = 0x2c4; + public const int ClipDistance2 = 0x2c8; + public const int ClipDistance3 = 0x2cc; + public const int ClipDistance4 = 0x2d0; + public const int ClipDistance5 = 0x2d4; + public const int ClipDistance6 = 0x2d8; + public const int ClipDistance7 = 0x2dc; + public const int PointCoordX = 0x2e0; + public const int PointCoordY = 0x2e4; + public const int TessCoordX = 0x2f0; + public const int TessCoordY = 0x2f4; + public const int InstanceId = 0x2f8; + public const int VertexId = 0x2fc; + public const int FrontFacing = 0x3fc; + + public const int UserAttributesCount = 32; + public const int UserAttributeBase = 0x80; + public const int UserAttributeEnd = UserAttributeBase + UserAttributesCount * 16; + + + // Note: Those attributes are used internally by the translator + // only, they don't exist on Maxwell. + public const int FragmentOutputDepth = 0x1000000; + public const int FragmentOutputColorBase = 0x1000010; + public const int FragmentOutputColorEnd = FragmentOutputColorBase + 8 * 16; + + public const int ThreadIdX = 0x2000000; + public const int ThreadIdY = 0x2000004; + public const int ThreadIdZ = 0x2000008; + + public const int CtaIdX = 0x2000010; + public const int CtaIdY = 0x2000014; + public const int CtaIdZ = 0x2000018; + + public const int LaneId = 0x2000020; + + public const int EqMask = 0x2000024; + public const int GeMask = 0x2000028; + public const int GtMask = 0x200002c; + public const int LeMask = 0x2000030; + public const int LtMask = 0x2000034; + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Translation/ControlFlowGraph.cs b/Ryujinx.Graphics.Shader/Translation/ControlFlowGraph.cs new file mode 100644 index 0000000000..e2ca74a4de --- /dev/null +++ b/Ryujinx.Graphics.Shader/Translation/ControlFlowGraph.cs @@ -0,0 +1,108 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.Translation +{ + static class ControlFlowGraph + { + public static BasicBlock[] MakeCfg(Operation[] operations) + { + Dictionary labels = new Dictionary(); + + List blocks = new List(); + + BasicBlock currentBlock = null; + + void NextBlock(BasicBlock nextBlock) + { + if (currentBlock != null && !EndsWithUnconditionalInst(currentBlock.GetLastOp())) + { + currentBlock.Next = nextBlock; + } + + currentBlock = nextBlock; + } + + void NewNextBlock() + { + BasicBlock block = new BasicBlock(blocks.Count); + + blocks.Add(block); + + NextBlock(block); + } + + bool needsNewBlock = true; + + for (int index = 0; index < operations.Length; index++) + { + Operation operation = operations[index]; + + if (operation.Inst == Instruction.MarkLabel) + { + Operand label = operation.Dest; + + if (labels.TryGetValue(label, out BasicBlock nextBlock)) + { + nextBlock.Index = blocks.Count; + + blocks.Add(nextBlock); + + NextBlock(nextBlock); + } + else + { + NewNextBlock(); + + labels.Add(label, currentBlock); + } + } + else + { + if (needsNewBlock) + { + NewNextBlock(); + } + + currentBlock.Operations.AddLast(operation); + } + + needsNewBlock = operation.Inst == Instruction.Branch || + operation.Inst == Instruction.BranchIfTrue || + operation.Inst == Instruction.BranchIfFalse; + + if (needsNewBlock) + { + Operand label = operation.Dest; + + if (!labels.TryGetValue(label, out BasicBlock branchBlock)) + { + branchBlock = new BasicBlock(); + + labels.Add(label, branchBlock); + } + + currentBlock.Branch = branchBlock; + } + } + + return blocks.ToArray(); + } + + private static bool EndsWithUnconditionalInst(INode node) + { + if (node is Operation operation) + { + switch (operation.Inst) + { + case Instruction.Branch: + case Instruction.Discard: + case Instruction.Return: + return true; + } + } + + return false; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Translation/Dominance.cs b/Ryujinx.Graphics.Shader/Translation/Dominance.cs new file mode 100644 index 0000000000..6a3ff35f0f --- /dev/null +++ b/Ryujinx.Graphics.Shader/Translation/Dominance.cs @@ -0,0 +1,127 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.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(BasicBlock entry, int blocksCount) + { + HashSet visited = new HashSet(); + + Stack blockStack = new Stack(); + + List postOrderBlocks = new List(blocksCount); + + int[] postOrderMap = new int[blocksCount]; + + visited.Add(entry); + + blockStack.Push(entry); + + while (blockStack.TryPop(out BasicBlock block)) + { + if (block.Next != null && visited.Add(block.Next)) + { + blockStack.Push(block); + blockStack.Push(block.Next); + } + else if (block.Branch != null && visited.Add(block.Branch)) + { + blockStack.Push(block); + blockStack.Push(block.Branch); + } + else + { + postOrderMap[block.Index] = postOrderBlocks.Count; + + postOrderBlocks.Add(block); + } + } + + BasicBlock Intersect(BasicBlock block1, BasicBlock block2) + { + while (block1 != block2) + { + while (postOrderMap[block1.Index] < postOrderMap[block2.Index]) + { + block1 = block1.ImmediateDominator; + } + + while (postOrderMap[block2.Index] < postOrderMap[block1.Index]) + { + block2 = block2.ImmediateDominator; + } + } + + return block1; + } + + entry.ImmediateDominator = entry; + + bool modified; + + do + { + modified = false; + + for (int blkIndex = postOrderBlocks.Count - 2; blkIndex >= 0; blkIndex--) + { + BasicBlock block = 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(BasicBlock[] blocks) + { + for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++) + { + BasicBlock block = blocks[blkIndex]; + + 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/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs b/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs new file mode 100644 index 0000000000..fbe197651e --- /dev/null +++ b/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs @@ -0,0 +1,103 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System.Collections.Generic; + +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Translation +{ + class EmitterContext + { + public Block CurrBlock { get; set; } + public OpCode CurrOp { get; set; } + + private ShaderConfig _config; + + public ShaderConfig Config => _config; + + private List _operations; + + private Dictionary _labels; + + public EmitterContext(ShaderConfig config) + { + _config = config; + + _operations = new List(); + + _labels = new Dictionary(); + } + + public Operand Add(Instruction inst, Operand dest = null, params Operand[] sources) + { + Operation operation = new Operation(inst, dest, sources); + + Add(operation); + + return dest; + } + + public void Add(Operation operation) + { + _operations.Add(operation); + } + + public void MarkLabel(Operand label) + { + Add(Instruction.MarkLabel, label); + } + + public Operand GetLabel(ulong address) + { + if (!_labels.TryGetValue(address, out Operand label)) + { + label = Label(); + + _labels.Add(address, label); + } + + return label; + } + + public void PrepareForReturn() + { + if (_config.Stage == ShaderStage.Fragment) + { + if (_config.OmapDepth) + { + Operand dest = Attribute(AttributeConsts.FragmentOutputDepth); + + Operand src = Register(_config.GetDepthRegister(), RegisterType.Gpr); + + this.Copy(dest, src); + } + + int regIndex = 0; + + for (int attachment = 0; attachment < 8; attachment++) + { + OutputMapTarget target = _config.OmapTargets[attachment]; + + for (int component = 0; component < 4; component++) + { + if (target.ComponentEnabled(component)) + { + Operand dest = Attribute(AttributeConsts.FragmentOutputColorBase + regIndex * 4); + + Operand src = Register(regIndex, RegisterType.Gpr); + + this.Copy(dest, src); + + regIndex++; + } + } + } + } + } + + public Operation[] GetOperations() + { + return _operations.ToArray(); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Translation/EmitterContextInsts.cs b/Ryujinx.Graphics.Shader/Translation/EmitterContextInsts.cs new file mode 100644 index 0000000000..14675a55d6 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Translation/EmitterContextInsts.cs @@ -0,0 +1,600 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; + +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Translation +{ + static class EmitterContextInsts + { + public static Operand AtomicAdd(this EmitterContext context, Instruction mr, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.AtomicAdd | mr, Local(), a, b, c); + } + + public static Operand AtomicAnd(this EmitterContext context, Instruction mr, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.AtomicAnd | mr, Local(), a, b, c); + } + + public static Operand AtomicCompareAndSwap(this EmitterContext context, Instruction mr, Operand a, Operand b, Operand c, Operand d) + { + return context.Add(Instruction.AtomicCompareAndSwap | mr, Local(), a, b, c, d); + } + + public static Operand AtomicMaxS32(this EmitterContext context, Instruction mr, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.AtomicMaxS32 | mr, Local(), a, b, c); + } + + public static Operand AtomicMaxU32(this EmitterContext context, Instruction mr, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.AtomicMaxU32 | mr, Local(), a, b, c); + } + + public static Operand AtomicMinS32(this EmitterContext context, Instruction mr, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.AtomicMinS32 | mr, Local(), a, b, c); + } + + public static Operand AtomicMinU32(this EmitterContext context, Instruction mr, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.AtomicMinU32 | mr, Local(), a, b, c); + } + + public static Operand AtomicOr(this EmitterContext context, Instruction mr, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.AtomicOr | mr, Local(), a, b, c); + } + + public static Operand AtomicSwap(this EmitterContext context, Instruction mr, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.AtomicSwap | mr, Local(), a, b, c); + } + + public static Operand AtomicXor(this EmitterContext context, Instruction mr, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.AtomicXor | mr, Local(), a, b, c); + } + + public static Operand Ballot(this EmitterContext context, Operand a) + { + return context.Add(Instruction.Ballot, Local(), a); + } + + public static Operand Barrier(this EmitterContext context) + { + return context.Add(Instruction.Barrier); + } + + public static Operand BitCount(this EmitterContext context, Operand a) + { + return context.Add(Instruction.BitCount, Local(), a); + } + + public static Operand BitfieldExtractS32(this EmitterContext context, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.BitfieldExtractS32, Local(), a, b, c); + } + + public static Operand BitfieldExtractU32(this EmitterContext context, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.BitfieldExtractU32, Local(), a, b, c); + } + + public static Operand BitfieldInsert(this EmitterContext context, Operand a, Operand b, Operand c, Operand d) + { + return context.Add(Instruction.BitfieldInsert, Local(), a, b, c, d); + } + + public static Operand BitfieldReverse(this EmitterContext context, Operand a) + { + return context.Add(Instruction.BitfieldReverse, Local(), a); + } + + public static Operand BitwiseAnd(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.BitwiseAnd, Local(), a, b); + } + + public static Operand BitwiseExclusiveOr(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.BitwiseExclusiveOr, Local(), a, b); + } + + public static Operand BitwiseNot(this EmitterContext context, Operand a, bool invert) + { + if (invert) + { + a = context.BitwiseNot(a); + } + + return a; + } + + public static Operand BitwiseNot(this EmitterContext context, Operand a) + { + return context.Add(Instruction.BitwiseNot, Local(), a); + } + + public static Operand BitwiseOr(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.BitwiseOr, Local(), a, b); + } + + public static Operand Branch(this EmitterContext context, Operand d) + { + return context.Add(Instruction.Branch, d); + } + + public static Operand BranchIfFalse(this EmitterContext context, Operand d, Operand a) + { + return context.Add(Instruction.BranchIfFalse, d, a); + } + + public static Operand BranchIfTrue(this EmitterContext context, Operand d, Operand a) + { + return context.Add(Instruction.BranchIfTrue, d, a); + } + + public static Operand ConditionalSelect(this EmitterContext context, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.ConditionalSelect, Local(), a, b, c); + } + + public static Operand Copy(this EmitterContext context, Operand a) + { + return context.Add(Instruction.Copy, Local(), a); + } + + public static void Copy(this EmitterContext context, Operand d, Operand a) + { + if (d.Type == OperandType.Constant) + { + return; + } + + context.Add(Instruction.Copy, d, a); + } + + public static Operand Discard(this EmitterContext context) + { + return context.Add(Instruction.Discard); + } + + public static Operand EmitVertex(this EmitterContext context) + { + return context.Add(Instruction.EmitVertex); + } + + public static Operand EndPrimitive(this EmitterContext context) + { + return context.Add(Instruction.EndPrimitive); + } + + public static Operand FindFirstSetS32(this EmitterContext context, Operand a) + { + return context.Add(Instruction.FindFirstSetS32, Local(), a); + } + + public static Operand FindFirstSetU32(this EmitterContext context, Operand a) + { + return context.Add(Instruction.FindFirstSetU32, Local(), a); + } + + public static Operand FPAbsNeg(this EmitterContext context, Operand a, bool abs, bool neg) + { + return context.FPNegate(context.FPAbsolute(a, abs), neg); + } + + public static Operand FPAbsolute(this EmitterContext context, Operand a, bool abs) + { + if (abs) + { + a = context.FPAbsolute(a); + } + + return a; + } + + public static Operand FPAbsolute(this EmitterContext context, Operand a) + { + return context.Add(Instruction.FP | Instruction.Absolute, Local(), a); + } + + public static Operand FPAdd(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.FP | Instruction.Add, Local(), a, b); + } + + public static Operand FPCeiling(this EmitterContext context, Operand a) + { + return context.Add(Instruction.FP | Instruction.Ceiling, Local(), a); + } + + public static Operand FPCompareEqual(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.FP | Instruction.CompareEqual, Local(), a, b); + } + + public static Operand FPCompareLess(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.FP | Instruction.CompareLess, Local(), a, b); + } + + public static Operand FPConvertToS32(this EmitterContext context, Operand a) + { + return context.Add(Instruction.ConvertFPToS32, Local(), a); + } + + public static Operand FPConvertToU32(this EmitterContext context, Operand a) + { + return context.Add(Instruction.ConvertFPToU32, Local(), a); + } + + public static Operand FPCosine(this EmitterContext context, Operand a) + { + return context.Add(Instruction.FP | Instruction.Cosine, Local(), a); + } + + public static Operand FPDivide(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.FP | Instruction.Divide, Local(), a, b); + } + + public static Operand FPExponentB2(this EmitterContext context, Operand a) + { + return context.Add(Instruction.FP | Instruction.ExponentB2, Local(), a); + } + + public static Operand FPFloor(this EmitterContext context, Operand a) + { + return context.Add(Instruction.FP | Instruction.Floor, Local(), a); + } + + public static Operand FPFusedMultiplyAdd(this EmitterContext context, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.FusedMultiplyAdd, Local(), a, b, c); + } + + public static Operand FPLogarithmB2(this EmitterContext context, Operand a) + { + return context.Add(Instruction.FP | Instruction.LogarithmB2, Local(), a); + } + + public static Operand FPMaximum(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.FP | Instruction.Maximum, Local(), a, b); + } + + public static Operand FPMinimum(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.FP | Instruction.Minimum, Local(), a, b); + } + + public static Operand FPMultiply(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.FP | Instruction.Multiply, Local(), a, b); + } + + public static Operand FPNegate(this EmitterContext context, Operand a, bool neg) + { + if (neg) + { + a = context.FPNegate(a); + } + + return a; + } + + public static Operand FPNegate(this EmitterContext context, Operand a) + { + return context.Add(Instruction.FP | Instruction.Negate, Local(), a); + } + + public static Operand FPReciprocal(this EmitterContext context, Operand a) + { + return context.FPDivide(ConstF(1), a); + } + + public static Operand FPReciprocalSquareRoot(this EmitterContext context, Operand a) + { + return context.Add(Instruction.FP | Instruction.ReciprocalSquareRoot, Local(), a); + } + + public static Operand FPRound(this EmitterContext context, Operand a) + { + return context.Add(Instruction.FP | Instruction.Round, Local(), a); + } + + public static Operand FPSaturate(this EmitterContext context, Operand a, bool sat) + { + if (sat) + { + a = context.FPSaturate(a); + } + + return a; + } + + public static Operand FPSaturate(this EmitterContext context, Operand a) + { + return context.Add(Instruction.FP | Instruction.Clamp, Local(), a, ConstF(0), ConstF(1)); + } + + public static Operand FPSine(this EmitterContext context, Operand a) + { + return context.Add(Instruction.FP | Instruction.Sine, Local(), a); + } + + public static Operand FPSquareRoot(this EmitterContext context, Operand a) + { + return context.Add(Instruction.FP | Instruction.SquareRoot, Local(), a); + } + + public static Operand FPTruncate(this EmitterContext context, Operand a) + { + return context.Add(Instruction.Truncate, Local(), a); + } + + public static Operand FPSwizzleAdd(this EmitterContext context, Operand a, Operand b, int mask) + { + return context.Add(Instruction.SwizzleAdd, Local(), a, b, Const(mask)); + } + + public static Operand GroupMemoryBarrier(this EmitterContext context) + { + return context.Add(Instruction.GroupMemoryBarrier); + } + + public static Operand IAbsNeg(this EmitterContext context, Operand a, bool abs, bool neg) + { + return context.INegate(context.IAbsolute(a, abs), neg); + } + + public static Operand IAbsolute(this EmitterContext context, Operand a, bool abs) + { + if (abs) + { + a = context.IAbsolute(a); + } + + return a; + } + + public static Operand IAbsolute(this EmitterContext context, Operand a) + { + return context.Add(Instruction.Absolute, Local(), a); + } + + public static Operand IAdd(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.Add, Local(), a, b); + } + + public static Operand IClampS32(this EmitterContext context, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.Clamp, Local(), a, b, c); + } + + public static Operand IClampU32(this EmitterContext context, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.ClampU32, Local(), a, b, c); + } + + public static Operand ICompareEqual(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.CompareEqual, Local(), a, b); + } + + public static Operand ICompareLess(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.CompareLess, Local(), a, b); + } + + public static Operand ICompareLessUnsigned(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.CompareLessU32, Local(), a, b); + } + + public static Operand ICompareNotEqual(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.CompareNotEqual, Local(), a, b); + } + + public static Operand IConvertS32ToFP(this EmitterContext context, Operand a) + { + return context.Add(Instruction.ConvertS32ToFP, Local(), a); + } + + public static Operand IConvertU32ToFP(this EmitterContext context, Operand a) + { + return context.Add(Instruction.ConvertU32ToFP, Local(), a); + } + + public static Operand IMaximumS32(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.Maximum, Local(), a, b); + } + + public static Operand IMaximumU32(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.MaximumU32, Local(), a, b); + } + + public static Operand IMinimumS32(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.Minimum, Local(), a, b); + } + + public static Operand IMinimumU32(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.MinimumU32, Local(), a, b); + } + + public static Operand IMultiply(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.Multiply, Local(), a, b); + } + + public static Operand INegate(this EmitterContext context, Operand a, bool neg) + { + if (neg) + { + a = context.INegate(a); + } + + return a; + } + + public static Operand INegate(this EmitterContext context, Operand a) + { + return context.Add(Instruction.Negate, Local(), a); + } + + public static Operand ISubtract(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.Subtract, Local(), a, b); + } + + public static Operand IsNan(this EmitterContext context, Operand a) + { + return context.Add(Instruction.IsNan, Local(), a); + } + + public static Operand LoadAttribute(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.LoadAttribute, Local(), a, b); + } + + public static Operand LoadConstant(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.LoadConstant, Local(), a, b); + } + + public static Operand LoadGlobal(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.LoadGlobal, Local(), a, b); + } + + public static Operand LoadLocal(this EmitterContext context, Operand a) + { + return context.Add(Instruction.LoadLocal, Local(), a); + } + + public static Operand LoadShared(this EmitterContext context, Operand a) + { + return context.Add(Instruction.LoadShared, Local(), a); + } + + public static Operand MemoryBarrier(this EmitterContext context) + { + return context.Add(Instruction.MemoryBarrier); + } + + public static Operand MultiplyHighS32(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.MultiplyHighS32, Local(), a, b); + } + + public static Operand MultiplyHighU32(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.MultiplyHighU32, Local(), a, b); + } + + public static Operand PackHalf2x16(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.PackHalf2x16, Local(), a, b); + } + + public static Operand Return(this EmitterContext context) + { + context.PrepareForReturn(); + + return context.Add(Instruction.Return); + } + + public static Operand ShiftLeft(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.ShiftLeft, Local(), a, b); + } + + public static Operand ShiftRightS32(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.ShiftRightS32, Local(), a, b); + } + + public static Operand ShiftRightU32(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.ShiftRightU32, Local(), a, b); + } + + public static Operand Shuffle(this EmitterContext context, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.Shuffle, Local(), a, b, c); + } + + public static Operand ShuffleDown(this EmitterContext context, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.ShuffleDown, Local(), a, b, c); + } + + public static Operand ShuffleUp(this EmitterContext context, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.ShuffleUp, Local(), a, b, c); + } + + public static Operand ShuffleXor(this EmitterContext context, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.ShuffleXor, Local(), a, b, c); + } + + public static Operand StoreGlobal(this EmitterContext context, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.StoreGlobal, null, a, b, c); + } + + public static Operand StoreLocal(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.StoreLocal, null, a, b); + } + + public static Operand StoreShared(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.StoreShared, null, a, b); + } + + public static Operand UnpackHalf2x16High(this EmitterContext context, Operand a) + { + return UnpackHalf2x16(context, a, 1); + } + + public static Operand UnpackHalf2x16Low(this EmitterContext context, Operand a) + { + return UnpackHalf2x16(context, a, 0); + } + + private static Operand UnpackHalf2x16(this EmitterContext context, Operand a, int index) + { + Operand dest = Local(); + + context.Add(new Operation(Instruction.UnpackHalf2x16, index, dest, a)); + + return dest; + } + + public static Operand VoteAll(this EmitterContext context, Operand a) + { + return context.Add(Instruction.VoteAll, Local(), a); + } + + public static Operand VoteAllEqual(this EmitterContext context, Operand a) + { + return context.Add(Instruction.VoteAllEqual, Local(), a); + } + + public static Operand VoteAny(this EmitterContext context, Operand a) + { + return context.Add(Instruction.VoteAny, Local(), a); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Translation/GlobalMemory.cs b/Ryujinx.Graphics.Shader/Translation/GlobalMemory.cs new file mode 100644 index 0000000000..a442357dd1 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Translation/GlobalMemory.cs @@ -0,0 +1,46 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; + +namespace Ryujinx.Graphics.Shader.Translation +{ + static class GlobalMemory + { + private const int StorageDescsBaseOffset = 0x44; // In words. + + public const int StorageDescSize = 4; // In words. + public const int StorageMaxCount = 16; + + public const int StorageDescsSize = StorageDescSize * StorageMaxCount; + + public static bool UsesGlobalMemory(Instruction inst) + { + return (inst.IsAtomic() && IsGlobalMr(inst)) || + inst == Instruction.LoadGlobal || + inst == Instruction.StoreGlobal; + } + + private static bool IsGlobalMr(Instruction inst) + { + return (inst & Instruction.MrMask) == Instruction.MrGlobal; + } + + public static int GetStorageCbOffset(ShaderStage stage, int slot) + { + return GetStorageBaseCbOffset(stage) + slot * StorageDescSize; + } + + public static int GetStorageBaseCbOffset(ShaderStage stage) + { + switch (stage) + { + case ShaderStage.Compute: return StorageDescsBaseOffset + 2 * StorageDescsSize; + case ShaderStage.Vertex: return StorageDescsBaseOffset; + case ShaderStage.TessellationControl: return StorageDescsBaseOffset + 1 * StorageDescsSize; + case ShaderStage.TessellationEvaluation: return StorageDescsBaseOffset + 2 * StorageDescsSize; + case ShaderStage.Geometry: return StorageDescsBaseOffset + 3 * StorageDescsSize; + case ShaderStage.Fragment: return StorageDescsBaseOffset + 4 * StorageDescsSize; + } + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Translation/Lowering.cs b/Ryujinx.Graphics.Shader/Translation/Lowering.cs new file mode 100644 index 0000000000..1ee21e0a04 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Translation/Lowering.cs @@ -0,0 +1,423 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System.Collections.Generic; +using System.Diagnostics; + +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; +using static Ryujinx.Graphics.Shader.Translation.GlobalMemory; + +namespace Ryujinx.Graphics.Shader.Translation +{ + static class Lowering + { + public static void RunPass(BasicBlock[] blocks, ShaderConfig config) + { + for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++) + { + BasicBlock block = blocks[blkIndex]; + + for (LinkedListNode node = block.Operations.First; node != null; node = node.Next) + { + if (!(node.Value is Operation operation)) + { + continue; + } + + if (UsesGlobalMemory(operation.Inst)) + { + node = RewriteGlobalAccess(node, config); + } + + if (operation.Inst == Instruction.TextureSample) + { + node = RewriteTextureSample(node, config); + } + } + } + } + + private static LinkedListNode RewriteGlobalAccess(LinkedListNode node, ShaderConfig config) + { + Operation operation = (Operation)node.Value; + + Operation storageOp; + + Operand PrependOperation(Instruction inst, params Operand[] sources) + { + Operand local = Local(); + + node.List.AddBefore(node, new Operation(inst, local, sources)); + + return local; + } + + Operand addrLow = operation.GetSource(0); + Operand addrHigh = operation.GetSource(1); + + Operand sbBaseAddrLow = Const(0); + Operand sbSlot = Const(0); + + for (int slot = 0; slot < StorageMaxCount; slot++) + { + int cbOffset = GetStorageCbOffset(config.Stage, slot); + + Operand baseAddrLow = Cbuf(0, cbOffset); + Operand baseAddrHigh = Cbuf(0, cbOffset + 1); + Operand size = Cbuf(0, cbOffset + 2); + + Operand offset = PrependOperation(Instruction.Subtract, addrLow, baseAddrLow); + Operand borrow = PrependOperation(Instruction.CompareLessU32, addrLow, baseAddrLow); + + Operand inRangeLow = PrependOperation(Instruction.CompareLessU32, offset, size); + + Operand addrHighBorrowed = PrependOperation(Instruction.Add, addrHigh, borrow); + + Operand inRangeHigh = PrependOperation(Instruction.CompareEqual, addrHighBorrowed, baseAddrHigh); + + Operand inRange = PrependOperation(Instruction.BitwiseAnd, inRangeLow, inRangeHigh); + + sbBaseAddrLow = PrependOperation(Instruction.ConditionalSelect, inRange, baseAddrLow, sbBaseAddrLow); + sbSlot = PrependOperation(Instruction.ConditionalSelect, inRange, Const(slot), sbSlot); + } + + Operand alignMask = Const(-config.QueryInfo(QueryInfoName.StorageBufferOffsetAlignment)); + + Operand baseAddrTrunc = PrependOperation(Instruction.BitwiseAnd, sbBaseAddrLow, Const(-64)); + Operand byteOffset = PrependOperation(Instruction.Subtract, addrLow, baseAddrTrunc); + Operand wordOffset = PrependOperation(Instruction.ShiftRightU32, byteOffset, Const(2)); + + Operand[] sources = new Operand[operation.SourcesCount]; + + sources[0] = sbSlot; + sources[1] = wordOffset; + + for (int index = 2; index < operation.SourcesCount; index++) + { + sources[index] = operation.GetSource(index); + } + + if (operation.Inst.IsAtomic()) + { + Instruction inst = (operation.Inst & ~Instruction.MrMask) | Instruction.MrStorage; + + storageOp = new Operation(inst, operation.Dest, sources); + } + else if (operation.Inst == Instruction.LoadGlobal) + { + storageOp = new Operation(Instruction.LoadStorage, operation.Dest, sources); + } + else + { + storageOp = new Operation(Instruction.StoreStorage, null, sources); + } + + for (int index = 0; index < operation.SourcesCount; index++) + { + operation.SetSource(index, null); + } + + LinkedListNode oldNode = node; + + node = node.List.AddBefore(node, storageOp); + + node.List.Remove(oldNode); + + return node; + } + + private static LinkedListNode RewriteTextureSample(LinkedListNode node, ShaderConfig config) + { + TextureOperation texOp = (TextureOperation)node.Value; + + bool hasOffset = (texOp.Flags & TextureFlags.Offset) != 0; + bool hasOffsets = (texOp.Flags & TextureFlags.Offsets) != 0; + + bool hasInvalidOffset = (hasOffset || hasOffsets) && !config.QueryInfoBool(QueryInfoName.SupportsNonConstantTextureOffset); + + bool isRect = config.QueryInfoBool(QueryInfoName.IsTextureRectangle, texOp.Handle); + + if (!(hasInvalidOffset || isRect)) + { + return node; + } + + bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; + bool isGather = (texOp.Flags & TextureFlags.Gather) != 0; + bool hasDerivatives = (texOp.Flags & TextureFlags.Derivatives) != 0; + bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0; + bool hasLodBias = (texOp.Flags & TextureFlags.LodBias) != 0; + bool hasLodLevel = (texOp.Flags & TextureFlags.LodLevel) != 0; + + bool isArray = (texOp.Type & SamplerType.Array) != 0; + bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; + bool isMultisample = (texOp.Type & SamplerType.Multisample) != 0; + bool isShadow = (texOp.Type & SamplerType.Shadow) != 0; + + int coordsCount = texOp.Type.GetDimensions(); + + int offsetsCount; + + if (hasOffsets) + { + offsetsCount = coordsCount * 4; + } + else if (hasOffset) + { + offsetsCount = coordsCount; + } + else + { + offsetsCount = 0; + } + + Operand[] offsets = new Operand[offsetsCount]; + Operand[] sources = new Operand[texOp.SourcesCount - offsetsCount]; + + int copyCount = 0; + + if (isBindless || isIndexed) + { + copyCount++; + } + + Operand[] lodSources = new Operand[copyCount + coordsCount]; + + for (int index = 0; index < lodSources.Length; index++) + { + lodSources[index] = texOp.GetSource(index); + } + + copyCount += coordsCount; + + if (isArray) + { + copyCount++; + } + + if (isShadow) + { + copyCount++; + } + + if (hasDerivatives) + { + copyCount += coordsCount * 2; + } + + if (isMultisample) + { + copyCount++; + } + else if (hasLodLevel) + { + copyCount++; + } + + int srcIndex = 0; + int dstIndex = 0; + + for (int index = 0; index < copyCount; index++) + { + sources[dstIndex++] = texOp.GetSource(srcIndex++); + } + + bool areAllOffsetsConstant = true; + + for (int index = 0; index < offsetsCount; index++) + { + Operand offset = texOp.GetSource(srcIndex++); + + areAllOffsetsConstant &= offset.Type == OperandType.Constant; + + offsets[index] = offset; + } + + hasInvalidOffset &= !areAllOffsetsConstant; + + if (!(hasInvalidOffset || isRect)) + { + return node; + } + + if (hasLodBias) + { + sources[dstIndex++] = texOp.GetSource(srcIndex++); + } + + if (isGather && !isShadow) + { + sources[dstIndex++] = texOp.GetSource(srcIndex++); + } + + int coordsIndex = isBindless || isIndexed ? 1 : 0; + + int componentIndex = texOp.Index; + + Operand Int(Operand value) + { + Operand res = Local(); + + node.List.AddBefore(node, new Operation(Instruction.ConvertFPToS32, res, value)); + + return res; + } + + Operand Float(Operand value) + { + Operand res = Local(); + + node.List.AddBefore(node, new Operation(Instruction.ConvertS32ToFP, res, value)); + + return res; + } + + // Emulate texture rectangle by normalizing the coordinates on the shader. + // When sampler*Rect is used, the coords are expected to the in the [0, W or H] range, + // and otherwise, it is expected to be in the [0, 1] range. + // We normalize by dividing the coords by the texture size. + if (isRect && !intCoords) + { + for (int index = 0; index < coordsCount; index++) + { + Operand coordSize = Local(); + + Operand[] texSizeSources; + + if (isBindless || isIndexed) + { + texSizeSources = new Operand[] { sources[0], Const(0) }; + } + else + { + texSizeSources = new Operand[] { Const(0) }; + } + + node.List.AddBefore(node, new TextureOperation( + Instruction.TextureSize, + texOp.Type, + texOp.Flags, + texOp.Handle, + index, + coordSize, + texSizeSources)); + + Operand source = sources[coordsIndex + index]; + + Operand coordNormalized = Local(); + + node.List.AddBefore(node, new Operation(Instruction.FP | Instruction.Divide, coordNormalized, source, Float(coordSize))); + + sources[coordsIndex + index] = coordNormalized; + } + } + + // Technically, non-constant texture offsets are not allowed (according to the spec), + // however some GPUs does support that. + // For GPUs where it is not supported, we can replace the instruction with the following: + // For texture*Offset, we replace it by texture*, and add the offset to the P coords. + // The offset can be calculated as offset / textureSize(lod), where lod = textureQueryLod(coords). + // For texelFetchOffset, we replace it by texelFetch and add the offset to the P coords directly. + // For textureGatherOffset, we take advantage of the fact that the operation is already broken down + // to read the 4 pixels separately, and just replace it with 4 textureGather with a different offset + // for each pixel. + if (hasInvalidOffset) + { + if (intCoords) + { + for (int index = 0; index < coordsCount; index++) + { + Operand source = sources[coordsIndex + index]; + + Operand coordPlusOffset = Local(); + + node.List.AddBefore(node, new Operation(Instruction.Add, coordPlusOffset, source, offsets[index])); + + sources[coordsIndex + index] = coordPlusOffset; + } + } + else + { + Operand lod = Local(); + + node.List.AddBefore(node, new TextureOperation( + Instruction.Lod, + texOp.Type, + texOp.Flags, + texOp.Handle, + 1, + lod, + lodSources)); + + for (int index = 0; index < coordsCount; index++) + { + Operand coordSize = Local(); + + Operand[] texSizeSources; + + if (isBindless || isIndexed) + { + texSizeSources = new Operand[] { sources[0], Int(lod) }; + } + else + { + texSizeSources = new Operand[] { Int(lod) }; + } + + node.List.AddBefore(node, new TextureOperation( + Instruction.TextureSize, + texOp.Type, + texOp.Flags, + texOp.Handle, + index, + coordSize, + texSizeSources)); + + Operand offset = Local(); + + Operand intOffset = offsets[index + (hasOffsets ? texOp.Index * coordsCount : 0)]; + + node.List.AddBefore(node, new Operation(Instruction.FP | Instruction.Divide, offset, Float(intOffset), Float(coordSize))); + + Operand source = sources[coordsIndex + index]; + + Operand coordPlusOffset = Local(); + + node.List.AddBefore(node, new Operation(Instruction.FP | Instruction.Add, coordPlusOffset, source, offset)); + + sources[coordsIndex + index] = coordPlusOffset; + } + } + + if (isGather && !isShadow) + { + Operand gatherComponent = sources[dstIndex - 1]; + + Debug.Assert(gatherComponent.Type == OperandType.Constant); + + componentIndex = gatherComponent.Value; + } + } + + TextureOperation newTexOp = new TextureOperation( + Instruction.TextureSample, + texOp.Type, + texOp.Flags & ~(TextureFlags.Offset | TextureFlags.Offsets), + texOp.Handle, + componentIndex, + texOp.Dest, + sources); + + for (int index = 0; index < texOp.SourcesCount; index++) + { + texOp.SetSource(index, null); + } + + LinkedListNode oldNode = node; + + node = node.List.AddBefore(node, newTexOp); + + node.List.Remove(oldNode); + + return node; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessToIndexed.cs b/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessToIndexed.cs new file mode 100644 index 0000000000..83f8fe9a7b --- /dev/null +++ b/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessToIndexed.cs @@ -0,0 +1,74 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System.Collections.Generic; + +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Translation.Optimizations +{ + static class BindlessToIndexed + { + public static void RunPass(BasicBlock block) + { + // We can turn a bindless texture access into a indexed access, + // as long the following conditions are true: + // - The handle is loaded using a LDC instruction. + // - The handle is loaded from the constant buffer with the handles (CB2 for NVN). + // - The load has a constant offset. + // The base offset of the array of handles on the constant buffer is the constant offset. + for (LinkedListNode node = block.Operations.First; node != null; node = node.Next) + { + if (!(node.Value is TextureOperation texOp)) + { + continue; + } + + if ((texOp.Flags & TextureFlags.Bindless) == 0) + { + continue; + } + + if (!(texOp.GetSource(0).AsgOp is Operation handleAsgOp)) + { + continue; + } + + if (handleAsgOp.Inst != Instruction.LoadConstant) + { + continue; + } + + Operand ldcSrc0 = handleAsgOp.GetSource(0); + Operand ldcSrc1 = handleAsgOp.GetSource(1); + + if (ldcSrc0.Type != OperandType.Constant || ldcSrc0.Value != 2) + { + continue; + } + + if (!(ldcSrc1.AsgOp is Operation addOp)) + { + continue; + } + + Operand addSrc1 = addOp.GetSource(1); + + if (addSrc1.Type != OperandType.Constant) + { + continue; + } + + texOp.TurnIntoIndexed(addSrc1.Value); + + Operand index = Local(); + + Operand source = addOp.GetSource(0); + + Operation shrBy1 = new Operation(Instruction.ShiftRightU32, index, source, Const(1)); + + block.Operations.AddBefore(node, shrBy1); + + texOp.SetSource(0, index); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Translation/Optimizations/BranchElimination.cs b/Ryujinx.Graphics.Shader/Translation/Optimizations/BranchElimination.cs new file mode 100644 index 0000000000..c87d147484 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Translation/Optimizations/BranchElimination.cs @@ -0,0 +1,64 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System; + +namespace Ryujinx.Graphics.Shader.Translation.Optimizations +{ + static class BranchElimination + { + public static bool RunPass(BasicBlock block) + { + if (block.HasBranch && IsRedundantBranch((Operation)block.GetLastOp(), Next(block))) + { + block.Branch = null; + + return true; + } + + return false; + } + + private static bool IsRedundantBranch(Operation current, BasicBlock nextBlock) + { + // Here we check that: + // - The current block ends with a branch. + // - The next block only contains a branch. + // - The branch on the next block is unconditional. + // - Both branches are jumping to the same location. + // In this case, the branch on the current block can be removed, + // as the next block is going to jump to the same place anyway. + if (nextBlock == null) + { + return false; + } + + if (!(nextBlock.Operations.First?.Value is Operation next)) + { + return false; + } + + if (next.Inst != Instruction.Branch) + { + return false; + } + + return current.Dest == next.Dest; + } + + private static BasicBlock Next(BasicBlock block) + { + block = block.Next; + + while (block != null && block.Operations.Count == 0) + { + if (block.HasBranch) + { + throw new InvalidOperationException("Found a bogus empty block that \"ends with a branch\"."); + } + + block = block.Next; + } + + return block; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Translation/Optimizations/ConstantFolding.cs b/Ryujinx.Graphics.Shader/Translation/Optimizations/ConstantFolding.cs new file mode 100644 index 0000000000..b69589294d --- /dev/null +++ b/Ryujinx.Graphics.Shader/Translation/Optimizations/ConstantFolding.cs @@ -0,0 +1,342 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System; + +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Translation.Optimizations +{ + static class ConstantFolding + { + public static void RunPass(Operation operation) + { + if (!AreAllSourcesConstant(operation)) + { + return; + } + + switch (operation.Inst) + { + case Instruction.Add: + EvaluateBinary(operation, (x, y) => x + y); + break; + + case Instruction.BitCount: + EvaluateUnary(operation, (x) => BitCount(x)); + break; + + case Instruction.BitwiseAnd: + EvaluateBinary(operation, (x, y) => x & y); + break; + + case Instruction.BitwiseExclusiveOr: + EvaluateBinary(operation, (x, y) => x ^ y); + break; + + case Instruction.BitwiseNot: + EvaluateUnary(operation, (x) => ~x); + break; + + case Instruction.BitwiseOr: + EvaluateBinary(operation, (x, y) => x | y); + break; + + case Instruction.BitfieldExtractS32: + BitfieldExtractS32(operation); + break; + + case Instruction.BitfieldExtractU32: + BitfieldExtractU32(operation); + break; + + case Instruction.Clamp: + EvaluateTernary(operation, (x, y, z) => Math.Clamp(x, y, z)); + break; + + case Instruction.ClampU32: + EvaluateTernary(operation, (x, y, z) => (int)Math.Clamp((uint)x, (uint)y, (uint)z)); + break; + + case Instruction.CompareEqual: + EvaluateBinary(operation, (x, y) => x == y); + break; + + case Instruction.CompareGreater: + EvaluateBinary(operation, (x, y) => x > y); + break; + + case Instruction.CompareGreaterOrEqual: + EvaluateBinary(operation, (x, y) => x >= y); + break; + + case Instruction.CompareGreaterOrEqualU32: + EvaluateBinary(operation, (x, y) => (uint)x >= (uint)y); + break; + + case Instruction.CompareGreaterU32: + EvaluateBinary(operation, (x, y) => (uint)x > (uint)y); + break; + + case Instruction.CompareLess: + EvaluateBinary(operation, (x, y) => x < y); + break; + + case Instruction.CompareLessOrEqual: + EvaluateBinary(operation, (x, y) => x <= y); + break; + + case Instruction.CompareLessOrEqualU32: + EvaluateBinary(operation, (x, y) => (uint)x <= (uint)y); + break; + + case Instruction.CompareLessU32: + EvaluateBinary(operation, (x, y) => (uint)x < (uint)y); + break; + + case Instruction.CompareNotEqual: + EvaluateBinary(operation, (x, y) => x != y); + break; + + case Instruction.Divide: + EvaluateBinary(operation, (x, y) => y != 0 ? x / y : 0); + break; + + case Instruction.FP | Instruction.Add: + EvaluateFPBinary(operation, (x, y) => x + y); + break; + + case Instruction.FP | Instruction.Clamp: + EvaluateFPTernary(operation, (x, y, z) => Math.Clamp(x, y, z)); + break; + + case Instruction.FP | Instruction.CompareEqual: + EvaluateFPBinary(operation, (x, y) => x == y); + break; + + case Instruction.FP | Instruction.CompareGreater: + EvaluateFPBinary(operation, (x, y) => x > y); + break; + + case Instruction.FP | Instruction.CompareGreaterOrEqual: + EvaluateFPBinary(operation, (x, y) => x >= y); + break; + + case Instruction.FP | Instruction.CompareLess: + EvaluateFPBinary(operation, (x, y) => x < y); + break; + + case Instruction.FP | Instruction.CompareLessOrEqual: + EvaluateFPBinary(operation, (x, y) => x <= y); + break; + + case Instruction.FP | Instruction.CompareNotEqual: + EvaluateFPBinary(operation, (x, y) => x != y); + break; + + case Instruction.FP | Instruction.Divide: + EvaluateFPBinary(operation, (x, y) => x / y); + break; + + case Instruction.FP | Instruction.Multiply: + EvaluateFPBinary(operation, (x, y) => x * y); + break; + + case Instruction.FP | Instruction.Negate: + EvaluateFPUnary(operation, (x) => -x); + break; + + case Instruction.FP | Instruction.Subtract: + EvaluateFPBinary(operation, (x, y) => x - y); + break; + + case Instruction.IsNan: + EvaluateFPUnary(operation, (x) => float.IsNaN(x)); + break; + + case Instruction.Maximum: + EvaluateBinary(operation, (x, y) => Math.Max(x, y)); + break; + + case Instruction.MaximumU32: + EvaluateBinary(operation, (x, y) => (int)Math.Max((uint)x, (uint)y)); + break; + + case Instruction.Minimum: + EvaluateBinary(operation, (x, y) => Math.Min(x, y)); + break; + + case Instruction.MinimumU32: + EvaluateBinary(operation, (x, y) => (int)Math.Min((uint)x, (uint)y)); + break; + + case Instruction.Multiply: + EvaluateBinary(operation, (x, y) => x * y); + break; + + case Instruction.Negate: + EvaluateUnary(operation, (x) => -x); + break; + + case Instruction.ShiftLeft: + EvaluateBinary(operation, (x, y) => x << y); + break; + + case Instruction.ShiftRightS32: + EvaluateBinary(operation, (x, y) => x >> y); + break; + + case Instruction.ShiftRightU32: + EvaluateBinary(operation, (x, y) => (int)((uint)x >> y)); + break; + + case Instruction.Subtract: + EvaluateBinary(operation, (x, y) => x - y); + break; + + case Instruction.UnpackHalf2x16: + UnpackHalf2x16(operation); + break; + } + } + + private static bool AreAllSourcesConstant(Operation operation) + { + for (int index = 0; index < operation.SourcesCount; index++) + { + if (operation.GetSource(index).Type != OperandType.Constant) + { + return false; + } + } + + return true; + } + + private static int BitCount(int value) + { + int count = 0; + + for (int bit = 0; bit < 32; bit++) + { + if (value.Extract(bit)) + { + count++; + } + } + + return count; + } + + private static void BitfieldExtractS32(Operation operation) + { + int value = GetBitfieldExtractValue(operation); + + int shift = 32 - operation.GetSource(2).Value; + + value = (value << shift) >> shift; + + operation.TurnIntoCopy(Const(value)); + } + + private static void BitfieldExtractU32(Operation operation) + { + operation.TurnIntoCopy(Const(GetBitfieldExtractValue(operation))); + } + + private static int GetBitfieldExtractValue(Operation operation) + { + int value = operation.GetSource(0).Value; + int lsb = operation.GetSource(1).Value; + int length = operation.GetSource(2).Value; + + return value.Extract(lsb, length); + } + + private static void UnpackHalf2x16(Operation operation) + { + int value = operation.GetSource(0).Value; + + value = (value >> operation.Index * 16) & 0xffff; + + operation.TurnIntoCopy(ConstF(HalfConversion.HalfToSingle(value))); + } + + private static void FPNegate(Operation operation) + { + float value = operation.GetSource(0).AsFloat(); + + operation.TurnIntoCopy(ConstF(-value)); + } + + private static void EvaluateUnary(Operation operation, Func op) + { + int x = operation.GetSource(0).Value; + + operation.TurnIntoCopy(Const(op(x))); + } + + private static void EvaluateFPUnary(Operation operation, Func op) + { + float x = operation.GetSource(0).AsFloat(); + + operation.TurnIntoCopy(ConstF(op(x))); + } + + private static void EvaluateFPUnary(Operation operation, Func op) + { + float x = operation.GetSource(0).AsFloat(); + + operation.TurnIntoCopy(Const(op(x) ? IrConsts.True : IrConsts.False)); + } + + private static void EvaluateBinary(Operation operation, Func op) + { + int x = operation.GetSource(0).Value; + int y = operation.GetSource(1).Value; + + operation.TurnIntoCopy(Const(op(x, y))); + } + + private static void EvaluateBinary(Operation operation, Func op) + { + int x = operation.GetSource(0).Value; + int y = operation.GetSource(1).Value; + + operation.TurnIntoCopy(Const(op(x, y) ? IrConsts.True : IrConsts.False)); + } + + private static void EvaluateFPBinary(Operation operation, Func op) + { + float x = operation.GetSource(0).AsFloat(); + float y = operation.GetSource(1).AsFloat(); + + operation.TurnIntoCopy(ConstF(op(x, y))); + } + + private static void EvaluateFPBinary(Operation operation, Func op) + { + float x = operation.GetSource(0).AsFloat(); + float y = operation.GetSource(1).AsFloat(); + + operation.TurnIntoCopy(Const(op(x, y) ? IrConsts.True : IrConsts.False)); + } + + private static void EvaluateTernary(Operation operation, Func op) + { + int x = operation.GetSource(0).Value; + int y = operation.GetSource(1).Value; + int z = operation.GetSource(2).Value; + + operation.TurnIntoCopy(Const(op(x, y, z))); + } + + private static void EvaluateFPTernary(Operation operation, Func op) + { + float x = operation.GetSource(0).AsFloat(); + float y = operation.GetSource(1).AsFloat(); + float z = operation.GetSource(2).AsFloat(); + + operation.TurnIntoCopy(ConstF(op(x, y, z))); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Translation/Optimizations/GlobalToStorage.cs b/Ryujinx.Graphics.Shader/Translation/Optimizations/GlobalToStorage.cs new file mode 100644 index 0000000000..8efd2c5209 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Translation/Optimizations/GlobalToStorage.cs @@ -0,0 +1,147 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System.Collections.Generic; + +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; +using static Ryujinx.Graphics.Shader.Translation.GlobalMemory; + +namespace Ryujinx.Graphics.Shader.Translation.Optimizations +{ + static class GlobalToStorage + { + public static void RunPass(BasicBlock block, ShaderConfig config) + { + int sbStart = GetStorageBaseCbOffset(config.Stage); + + int sbEnd = sbStart + StorageDescsSize; + + for (LinkedListNode node = block.Operations.First; node != null; node = node.Next) + { + if (!(node.Value is Operation operation)) + { + continue; + } + + if (UsesGlobalMemory(operation.Inst)) + { + Operand source = operation.GetSource(0); + + if (source.AsgOp is Operation asgOperation) + { + int storageIndex = SearchForStorageBase(asgOperation, sbStart, sbEnd); + + if (storageIndex >= 0) + { + node = ReplaceGlobalWithStorage(node, config, storageIndex); + } + } + } + } + } + + private static LinkedListNode ReplaceGlobalWithStorage(LinkedListNode node, ShaderConfig config, int storageIndex) + { + Operation operation = (Operation)node.Value; + + Operation storageOp; + + Operand GetStorageOffset() + { + Operand addrLow = operation.GetSource(0); + + Operand baseAddrLow = Cbuf(0, GetStorageCbOffset(config.Stage, storageIndex)); + + Operand baseAddrTrunc = Local(); + + Operand alignMask = Const(-config.QueryInfo(QueryInfoName.StorageBufferOffsetAlignment)); + + Operation andOp = new Operation(Instruction.BitwiseAnd, baseAddrTrunc, baseAddrLow, alignMask); + + node.List.AddBefore(node, andOp); + + Operand byteOffset = Local(); + Operand wordOffset = Local(); + + Operation subOp = new Operation(Instruction.Subtract, byteOffset, addrLow, baseAddrTrunc); + Operation shrOp = new Operation(Instruction.ShiftRightU32, wordOffset, byteOffset, Const(2)); + + node.List.AddBefore(node, subOp); + node.List.AddBefore(node, shrOp); + + return wordOffset; + } + + Operand[] sources = new Operand[operation.SourcesCount]; + + sources[0] = Const(storageIndex); + sources[1] = GetStorageOffset(); + + for (int index = 2; index < operation.SourcesCount; index++) + { + sources[index] = operation.GetSource(index); + } + + if (operation.Inst.IsAtomic()) + { + Instruction inst = (operation.Inst & ~Instruction.MrMask) | Instruction.MrStorage; + + storageOp = new Operation(inst, operation.Dest, sources); + } + else if (operation.Inst == Instruction.LoadGlobal) + { + storageOp = new Operation(Instruction.LoadStorage, operation.Dest, sources); + } + else + { + storageOp = new Operation(Instruction.StoreStorage, null, sources); + } + + for (int index = 0; index < operation.SourcesCount; index++) + { + operation.SetSource(index, null); + } + + LinkedListNode oldNode = node; + + node = node.List.AddBefore(node, storageOp); + + node.List.Remove(oldNode); + + return node; + } + + private static int SearchForStorageBase(Operation operation, int sbStart, int sbEnd) + { + Queue assignments = new Queue(); + + assignments.Enqueue(operation); + + while (assignments.TryDequeue(out operation)) + { + for (int index = 0; index < operation.SourcesCount; index++) + { + Operand source = operation.GetSource(index); + + if (source.Type == OperandType.ConstantBuffer) + { + int slot = source.GetCbufSlot(); + int offset = source.GetCbufOffset(); + + if (slot == 0 && offset >= sbStart && offset < sbEnd) + { + int storageIndex = (offset - sbStart) / StorageDescSize; + + return storageIndex; + } + } + + if (source.AsgOp is Operation asgOperation) + { + assignments.Enqueue(asgOperation); + } + } + } + + return -1; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Translation/Optimizations/HalfConversion.cs b/Ryujinx.Graphics.Shader/Translation/Optimizations/HalfConversion.cs new file mode 100644 index 0000000000..960602726a --- /dev/null +++ b/Ryujinx.Graphics.Shader/Translation/Optimizations/HalfConversion.cs @@ -0,0 +1,47 @@ +using System; + +namespace Ryujinx.Graphics.Shader.Translation.Optimizations +{ + static class HalfConversion + { + public static float HalfToSingle(int value) + { + int mantissa = (value >> 0) & 0x3ff; + int exponent = (value >> 10) & 0x1f; + int sign = (value >> 15) & 0x1; + + if (exponent == 0x1f) + { + // NaN or Infinity. + mantissa <<= 13; + exponent = 0xff; + } + else if (exponent != 0 || mantissa != 0 ) + { + if (exponent == 0) + { + // Denormal. + int e = -1; + int m = mantissa; + + do + { + e++; + m <<= 1; + } + while ((m & 0x400) == 0); + + mantissa = m & 0x3ff; + exponent = e; + } + + mantissa <<= 13; + exponent = 127 - 15 + exponent; + } + + int output = (sign << 31) | (exponent << 23) | mantissa; + + return BitConverter.Int32BitsToSingle(output); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Translation/Optimizations/Optimizer.cs b/Ryujinx.Graphics.Shader/Translation/Optimizations/Optimizer.cs new file mode 100644 index 0000000000..c5db4678b7 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Translation/Optimizations/Optimizer.cs @@ -0,0 +1,285 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace Ryujinx.Graphics.Shader.Translation.Optimizations +{ + static class Optimizer + { + public static void RunPass(BasicBlock[] blocks, ShaderConfig config) + { + for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++) + { + GlobalToStorage.RunPass(blocks[blkIndex], config); + } + + bool modified; + + do + { + modified = false; + + for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++) + { + BasicBlock block = blocks[blkIndex]; + + LinkedListNode node = block.Operations.First; + + while (node != null) + { + LinkedListNode nextNode = node.Next; + + bool isUnused = IsUnused(node.Value); + + if (!(node.Value is Operation operation) || isUnused) + { + if (isUnused) + { + RemoveNode(block, node); + + modified = true; + } + + node = nextNode; + + continue; + } + + ConstantFolding.RunPass(operation); + + Simplification.RunPass(operation); + + if (DestIsLocalVar(operation)) + { + if (operation.Inst == Instruction.Copy) + { + PropagateCopy(operation); + + RemoveNode(block, node); + + modified = true; + } + else if ((operation.Inst == Instruction.PackHalf2x16 && PropagatePack(operation)) || + (operation.Inst == Instruction.ShuffleXor && MatchDdxOrDdy(operation))) + { + if (operation.Dest.UseOps.Count == 0) + { + RemoveNode(block, node); + } + + modified = true; + } + } + + node = nextNode; + } + + if (BranchElimination.RunPass(block)) + { + RemoveNode(block, block.Operations.Last); + + modified = true; + } + } + } + while (modified); + + for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++) + { + BindlessToIndexed.RunPass(blocks[blkIndex]); + } + } + + private static void PropagateCopy(Operation copyOp) + { + // Propagate copy source operand to all uses of + // the destination operand. + Operand dest = copyOp.Dest; + Operand src = copyOp.GetSource(0); + + INode[] uses = dest.UseOps.ToArray(); + + foreach (INode useNode in uses) + { + for (int index = 0; index < useNode.SourcesCount; index++) + { + if (useNode.GetSource(index) == dest) + { + useNode.SetSource(index, src); + } + } + } + } + + private static bool PropagatePack(Operation packOp) + { + // Propagate pack source operands to uses by unpack + // instruction. The source depends on the unpack instruction. + bool modified = false; + + Operand dest = packOp.Dest; + Operand src0 = packOp.GetSource(0); + Operand src1 = packOp.GetSource(1); + + INode[] uses = dest.UseOps.ToArray(); + + foreach (INode useNode in uses) + { + if (!(useNode is Operation operation) || operation.Inst != Instruction.UnpackHalf2x16) + { + continue; + } + + if (operation.GetSource(0) == dest) + { + operation.TurnIntoCopy(operation.Index == 1 ? src1 : src0); + + modified = true; + } + } + + return modified; + } + + public static bool MatchDdxOrDdy(Operation operation) + { + // It's assumed that "operation.Inst" is ShuffleXor, + // that should be checked before calling this method. + Debug.Assert(operation.Inst == Instruction.ShuffleXor); + + bool modified = false; + + Operand src2 = operation.GetSource(1); + Operand src3 = operation.GetSource(2); + + if (src2.Type != OperandType.Constant || (src2.Value != 1 && src2.Value != 2)) + { + return false; + } + + if (src3.Type != OperandType.Constant || src3.Value != 0x1c03) + { + return false; + } + + bool isDdy = src2.Value == 2; + bool isDdx = !isDdy; + + // We can replace any use by a FSWZADD with DDX/DDY, when + // the following conditions are true: + // - The mask should be 0b10100101 for DDY, or 0b10011001 for DDX. + // - The first source operand must be the shuffle output. + // - The second source operand must be the shuffle first source operand. + INode[] uses = operation.Dest.UseOps.ToArray(); + + foreach (INode use in uses) + { + if (!(use is Operation test)) + { + continue; + } + + if (!(use is Operation useOp) || useOp.Inst != Instruction.SwizzleAdd) + { + continue; + } + + Operand fswzaddSrc1 = useOp.GetSource(0); + Operand fswzaddSrc2 = useOp.GetSource(1); + Operand fswzaddSrc3 = useOp.GetSource(2); + + if (fswzaddSrc1 != operation.Dest) + { + continue; + } + + if (fswzaddSrc2 != operation.GetSource(0)) + { + continue; + } + + if (fswzaddSrc3.Type != OperandType.Constant) + { + continue; + } + + int mask = fswzaddSrc3.Value; + + if ((isDdx && mask != 0b10011001) || + (isDdy && mask != 0b10100101)) + { + continue; + } + + useOp.TurnInto(isDdx ? Instruction.Ddx : Instruction.Ddy, fswzaddSrc2); + + modified = true; + } + + return modified; + } + + private static void RemoveNode(BasicBlock block, LinkedListNode llNode) + { + // 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(llNode); + + Queue nodes = new Queue(); + + nodes.Enqueue(llNode.Value); + + while (nodes.TryDequeue(out INode node)) + { + for (int index = 0; index < node.SourcesCount; index++) + { + Operand src = node.GetSource(index); + + if (src.Type != OperandType.LocalVariable) + { + continue; + } + + if (src.UseOps.Remove(node) && src.UseOps.Count == 0) + { + nodes.Enqueue(src.AsgOp); + } + } + } + } + + private static bool IsUnused(INode node) + { + return !HasSideEffects(node) && DestIsLocalVar(node) && node.Dest.UseOps.Count == 0; + } + + private static bool HasSideEffects(INode node) + { + if (node is Operation operation) + { + switch (operation.Inst & Instruction.Mask) + { + case Instruction.AtomicAdd: + case Instruction.AtomicAnd: + case Instruction.AtomicCompareAndSwap: + case Instruction.AtomicMaxS32: + case Instruction.AtomicMaxU32: + case Instruction.AtomicMinS32: + case Instruction.AtomicMinU32: + case Instruction.AtomicOr: + case Instruction.AtomicSwap: + case Instruction.AtomicXor: + return true; + } + } + + return false; + } + + private static bool DestIsLocalVar(INode node) + { + return node.Dest != null && node.Dest.Type == OperandType.LocalVariable; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Translation/Optimizations/Simplification.cs b/Ryujinx.Graphics.Shader/Translation/Optimizations/Simplification.cs new file mode 100644 index 0000000000..8d05f99afa --- /dev/null +++ b/Ryujinx.Graphics.Shader/Translation/Optimizations/Simplification.cs @@ -0,0 +1,147 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; + +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Translation.Optimizations +{ + static class Simplification + { + private const int AllOnes = ~0; + + public static void RunPass(Operation operation) + { + switch (operation.Inst) + { + case Instruction.Add: + case Instruction.BitwiseExclusiveOr: + TryEliminateBinaryOpCommutative(operation, 0); + break; + + case Instruction.BitwiseAnd: + TryEliminateBitwiseAnd(operation); + break; + + case Instruction.BitwiseOr: + TryEliminateBitwiseOr(operation); + break; + + case Instruction.ConditionalSelect: + TryEliminateConditionalSelect(operation); + break; + + case Instruction.Divide: + TryEliminateBinaryOpY(operation, 1); + break; + + case Instruction.Multiply: + TryEliminateBinaryOpCommutative(operation, 1); + break; + + case Instruction.ShiftLeft: + case Instruction.ShiftRightS32: + case Instruction.ShiftRightU32: + 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)) + { + operation.TurnIntoCopy(y); + } + else if (IsConstEqual(y, AllOnes)) + { + operation.TurnIntoCopy(x); + } + else if (IsConstEqual(x, 0) || IsConstEqual(y, 0)) + { + operation.TurnIntoCopy(Const(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) || IsConstEqual(y, AllOnes)) + { + operation.TurnIntoCopy(Const(AllOnes)); + } + } + + private static void TryEliminateBinaryOpY(Operation operation, int comparand) + { + Operand x = operation.GetSource(0); + Operand y = operation.GetSource(1); + + if (IsConstEqual(y, comparand)) + { + operation.TurnIntoCopy(x); + } + } + + private static void TryEliminateBinaryOpCommutative(Operation operation, int 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.Type != OperandType.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, int comparand) + { + if (operand.Type != OperandType.Constant) + { + return false; + } + + return operand.Value == comparand; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs b/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs new file mode 100644 index 0000000000..8a0f25fe45 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs @@ -0,0 +1,106 @@ +using System; + +namespace Ryujinx.Graphics.Shader.Translation +{ + struct ShaderConfig + { + public ShaderStage Stage { get; } + + public OutputTopology OutputTopology { get; } + + public int MaxOutputVertices { get; } + + public OutputMapTarget[] OmapTargets { get; } + public bool OmapSampleMask { get; } + public bool OmapDepth { get; } + + public TranslationFlags Flags { get; } + + private TranslatorCallbacks _callbacks; + + public ShaderConfig(TranslationFlags flags, TranslatorCallbacks callbacks) + { + Stage = ShaderStage.Compute; + OutputTopology = OutputTopology.PointList; + MaxOutputVertices = 0; + OmapTargets = null; + OmapSampleMask = false; + OmapDepth = false; + Flags = flags; + _callbacks = callbacks; + } + + public ShaderConfig(ShaderHeader header, TranslationFlags flags, TranslatorCallbacks callbacks) + { + Stage = header.Stage; + OutputTopology = header.OutputTopology; + MaxOutputVertices = header.MaxOutputVertexCount; + OmapTargets = header.OmapTargets; + OmapSampleMask = header.OmapSampleMask; + OmapDepth = header.OmapDepth; + Flags = flags; + _callbacks = callbacks; + } + + public int GetDepthRegister() + { + int count = 0; + + for (int index = 0; index < OmapTargets.Length; index++) + { + for (int component = 0; component < 4; component++) + { + if (OmapTargets[index].ComponentEnabled(component)) + { + count++; + } + } + } + + // The depth register is always two registers after the last color output. + return count + 1; + } + + public bool QueryInfoBool(QueryInfoName info, int index = 0) + { + return Convert.ToBoolean(QueryInfo(info, index)); + } + + public int QueryInfo(QueryInfoName info, int index = 0) + { + if (_callbacks.QueryInfo != null) + { + return _callbacks.QueryInfo(info, index); + } + else + { + switch (info) + { + case QueryInfoName.ComputeLocalSizeX: + case QueryInfoName.ComputeLocalSizeY: + case QueryInfoName.ComputeLocalSizeZ: + return 1; + case QueryInfoName.ComputeSharedMemorySize: + return 0xc000; + case QueryInfoName.IsTextureBuffer: + return Convert.ToInt32(false); + case QueryInfoName.IsTextureRectangle: + return Convert.ToInt32(false); + case QueryInfoName.PrimitiveTopology: + return (int)InputTopology.Points; + case QueryInfoName.StorageBufferOffsetAlignment: + return 16; + case QueryInfoName.SupportsNonConstantTextureOffset: + return Convert.ToInt32(true); + } + } + + return 0; + } + + public void PrintLog(string message) + { + _callbacks.PrintLog?.Invoke(message); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Translation/ShaderHeader.cs b/Ryujinx.Graphics.Shader/Translation/ShaderHeader.cs new file mode 100644 index 0000000000..42701fbdca --- /dev/null +++ b/Ryujinx.Graphics.Shader/Translation/ShaderHeader.cs @@ -0,0 +1,148 @@ +using Ryujinx.Graphics.Shader.Decoders; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Shader.Translation +{ + struct OutputMapTarget + { + public bool Red { get; } + public bool Green { get; } + public bool Blue { get; } + public bool Alpha { get; } + + public bool Enabled => Red || Green || Blue || Alpha; + + public OutputMapTarget(bool red, bool green, bool blue, bool alpha) + { + Red = red; + Green = green; + Blue = blue; + Alpha = alpha; + } + + public bool ComponentEnabled(int component) + { + switch (component) + { + case 0: return Red; + case 1: return Green; + case 2: return Blue; + case 3: return Alpha; + } + + throw new ArgumentOutOfRangeException(nameof(component)); + } + } + + class ShaderHeader + { + public int SphType { get; } + public int Version { get; } + + public ShaderStage Stage { get; } + + public bool MrtEnable { get; } + + public bool KillsPixels { get; } + + public bool DoesGlobalStore { get; } + + public int SassVersion { get; } + + public bool DoesLoadOrStore { get; } + public bool DoesFp64 { get; } + + public int StreamOutMask { get; } + + public int ShaderLocalMemoryLowSize { get; } + + public int PerPatchAttributeCount { get; } + + public int ShaderLocalMemoryHighSize { get; } + + public int ThreadsPerInputPrimitive { get; } + + public int ShaderLocalMemoryCrsSize { get; } + + public OutputTopology OutputTopology { get; } + + public int MaxOutputVertexCount { get; } + + public int StoreReqStart { get; } + public int StoreReqEnd { get; } + + public OutputMapTarget[] OmapTargets { get; } + public bool OmapSampleMask { get; } + public bool OmapDepth { get; } + + public ShaderHeader(ReadOnlySpan code) + { + ReadOnlySpan header = MemoryMarshal.Cast(code); + + int commonWord0 = header[0]; + int commonWord1 = header[1]; + int commonWord2 = header[2]; + int commonWord3 = header[3]; + int commonWord4 = header[4]; + + SphType = commonWord0.Extract(0, 5); + Version = commonWord0.Extract(5, 5); + + Stage = (ShaderStage)commonWord0.Extract(10, 4); + + // Invalid. + if (Stage == ShaderStage.Compute) + { + Stage = ShaderStage.Vertex; + } + + MrtEnable = commonWord0.Extract(14); + + KillsPixels = commonWord0.Extract(15); + + DoesGlobalStore = commonWord0.Extract(16); + + SassVersion = commonWord0.Extract(17, 4); + + DoesLoadOrStore = commonWord0.Extract(26); + DoesFp64 = commonWord0.Extract(27); + + StreamOutMask = commonWord0.Extract(28, 4); + + ShaderLocalMemoryLowSize = commonWord1.Extract(0, 24); + + PerPatchAttributeCount = commonWord1.Extract(24, 8); + + ShaderLocalMemoryHighSize = commonWord2.Extract(0, 24); + + ThreadsPerInputPrimitive = commonWord2.Extract(24, 8); + + ShaderLocalMemoryCrsSize = commonWord3.Extract(0, 24); + + OutputTopology = (OutputTopology)commonWord3.Extract(24, 4); + + MaxOutputVertexCount = commonWord4.Extract(0, 12); + + StoreReqStart = commonWord4.Extract(12, 8); + StoreReqEnd = commonWord4.Extract(24, 8); + + int type2OmapTarget = header[18]; + int type2Omap = header[19]; + + OmapTargets = new OutputMapTarget[8]; + + for (int offset = 0; offset < OmapTargets.Length * 4; offset += 4) + { + OmapTargets[offset >> 2] = new OutputMapTarget( + type2OmapTarget.Extract(offset + 0), + type2OmapTarget.Extract(offset + 1), + type2OmapTarget.Extract(offset + 2), + type2OmapTarget.Extract(offset + 3)); + } + + OmapSampleMask = type2Omap.Extract(0); + OmapDepth = type2Omap.Extract(1); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Translation/Ssa.cs b/Ryujinx.Graphics.Shader/Translation/Ssa.cs new file mode 100644 index 0000000000..a4d763be71 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Translation/Ssa.cs @@ -0,0 +1,330 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System.Collections.Generic; + +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Translation +{ + static class Ssa + { + private const int GprsAndPredsCount = RegisterConsts.GprsCount + RegisterConsts.PredsCount; + + private class DefMap + { + private Dictionary _map; + + private long[] _phiMasks; + + public DefMap() + { + _map = new Dictionary(); + + _phiMasks = new long[(RegisterConsts.TotalCount + 63) / 64]; + } + + public bool TryAddOperand(Register reg, Operand operand) + { + return _map.TryAdd(reg, operand); + } + + public bool TryGetOperand(Register reg, out Operand operand) + { + return _map.TryGetValue(reg, out operand); + } + + public bool AddPhi(Register reg) + { + int key = GetKeyFromRegister(reg); + + int index = key / 64; + int bit = key & 63; + + long mask = 1L << bit; + + if ((_phiMasks[index] & mask) != 0) + { + return false; + } + + _phiMasks[index] |= mask; + + return true; + } + + public bool HasPhi(Register reg) + { + int key = GetKeyFromRegister(reg); + + int index = key / 64; + int bit = key & 63; + + return (_phiMasks[index] & (1L << bit)) != 0; + } + } + + private struct Definition + { + public BasicBlock Block { get; } + public Operand Local { get; } + + public Definition(BasicBlock block, Operand local) + { + Block = block; + Local = local; + } + } + + public static void Rename(BasicBlock[] blocks) + { + DefMap[] globalDefs = new DefMap[blocks.Length]; + + for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++) + { + globalDefs[blkIndex] = new DefMap(); + } + + Queue dfPhiBlocks = new Queue(); + + // First pass, get all defs and locals uses. + for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++) + { + Operand[] localDefs = new Operand[RegisterConsts.TotalCount]; + + Operand RenameLocal(Operand operand) + { + if (operand != null && operand.Type == OperandType.Register) + { + Operand local = localDefs[GetKeyFromRegister(operand.GetRegister())]; + + operand = local ?? operand; + } + + return operand; + } + + BasicBlock block = blocks[blkIndex]; + + LinkedListNode node = block.Operations.First; + + while (node != null) + { + if (node.Value is Operation operation) + { + for (int index = 0; index < operation.SourcesCount; index++) + { + operation.SetSource(index, RenameLocal(operation.GetSource(index))); + } + + if (operation.Dest != null && operation.Dest.Type == OperandType.Register) + { + Operand local = Local(); + + localDefs[GetKeyFromRegister(operation.Dest.GetRegister())] = local; + + operation.Dest = local; + } + } + + node = node.Next; + } + + for (int index = 0; index < RegisterConsts.TotalCount; index++) + { + Operand local = localDefs[index]; + + if (local == null) + { + continue; + } + + Register reg = GetRegisterFromKey(index); + + globalDefs[block.Index].TryAddOperand(reg, local); + + dfPhiBlocks.Enqueue(block); + + while (dfPhiBlocks.TryDequeue(out BasicBlock dfPhiBlock)) + { + foreach (BasicBlock domFrontier in dfPhiBlock.DominanceFrontiers) + { + if (globalDefs[domFrontier.Index].AddPhi(reg)) + { + dfPhiBlocks.Enqueue(domFrontier); + } + } + } + } + } + + // Second pass, rename variables with definitions on different blocks. + for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++) + { + Operand[] localDefs = new Operand[RegisterConsts.TotalCount]; + + BasicBlock block = blocks[blkIndex]; + + Operand RenameGlobal(Operand operand) + { + if (operand != null && operand.Type == OperandType.Register) + { + int key = GetKeyFromRegister(operand.GetRegister()); + + Operand local = localDefs[key]; + + if (local != null) + { + return local; + } + + operand = FindDefinitionForCurr(globalDefs, block, operand.GetRegister()); + + localDefs[key] = operand; + } + + return operand; + } + + LinkedListNode node = block.Operations.First; + + while (node != null) + { + if (node.Value is Operation operation) + { + for (int index = 0; index < operation.SourcesCount; index++) + { + operation.SetSource(index, RenameGlobal(operation.GetSource(index))); + } + } + + node = node.Next; + } + } + } + + private static Operand FindDefinitionForCurr(DefMap[] globalDefs, BasicBlock current, Register reg) + { + if (globalDefs[current.Index].HasPhi(reg)) + { + return InsertPhi(globalDefs, current, reg); + } + + if (current != current.ImmediateDominator) + { + return FindDefinition(globalDefs, current.ImmediateDominator, reg).Local; + } + + return Undef(); + } + + private static Definition FindDefinition(DefMap[] globalDefs, BasicBlock current, Register reg) + { + foreach (BasicBlock block in SelfAndImmediateDominators(current)) + { + DefMap defMap = globalDefs[block.Index]; + + if (defMap.TryGetOperand(reg, out Operand lastDef)) + { + return new Definition(block, lastDef); + } + + if (defMap.HasPhi(reg)) + { + return new Definition(block, InsertPhi(globalDefs, block, reg)); + } + } + + return new Definition(current, Undef()); + } + + private static IEnumerable SelfAndImmediateDominators(BasicBlock block) + { + while (block != block.ImmediateDominator) + { + yield return block; + + block = block.ImmediateDominator; + } + + yield return block; + } + + private static Operand InsertPhi(DefMap[] globalDefs, BasicBlock block, Register reg) + { + // 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(); + + PhiNode phi = new PhiNode(local); + + AddPhi(block, phi); + + globalDefs[block.Index].TryAddOperand(reg, local); + + foreach (BasicBlock predecessor in block.Predecessors) + { + Definition def = FindDefinition(globalDefs, predecessor, reg); + + phi.AddSource(def.Block, def.Local); + } + + return local; + } + + private static void AddPhi(BasicBlock block, PhiNode phi) + { + LinkedListNode node = block.Operations.First; + + if (node != null) + { + while (node.Next?.Value is PhiNode) + { + node = node.Next; + } + } + + if (node?.Value is PhiNode) + { + block.Operations.AddAfter(node, phi); + } + else + { + block.Operations.AddFirst(phi); + } + } + + private static int GetKeyFromRegister(Register reg) + { + if (reg.Type == RegisterType.Gpr) + { + return reg.Index; + } + else if (reg.Type == RegisterType.Predicate) + { + return RegisterConsts.GprsCount + reg.Index; + } + else /* if (reg.Type == RegisterType.Flag) */ + { + return GprsAndPredsCount + reg.Index; + } + } + + private static Register GetRegisterFromKey(int key) + { + if (key < RegisterConsts.GprsCount) + { + return new Register(key, RegisterType.Gpr); + } + else if (key < GprsAndPredsCount) + { + return new Register(key - RegisterConsts.GprsCount, RegisterType.Predicate); + } + else /* if (key < RegisterConsts.TotalCount) */ + { + return new Register(key - GprsAndPredsCount, RegisterType.Flag); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Translation/TranslationFlags.cs b/Ryujinx.Graphics.Shader/Translation/TranslationFlags.cs new file mode 100644 index 0000000000..b871574627 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Translation/TranslationFlags.cs @@ -0,0 +1,13 @@ +using System; + +namespace Ryujinx.Graphics.Shader.Translation +{ + [Flags] + public enum TranslationFlags + { + None = 0, + + Compute = 1 << 0, + DebugMode = 1 << 1 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Translation/Translator.cs b/Ryujinx.Graphics.Shader/Translation/Translator.cs new file mode 100644 index 0000000000..a333db9529 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Translation/Translator.cs @@ -0,0 +1,305 @@ +using Ryujinx.Graphics.Shader.CodeGen.Glsl; +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.StructuredIr; +using Ryujinx.Graphics.Shader.Translation.Optimizations; +using System; +using System.Collections.Generic; + +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Translation +{ + public static class Translator + { + private const int HeaderSize = 0x50; + + public static ReadOnlySpan ExtractCode(ReadOnlySpan code, bool compute, out int headerSize) + { + headerSize = compute ? 0 : HeaderSize; + + Block[] cfg = Decoder.Decode(code, (ulong)headerSize); + + if (cfg == null) + { + return code; + } + + ulong endAddress = 0; + + foreach (Block block in cfg) + { + if (endAddress < block.EndAddress) + { + endAddress = block.EndAddress; + } + } + + return code.Slice(0, headerSize + (int)endAddress); + } + + public static ShaderProgram Translate(ReadOnlySpan code, TranslatorCallbacks callbacks, TranslationFlags flags) + { + Operation[] ops = DecodeShader(code, callbacks, flags, out ShaderConfig config, out int size); + + return Translate(ops, config, size); + } + + public static ShaderProgram Translate(ReadOnlySpan vpACode, ReadOnlySpan vpBCode, TranslatorCallbacks callbacks, TranslationFlags flags) + { + Operation[] vpAOps = DecodeShader(vpACode, callbacks, flags, out _, out _); + Operation[] vpBOps = DecodeShader(vpBCode, callbacks, flags, out ShaderConfig config, out int sizeB); + + return Translate(Combine(vpAOps, vpBOps), config, sizeB); + } + + private static ShaderProgram Translate(Operation[] ops, ShaderConfig config, int size) + { + BasicBlock[] blocks = ControlFlowGraph.MakeCfg(ops); + + if (blocks.Length > 0) + { + Dominance.FindDominators(blocks[0], blocks.Length); + + Dominance.FindDominanceFrontiers(blocks); + + Ssa.Rename(blocks); + + Optimizer.RunPass(blocks, config); + + Lowering.RunPass(blocks, config); + } + + StructuredProgramInfo sInfo = StructuredProgram.MakeStructuredProgram(blocks, config); + + GlslProgram program = GlslGenerator.Generate(sInfo, config); + + ShaderProgramInfo spInfo = new ShaderProgramInfo( + program.CBufferDescriptors, + program.SBufferDescriptors, + program.TextureDescriptors, + program.ImageDescriptors, + sInfo.InterpolationQualifiers, + sInfo.UsesInstanceId); + + string glslCode = program.Code; + + return new ShaderProgram(spInfo, config.Stage, glslCode, size); + } + + private static Operation[] DecodeShader( + ReadOnlySpan code, + TranslatorCallbacks callbacks, + TranslationFlags flags, + out ShaderConfig config, + out int size) + { + Block[] cfg; + + if ((flags & TranslationFlags.Compute) != 0) + { + config = new ShaderConfig(flags, callbacks); + + cfg = Decoder.Decode(code, 0); + } + else + { + config = new ShaderConfig(new ShaderHeader(code), flags, callbacks); + + cfg = Decoder.Decode(code, HeaderSize); + } + + if (cfg == null) + { + config.PrintLog("Invalid branch detected, failed to build CFG."); + + size = 0; + + return Array.Empty(); + } + + EmitterContext context = new EmitterContext(config); + + ulong maxEndAddress = 0; + + for (int blkIndex = 0; blkIndex < cfg.Length; blkIndex++) + { + Block block = cfg[blkIndex]; + + if (maxEndAddress < block.EndAddress) + { + maxEndAddress = block.EndAddress; + } + + context.CurrBlock = block; + + context.MarkLabel(context.GetLabel(block.Address)); + + for (int opIndex = 0; opIndex < block.OpCodes.Count; opIndex++) + { + OpCode op = block.OpCodes[opIndex]; + + if ((flags & TranslationFlags.DebugMode) != 0) + { + string instName; + + if (op.Emitter != null) + { + instName = op.Emitter.Method.Name; + } + else + { + instName = "???"; + + config.PrintLog($"Invalid instruction at 0x{op.Address:X6} (0x{op.RawOpCode:X16})."); + } + + string dbgComment = $"0x{op.Address:X6}: 0x{op.RawOpCode:X16} {instName}"; + + context.Add(new CommentNode(dbgComment)); + } + + if (op.NeverExecute) + { + continue; + } + + Operand predSkipLbl = null; + + bool skipPredicateCheck = op is OpCodeBranch opBranch && !opBranch.PushTarget; + + if (op is OpCodeBranchPop opBranchPop) + { + // If the instruction is a SYNC or BRK instruction with only one + // possible target address, then the instruction is basically + // just a simple branch, we can generate code similar to branch + // instructions, with the condition check on the branch itself. + skipPredicateCheck = opBranchPop.Targets.Count < 2; + } + + if (!(op.Predicate.IsPT || skipPredicateCheck)) + { + Operand label; + + if (opIndex == block.OpCodes.Count - 1 && block.Next != null) + { + label = context.GetLabel(block.Next.Address); + } + else + { + label = Label(); + + predSkipLbl = label; + } + + Operand pred = Register(op.Predicate); + + if (op.InvertPredicate) + { + context.BranchIfTrue(label, pred); + } + else + { + context.BranchIfFalse(label, pred); + } + } + + context.CurrOp = op; + + op.Emitter?.Invoke(context); + + if (predSkipLbl != null) + { + context.MarkLabel(predSkipLbl); + } + } + } + + size = (int)maxEndAddress + (((flags & TranslationFlags.Compute) != 0) ? 0 : HeaderSize); + + return context.GetOperations(); + } + + private static Operation[] Combine(Operation[] a, Operation[] b) + { + // Here we combine two shaders. + // For shader A: + // - All user attribute stores on shader A are turned into copies to a + // temporary variable. It's assumed that shader B will consume them. + // - All return instructions are turned into branch instructions, the + // branch target being the start of the shader B code. + // For shader B: + // - All user attribute loads on shader B are turned into copies from a + // temporary variable, as long that attribute is written by shader A. + List output = new List(a.Length + b.Length); + + Operand[] temps = new Operand[AttributeConsts.UserAttributesCount * 4]; + + Operand lblB = Label(); + + for (int index = 0; index < a.Length; index++) + { + Operation operation = a[index]; + + if (IsUserAttribute(operation.Dest)) + { + int tIndex = (operation.Dest.Value - AttributeConsts.UserAttributeBase) / 4; + + Operand temp = temps[tIndex]; + + if (temp == null) + { + temp = Local(); + + temps[tIndex] = temp; + } + + operation.Dest = temp; + } + + if (operation.Inst == Instruction.Return) + { + output.Add(new Operation(Instruction.Branch, lblB)); + } + else + { + output.Add(operation); + } + } + + output.Add(new Operation(Instruction.MarkLabel, lblB)); + + for (int index = 0; index < b.Length; index++) + { + Operation operation = b[index]; + + for (int srcIndex = 0; srcIndex < operation.SourcesCount; srcIndex++) + { + Operand src = operation.GetSource(srcIndex); + + if (IsUserAttribute(src)) + { + Operand temp = temps[(src.Value - AttributeConsts.UserAttributeBase) / 4]; + + if (temp != null) + { + operation.SetSource(srcIndex, temp); + } + } + } + + output.Add(operation); + } + + return output.ToArray(); + } + + private static bool IsUserAttribute(Operand operand) + { + return operand != null && + operand.Type == OperandType.Attribute && + operand.Value >= AttributeConsts.UserAttributeBase && + operand.Value < AttributeConsts.UserAttributeEnd; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Translation/TranslatorCallbacks.cs b/Ryujinx.Graphics.Shader/Translation/TranslatorCallbacks.cs new file mode 100644 index 0000000000..e0e9852fa3 --- /dev/null +++ b/Ryujinx.Graphics.Shader/Translation/TranslatorCallbacks.cs @@ -0,0 +1,17 @@ +using System; + +namespace Ryujinx.Graphics.Shader.Translation +{ + public struct TranslatorCallbacks + { + internal Func QueryInfo { get; } + + internal Action PrintLog { get; } + + public TranslatorCallbacks(Func queryInfoCallback, Action printLogCallback) + { + QueryInfo = queryInfoCallback; + PrintLog = printLogCallback; + } + } +} diff --git a/Ryujinx.Graphics.Texture/Astc/AstcDecoder.cs b/Ryujinx.Graphics.Texture/Astc/AstcDecoder.cs new file mode 100644 index 0000000000..74623b38f5 --- /dev/null +++ b/Ryujinx.Graphics.Texture/Astc/AstcDecoder.cs @@ -0,0 +1,1613 @@ +using Ryujinx.Common.Utilities; +using System; +using System.Diagnostics; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Texture.Astc +{ + // https://github.com/GammaUNC/FasTC/blob/master/ASTCEncoder/src/Decompressor.cpp + public class AstcDecoder + { + private ReadOnlyMemory InputBuffer { get; } + private Memory OutputBuffer { get; } + + private int BlockSizeX { get; } + private int BlockSizeY { get; } + + private AstcLevel[] Levels { get; } + + private bool Success { get; set; } + + public int TotalBlockCount { get; } + + public AstcDecoder( + ReadOnlyMemory inputBuffer, + Memory outputBuffer, + int blockWidth, + int blockHeight, + int width, + int height, + int depth, + int levels) + { + if ((uint)blockWidth > 12) + { + throw new ArgumentOutOfRangeException(nameof(blockWidth)); + } + + if ((uint)blockHeight > 12) + { + throw new ArgumentOutOfRangeException(nameof(blockHeight)); + } + + InputBuffer = inputBuffer; + OutputBuffer = outputBuffer; + + BlockSizeX = blockWidth; + BlockSizeY = blockHeight; + + Levels = new AstcLevel[levels]; + + Success = true; + + TotalBlockCount = 0; + + int currentInputBlock = 0; + int currentOutputOffset = 0; + + for (int i = 0; i < levels; i++) + { + ref AstcLevel level = ref Levels[i]; + + level.ImageSizeX = Math.Max(1, width >> i); + level.ImageSizeY = Math.Max(1, height >> i); + level.ImageSizeZ = Math.Max(1, depth >> i); + + level.BlockCountX = (level.ImageSizeX + blockWidth - 1) / blockWidth; + level.BlockCountY = (level.ImageSizeY + blockHeight - 1) / blockHeight; + + level.StartBlock = currentInputBlock; + level.OutputByteOffset = currentOutputOffset; + + currentInputBlock += level.TotalBlockCount; + currentOutputOffset += level.PixelCount * 4; + } + + TotalBlockCount = currentInputBlock; + } + + private struct AstcLevel + { + public int ImageSizeX { get; set; } + public int ImageSizeY { get; set; } + public int ImageSizeZ { get; set; } + + public int BlockCountX { get; set; } + public int BlockCountY { get; set; } + + public int StartBlock { get; set; } + public int OutputByteOffset { get; set; } + + public int TotalBlockCount => BlockCountX * BlockCountY * ImageSizeZ; + public int PixelCount => ImageSizeX * ImageSizeY * ImageSizeZ; + } + + public static int QueryDecompressedSize(int sizeX, int sizeY, int sizeZ, int levelCount) + { + int size = 0; + + for (int i = 0; i < levelCount; i++) + { + int levelSizeX = Math.Max(1, sizeX >> i); + int levelSizeY = Math.Max(1, sizeY >> i); + int levelSizeZ = Math.Max(1, sizeZ >> i); + + size += levelSizeX * levelSizeY * levelSizeZ; + } + + return size * 4; + } + + public void ProcessBlock(int index) + { + Buffer16 inputBlock = MemoryMarshal.Cast(InputBuffer.Span)[index]; + + Span decompressedData = stackalloc int[144]; + + try + { + DecompressBlock(inputBlock, decompressedData, BlockSizeX, BlockSizeY); + } + catch (Exception) + { + Success = false; + } + + Span decompressedBytes = MemoryMarshal.Cast(decompressedData); + + AstcLevel levelInfo = GetLevelInfo(index); + + WriteDecompressedBlock(decompressedBytes, OutputBuffer.Span.Slice(levelInfo.OutputByteOffset), + index - levelInfo.StartBlock, levelInfo); + } + + private AstcLevel GetLevelInfo(int blockIndex) + { + foreach (AstcLevel levelInfo in Levels) + { + if (blockIndex < levelInfo.StartBlock + levelInfo.TotalBlockCount) + { + return levelInfo; + } + } + + throw new AstcDecoderException("Invalid block index."); + } + + private void WriteDecompressedBlock(ReadOnlySpan block, Span outputBuffer, int blockIndex, AstcLevel level) + { + int stride = level.ImageSizeX * 4; + + int blockCordX = blockIndex % level.BlockCountX; + int blockCordY = blockIndex / level.BlockCountX; + + int pixelCordX = blockCordX * BlockSizeX; + int pixelCordY = blockCordY * BlockSizeY; + + int outputPixelsX = Math.Min(pixelCordX + BlockSizeX, level.ImageSizeX) - pixelCordX; + int outputPixelsY = Math.Min(pixelCordY + BlockSizeY, level.ImageSizeY * level.ImageSizeZ) - pixelCordY; + + int outputStart = pixelCordX * 4 + pixelCordY * stride; + int outputOffset = outputStart; + + int inputOffset = 0; + + for (int i = 0; i < outputPixelsY; i++) + { + ReadOnlySpan blockRow = block.Slice(inputOffset, outputPixelsX * 4); + Span outputRow = outputBuffer.Slice(outputOffset); + blockRow.CopyTo(outputRow); + + inputOffset += BlockSizeX * 4; + outputOffset += stride; + } + } + + struct TexelWeightParams + { + public int Width; + public int Height; + public int MaxWeight; + public bool DualPlane; + public bool Error; + public bool VoidExtentLdr; + public bool VoidExtentHdr; + + public int GetPackedBitSize() + { + // How many indices do we have? + int indices = Height * Width; + + if (DualPlane) + { + indices *= 2; + } + + IntegerEncoded intEncoded = IntegerEncoded.CreateEncoding(MaxWeight); + + return intEncoded.GetBitLength(indices); + } + + public int GetNumWeightValues() + { + int ret = Width * Height; + + if (DualPlane) + { + ret *= 2; + } + + return ret; + } + } + + public static bool TryDecodeToRgba8( + ReadOnlyMemory data, + int blockWidth, + int blockHeight, + int width, + int height, + int depth, + int levels, + out Span decoded) + { + byte[] output = new byte[QueryDecompressedSize(width, height, depth, levels)]; + + AstcDecoder decoder = new AstcDecoder(data, output, blockWidth, blockHeight, width, height, depth, levels); + + for (int i = 0; i < decoder.TotalBlockCount; i++) + { + decoder.ProcessBlock(i); + } + + decoded = output; + + return decoder.Success; + } + + public static bool TryDecodeToRgba8( + ReadOnlyMemory data, + Memory outputBuffer, + int blockWidth, + int blockHeight, + int width, + int height, + int depth, + int levels) + { + AstcDecoder decoder = new AstcDecoder(data, outputBuffer, blockWidth, blockHeight, width, height, depth, levels); + + for (int i = 0; i < decoder.TotalBlockCount; i++) + { + decoder.ProcessBlock(i); + } + + return decoder.Success; + } + + public static bool TryDecodeToRgba8P( + ReadOnlyMemory data, + Memory outputBuffer, + int blockWidth, + int blockHeight, + int width, + int height, + int depth, + int levels) + { + AstcDecoder decoder = new AstcDecoder(data, outputBuffer, blockWidth, blockHeight, width, height, depth, levels); + + // Lazy parallelism + Enumerable.Range(0, decoder.TotalBlockCount).AsParallel().ForAll(x => decoder.ProcessBlock(x)); + + return decoder.Success; + } + + public static bool TryDecodeToRgba8P( + ReadOnlyMemory data, + int blockWidth, + int blockHeight, + int width, + int height, + int depth, + int levels, + out Span decoded) + { + byte[] output = new byte[QueryDecompressedSize(width, height, depth, levels)]; + + AstcDecoder decoder = new AstcDecoder(data, output, blockWidth, blockHeight, width, height, depth, levels); + + Enumerable.Range(0, decoder.TotalBlockCount).AsParallel().ForAll(x => decoder.ProcessBlock(x)); + + decoded = output; + + return decoder.Success; + } + + public static bool DecompressBlock( + Buffer16 inputBlock, + Span outputBuffer, + int blockWidth, + int blockHeight) + { + BitStream128 bitStream = new BitStream128(inputBlock); + + DecodeBlockInfo(ref bitStream, out TexelWeightParams texelParams); + + if (texelParams.Error) + { + throw new AstcDecoderException("Invalid block mode"); + } + + if (texelParams.VoidExtentLdr) + { + FillVoidExtentLdr(ref bitStream, outputBuffer, blockWidth, blockHeight); + + return true; + } + + if (texelParams.VoidExtentHdr) + { + throw new AstcDecoderException("HDR void extent blocks are not supported."); + } + + if (texelParams.Width > blockWidth) + { + throw new AstcDecoderException("Texel weight grid width should be smaller than block width."); + } + + if (texelParams.Height > blockHeight) + { + throw new AstcDecoderException("Texel weight grid height should be smaller than block height."); + } + + // Read num partitions + int numberPartitions = bitStream.ReadBits(2) + 1; + Debug.Assert(numberPartitions <= 4); + + if (numberPartitions == 4 && texelParams.DualPlane) + { + throw new AstcDecoderException("Dual plane mode is incompatible with four partition blocks."); + } + + // Based on the number of partitions, read the color endpoint mode for + // each partition. + + // Determine partitions, partition index, and color endpoint modes + int planeIndices; + int partitionIndex; + + Span colorEndpointMode = stackalloc uint[4]; + + BitStream128 colorEndpointStream = new BitStream128(); + + // Read extra config data... + uint baseColorEndpointMode = 0; + + if (numberPartitions == 1) + { + colorEndpointMode[0] = (uint)bitStream.ReadBits(4); + partitionIndex = 0; + } + else + { + partitionIndex = bitStream.ReadBits(10); + baseColorEndpointMode = (uint)bitStream.ReadBits(6); + } + + uint baseMode = (baseColorEndpointMode & 3); + + // Remaining bits are color endpoint data... + int numberWeightBits = texelParams.GetPackedBitSize(); + int remainingBits = bitStream.BitsLeft - numberWeightBits; + + // Consider extra bits prior to texel data... + uint extraColorEndpointModeBits = 0; + + if (baseMode != 0) + { + switch (numberPartitions) + { + case 2: extraColorEndpointModeBits += 2; break; + case 3: extraColorEndpointModeBits += 5; break; + case 4: extraColorEndpointModeBits += 8; break; + default: Debug.Assert(false); break; + } + } + + remainingBits -= (int)extraColorEndpointModeBits; + + // Do we have a dual plane situation? + int planeSelectorBits = 0; + + if (texelParams.DualPlane) + { + planeSelectorBits = 2; + } + + remainingBits -= planeSelectorBits; + + // Read color data... + int colorDataBits = remainingBits; + + while (remainingBits > 0) + { + int numberBits = Math.Min(remainingBits, 8); + int bits = bitStream.ReadBits(numberBits); + colorEndpointStream.WriteBits(bits, numberBits); + remainingBits -= 8; + } + + // Read the plane selection bits + planeIndices = bitStream.ReadBits(planeSelectorBits); + + // Read the rest of the CEM + if (baseMode != 0) + { + uint extraColorEndpointMode = (uint)bitStream.ReadBits((int)extraColorEndpointModeBits); + uint tempColorEndpointMode = (extraColorEndpointMode << 6) | baseColorEndpointMode; + tempColorEndpointMode >>= 2; + + Span c = stackalloc bool[4]; + + for (int i = 0; i < numberPartitions; i++) + { + c[i] = (tempColorEndpointMode & 1) != 0; + tempColorEndpointMode >>= 1; + } + + Span m = stackalloc byte[4]; + + for (int i = 0; i < numberPartitions; i++) + { + m[i] = (byte)(tempColorEndpointMode & 3); + tempColorEndpointMode >>= 2; + Debug.Assert(m[i] <= 3); + } + + for (int i = 0; i < numberPartitions; i++) + { + colorEndpointMode[i] = baseMode; + if (!(c[i])) colorEndpointMode[i] -= 1; + colorEndpointMode[i] <<= 2; + colorEndpointMode[i] |= m[i]; + } + } + else if (numberPartitions > 1) + { + uint tempColorEndpointMode = baseColorEndpointMode >> 2; + + for (int i = 0; i < numberPartitions; i++) + { + colorEndpointMode[i] = tempColorEndpointMode; + } + } + + // Make sure everything up till here is sane. + for (int i = 0; i < numberPartitions; i++) + { + Debug.Assert(colorEndpointMode[i] < 16); + } + Debug.Assert(bitStream.BitsLeft == texelParams.GetPackedBitSize()); + + // Decode both color data and texel weight data + Span colorValues = stackalloc int[32]; // Four values * two endpoints * four maximum partitions + DecodeColorValues(colorValues, ref colorEndpointStream, colorEndpointMode, numberPartitions, colorDataBits); + + EndPointSet endPoints; + unsafe { _ = &endPoints; } // Skip struct initialization + + int colorValuesPosition = 0; + + for (int i = 0; i < numberPartitions; i++) + { + ComputeEndpoints(endPoints.Get(i), colorValues, colorEndpointMode[i], ref colorValuesPosition); + } + + // Read the texel weight data. + Buffer16 texelWeightData = inputBlock; + + // Reverse everything + for (int i = 0; i < 8; i++) + { + byte a = ReverseByte(texelWeightData[i]); + byte b = ReverseByte(texelWeightData[15 - i]); + + texelWeightData[i] = b; + texelWeightData[15 - i] = a; + } + + // Make sure that higher non-texel bits are set to zero + int clearByteStart = (texelParams.GetPackedBitSize() >> 3) + 1; + texelWeightData[clearByteStart - 1] &= (byte)((1 << (texelParams.GetPackedBitSize() % 8)) - 1); + + int cLen = 16 - clearByteStart; + for (int i = clearByteStart; i < clearByteStart + cLen; i++) texelWeightData[i] = 0; + + IntegerSequence texelWeightValues; + unsafe { _ = &texelWeightValues; } // Skip struct initialization + texelWeightValues.Reset(); + + BitStream128 weightBitStream = new BitStream128(texelWeightData); + + IntegerEncoded.DecodeIntegerSequence(ref texelWeightValues, ref weightBitStream, texelParams.MaxWeight, texelParams.GetNumWeightValues()); + + // Blocks can be at most 12x12, so we can have as many as 144 weights + Weights weights; + unsafe { _ = &weights; } // Skip struct initialization + + UnquantizeTexelWeights(ref weights, ref texelWeightValues, ref texelParams, blockWidth, blockHeight); + + ushort[] table = Bits.Replicate8_16Table; + + // Now that we have endpoints and weights, we can interpolate and generate + // the proper decoding... + for (int j = 0; j < blockHeight; j++) + { + for (int i = 0; i < blockWidth; i++) + { + int partition = Select2dPartition(partitionIndex, i, j, numberPartitions, ((blockHeight * blockWidth) < 32)); + Debug.Assert(partition < numberPartitions); + + AstcPixel pixel = new AstcPixel(); + for (int component = 0; component < 4; component++) + { + int component0 = endPoints.Get(partition)[0].GetComponent(component); + component0 = table[component0]; + int component1 = endPoints.Get(partition)[1].GetComponent(component); + component1 = table[component1]; + + int plane = 0; + + if (texelParams.DualPlane && (((planeIndices + 1) & 3) == component)) + { + plane = 1; + } + + int weight = weights.Get(plane)[j * blockWidth + i]; + int finalComponent = (component0 * (64 - weight) + component1 * weight + 32) / 64; + + if (finalComponent == 65535) + { + pixel.SetComponent(component, 255); + } + else + { + double finalComponentFloat = finalComponent; + pixel.SetComponent(component, (int)(255.0 * (finalComponentFloat / 65536.0) + 0.5)); + } + } + + outputBuffer[j * blockWidth + i] = pixel.Pack(); + } + } + + return true; + } + + // Blocks can be at most 12x12, so we can have as many as 144 weights + [StructLayout(LayoutKind.Sequential, Size = 144 * sizeof(int) * Count)] + private struct Weights + { + private int _start; + + public const int Count = 2; + + public Span this[int index] + { + get + { + if ((uint)index >= Count) + { + throw new ArgumentOutOfRangeException(); + } + + ref int start = ref Unsafe.Add(ref _start, index * 144); + + return MemoryMarshal.CreateSpan(ref start, 144); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span Get(int index) + { + ref int start = ref Unsafe.Add(ref _start, index * 144); + + return MemoryMarshal.CreateSpan(ref start, 144); + } + } + + private static int Select2dPartition(int seed, int x, int y, int partitionCount, bool isSmallBlock) + { + return SelectPartition(seed, x, y, 0, partitionCount, isSmallBlock); + } + + private static int SelectPartition(int seed, int x, int y, int z, int partitionCount, bool isSmallBlock) + { + if (partitionCount == 1) + { + return 0; + } + + if (isSmallBlock) + { + x <<= 1; + y <<= 1; + z <<= 1; + } + + seed += (partitionCount - 1) * 1024; + + int rightNum = Hash52((uint)seed); + byte seed01 = (byte)(rightNum & 0xF); + byte seed02 = (byte)((rightNum >> 4) & 0xF); + byte seed03 = (byte)((rightNum >> 8) & 0xF); + byte seed04 = (byte)((rightNum >> 12) & 0xF); + byte seed05 = (byte)((rightNum >> 16) & 0xF); + byte seed06 = (byte)((rightNum >> 20) & 0xF); + byte seed07 = (byte)((rightNum >> 24) & 0xF); + byte seed08 = (byte)((rightNum >> 28) & 0xF); + byte seed09 = (byte)((rightNum >> 18) & 0xF); + byte seed10 = (byte)((rightNum >> 22) & 0xF); + byte seed11 = (byte)((rightNum >> 26) & 0xF); + byte seed12 = (byte)(((rightNum >> 30) | (rightNum << 2)) & 0xF); + + seed01 *= seed01; seed02 *= seed02; + seed03 *= seed03; seed04 *= seed04; + seed05 *= seed05; seed06 *= seed06; + seed07 *= seed07; seed08 *= seed08; + seed09 *= seed09; seed10 *= seed10; + seed11 *= seed11; seed12 *= seed12; + + int seedHash1, seedHash2, seedHash3; + + if ((seed & 1) != 0) + { + seedHash1 = (seed & 2) != 0 ? 4 : 5; + seedHash2 = (partitionCount == 3) ? 6 : 5; + } + else + { + seedHash1 = (partitionCount == 3) ? 6 : 5; + seedHash2 = (seed & 2) != 0 ? 4 : 5; + } + + seedHash3 = (seed & 0x10) != 0 ? seedHash1 : seedHash2; + + seed01 >>= seedHash1; seed02 >>= seedHash2; seed03 >>= seedHash1; seed04 >>= seedHash2; + seed05 >>= seedHash1; seed06 >>= seedHash2; seed07 >>= seedHash1; seed08 >>= seedHash2; + seed09 >>= seedHash3; seed10 >>= seedHash3; seed11 >>= seedHash3; seed12 >>= seedHash3; + + int a = seed01 * x + seed02 * y + seed11 * z + (rightNum >> 14); + int b = seed03 * x + seed04 * y + seed12 * z + (rightNum >> 10); + int c = seed05 * x + seed06 * y + seed09 * z + (rightNum >> 6); + int d = seed07 * x + seed08 * y + seed10 * z + (rightNum >> 2); + + a &= 0x3F; b &= 0x3F; c &= 0x3F; d &= 0x3F; + + if (partitionCount < 4) d = 0; + if (partitionCount < 3) c = 0; + + if (a >= b && a >= c && a >= d) return 0; + else if (b >= c && b >= d) return 1; + else if (c >= d) return 2; + return 3; + } + + static int Hash52(uint val) + { + val ^= val >> 15; val -= val << 17; val += val << 7; val += val << 4; + val ^= val >> 5; val += val << 16; val ^= val >> 7; val ^= val >> 3; + val ^= val << 6; val ^= val >> 17; + + return (int)val; + } + + static void UnquantizeTexelWeights( + ref Weights outputBuffer, + ref IntegerSequence weights, + ref TexelWeightParams texelParams, + int blockWidth, + int blockHeight) + { + int weightIndices = 0; + Weights unquantized; + unsafe { _ = &unquantized; } // Skip struct initialization + + Span weightsList = weights.List; + Span unquantized0 = unquantized[0]; + Span unquantized1 = unquantized[1]; + + for (int i = 0; i < weightsList.Length; i++) + { + unquantized0[weightIndices] = UnquantizeTexelWeight(weightsList[i]); + + if (texelParams.DualPlane) + { + i++; + unquantized1[weightIndices] = UnquantizeTexelWeight(weightsList[i]); + + if (i == weightsList.Length) + { + break; + } + } + + if (++weightIndices >= texelParams.Width * texelParams.Height) break; + } + + // Do infill if necessary (Section C.2.18) ... + int ds = (1024 + blockWidth / 2) / (blockWidth - 1); + int dt = (1024 + blockHeight / 2) / (blockHeight - 1); + + int planeScale = texelParams.DualPlane ? 2 : 1; + + for (int plane = 0; plane < planeScale; plane++) + { + Span unquantizedSpan = unquantized.Get(plane); + Span outputSpan = outputBuffer.Get(plane); + + for (int t = 0; t < blockHeight; t++) + { + for (int s = 0; s < blockWidth; s++) + { + int cs = ds * s; + int ct = dt * t; + + int gs = (cs * (texelParams.Width - 1) + 32) >> 6; + int gt = (ct * (texelParams.Height - 1) + 32) >> 6; + + int js = gs >> 4; + int fs = gs & 0xF; + + int jt = gt >> 4; + int ft = gt & 0x0F; + + int w11 = (fs * ft + 8) >> 4; + + int v0 = js + jt * texelParams.Width; + + int weight = 8; + + int wxh = texelParams.Width * texelParams.Height; + + if (v0 < wxh) + { + weight += unquantizedSpan[v0] * (16 - fs - ft + w11); + + if (v0 + 1 < wxh) + { + weight += unquantizedSpan[v0 + 1] * (fs - w11); + } + } + + if (v0 + texelParams.Width < wxh) + { + weight += unquantizedSpan[v0 + texelParams.Width] * (ft - w11); + + if (v0 + texelParams.Width + 1 < wxh) + { + weight += unquantizedSpan[v0 + texelParams.Width + 1] * w11; + } + } + + outputSpan[t * blockWidth + s] = weight >> 4; + } + } + } + } + + static int UnquantizeTexelWeight(IntegerEncoded intEncoded) + { + int bitValue = intEncoded.BitValue; + int bitLength = intEncoded.NumberBits; + + int a = Bits.Replicate1_7(bitValue & 1); + int b = 0, c = 0, d = 0; + + int result = 0; + + switch (intEncoded.GetEncoding()) + { + case IntegerEncoded.EIntegerEncoding.JustBits: + result = Bits.Replicate(bitValue, bitLength, 6); + break; + + case IntegerEncoded.EIntegerEncoding.Trit: + { + d = intEncoded.TritValue; + Debug.Assert(d < 3); + + switch (bitLength) + { + case 0: + { + result = d switch + { + 0 => 0, + 1 => 32, + 2 => 63, + _ => 0 + }; + + break; + } + + case 1: + { + c = 50; + break; + } + + case 2: + { + c = 23; + int b2 = (bitValue >> 1) & 1; + b = (b2 << 6) | (b2 << 2) | b2; + + break; + } + + case 3: + { + c = 11; + int cb = (bitValue >> 1) & 3; + b = (cb << 5) | cb; + + break; + } + + default: + throw new AstcDecoderException("Invalid trit encoding for texel weight."); + } + + break; + } + + case IntegerEncoded.EIntegerEncoding.Quint: + { + d = intEncoded.QuintValue; + Debug.Assert(d < 5); + + switch (bitLength) + { + case 0: + { + result = d switch + { + 0 => 0, + 1 => 16, + 2 => 32, + 3 => 47, + 4 => 63, + _ => 0 + }; + + break; + } + + case 1: + { + c = 28; + + break; + } + + case 2: + { + c = 13; + int b2 = (bitValue >> 1) & 1; + b = (b2 << 6) | (b2 << 1); + + break; + } + + default: + throw new AstcDecoderException("Invalid quint encoding for texel weight."); + } + + break; + } + } + + if (intEncoded.GetEncoding() != IntegerEncoded.EIntegerEncoding.JustBits && bitLength > 0) + { + // Decode the value... + result = d * c + b; + result ^= a; + result = (a & 0x20) | (result >> 2); + } + + Debug.Assert(result < 64); + + // Change from [0,63] to [0,64] + if (result > 32) + { + result += 1; + } + + return result; + } + + static byte ReverseByte(byte b) + { + // Taken from http://graphics.stanford.edu/~seander/bithacks.html#ReverseByteWith64Bits + return (byte)((((b) * 0x80200802L) & 0x0884422110L) * 0x0101010101L >> 32); + } + + static Span ReadUintColorValues(int number, Span colorValues, ref int colorValuesPosition) + { + Span ret = colorValues.Slice(colorValuesPosition, number); + + colorValuesPosition += number; + + return MemoryMarshal.Cast(ret); + } + + static Span ReadIntColorValues(int number, Span colorValues, ref int colorValuesPosition) + { + Span ret = colorValues.Slice(colorValuesPosition, number); + + colorValuesPosition += number; + + return ret; + } + + static void ComputeEndpoints( + Span endPoints, + Span colorValues, + uint colorEndpointMode, + ref int colorValuesPosition) + { + switch (colorEndpointMode) + { + case 0: + { + Span val = ReadUintColorValues(2, colorValues, ref colorValuesPosition); + + endPoints[0] = new AstcPixel(0xFF, (short)val[0], (short)val[0], (short)val[0]); + endPoints[1] = new AstcPixel(0xFF, (short)val[1], (short)val[1], (short)val[1]); + + break; + } + + + case 1: + { + Span val = ReadUintColorValues(2, colorValues, ref colorValuesPosition); + int l0 = (int)((val[0] >> 2) | (val[1] & 0xC0)); + int l1 = (int)Math.Max(l0 + (val[1] & 0x3F), 0xFFU); + + endPoints[0] = new AstcPixel(0xFF, (short)l0, (short)l0, (short)l0); + endPoints[1] = new AstcPixel(0xFF, (short)l1, (short)l1, (short)l1); + + break; + } + + case 4: + { + Span val = ReadUintColorValues(4, colorValues, ref colorValuesPosition); + + endPoints[0] = new AstcPixel((short)val[2], (short)val[0], (short)val[0], (short)val[0]); + endPoints[1] = new AstcPixel((short)val[3], (short)val[1], (short)val[1], (short)val[1]); + + break; + } + + case 5: + { + Span val = ReadIntColorValues(4, colorValues, ref colorValuesPosition); + + Bits.BitTransferSigned(ref val[1], ref val[0]); + Bits.BitTransferSigned(ref val[3], ref val[2]); + + endPoints[0] = new AstcPixel((short)val[2], (short)val[0], (short)val[0], (short)val[0]); + endPoints[1] = new AstcPixel((short)(val[2] + val[3]), (short)(val[0] + val[1]), (short)(val[0] + val[1]), (short)(val[0] + val[1])); + + endPoints[0].ClampByte(); + endPoints[1].ClampByte(); + + break; + } + + case 6: + { + Span val = ReadUintColorValues(4, colorValues, ref colorValuesPosition); + + endPoints[0] = new AstcPixel(0xFF, (short)(val[0] * val[3] >> 8), (short)(val[1] * val[3] >> 8), (short)(val[2] * val[3] >> 8)); + endPoints[1] = new AstcPixel(0xFF, (short)val[0], (short)val[1], (short)val[2]); + + break; + } + + case 8: + { + Span val = ReadUintColorValues(6, colorValues, ref colorValuesPosition); + + if (val[1] + val[3] + val[5] >= val[0] + val[2] + val[4]) + { + endPoints[0] = new AstcPixel(0xFF, (short)val[0], (short)val[2], (short)val[4]); + endPoints[1] = new AstcPixel(0xFF, (short)val[1], (short)val[3], (short)val[5]); + } + else + { + endPoints[0] = AstcPixel.BlueContract(0xFF, (short)val[1], (short)val[3], (short)val[5]); + endPoints[1] = AstcPixel.BlueContract(0xFF, (short)val[0], (short)val[2], (short)val[4]); + } + + break; + } + + case 9: + { + Span val = ReadIntColorValues(6, colorValues, ref colorValuesPosition); + + Bits.BitTransferSigned(ref val[1], ref val[0]); + Bits.BitTransferSigned(ref val[3], ref val[2]); + Bits.BitTransferSigned(ref val[5], ref val[4]); + + if (val[1] + val[3] + val[5] >= 0) + { + endPoints[0] = new AstcPixel(0xFF, (short)val[0], (short)val[2], (short)val[4]); + endPoints[1] = new AstcPixel(0xFF, (short)(val[0] + val[1]), (short)(val[2] + val[3]), (short)(val[4] + val[5])); + } + else + { + endPoints[0] = AstcPixel.BlueContract(0xFF, val[0] + val[1], val[2] + val[3], val[4] + val[5]); + endPoints[1] = AstcPixel.BlueContract(0xFF, val[0], val[2], val[4]); + } + + endPoints[0].ClampByte(); + endPoints[1].ClampByte(); + + break; + } + + case 10: + { + Span val = ReadUintColorValues(6, colorValues, ref colorValuesPosition); + + endPoints[0] = new AstcPixel((short)val[4], (short)(val[0] * val[3] >> 8), (short)(val[1] * val[3] >> 8), (short)(val[2] * val[3] >> 8)); + endPoints[1] = new AstcPixel((short)val[5], (short)val[0], (short)val[1], (short)val[2]); + + break; + } + + case 12: + { + Span val = ReadUintColorValues(8, colorValues, ref colorValuesPosition); + + if (val[1] + val[3] + val[5] >= val[0] + val[2] + val[4]) + { + endPoints[0] = new AstcPixel((short)val[6], (short)val[0], (short)val[2], (short)val[4]); + endPoints[1] = new AstcPixel((short)val[7], (short)val[1], (short)val[3], (short)val[5]); + } + else + { + endPoints[0] = AstcPixel.BlueContract((short)val[7], (short)val[1], (short)val[3], (short)val[5]); + endPoints[1] = AstcPixel.BlueContract((short)val[6], (short)val[0], (short)val[2], (short)val[4]); + } + + break; + } + + case 13: + { + Span val = ReadIntColorValues(8, colorValues, ref colorValuesPosition); + + Bits.BitTransferSigned(ref val[1], ref val[0]); + Bits.BitTransferSigned(ref val[3], ref val[2]); + Bits.BitTransferSigned(ref val[5], ref val[4]); + Bits.BitTransferSigned(ref val[7], ref val[6]); + + if (val[1] + val[3] + val[5] >= 0) + { + endPoints[0] = new AstcPixel((short)val[6], (short)val[0], (short)val[2], (short)val[4]); + endPoints[1] = new AstcPixel((short)(val[7] + val[6]), (short)(val[0] + val[1]), (short)(val[2] + val[3]), (short)(val[4] + val[5])); + } + else + { + endPoints[0] = AstcPixel.BlueContract(val[6] + val[7], val[0] + val[1], val[2] + val[3], val[4] + val[5]); + endPoints[1] = AstcPixel.BlueContract(val[6], val[0], val[2], val[4]); + } + + endPoints[0].ClampByte(); + endPoints[1].ClampByte(); + + break; + } + + default: + throw new AstcDecoderException("Unsupported color endpoint mode (is it HDR?)"); + } + } + + static void DecodeColorValues( + Span outputValues, + ref BitStream128 colorBitStream, + Span modes, + int numberPartitions, + int numberBitsForColorData) + { + // First figure out how many color values we have + int numberValues = 0; + + for (int i = 0; i < numberPartitions; i++) + { + numberValues += (int)((modes[i] >> 2) + 1) << 1; + } + + // Then based on the number of values and the remaining number of bits, + // figure out the max value for each of them... + int range = 256; + + while (--range > 0) + { + IntegerEncoded intEncoded = IntegerEncoded.CreateEncoding(range); + int bitLength = intEncoded.GetBitLength(numberValues); + + if (bitLength <= numberBitsForColorData) + { + // Find the smallest possible range that matches the given encoding + while (--range > 0) + { + IntegerEncoded newIntEncoded = IntegerEncoded.CreateEncoding(range); + if (!newIntEncoded.MatchesEncoding(intEncoded)) + { + break; + } + } + + // Return to last matching range. + range++; + break; + } + } + + // We now have enough to decode our integer sequence. + IntegerSequence integerEncodedSequence; + unsafe { _ = &integerEncodedSequence; } // Skip struct initialization + integerEncodedSequence.Reset(); + + IntegerEncoded.DecodeIntegerSequence(ref integerEncodedSequence, ref colorBitStream, range, numberValues); + + // Once we have the decoded values, we need to dequantize them to the 0-255 range + // This procedure is outlined in ASTC spec C.2.13 + int outputIndices = 0; + + foreach (ref IntegerEncoded intEncoded in integerEncodedSequence.List) + { + int bitLength = intEncoded.NumberBits; + int bitValue = intEncoded.BitValue; + + Debug.Assert(bitLength >= 1); + + int a = 0, b = 0, c = 0, d = 0; + // A is just the lsb replicated 9 times. + a = Bits.Replicate(bitValue & 1, 1, 9); + + switch (intEncoded.GetEncoding()) + { + case IntegerEncoded.EIntegerEncoding.JustBits: + { + outputValues[outputIndices++] = Bits.Replicate(bitValue, bitLength, 8); + + break; + } + + case IntegerEncoded.EIntegerEncoding.Trit: + { + d = intEncoded.TritValue; + + switch (bitLength) + { + case 1: + { + c = 204; + + break; + } + + case 2: + { + c = 93; + // B = b000b0bb0 + int b2 = (bitValue >> 1) & 1; + b = (b2 << 8) | (b2 << 4) | (b2 << 2) | (b2 << 1); + + break; + } + + case 3: + { + c = 44; + // B = cb000cbcb + int cb = (bitValue >> 1) & 3; + b = (cb << 7) | (cb << 2) | cb; + + break; + } + + + case 4: + { + c = 22; + // B = dcb000dcb + int dcb = (bitValue >> 1) & 7; + b = (dcb << 6) | dcb; + + break; + } + + case 5: + { + c = 11; + // B = edcb000ed + int edcb = (bitValue >> 1) & 0xF; + b = (edcb << 5) | (edcb >> 2); + + break; + } + + case 6: + { + c = 5; + // B = fedcb000f + int fedcb = (bitValue >> 1) & 0x1F; + b = (fedcb << 4) | (fedcb >> 4); + + break; + } + + default: + throw new AstcDecoderException("Unsupported trit encoding for color values."); + } + + break; + } + + case IntegerEncoded.EIntegerEncoding.Quint: + { + d = intEncoded.QuintValue; + + switch (bitLength) + { + case 1: + { + c = 113; + + break; + } + + case 2: + { + c = 54; + // B = b0000bb00 + int b2 = (bitValue >> 1) & 1; + b = (b2 << 8) | (b2 << 3) | (b2 << 2); + + break; + } + + case 3: + { + c = 26; + // B = cb0000cbc + int cb = (bitValue >> 1) & 3; + b = (cb << 7) | (cb << 1) | (cb >> 1); + + break; + } + + case 4: + { + c = 13; + // B = dcb0000dc + int dcb = (bitValue >> 1) & 7; + b = (dcb << 6) | (dcb >> 1); + + break; + } + + case 5: + { + c = 6; + // B = edcb0000e + int edcb = (bitValue >> 1) & 0xF; + b = (edcb << 5) | (edcb >> 3); + + break; + } + + default: + throw new AstcDecoderException("Unsupported quint encoding for color values."); + } + break; + } + } + + if (intEncoded.GetEncoding() != IntegerEncoded.EIntegerEncoding.JustBits) + { + int T = d * c + b; + T ^= a; + T = (a & 0x80) | (T >> 2); + + outputValues[outputIndices++] = T; + } + } + + // Make sure that each of our values is in the proper range... + for (int i = 0; i < numberValues; i++) + { + Debug.Assert(outputValues[i] <= 255); + } + } + + static void FillVoidExtentLdr(ref BitStream128 bitStream, Span outputBuffer, int blockWidth, int blockHeight) + { + // Don't actually care about the void extent, just read the bits... + for (int i = 0; i < 4; ++i) + { + bitStream.ReadBits(13); + } + + // Decode the RGBA components and renormalize them to the range [0, 255] + ushort r = (ushort)bitStream.ReadBits(16); + ushort g = (ushort)bitStream.ReadBits(16); + ushort b = (ushort)bitStream.ReadBits(16); + ushort a = (ushort)bitStream.ReadBits(16); + + int rgba = (r >> 8) | (g & 0xFF00) | ((b) & 0xFF00) << 8 | ((a) & 0xFF00) << 16; + + for (int j = 0; j < blockHeight; j++) + { + for (int i = 0; i < blockWidth; i++) + { + outputBuffer[j * blockWidth + i] = rgba; + } + } + } + + static void DecodeBlockInfo(ref BitStream128 bitStream, out TexelWeightParams texelParams) + { + texelParams = new TexelWeightParams(); + + // Read the entire block mode all at once + ushort modeBits = (ushort)bitStream.ReadBits(11); + + // Does this match the void extent block mode? + if ((modeBits & 0x01FF) == 0x1FC) + { + if ((modeBits & 0x200) != 0) + { + texelParams.VoidExtentHdr = true; + } + else + { + texelParams.VoidExtentLdr = true; + } + + // Next two bits must be one. + if ((modeBits & 0x400) == 0 || bitStream.ReadBits(1) == 0) + { + texelParams.Error = true; + } + + return; + } + + // First check if the last four bits are zero + if ((modeBits & 0xF) == 0) + { + texelParams.Error = true; + + return; + } + + // If the last two bits are zero, then if bits + // [6-8] are all ones, this is also reserved. + if ((modeBits & 0x3) == 0 && (modeBits & 0x1C0) == 0x1C0) + { + texelParams.Error = true; + + return; + } + + // Otherwise, there is no error... Figure out the layout + // of the block mode. Layout is determined by a number + // between 0 and 9 corresponding to table C.2.8 of the + // ASTC spec. + int layout; + + if ((modeBits & 0x1) != 0 || (modeBits & 0x2) != 0) + { + // layout is in [0-4] + if ((modeBits & 0x8) != 0) + { + // layout is in [2-4] + if ((modeBits & 0x4) != 0) + { + // layout is in [3-4] + if ((modeBits & 0x100) != 0) + { + layout = 4; + } + else + { + layout = 3; + } + } + else + { + layout = 2; + } + } + else + { + // layout is in [0-1] + if ((modeBits & 0x4) != 0) + { + layout = 1; + } + else + { + layout = 0; + } + } + } + else + { + // layout is in [5-9] + if ((modeBits & 0x100) != 0) + { + // layout is in [7-9] + if ((modeBits & 0x80) != 0) + { + // layout is in [7-8] + Debug.Assert((modeBits & 0x40) == 0); + + if ((modeBits & 0x20) != 0) + { + layout = 8; + } + else + { + layout = 7; + } + } + else + { + layout = 9; + } + } + else + { + // layout is in [5-6] + if ((modeBits & 0x80) != 0) + { + layout = 6; + } + else + { + layout = 5; + } + } + } + + Debug.Assert(layout < 10); + + // Determine R + int r = (modeBits >> 4) & 1; + if (layout < 5) + { + r |= (modeBits & 0x3) << 1; + } + else + { + r |= (modeBits & 0xC) >> 1; + } + + Debug.Assert(2 <= r && r <= 7); + + // Determine width & height + switch (layout) + { + case 0: + { + int a = (modeBits >> 5) & 0x3; + int b = (modeBits >> 7) & 0x3; + + texelParams.Width = b + 4; + texelParams.Height = a + 2; + + break; + } + + case 1: + { + int a = (modeBits >> 5) & 0x3; + int b = (modeBits >> 7) & 0x3; + + texelParams.Width = b + 8; + texelParams.Height = a + 2; + + break; + } + + case 2: + { + int a = (modeBits >> 5) & 0x3; + int b = (modeBits >> 7) & 0x3; + + texelParams.Width = a + 2; + texelParams.Height = b + 8; + + break; + } + + case 3: + { + int a = (modeBits >> 5) & 0x3; + int b = (modeBits >> 7) & 0x1; + + texelParams.Width = a + 2; + texelParams.Height = b + 6; + + break; + } + + case 4: + { + int a = (modeBits >> 5) & 0x3; + int b = (modeBits >> 7) & 0x1; + + texelParams.Width = b + 2; + texelParams.Height = a + 2; + + break; + } + + case 5: + { + int a = (modeBits >> 5) & 0x3; + + texelParams.Width = 12; + texelParams.Height = a + 2; + + break; + } + + case 6: + { + int a = (modeBits >> 5) & 0x3; + + texelParams.Width = a + 2; + texelParams.Height = 12; + + break; + } + + case 7: + { + texelParams.Width = 6; + texelParams.Height = 10; + + break; + } + + case 8: + { + texelParams.Width = 10; + texelParams.Height = 6; + break; + } + + case 9: + { + int a = (modeBits >> 5) & 0x3; + int b = (modeBits >> 9) & 0x3; + + texelParams.Width = a + 6; + texelParams.Height = b + 6; + + break; + } + + default: + // Don't know this layout... + texelParams.Error = true; + break; + } + + // Determine whether or not we're using dual planes + // and/or high precision layouts. + bool d = ((layout != 9) && ((modeBits & 0x400) != 0)); + bool h = (layout != 9) && ((modeBits & 0x200) != 0); + + if (h) + { + ReadOnlySpan maxWeights = new byte[] { 9, 11, 15, 19, 23, 31 }; + texelParams.MaxWeight = maxWeights[r - 2]; + } + else + { + ReadOnlySpan maxWeights = new byte[] { 1, 2, 3, 4, 5, 7 }; + texelParams.MaxWeight = maxWeights[r - 2]; + } + + texelParams.DualPlane = d; + } + } +} diff --git a/Ryujinx.Graphics.Texture/Astc/AstcDecoderException.cs b/Ryujinx.Graphics.Texture/Astc/AstcDecoderException.cs new file mode 100644 index 0000000000..fdc4826716 --- /dev/null +++ b/Ryujinx.Graphics.Texture/Astc/AstcDecoderException.cs @@ -0,0 +1,9 @@ +using System; + +namespace Ryujinx.Graphics.Texture.Astc +{ + public class AstcDecoderException : Exception + { + public AstcDecoderException(string exMsg) : base(exMsg) { } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Texture/Astc/AstcPixel.cs b/Ryujinx.Graphics.Texture/Astc/AstcPixel.cs new file mode 100644 index 0000000000..13197714e4 --- /dev/null +++ b/Ryujinx.Graphics.Texture/Astc/AstcPixel.cs @@ -0,0 +1,68 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Texture.Astc +{ + [StructLayout(LayoutKind.Sequential)] + struct AstcPixel + { + internal const int StructSize = 12; + + public short A; + public short R; + public short G; + public short B; + + private uint _bitDepthInt; + + private Span BitDepth => MemoryMarshal.CreateSpan(ref Unsafe.As(ref _bitDepthInt), 4); + private Span Components => MemoryMarshal.CreateSpan(ref A, 4); + + public AstcPixel(short a, short r, short g, short b) + { + A = a; + R = r; + G = g; + B = b; + + _bitDepthInt = 0x08080808; + } + + public void ClampByte() + { + R = Math.Min(Math.Max(R, (short)0), (short)255); + G = Math.Min(Math.Max(G, (short)0), (short)255); + B = Math.Min(Math.Max(B, (short)0), (short)255); + A = Math.Min(Math.Max(A, (short)0), (short)255); + } + + public short GetComponent(int index) + { + return Components[index]; + } + + public void SetComponent(int index, int value) + { + Components[index] = (short)value; + } + + public int Pack() + { + return A << 24 | + B << 16 | + G << 8 | + R << 0; + } + + // Adds more precision to the blue channel as described + // in C.2.14 + public static AstcPixel BlueContract(int a, int r, int g, int b) + { + return new AstcPixel((short)(a), + (short)((r + b) >> 1), + (short)((g + b) >> 1), + (short)(b)); + } + } +} diff --git a/Ryujinx.Graphics.Texture/Astc/BitStream128.cs b/Ryujinx.Graphics.Texture/Astc/BitStream128.cs new file mode 100644 index 0000000000..3bf9769f53 --- /dev/null +++ b/Ryujinx.Graphics.Texture/Astc/BitStream128.cs @@ -0,0 +1,72 @@ +using Ryujinx.Common.Utilities; +using System; +using System.Diagnostics; + +namespace Ryujinx.Graphics.Texture.Astc +{ + public struct BitStream128 + { + private Buffer16 _data; + public int BitsLeft { get; set; } + + public BitStream128(Buffer16 data) + { + _data = data; + BitsLeft = 128; + } + + public int ReadBits(int bitCount) + { + Debug.Assert(bitCount < 32); + + if (bitCount == 0) + { + return 0; + } + + int mask = (1 << bitCount) - 1; + int value = _data.As() & mask; + + Span span = _data.AsSpan(); + + ulong carry = span[1] << (64 - bitCount); + span[0] = (span[0] >> bitCount) | carry; + span[1] >>= bitCount; + + BitsLeft -= bitCount; + + return value; + } + + public void WriteBits(int value, int bitCount) + { + Debug.Assert(bitCount < 32); + + if (bitCount == 0) return; + + ulong maskedValue = (uint)(value & ((1 << bitCount) - 1)); + + Span span = _data.AsSpan(); + + if (BitsLeft < 64) + { + ulong lowMask = maskedValue << BitsLeft; + span[0] |= lowMask; + } + + if (BitsLeft + bitCount > 64) + { + if (BitsLeft > 64) + { + span[1] |= maskedValue << (BitsLeft - 64); + } + else + { + span[1] |= maskedValue >> (64 - BitsLeft); + } + } + + BitsLeft += bitCount; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Texture/Astc/Bits.cs b/Ryujinx.Graphics.Texture/Astc/Bits.cs new file mode 100644 index 0000000000..b140a20a02 --- /dev/null +++ b/Ryujinx.Graphics.Texture/Astc/Bits.cs @@ -0,0 +1,66 @@ +namespace Ryujinx.Graphics.Texture.Astc +{ + internal static class Bits + { + public static readonly ushort[] Replicate8_16Table; + public static readonly byte[] Replicate1_7Table; + + static Bits() + { + Replicate8_16Table = new ushort[0x200]; + Replicate1_7Table = new byte[0x200]; + + for (int i = 0; i < 0x200; i++) + { + Replicate8_16Table[i] = (ushort)Replicate(i, 8, 16); + Replicate1_7Table[i] = (byte)Replicate(i, 1, 7); + } + } + + public static int Replicate8_16(int value) + { + return Replicate8_16Table[value]; + } + + public static int Replicate1_7(int value) + { + return Replicate1_7Table[value]; + } + + public static int Replicate(int value, int numberBits, int toBit) + { + if (numberBits == 0) return 0; + if (toBit == 0) return 0; + + int tempValue = value & ((1 << numberBits) - 1); + int retValue = tempValue; + int resLength = numberBits; + + while (resLength < toBit) + { + int comp = 0; + if (numberBits > toBit - resLength) + { + int newShift = toBit - resLength; + comp = numberBits - newShift; + numberBits = newShift; + } + retValue <<= numberBits; + retValue |= tempValue >> comp; + resLength += numberBits; + } + + return retValue; + } + + // Transfers a bit as described in C.2.14 + public static void BitTransferSigned(ref int a, ref int b) + { + b >>= 1; + b |= a & 0x80; + a >>= 1; + a &= 0x3F; + if ((a & 0x20) != 0) a -= 0x40; + } + } +} diff --git a/Ryujinx.Graphics.Texture/Astc/EndPointSet.cs b/Ryujinx.Graphics.Texture/Astc/EndPointSet.cs new file mode 100644 index 0000000000..45e61ca2ca --- /dev/null +++ b/Ryujinx.Graphics.Texture/Astc/EndPointSet.cs @@ -0,0 +1,23 @@ +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Texture.Astc +{ + [StructLayout(LayoutKind.Sequential, Size = AstcPixel.StructSize * 8)] + internal struct EndPointSet + { + private AstcPixel _start; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span Get(int index) + { + Debug.Assert(index < 4); + + ref AstcPixel start = ref Unsafe.Add(ref _start, index * 2); + + return MemoryMarshal.CreateSpan(ref start, 2); + } + } +} diff --git a/Ryujinx.Graphics.Texture/Astc/IntegerEncoded.cs b/Ryujinx.Graphics.Texture/Astc/IntegerEncoded.cs new file mode 100644 index 0000000000..065de46be9 --- /dev/null +++ b/Ryujinx.Graphics.Texture/Astc/IntegerEncoded.cs @@ -0,0 +1,345 @@ +using System; +using System.Numerics; + +namespace Ryujinx.Graphics.Texture.Astc +{ + internal struct IntegerEncoded + { + internal const int StructSize = 8; + private static readonly IntegerEncoded[] Encodings; + + public enum EIntegerEncoding : byte + { + JustBits, + Quint, + Trit + } + + EIntegerEncoding _encoding; + public byte NumberBits { get; private set; } + public byte TritValue { get; private set; } + public byte QuintValue { get; private set; } + public int BitValue { get; private set; } + + static IntegerEncoded() + { + Encodings = new IntegerEncoded[0x100]; + + for (int i = 0; i < Encodings.Length; i++) + { + Encodings[i] = CreateEncodingCalc(i); + } + } + + public IntegerEncoded(EIntegerEncoding encoding, int numBits) + { + _encoding = encoding; + NumberBits = (byte)numBits; + BitValue = 0; + TritValue = 0; + QuintValue = 0; + } + + public bool MatchesEncoding(IntegerEncoded other) + { + return _encoding == other._encoding && NumberBits == other.NumberBits; + } + + public EIntegerEncoding GetEncoding() + { + return _encoding; + } + + public int GetBitLength(int numberVals) + { + int totalBits = NumberBits * numberVals; + if (_encoding == EIntegerEncoding.Trit) + { + totalBits += (numberVals * 8 + 4) / 5; + } + else if (_encoding == EIntegerEncoding.Quint) + { + totalBits += (numberVals * 7 + 2) / 3; + } + return totalBits; + } + + public static IntegerEncoded CreateEncoding(int maxVal) + { + return Encodings[maxVal]; + } + + private static IntegerEncoded CreateEncodingCalc(int maxVal) + { + while (maxVal > 0) + { + int check = maxVal + 1; + + // Is maxVal a power of two? + if ((check & (check - 1)) == 0) + { + return new IntegerEncoded(EIntegerEncoding.JustBits, BitOperations.PopCount((uint)maxVal)); + } + + // Is maxVal of the type 3*2^n - 1? + if ((check % 3 == 0) && ((check / 3) & ((check / 3) - 1)) == 0) + { + return new IntegerEncoded(EIntegerEncoding.Trit, BitOperations.PopCount((uint)(check / 3 - 1))); + } + + // Is maxVal of the type 5*2^n - 1? + if ((check % 5 == 0) && ((check / 5) & ((check / 5) - 1)) == 0) + { + return new IntegerEncoded(EIntegerEncoding.Quint, BitOperations.PopCount((uint)(check / 5 - 1))); + } + + // Apparently it can't be represented with a bounded integer sequence... + // just iterate. + maxVal--; + } + + return new IntegerEncoded(EIntegerEncoding.JustBits, 0); + } + + public static void DecodeTritBlock( + ref BitStream128 bitStream, + ref IntegerSequence listIntegerEncoded, + int numberBitsPerValue) + { + // Implement the algorithm in section C.2.12 + Span m = stackalloc int[5]; + + m[0] = bitStream.ReadBits(numberBitsPerValue); + int encoded = bitStream.ReadBits(2); + m[1] = bitStream.ReadBits(numberBitsPerValue); + encoded |= bitStream.ReadBits(2) << 2; + m[2] = bitStream.ReadBits(numberBitsPerValue); + encoded |= bitStream.ReadBits(1) << 4; + m[3] = bitStream.ReadBits(numberBitsPerValue); + encoded |= bitStream.ReadBits(2) << 5; + m[4] = bitStream.ReadBits(numberBitsPerValue); + encoded |= bitStream.ReadBits(1) << 7; + + ReadOnlySpan encodings = GetTritEncoding(encoded); + + IntegerEncoded intEncoded = new IntegerEncoded(EIntegerEncoding.Trit, numberBitsPerValue); + + for (int i = 0; i < 5; i++) + { + intEncoded.BitValue = m[i]; + intEncoded.TritValue = encodings[i]; + + listIntegerEncoded.Add(ref intEncoded); + } + } + + public static void DecodeQuintBlock( + ref BitStream128 bitStream, + ref IntegerSequence listIntegerEncoded, + int numberBitsPerValue) + { + ReadOnlySpan interleavedBits = new byte[] { 3, 2, 2 }; + + // Implement the algorithm in section C.2.12 + Span m = stackalloc int[3]; + ulong encoded = 0; + int encodedBitsRead = 0; + + for (int i = 0; i < m.Length; i++) + { + m[i] = bitStream.ReadBits(numberBitsPerValue); + + uint encodedBits = (uint)bitStream.ReadBits(interleavedBits[i]); + + encoded |= encodedBits << encodedBitsRead; + encodedBitsRead += interleavedBits[i]; + } + + ReadOnlySpan encodings = GetQuintEncoding((int)encoded); + + for (int i = 0; i < 3; i++) + { + IntegerEncoded intEncoded = new IntegerEncoded(EIntegerEncoding.Quint, numberBitsPerValue) + { + BitValue = m[i], + QuintValue = encodings[i] + }; + + listIntegerEncoded.Add(ref intEncoded); + } + } + + public static void DecodeIntegerSequence( + ref IntegerSequence decodeIntegerSequence, + ref BitStream128 bitStream, + int maxRange, + int numberValues) + { + // Determine encoding parameters + IntegerEncoded intEncoded = CreateEncoding(maxRange); + + // Start decoding + int numberValuesDecoded = 0; + while (numberValuesDecoded < numberValues) + { + switch (intEncoded.GetEncoding()) + { + case EIntegerEncoding.Quint: + { + DecodeQuintBlock(ref bitStream, ref decodeIntegerSequence, intEncoded.NumberBits); + numberValuesDecoded += 3; + + break; + } + + case EIntegerEncoding.Trit: + { + DecodeTritBlock(ref bitStream, ref decodeIntegerSequence, intEncoded.NumberBits); + numberValuesDecoded += 5; + + break; + } + + case EIntegerEncoding.JustBits: + { + intEncoded.BitValue = bitStream.ReadBits(intEncoded.NumberBits); + decodeIntegerSequence.Add(ref intEncoded); + numberValuesDecoded++; + + break; + } + } + } + } + + private static ReadOnlySpan GetTritEncoding(int index) + { + return TritEncodings.Slice(index * 5, 5); + } + + private static ReadOnlySpan GetQuintEncoding(int index) + { + return QuintEncodings.Slice(index * 3, 3); + } + + private static ReadOnlySpan TritEncodings => new byte[] + { + 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 2, 0, 0, 0, 0, + 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, + 2, 1, 0, 0, 0, 1, 0, 2, 0, 0, 0, 2, 0, 0, 0, + 1, 2, 0, 0, 0, 2, 2, 0, 0, 0, 2, 0, 2, 0, 0, + 0, 2, 2, 0, 0, 1, 2, 2, 0, 0, 2, 2, 2, 0, 0, + 2, 0, 2, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, + 2, 0, 1, 0, 0, 0, 1, 2, 0, 0, 0, 1, 1, 0, 0, + 1, 1, 1, 0, 0, 2, 1, 1, 0, 0, 1, 1, 2, 0, 0, + 0, 2, 1, 0, 0, 1, 2, 1, 0, 0, 2, 2, 1, 0, 0, + 2, 1, 2, 0, 0, 0, 0, 0, 2, 2, 1, 0, 0, 2, 2, + 2, 0, 0, 2, 2, 0, 0, 2, 2, 2, 0, 0, 0, 1, 0, + 1, 0, 0, 1, 0, 2, 0, 0, 1, 0, 0, 0, 2, 1, 0, + 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 2, 1, 0, 1, 0, + 1, 0, 2, 1, 0, 0, 2, 0, 1, 0, 1, 2, 0, 1, 0, + 2, 2, 0, 1, 0, 2, 0, 2, 1, 0, 0, 2, 2, 1, 0, + 1, 2, 2, 1, 0, 2, 2, 2, 1, 0, 2, 0, 2, 1, 0, + 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 2, 0, 1, 1, 0, + 0, 1, 2, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, + 2, 1, 1, 1, 0, 1, 1, 2, 1, 0, 0, 2, 1, 1, 0, + 1, 2, 1, 1, 0, 2, 2, 1, 1, 0, 2, 1, 2, 1, 0, + 0, 1, 0, 2, 2, 1, 1, 0, 2, 2, 2, 1, 0, 2, 2, + 1, 0, 2, 2, 2, 0, 0, 0, 2, 0, 1, 0, 0, 2, 0, + 2, 0, 0, 2, 0, 0, 0, 2, 2, 0, 0, 1, 0, 2, 0, + 1, 1, 0, 2, 0, 2, 1, 0, 2, 0, 1, 0, 2, 2, 0, + 0, 2, 0, 2, 0, 1, 2, 0, 2, 0, 2, 2, 0, 2, 0, + 2, 0, 2, 2, 0, 0, 2, 2, 2, 0, 1, 2, 2, 2, 0, + 2, 2, 2, 2, 0, 2, 0, 2, 2, 0, 0, 0, 1, 2, 0, + 1, 0, 1, 2, 0, 2, 0, 1, 2, 0, 0, 1, 2, 2, 0, + 0, 1, 1, 2, 0, 1, 1, 1, 2, 0, 2, 1, 1, 2, 0, + 1, 1, 2, 2, 0, 0, 2, 1, 2, 0, 1, 2, 1, 2, 0, + 2, 2, 1, 2, 0, 2, 1, 2, 2, 0, 0, 2, 0, 2, 2, + 1, 2, 0, 2, 2, 2, 2, 0, 2, 2, 2, 0, 2, 2, 2, + 0, 0, 0, 0, 2, 1, 0, 0, 0, 2, 2, 0, 0, 0, 2, + 0, 0, 2, 0, 2, 0, 1, 0, 0, 2, 1, 1, 0, 0, 2, + 2, 1, 0, 0, 2, 1, 0, 2, 0, 2, 0, 2, 0, 0, 2, + 1, 2, 0, 0, 2, 2, 2, 0, 0, 2, 2, 0, 2, 0, 2, + 0, 2, 2, 0, 2, 1, 2, 2, 0, 2, 2, 2, 2, 0, 2, + 2, 0, 2, 0, 2, 0, 0, 1, 0, 2, 1, 0, 1, 0, 2, + 2, 0, 1, 0, 2, 0, 1, 2, 0, 2, 0, 1, 1, 0, 2, + 1, 1, 1, 0, 2, 2, 1, 1, 0, 2, 1, 1, 2, 0, 2, + 0, 2, 1, 0, 2, 1, 2, 1, 0, 2, 2, 2, 1, 0, 2, + 2, 1, 2, 0, 2, 0, 2, 2, 2, 2, 1, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, + 1, 0, 0, 0, 1, 2, 0, 0, 0, 1, 0, 0, 2, 0, 1, + 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 2, 1, 0, 0, 1, + 1, 0, 2, 0, 1, 0, 2, 0, 0, 1, 1, 2, 0, 0, 1, + 2, 2, 0, 0, 1, 2, 0, 2, 0, 1, 0, 2, 2, 0, 1, + 1, 2, 2, 0, 1, 2, 2, 2, 0, 1, 2, 0, 2, 0, 1, + 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 2, 0, 1, 0, 1, + 0, 1, 2, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, + 2, 1, 1, 0, 1, 1, 1, 2, 0, 1, 0, 2, 1, 0, 1, + 1, 2, 1, 0, 1, 2, 2, 1, 0, 1, 2, 1, 2, 0, 1, + 0, 0, 1, 2, 2, 1, 0, 1, 2, 2, 2, 0, 1, 2, 2, + 0, 1, 2, 2, 2, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, + 2, 0, 0, 1, 1, 0, 0, 2, 1, 1, 0, 1, 0, 1, 1, + 1, 1, 0, 1, 1, 2, 1, 0, 1, 1, 1, 0, 2, 1, 1, + 0, 2, 0, 1, 1, 1, 2, 0, 1, 1, 2, 2, 0, 1, 1, + 2, 0, 2, 1, 1, 0, 2, 2, 1, 1, 1, 2, 2, 1, 1, + 2, 2, 2, 1, 1, 2, 0, 2, 1, 1, 0, 0, 1, 1, 1, + 1, 0, 1, 1, 1, 2, 0, 1, 1, 1, 0, 1, 2, 1, 1, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, + 1, 1, 2, 1, 1, 0, 2, 1, 1, 1, 1, 2, 1, 1, 1, + 2, 2, 1, 1, 1, 2, 1, 2, 1, 1, 0, 1, 1, 2, 2, + 1, 1, 1, 2, 2, 2, 1, 1, 2, 2, 1, 1, 2, 2, 2, + 0, 0, 0, 2, 1, 1, 0, 0, 2, 1, 2, 0, 0, 2, 1, + 0, 0, 2, 2, 1, 0, 1, 0, 2, 1, 1, 1, 0, 2, 1, + 2, 1, 0, 2, 1, 1, 0, 2, 2, 1, 0, 2, 0, 2, 1, + 1, 2, 0, 2, 1, 2, 2, 0, 2, 1, 2, 0, 2, 2, 1, + 0, 2, 2, 2, 1, 1, 2, 2, 2, 1, 2, 2, 2, 2, 1, + 2, 0, 2, 2, 1, 0, 0, 1, 2, 1, 1, 0, 1, 2, 1, + 2, 0, 1, 2, 1, 0, 1, 2, 2, 1, 0, 1, 1, 2, 1, + 1, 1, 1, 2, 1, 2, 1, 1, 2, 1, 1, 1, 2, 2, 1, + 0, 2, 1, 2, 1, 1, 2, 1, 2, 1, 2, 2, 1, 2, 1, + 2, 1, 2, 2, 1, 0, 2, 1, 2, 2, 1, 2, 1, 2, 2, + 2, 2, 1, 2, 2, 2, 1, 2, 2, 2, 0, 0, 0, 1, 2, + 1, 0, 0, 1, 2, 2, 0, 0, 1, 2, 0, 0, 2, 1, 2, + 0, 1, 0, 1, 2, 1, 1, 0, 1, 2, 2, 1, 0, 1, 2, + 1, 0, 2, 1, 2, 0, 2, 0, 1, 2, 1, 2, 0, 1, 2, + 2, 2, 0, 1, 2, 2, 0, 2, 1, 2, 0, 2, 2, 1, 2, + 1, 2, 2, 1, 2, 2, 2, 2, 1, 2, 2, 0, 2, 1, 2, + 0, 0, 1, 1, 2, 1, 0, 1, 1, 2, 2, 0, 1, 1, 2, + 0, 1, 2, 1, 2, 0, 1, 1, 1, 2, 1, 1, 1, 1, 2, + 2, 1, 1, 1, 2, 1, 1, 2, 1, 2, 0, 2, 1, 1, 2, + 1, 2, 1, 1, 2, 2, 2, 1, 1, 2, 2, 1, 2, 1, 2, + 0, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 1, 2, 2, 2 + }; + + private static ReadOnlySpan QuintEncodings => new byte[] + { + 0, 0, 0, 1, 0, 0, 2, 0, 0, 3, 0, 0, 4, 0, 0, + 0, 4, 0, 4, 4, 0, 4, 4, 4, 0, 1, 0, 1, 1, 0, + 2, 1, 0, 3, 1, 0, 4, 1, 0, 1, 4, 0, 4, 4, 1, + 4, 4, 4, 0, 2, 0, 1, 2, 0, 2, 2, 0, 3, 2, 0, + 4, 2, 0, 2, 4, 0, 4, 4, 2, 4, 4, 4, 0, 3, 0, + 1, 3, 0, 2, 3, 0, 3, 3, 0, 4, 3, 0, 3, 4, 0, + 4, 4, 3, 4, 4, 4, 0, 0, 1, 1, 0, 1, 2, 0, 1, + 3, 0, 1, 4, 0, 1, 0, 4, 1, 4, 0, 4, 0, 4, 4, + 0, 1, 1, 1, 1, 1, 2, 1, 1, 3, 1, 1, 4, 1, 1, + 1, 4, 1, 4, 1, 4, 1, 4, 4, 0, 2, 1, 1, 2, 1, + 2, 2, 1, 3, 2, 1, 4, 2, 1, 2, 4, 1, 4, 2, 4, + 2, 4, 4, 0, 3, 1, 1, 3, 1, 2, 3, 1, 3, 3, 1, + 4, 3, 1, 3, 4, 1, 4, 3, 4, 3, 4, 4, 0, 0, 2, + 1, 0, 2, 2, 0, 2, 3, 0, 2, 4, 0, 2, 0, 4, 2, + 2, 0, 4, 3, 0, 4, 0, 1, 2, 1, 1, 2, 2, 1, 2, + 3, 1, 2, 4, 1, 2, 1, 4, 2, 2, 1, 4, 3, 1, 4, + 0, 2, 2, 1, 2, 2, 2, 2, 2, 3, 2, 2, 4, 2, 2, + 2, 4, 2, 2, 2, 4, 3, 2, 4, 0, 3, 2, 1, 3, 2, + 2, 3, 2, 3, 3, 2, 4, 3, 2, 3, 4, 2, 2, 3, 4, + 3, 3, 4, 0, 0, 3, 1, 0, 3, 2, 0, 3, 3, 0, 3, + 4, 0, 3, 0, 4, 3, 0, 0, 4, 1, 0, 4, 0, 1, 3, + 1, 1, 3, 2, 1, 3, 3, 1, 3, 4, 1, 3, 1, 4, 3, + 0, 1, 4, 1, 1, 4, 0, 2, 3, 1, 2, 3, 2, 2, 3, + 3, 2, 3, 4, 2, 3, 2, 4, 3, 0, 2, 4, 1, 2, 4, + 0, 3, 3, 1, 3, 3, 2, 3, 3, 3, 3, 3, 4, 3, 3, + 3, 4, 3, 0, 3, 4, 1, 3, 4 + }; + } +} diff --git a/Ryujinx.Graphics.Texture/Astc/IntegerSequence.cs b/Ryujinx.Graphics.Texture/Astc/IntegerSequence.cs new file mode 100644 index 0000000000..367b68095e --- /dev/null +++ b/Ryujinx.Graphics.Texture/Astc/IntegerSequence.cs @@ -0,0 +1,31 @@ +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Texture.Astc +{ + [StructLayout(LayoutKind.Sequential, Size = IntegerEncoded.StructSize * Capacity + sizeof(int))] + internal struct IntegerSequence + { + private const int Capacity = 100; + + private int _length; + private IntegerEncoded _start; + + public Span List => MemoryMarshal.CreateSpan(ref _start, _length); + + public void Reset() => _length = 0; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(ref IntegerEncoded item) + { + Debug.Assert(_length < Capacity); + + int oldLength = _length; + _length++; + + List[oldLength] = item; + } + } +} diff --git a/Ryujinx.Graphics.Texture/BlockLinearConstants.cs b/Ryujinx.Graphics.Texture/BlockLinearConstants.cs new file mode 100644 index 0000000000..d95691cf65 --- /dev/null +++ b/Ryujinx.Graphics.Texture/BlockLinearConstants.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Graphics.Texture +{ + static class BlockLinearConstants + { + public const int GobStride = 64; + public const int GobHeight = 8; + + public const int GobSize = GobStride * GobHeight; + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Texture/BlockLinearLayout.cs b/Ryujinx.Graphics.Texture/BlockLinearLayout.cs new file mode 100644 index 0000000000..b95db70290 --- /dev/null +++ b/Ryujinx.Graphics.Texture/BlockLinearLayout.cs @@ -0,0 +1,101 @@ +using Ryujinx.Common; +using System.Runtime.CompilerServices; + +using static Ryujinx.Graphics.Texture.BlockLinearConstants; + +namespace Ryujinx.Graphics.Texture +{ + class BlockLinearLayout + { + private struct RobAndSliceSizes + { + public int RobSize; + public int SliceSize; + + public RobAndSliceSizes(int robSize, int sliceSize) + { + RobSize = robSize; + SliceSize = sliceSize; + } + } + + private int _texBpp; + + private int _bhMask; + private int _bdMask; + + private int _bhShift; + private int _bdShift; + private int _bppShift; + + private int _xShift; + + private int _robSize; + private int _sliceSize; + + public BlockLinearLayout( + int width, + int height, + int depth, + int gobBlocksInY, + int gobBlocksInZ, + int bpp) + { + _texBpp = bpp; + + _bppShift = BitUtils.CountTrailingZeros32(bpp); + + _bhMask = gobBlocksInY - 1; + _bdMask = gobBlocksInZ - 1; + + _bhShift = BitUtils.CountTrailingZeros32(gobBlocksInY); + _bdShift = BitUtils.CountTrailingZeros32(gobBlocksInZ); + + _xShift = BitUtils.CountTrailingZeros32(GobSize * gobBlocksInY * gobBlocksInZ); + + RobAndSliceSizes rsSizes = GetRobAndSliceSizes(width, height, gobBlocksInY, gobBlocksInZ); + + _robSize = rsSizes.RobSize; + _sliceSize = rsSizes.SliceSize; + } + + private RobAndSliceSizes GetRobAndSliceSizes(int width, int height, int gobBlocksInY, int gobBlocksInZ) + { + int widthInGobs = BitUtils.DivRoundUp(width * _texBpp, GobStride); + + int robSize = GobSize * gobBlocksInY * gobBlocksInZ * widthInGobs; + + int sliceSize = BitUtils.DivRoundUp(height, gobBlocksInY * GobHeight) * robSize; + + return new RobAndSliceSizes(robSize, sliceSize); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int GetOffset(int x, int y, int z) + { + return GetOffsetWithLineOffset(x << _bppShift, y, z); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int GetOffsetWithLineOffset(int x, int y, int z) + { + int yh = y / GobHeight; + + int offset = (z >> _bdShift) * _sliceSize + (yh >> _bhShift) * _robSize; + + offset += (x / GobStride) << _xShift; + + offset += (yh & _bhMask) * GobSize; + + offset += ((z & _bdMask) * GobSize) << _bhShift; + + offset += ((x & 0x3f) >> 5) << 8; + offset += ((y & 0x07) >> 1) << 6; + offset += ((x & 0x1f) >> 4) << 5; + offset += ((y & 0x01) >> 0) << 4; + offset += ((x & 0x0f) >> 0) << 0; + + return offset; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Texture/LayoutConverter.cs b/Ryujinx.Graphics.Texture/LayoutConverter.cs new file mode 100644 index 0000000000..2c3d641bb2 --- /dev/null +++ b/Ryujinx.Graphics.Texture/LayoutConverter.cs @@ -0,0 +1,290 @@ +using Ryujinx.Common; +using System; + +using static Ryujinx.Graphics.Texture.BlockLinearConstants; + +namespace Ryujinx.Graphics.Texture +{ + public static class LayoutConverter + { + private const int HostStrideAlignment = 4; + + public static Span ConvertBlockLinearToLinear( + int width, + int height, + int depth, + int levels, + int layers, + int blockWidth, + int blockHeight, + int bytesPerPixel, + int gobBlocksInY, + int gobBlocksInZ, + int gobBlocksInTileX, + SizeInfo sizeInfo, + ReadOnlySpan data) + { + int outSize = GetTextureSize( + width, + height, + depth, + levels, + layers, + blockWidth, + blockHeight, + bytesPerPixel); + + Span output = new byte[outSize]; + + int outOffs = 0; + + int wAlignment = gobBlocksInTileX * (GobStride / bytesPerPixel); + + int mipGobBlocksInY = gobBlocksInY; + int mipGobBlocksInZ = gobBlocksInZ; + + for (int level = 0; level < levels; level++) + { + int w = Math.Max(1, width >> level); + int h = Math.Max(1, height >> level); + int d = Math.Max(1, depth >> level); + + w = BitUtils.DivRoundUp(w, blockWidth); + h = BitUtils.DivRoundUp(h, blockHeight); + + while (h <= (mipGobBlocksInY >> 1) * GobHeight && mipGobBlocksInY != 1) + { + mipGobBlocksInY >>= 1; + } + + while (d <= (mipGobBlocksInZ >> 1) && mipGobBlocksInZ != 1) + { + mipGobBlocksInZ >>= 1; + } + + int strideTrunc = BitUtils.AlignDown(w * bytesPerPixel, 16); + + int xStart = strideTrunc / bytesPerPixel; + + int stride = BitUtils.AlignUp(w * bytesPerPixel, HostStrideAlignment); + int wAligned = BitUtils.AlignUp(w, wAlignment); + + BlockLinearLayout layoutConverter = new BlockLinearLayout( + wAligned, + h, + d, + mipGobBlocksInY, + mipGobBlocksInZ, + bytesPerPixel); + + for (int layer = 0; layer < layers; layer++) + { + int inBaseOffset = layer * sizeInfo.LayerSize + sizeInfo.GetMipOffset(level); + + for (int z = 0; z < d; z++) + for (int y = 0; y < h; y++) + { + for (int x = 0; x < strideTrunc; x += 16) + { + int offset = inBaseOffset + layoutConverter.GetOffsetWithLineOffset(x, y, z); + + Span dest = output.Slice(outOffs + x, 16); + + data.Slice(offset, 16).CopyTo(dest); + } + + for (int x = xStart; x < w; x++) + { + int offset = inBaseOffset + layoutConverter.GetOffset(x, y, z); + + Span dest = output.Slice(outOffs + x * bytesPerPixel, bytesPerPixel); + + data.Slice(offset, bytesPerPixel).CopyTo(dest); + } + + outOffs += stride; + } + } + } + + return output; + } + + public static Span ConvertLinearStridedToLinear( + int width, + int height, + int blockWidth, + int blockHeight, + int stride, + int bytesPerPixel, + ReadOnlySpan data) + { + int w = BitUtils.DivRoundUp(width, blockWidth); + int h = BitUtils.DivRoundUp(height, blockHeight); + + int outStride = BitUtils.AlignUp(w * bytesPerPixel, HostStrideAlignment); + + Span output = new byte[h * outStride]; + + int outOffs = 0; + + for (int y = 0; y < h; y++) + { + for (int x = 0; x < w; x++) + { + int offset = y * stride + x * bytesPerPixel; + + Span dest = output.Slice(outOffs + x * bytesPerPixel, bytesPerPixel); + + data.Slice(offset, bytesPerPixel).CopyTo(dest); + } + + outOffs += outStride; + } + + return output; + } + + public static Span ConvertLinearToBlockLinear( + int width, + int height, + int depth, + int levels, + int layers, + int blockWidth, + int blockHeight, + int bytesPerPixel, + int gobBlocksInY, + int gobBlocksInZ, + int gobBlocksInTileX, + SizeInfo sizeInfo, + ReadOnlySpan data) + { + Span output = new byte[sizeInfo.TotalSize]; + + int inOffs = 0; + + int wAlignment = gobBlocksInTileX * (GobStride / bytesPerPixel); + + int mipGobBlocksInY = gobBlocksInY; + int mipGobBlocksInZ = gobBlocksInZ; + + for (int level = 0; level < levels; level++) + { + int w = Math.Max(1, width >> level); + int h = Math.Max(1, height >> level); + int d = Math.Max(1, depth >> level); + + w = BitUtils.DivRoundUp(w, blockWidth); + h = BitUtils.DivRoundUp(h, blockHeight); + + while (h <= (mipGobBlocksInY >> 1) * GobHeight && mipGobBlocksInY != 1) + { + mipGobBlocksInY >>= 1; + } + + while (d <= (mipGobBlocksInZ >> 1) && mipGobBlocksInZ != 1) + { + mipGobBlocksInZ >>= 1; + } + + int stride = BitUtils.AlignUp(w * bytesPerPixel, HostStrideAlignment); + int wAligned = BitUtils.AlignUp(w, wAlignment); + + BlockLinearLayout layoutConverter = new BlockLinearLayout( + wAligned, + h, + d, + mipGobBlocksInY, + mipGobBlocksInZ, + bytesPerPixel); + + for (int layer = 0; layer < layers; layer++) + { + int outBaseOffset = layer * sizeInfo.LayerSize + sizeInfo.GetMipOffset(level); + + for (int z = 0; z < d; z++) + for (int y = 0; y < h; y++) + { + for (int x = 0; x < w; x++) + { + int offset = outBaseOffset + layoutConverter.GetOffset(x, y, z); + + Span dest = output.Slice(offset, bytesPerPixel); + + data.Slice(inOffs + x * bytesPerPixel, bytesPerPixel).CopyTo(dest); + } + + inOffs += stride; + } + } + } + + return output; + } + + public static Span ConvertLinearToLinearStrided( + int width, + int height, + int blockWidth, + int blockHeight, + int stride, + int bytesPerPixel, + ReadOnlySpan data) + { + int w = BitUtils.DivRoundUp(width, blockWidth); + int h = BitUtils.DivRoundUp(height, blockHeight); + + int inStride = BitUtils.AlignUp(w * bytesPerPixel, HostStrideAlignment); + + Span output = new byte[h * stride]; + + int inOffs = 0; + + for (int y = 0; y < h; y++) + { + for (int x = 0; x < w; x++) + { + int offset = y * stride + x * bytesPerPixel; + + Span dest = output.Slice(offset, bytesPerPixel); + + data.Slice(inOffs + x * bytesPerPixel, bytesPerPixel).CopyTo(dest); + } + + inOffs += inStride; + } + + return output; + } + + private static int GetTextureSize( + int width, + int height, + int depth, + int levels, + int layers, + int blockWidth, + int blockHeight, + int bytesPerPixel) + { + int layerSize = 0; + + for (int level = 0; level < levels; level++) + { + int w = Math.Max(1, width >> level); + int h = Math.Max(1, height >> level); + int d = Math.Max(1, depth >> level); + + w = BitUtils.DivRoundUp(w, blockWidth); + h = BitUtils.DivRoundUp(h, blockHeight); + + int stride = BitUtils.AlignUp(w * bytesPerPixel, HostStrideAlignment); + + layerSize += stride * h * d; + } + + return layerSize * layers; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Texture/OffsetCalculator.cs b/Ryujinx.Graphics.Texture/OffsetCalculator.cs new file mode 100644 index 0000000000..bb5d606ca4 --- /dev/null +++ b/Ryujinx.Graphics.Texture/OffsetCalculator.cs @@ -0,0 +1,55 @@ +using Ryujinx.Common; + +using static Ryujinx.Graphics.Texture.BlockLinearConstants; + +namespace Ryujinx.Graphics.Texture +{ + public class OffsetCalculator + { + private int _stride; + private bool _isLinear; + private int _bytesPerPixel; + + private BlockLinearLayout _layoutConverter; + + public OffsetCalculator( + int width, + int height, + int stride, + bool isLinear, + int gobBlocksInY, + int bytesPerPixel) + { + _stride = stride; + _isLinear = isLinear; + _bytesPerPixel = bytesPerPixel; + + int wAlignment = GobStride / bytesPerPixel; + + int wAligned = BitUtils.AlignUp(width, wAlignment); + + if (!isLinear) + { + _layoutConverter = new BlockLinearLayout( + wAligned, + height, + 1, + gobBlocksInY, + 1, + bytesPerPixel); + } + } + + public int GetOffset(int x, int y) + { + if (_isLinear) + { + return x * _bytesPerPixel + y * _stride; + } + else + { + return _layoutConverter.GetOffset(x, y, 0); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Texture/Ryujinx.Graphics.Texture.csproj b/Ryujinx.Graphics.Texture/Ryujinx.Graphics.Texture.csproj new file mode 100644 index 0000000000..2009fbeef9 --- /dev/null +++ b/Ryujinx.Graphics.Texture/Ryujinx.Graphics.Texture.csproj @@ -0,0 +1,13 @@ + + + + + + + + netcoreapp3.0 + win-x64;osx-x64;linux-x64 + true + + + diff --git a/Ryujinx.Graphics.Texture/Size.cs b/Ryujinx.Graphics.Texture/Size.cs new file mode 100644 index 0000000000..4e070c9088 --- /dev/null +++ b/Ryujinx.Graphics.Texture/Size.cs @@ -0,0 +1,19 @@ +using Ryujinx.Common; +using System; + +namespace Ryujinx.Graphics.Texture +{ + public struct Size + { + public int Width { get; } + public int Height { get; } + public int Depth { get; } + + public Size(int width, int height, int depth) + { + Width = width; + Height = height; + Depth = depth; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Texture/SizeCalculator.cs b/Ryujinx.Graphics.Texture/SizeCalculator.cs new file mode 100644 index 0000000000..11385d28d3 --- /dev/null +++ b/Ryujinx.Graphics.Texture/SizeCalculator.cs @@ -0,0 +1,218 @@ +using Ryujinx.Common; +using System; + +using static Ryujinx.Graphics.Texture.BlockLinearConstants; + +namespace Ryujinx.Graphics.Texture +{ + public static class SizeCalculator + { + private const int StrideAlignment = 32; + + public static SizeInfo GetBlockLinearTextureSize( + int width, + int height, + int depth, + int levels, + int layers, + int blockWidth, + int blockHeight, + int bytesPerPixel, + int gobBlocksInY, + int gobBlocksInZ, + int gobBlocksInTileX) + { + bool is3D = depth > 1; + + int layerSize = 0; + + int[] allOffsets = new int[levels * layers * depth]; + int[] mipOffsets = new int[levels]; + + int mipGobBlocksInY = gobBlocksInY; + int mipGobBlocksInZ = gobBlocksInZ; + + for (int level = 0; level < levels; level++) + { + int w = Math.Max(1, width >> level); + int h = Math.Max(1, height >> level); + int d = Math.Max(1, depth >> level); + + w = BitUtils.DivRoundUp(w, blockWidth); + h = BitUtils.DivRoundUp(h, blockHeight); + + while (h <= (mipGobBlocksInY >> 1) * GobHeight && mipGobBlocksInY != 1) + { + mipGobBlocksInY >>= 1; + } + + while (d <= (mipGobBlocksInZ >> 1) && mipGobBlocksInZ != 1) + { + mipGobBlocksInZ >>= 1; + } + + int widthInGobs = BitUtils.AlignUp(BitUtils.DivRoundUp(w * bytesPerPixel, GobStride), gobBlocksInTileX); + + int totalBlocksOfGobsInZ = BitUtils.DivRoundUp(d, mipGobBlocksInZ); + int totalBlocksOfGobsInY = BitUtils.DivRoundUp(BitUtils.DivRoundUp(h, GobHeight), mipGobBlocksInY); + + int robSize = widthInGobs * mipGobBlocksInY * mipGobBlocksInZ * GobSize; + + if (is3D) + { + int gobSize = mipGobBlocksInY * GobSize; + + int sliceSize = totalBlocksOfGobsInY * widthInGobs * gobSize; + + int baseOffset = layerSize; + + int mask = gobBlocksInZ - 1; + + for (int z = 0; z < d; z++) + { + int zLow = z & mask; + int zHigh = z & ~mask; + + allOffsets[z * levels + level] = baseOffset + zLow * gobSize + zHigh * sliceSize; + } + } + + mipOffsets[level] = layerSize; + + layerSize += totalBlocksOfGobsInZ * totalBlocksOfGobsInY * robSize; + } + + layerSize = AlignLayerSize( + layerSize, + height, + depth, + blockHeight, + gobBlocksInY, + gobBlocksInZ); + + if (!is3D) + { + for (int layer = 0; layer < layers; layer++) + { + int baseIndex = layer * levels; + int baseOffset = layer * layerSize; + + for (int level = 0; level < levels; level++) + { + allOffsets[baseIndex + level] = baseOffset + mipOffsets[level]; + } + } + } + + int totalSize = layerSize * layers; + + return new SizeInfo(mipOffsets, allOffsets, levels, layerSize, totalSize); + } + + public static SizeInfo GetLinearTextureSize(int stride, int height, int blockHeight) + { + // Non-2D or mipmapped linear textures are not supported by the Switch GPU, + // so we only need to handle a single case (2D textures without mipmaps). + int totalSize = stride * BitUtils.DivRoundUp(height, blockHeight); + + return new SizeInfo(new int[] { 0 }, new int[] { 0 }, 1, totalSize, totalSize); + } + + private static int AlignLayerSize( + int size, + int height, + int depth, + int blockHeight, + int gobBlocksInY, + int gobBlocksInZ) + { + height = BitUtils.DivRoundUp(height, blockHeight); + + while (height <= (gobBlocksInY >> 1) * GobHeight && gobBlocksInY != 1) + { + gobBlocksInY >>= 1; + } + + while (depth <= (gobBlocksInZ >> 1) && gobBlocksInZ != 1) + { + gobBlocksInZ >>= 1; + } + + int blockOfGobsSize = gobBlocksInY * gobBlocksInZ * GobSize; + + int sizeInBlockOfGobs = size / blockOfGobsSize; + + if (size != sizeInBlockOfGobs * blockOfGobsSize) + { + size = (sizeInBlockOfGobs + 1) * blockOfGobsSize; + } + + return size; + } + + public static Size GetBlockLinearAlignedSize( + int width, + int height, + int depth, + int blockWidth, + int blockHeight, + int bytesPerPixel, + int gobBlocksInY, + int gobBlocksInZ, + int gobBlocksInTileX) + { + width = BitUtils.DivRoundUp(width, blockWidth); + height = BitUtils.DivRoundUp(height, blockHeight); + + int gobWidth = gobBlocksInTileX * (GobStride / bytesPerPixel); + + int blockOfGobsHeight = gobBlocksInY * GobHeight; + int blockOfGobsDepth = gobBlocksInZ; + + width = BitUtils.AlignUp(width, gobWidth); + height = BitUtils.AlignUp(height, blockOfGobsHeight); + depth = BitUtils.AlignUp(depth, blockOfGobsDepth); + + return new Size(width, height, depth); + } + + public static Size GetLinearAlignedSize( + int width, + int height, + int blockWidth, + int blockHeight, + int bytesPerPixel) + { + width = BitUtils.DivRoundUp(width, blockWidth); + height = BitUtils.DivRoundUp(height, blockHeight); + + int widthAlignment = StrideAlignment / bytesPerPixel; + + width = BitUtils.AlignUp(width, widthAlignment); + + return new Size(width, height, 1); + } + + public static (int, int) GetMipGobBlockSizes( + int height, + int depth, + int blockHeight, + int gobBlocksInY, + int gobBlocksInZ) + { + height = BitUtils.DivRoundUp(height, blockHeight); + + while (height <= (gobBlocksInY >> 1) * GobHeight && gobBlocksInY != 1) + { + gobBlocksInY >>= 1; + } + + while (depth <= (gobBlocksInZ >> 1) && gobBlocksInZ != 1) + { + gobBlocksInZ >>= 1; + } + + return (gobBlocksInY, gobBlocksInZ); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Texture/SizeInfo.cs b/Ryujinx.Graphics.Texture/SizeInfo.cs new file mode 100644 index 0000000000..37d824cc55 --- /dev/null +++ b/Ryujinx.Graphics.Texture/SizeInfo.cs @@ -0,0 +1,58 @@ +using Ryujinx.Common; +using System; + +namespace Ryujinx.Graphics.Texture +{ + public struct SizeInfo + { + private int[] _mipOffsets; + private int[] _allOffsets; + + private int _levels; + + public int LayerSize { get; } + public int TotalSize { get; } + + public SizeInfo( + int[] mipOffsets, + int[] allOffsets, + int levels, + int layerSize, + int totalSize) + { + _mipOffsets = mipOffsets; + _allOffsets = allOffsets; + _levels = levels; + LayerSize = layerSize; + TotalSize = totalSize; + } + + public int GetMipOffset(int level) + { + if ((uint)level > _mipOffsets.Length) + { + throw new ArgumentOutOfRangeException(nameof(level)); + } + + return _mipOffsets[level]; + } + + public bool FindView(int offset, int size, out int firstLayer, out int firstLevel) + { + int index = Array.BinarySearch(_allOffsets, offset); + + if (index < 0) + { + firstLayer = 0; + firstLevel = 0; + + return false; + } + + firstLayer = index / _levels; + firstLevel = index - (firstLayer * _levels); + + return true; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/EmbeddedResource.cs b/Ryujinx.Graphics/Gal/EmbeddedResource.cs deleted file mode 100644 index 45b77da71c..0000000000 --- a/Ryujinx.Graphics/Gal/EmbeddedResource.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.IO; -using System.Reflection; - -namespace Ryujinx.Graphics.Gal -{ - static class EmbeddedResource - { - public static string GetString(string Name) - { - Assembly Asm = typeof(EmbeddedResource).Assembly; - - using (Stream ResStream = Asm.GetManifestResourceStream(Name)) - { - StreamReader Reader = new StreamReader(ResStream); - - return Reader.ReadToEnd(); - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalBlendEquation.cs b/Ryujinx.Graphics/Gal/GalBlendEquation.cs deleted file mode 100644 index 7fd4ba5fa6..0000000000 --- a/Ryujinx.Graphics/Gal/GalBlendEquation.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Ryujinx.Graphics.Gal -{ - public enum GalBlendEquation - { - FuncAdd = 1, - FuncSubtract = 2, - FuncReverseSubtract = 3, - Min = 4, - Max = 5 - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalBlendFactor.cs b/Ryujinx.Graphics/Gal/GalBlendFactor.cs deleted file mode 100644 index 001aaaeca8..0000000000 --- a/Ryujinx.Graphics/Gal/GalBlendFactor.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace Ryujinx.Graphics.Gal -{ - public enum GalBlendFactor - { - Zero = 0x1, - One = 0x2, - SrcColor = 0x3, - OneMinusSrcColor = 0x4, - SrcAlpha = 0x5, - OneMinusSrcAlpha = 0x6, - DstAlpha = 0x7, - OneMinusDstAlpha = 0x8, - DstColor = 0x9, - OneMinusDstColor = 0xa, - SrcAlphaSaturate = 0xb, - Src1Color = 0x10, - OneMinusSrc1Color = 0x11, - Src1Alpha = 0x12, - OneMinusSrc1Alpha = 0x13, - ConstantColor = 0x61, - OneMinusConstantColor = 0x62, - ConstantAlpha = 0x63, - OneMinusConstantAlpha = 0x64, - ConstantColorG80 = 0xc001 - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalClearBufferFlags.cs b/Ryujinx.Graphics/Gal/GalClearBufferFlags.cs deleted file mode 100644 index 8565051cac..0000000000 --- a/Ryujinx.Graphics/Gal/GalClearBufferFlags.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace Ryujinx.Graphics.Gal -{ - [Flags] - public enum GalClearBufferFlags - { - Depth = 1 << 0, - Stencil = 1 << 1, - ColorRed = 1 << 2, - ColorGreen = 1 << 3, - ColorBlue = 1 << 4, - ColorAlpha = 1 << 5 - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalColorF.cs b/Ryujinx.Graphics/Gal/GalColorF.cs deleted file mode 100644 index 7cfb171dcd..0000000000 --- a/Ryujinx.Graphics/Gal/GalColorF.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace Ryujinx.Graphics.Gal -{ - public struct GalColorF - { - public float Red { get; private set; } - public float Green { get; private set; } - public float Blue { get; private set; } - public float Alpha { get; private set; } - - public GalColorF( - float Red, - float Green, - float Blue, - float Alpha) - { - this.Red = Red; - this.Green = Green; - this.Blue = Blue; - this.Alpha = Alpha; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalComparisonOp.cs b/Ryujinx.Graphics/Gal/GalComparisonOp.cs deleted file mode 100644 index f26a775337..0000000000 --- a/Ryujinx.Graphics/Gal/GalComparisonOp.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Ryujinx.Graphics.Gal -{ - public enum GalComparisonOp - { - Never = 0x1, - Less = 0x2, - Equal = 0x3, - Lequal = 0x4, - Greater = 0x5, - NotEqual = 0x6, - Gequal = 0x7, - Always = 0x8 - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalFrontFace.cs b/Ryujinx.Graphics/Gal/GalFrontFace.cs deleted file mode 100644 index 17ad11267b..0000000000 --- a/Ryujinx.Graphics/Gal/GalFrontFace.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Ryujinx.Graphics.Gal -{ - public enum GalFrontFace - { - CW = 0x900, - CCW = 0x901 - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalIndexFormat.cs b/Ryujinx.Graphics/Gal/GalIndexFormat.cs deleted file mode 100644 index 71a50cdba8..0000000000 --- a/Ryujinx.Graphics/Gal/GalIndexFormat.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Ryujinx.Graphics.Gal -{ - public enum GalIndexFormat - { - Byte = 0, - Int16 = 1, - Int32 = 2 - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalPrimitiveType.cs b/Ryujinx.Graphics/Gal/GalPrimitiveType.cs deleted file mode 100644 index ce084149d1..0000000000 --- a/Ryujinx.Graphics/Gal/GalPrimitiveType.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace Ryujinx.Graphics.Gal -{ - public enum GalPrimitiveType - { - Points = 0x0, - Lines = 0x1, - LineLoop = 0x2, - LineStrip = 0x3, - Triangles = 0x4, - TriangleStrip = 0x5, - TriangleFan = 0x6, - Quads = 0x7, - QuadStrip = 0x8, - Polygon = 0x9, - LinesAdjacency = 0xa, - LineStripAdjacency = 0xb, - TrianglesAdjacency = 0xc, - TriangleStripAdjacency = 0xd, - Patches = 0xe - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalShaderType.cs b/Ryujinx.Graphics/Gal/GalShaderType.cs deleted file mode 100644 index eb5aaf889a..0000000000 --- a/Ryujinx.Graphics/Gal/GalShaderType.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Ryujinx.Graphics.Gal -{ - public enum GalShaderType - { - Vertex = 0, - TessControl = 1, - TessEvaluation = 2, - Geometry = 3, - Fragment = 4 - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalStencilOp.cs b/Ryujinx.Graphics/Gal/GalStencilOp.cs deleted file mode 100644 index fc83ca5ea6..0000000000 --- a/Ryujinx.Graphics/Gal/GalStencilOp.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Ryujinx.Graphics.Gal -{ - public enum GalStencilOp - { - Keep = 0x1, - Zero = 0x2, - Replace = 0x3, - Incr = 0x4, - Decr = 0x5, - Invert = 0x6, - IncrWrap = 0x7, - DecrWrap = 0x8 - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalTexture.cs b/Ryujinx.Graphics/Gal/GalTexture.cs deleted file mode 100644 index 2c1be65b21..0000000000 --- a/Ryujinx.Graphics/Gal/GalTexture.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace Ryujinx.Graphics.Gal -{ - public struct GalTexture - { - public int Width; - public int Height; - - public GalTextureFormat Format; - - public GalTextureSource XSource; - public GalTextureSource YSource; - public GalTextureSource ZSource; - public GalTextureSource WSource; - - public GalTexture( - int Width, - int Height, - GalTextureFormat Format, - GalTextureSource XSource, - GalTextureSource YSource, - GalTextureSource ZSource, - GalTextureSource WSource) - { - this.Width = Width; - this.Height = Height; - this.Format = Format; - this.XSource = XSource; - this.YSource = YSource; - this.ZSource = ZSource; - this.WSource = WSource; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalTextureFilter.cs b/Ryujinx.Graphics/Gal/GalTextureFilter.cs deleted file mode 100644 index 8e9669f00f..0000000000 --- a/Ryujinx.Graphics/Gal/GalTextureFilter.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Ryujinx.Graphics.Gal -{ - public enum GalTextureFilter - { - Nearest = 1, - Linear = 2 - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalTextureFormat.cs b/Ryujinx.Graphics/Gal/GalTextureFormat.cs deleted file mode 100644 index 231d33ec0e..0000000000 --- a/Ryujinx.Graphics/Gal/GalTextureFormat.cs +++ /dev/null @@ -1,36 +0,0 @@ -namespace Ryujinx.Graphics.Gal -{ - public enum GalTextureFormat - { - R32G32B32A32 = 0x1, - R16G16B16A16 = 0x3, - A8B8G8R8 = 0x8, - R32 = 0xf, - A1B5G5R5 = 0x14, - B5G6R5 = 0x15, - BC7U = 0x17, - G8R8 = 0x18, - R16 = 0x1b, - R8 = 0x1d, - BC1 = 0x24, - BC2 = 0x25, - BC3 = 0x26, - BC4 = 0x27, - BC5 = 0x28, - ZF32 = 0x2f, - Astc2D4x4 = 0x40, - Astc2D5x5 = 0x41, - Astc2D6x6 = 0x42, - Astc2D8x8 = 0x44, - Astc2D10x10 = 0x45, - Astc2D12x12 = 0x46, - Astc2D5x4 = 0x50, - Astc2D6x5 = 0x51, - Astc2D8x6 = 0x52, - Astc2D10x8 = 0x53, - Astc2D12x10 = 0x54, - Astc2D8x5 = 0x55, - Astc2D10x5 = 0x56, - Astc2D10x6 = 0x57 - } -} diff --git a/Ryujinx.Graphics/Gal/GalTextureMipFilter.cs b/Ryujinx.Graphics/Gal/GalTextureMipFilter.cs deleted file mode 100644 index 2123ec7d24..0000000000 --- a/Ryujinx.Graphics/Gal/GalTextureMipFilter.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Ryujinx.Graphics.Gal -{ - public enum GalTextureMipFilter - { - None = 1, - Nearest = 2, - Linear = 3 - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalTextureSampler.cs b/Ryujinx.Graphics/Gal/GalTextureSampler.cs deleted file mode 100644 index b9e5c7658d..0000000000 --- a/Ryujinx.Graphics/Gal/GalTextureSampler.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace Ryujinx.Graphics.Gal -{ - public struct GalTextureSampler - { - public GalTextureWrap AddressU { get; private set; } - public GalTextureWrap AddressV { get; private set; } - public GalTextureWrap AddressP { get; private set; } - - public GalTextureFilter MinFilter { get; private set; } - public GalTextureFilter MagFilter { get; private set; } - public GalTextureMipFilter MipFilter { get; private set; } - - public GalColorF BorderColor { get; private set; } - - public GalTextureSampler( - GalTextureWrap AddressU, - GalTextureWrap AddressV, - GalTextureWrap AddressP, - GalTextureFilter MinFilter, - GalTextureFilter MagFilter, - GalTextureMipFilter MipFilter, - GalColorF BorderColor) - { - this.AddressU = AddressU; - this.AddressV = AddressV; - this.AddressP = AddressP; - this.MinFilter = MinFilter; - this.MagFilter = MagFilter; - this.MipFilter = MipFilter; - this.BorderColor = BorderColor; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalTextureSource.cs b/Ryujinx.Graphics/Gal/GalTextureSource.cs deleted file mode 100644 index 72dbec6066..0000000000 --- a/Ryujinx.Graphics/Gal/GalTextureSource.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Ryujinx.Graphics.Gal -{ - public enum GalTextureSource - { - Zero = 0, - Red = 2, - Green = 3, - Blue = 4, - Alpha = 5, - OneInt = 6, - OneFloat = 7 - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalTextureWrap.cs b/Ryujinx.Graphics/Gal/GalTextureWrap.cs deleted file mode 100644 index 66e5315409..0000000000 --- a/Ryujinx.Graphics/Gal/GalTextureWrap.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Ryujinx.Graphics.Gal -{ - public enum GalTextureWrap - { - Repeat = 0, - MirroredRepeat = 1, - ClampToEdge = 2, - ClampToBorder = 3, - Clamp = 4, - MirrorClampToEdge = 5, - MirrorClampToBorder = 6, - MirrorClamp = 7 - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalVertexAttrib.cs b/Ryujinx.Graphics/Gal/GalVertexAttrib.cs deleted file mode 100644 index dd04006025..0000000000 --- a/Ryujinx.Graphics/Gal/GalVertexAttrib.cs +++ /dev/null @@ -1,30 +0,0 @@ -namespace Ryujinx.Graphics.Gal -{ - public struct GalVertexAttrib - { - public int Index { get; private set; } - public bool IsConst { get; private set; } - public int Offset { get; private set; } - - public GalVertexAttribSize Size { get; private set; } - public GalVertexAttribType Type { get; private set; } - - public bool IsBgra { get; private set; } - - public GalVertexAttrib( - int Index, - bool IsConst, - int Offset, - GalVertexAttribSize Size, - GalVertexAttribType Type, - bool IsBgra) - { - this.Index = Index; - this.IsConst = IsConst; - this.Offset = Offset; - this.Size = Size; - this.Type = Type; - this.IsBgra = IsBgra; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalVertexAttribSize.cs b/Ryujinx.Graphics/Gal/GalVertexAttribSize.cs deleted file mode 100644 index d3ce60ace3..0000000000 --- a/Ryujinx.Graphics/Gal/GalVertexAttribSize.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace Ryujinx.Graphics.Gal -{ - public enum GalVertexAttribSize - { - _32_32_32_32 = 0x1, - _32_32_32 = 0x2, - _16_16_16_16 = 0x3, - _32_32 = 0x4, - _16_16_16 = 0x5, - _8_8_8_8 = 0xa, - _16_16 = 0xf, - _32 = 0x12, - _8_8_8 = 0x13, - _8_8 = 0x18, - _16 = 0x1b, - _8 = 0x1d, - _10_10_10_2 = 0x30, - _11_11_10 = 0x31 - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalVertexAttribType.cs b/Ryujinx.Graphics/Gal/GalVertexAttribType.cs deleted file mode 100644 index 358836fdaf..0000000000 --- a/Ryujinx.Graphics/Gal/GalVertexAttribType.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Ryujinx.Graphics.Gal -{ - public enum GalVertexAttribType - { - Snorm = 1, - Unorm = 2, - Sint = 3, - Uint = 4, - Uscaled = 5, - Sscaled = 6, - Float = 7 - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/IGalBlend.cs b/Ryujinx.Graphics/Gal/IGalBlend.cs deleted file mode 100644 index 5c96a49231..0000000000 --- a/Ryujinx.Graphics/Gal/IGalBlend.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace Ryujinx.Graphics.Gal -{ - public interface IGalBlend - { - void Enable(); - - void Disable(); - - void Set( - GalBlendEquation Equation, - GalBlendFactor FuncSrc, - GalBlendFactor FuncDst); - - void SetSeparate( - GalBlendEquation EquationRgb, - GalBlendEquation EquationAlpha, - GalBlendFactor FuncSrcRgb, - GalBlendFactor FuncDstRgb, - GalBlendFactor FuncSrcAlpha, - GalBlendFactor FuncDstAlpha); - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/IGalFrameBuffer.cs b/Ryujinx.Graphics/Gal/IGalFrameBuffer.cs deleted file mode 100644 index eaae0a492e..0000000000 --- a/Ryujinx.Graphics/Gal/IGalFrameBuffer.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; - -namespace Ryujinx.Graphics.Gal -{ - public interface IGalFrameBuffer - { - void Create(long Key, int Width, int Height); - - void Bind(long Key); - - void BindTexture(long Key, int Index); - - void Set(long Key); - - void Set(byte[] Data, int Width, int Height); - - void SetTransform(float SX, float SY, float Rotate, float TX, float TY); - - void SetWindowSize(int Width, int Height); - - void SetViewport(int X, int Y, int Width, int Height); - - void Render(); - - void GetBufferData(long Key, Action Callback); - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/IGalMemory.cs b/Ryujinx.Graphics/Gal/IGalMemory.cs deleted file mode 100644 index e6762b50cc..0000000000 --- a/Ryujinx.Graphics/Gal/IGalMemory.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Ryujinx.Graphics.Gal -{ - public unsafe interface IGalMemory - { - int ReadInt32(long Position); - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/IGalRasterizer.cs b/Ryujinx.Graphics/Gal/IGalRasterizer.cs deleted file mode 100644 index 0c5d37e40e..0000000000 --- a/Ryujinx.Graphics/Gal/IGalRasterizer.cs +++ /dev/null @@ -1,60 +0,0 @@ -namespace Ryujinx.Graphics.Gal -{ - public interface IGalRasterizer - { - void LockCaches(); - void UnlockCaches(); - - void ClearBuffers(GalClearBufferFlags Flags); - - bool IsVboCached(long Key, long DataSize); - - bool IsIboCached(long Key, long DataSize); - - void SetFrontFace(GalFrontFace FrontFace); - - void EnableCullFace(); - - void DisableCullFace(); - - void SetCullFace(GalCullFace CullFace); - - void EnableDepthTest(); - - void DisableDepthTest(); - - void SetDepthFunction(GalComparisonOp Func); - - void SetClearDepth(float Depth); - - void EnableStencilTest(); - - void DisableStencilTest(); - - void SetStencilFunction(bool IsFrontFace, GalComparisonOp Func, int Ref, int Mask); - - void SetStencilOp(bool IsFrontFace, GalStencilOp Fail, GalStencilOp ZFail, GalStencilOp ZPass); - - void SetStencilMask(bool IsFrontFace, int Mask); - - void SetClearStencil(int Stencil); - - void EnablePrimitiveRestart(); - - void DisablePrimitiveRestart(); - - void SetPrimitiveRestartIndex(uint Index); - - void CreateVbo(long Key, byte[] Buffer); - - void CreateIbo(long Key, byte[] Buffer); - - void SetVertexArray(int Stride, long VboKey, GalVertexAttrib[] Attribs); - - void SetIndexArray(int Size, GalIndexFormat Format); - - void DrawArrays(int First, int PrimCount, GalPrimitiveType PrimType); - - void DrawElements(long IboKey, int First, int VertexBase, GalPrimitiveType PrimType); - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/IGalRenderer.cs b/Ryujinx.Graphics/Gal/IGalRenderer.cs deleted file mode 100644 index c6324c4a35..0000000000 --- a/Ryujinx.Graphics/Gal/IGalRenderer.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; - -namespace Ryujinx.Graphics.Gal -{ - public interface IGalRenderer - { - void QueueAction(Action ActionMthd); - - void RunActions(); - - IGalBlend Blend { get; } - - IGalFrameBuffer FrameBuffer { get; } - - IGalRasterizer Rasterizer { get; } - - IGalShader Shader { get; } - - IGalTexture Texture { get; } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/IGalShader.cs b/Ryujinx.Graphics/Gal/IGalShader.cs deleted file mode 100644 index 06f3fac979..0000000000 --- a/Ryujinx.Graphics/Gal/IGalShader.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Collections.Generic; - -namespace Ryujinx.Graphics.Gal -{ - public interface IGalShader - { - void Create(IGalMemory Memory, long Key, GalShaderType Type); - - void Create(IGalMemory Memory, long VpAPos, long Key, GalShaderType Type); - - IEnumerable GetTextureUsage(long Key); - - void SetConstBuffer(long Key, int Cbuf, byte[] Data); - - void EnsureTextureBinding(string UniformName, int Value); - - void SetFlip(float X, float Y); - - void Bind(long Key); - - void BindProgram(); - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/IGalTexture.cs b/Ryujinx.Graphics/Gal/IGalTexture.cs deleted file mode 100644 index 2ab4119904..0000000000 --- a/Ryujinx.Graphics/Gal/IGalTexture.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace Ryujinx.Graphics.Gal -{ - public interface IGalTexture - { - void LockCache(); - void UnlockCache(); - - void Create(long Key, byte[] Data, GalTexture Texture); - - bool TryGetCachedTexture(long Key, long DataSize, out GalTexture Texture); - - void Bind(long Key, int Index); - - void SetSampler(GalTextureSampler Sampler); - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/OpenGL/DeleteValueCallback.cs b/Ryujinx.Graphics/Gal/OpenGL/DeleteValueCallback.cs deleted file mode 100644 index acd8d72f66..0000000000 --- a/Ryujinx.Graphics/Gal/OpenGL/DeleteValueCallback.cs +++ /dev/null @@ -1,4 +0,0 @@ -namespace Ryujinx.Graphics.Gal.OpenGL -{ - delegate void DeleteValue(T Value); -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/OpenGL/FbFragShader.glsl b/Ryujinx.Graphics/Gal/OpenGL/FbFragShader.glsl deleted file mode 100644 index 74e33bd7c0..0000000000 --- a/Ryujinx.Graphics/Gal/OpenGL/FbFragShader.glsl +++ /dev/null @@ -1,13 +0,0 @@ -#version 330 core - -precision highp float; - -uniform sampler2D tex; - -in vec2 tex_coord; - -out vec4 out_frag_color; - -void main(void) { - out_frag_color = texture(tex, tex_coord); -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/OpenGL/FbVtxShader.glsl b/Ryujinx.Graphics/Gal/OpenGL/FbVtxShader.glsl deleted file mode 100644 index 35d560c09b..0000000000 --- a/Ryujinx.Graphics/Gal/OpenGL/FbVtxShader.glsl +++ /dev/null @@ -1,28 +0,0 @@ -#version 330 core - -precision highp float; - -uniform mat2 transform; -uniform vec2 window_size; -uniform vec2 offset; - -layout(location = 0) in vec2 in_position; -layout(location = 1) in vec2 in_tex_coord; - -out vec2 tex_coord; - -// Have a fixed aspect ratio, fit the image within the available space. -vec2 get_scale_ratio(void) { - vec2 native_size = vec2(1280, 720); - vec2 ratio = vec2( - (window_size.y * native_size.x) / (native_size.y * window_size.x), - (window_size.x * native_size.y) / (native_size.x * window_size.y) - ); - return min(ratio, 1); -} - -void main(void) { - tex_coord = in_tex_coord; - vec2 t_pos = (transform * in_position) + offset; - gl_Position = vec4(t_pos * get_scale_ratio(), 0, 1); -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLBlend.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLBlend.cs deleted file mode 100644 index 7175e3a0ef..0000000000 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLBlend.cs +++ /dev/null @@ -1,49 +0,0 @@ -using OpenTK.Graphics.OpenGL; - -namespace Ryujinx.Graphics.Gal.OpenGL -{ - public class OGLBlend : IGalBlend - { - public void Enable() - { - GL.Enable(EnableCap.Blend); - } - - public void Disable() - { - GL.Disable(EnableCap.Blend); - } - - public void Set( - GalBlendEquation Equation, - GalBlendFactor FuncSrc, - GalBlendFactor FuncDst) - { - GL.BlendEquation( - OGLEnumConverter.GetBlendEquation(Equation)); - - GL.BlendFunc( - OGLEnumConverter.GetBlendFactor(FuncSrc), - OGLEnumConverter.GetBlendFactor(FuncDst)); - } - - public void SetSeparate( - GalBlendEquation EquationRgb, - GalBlendEquation EquationAlpha, - GalBlendFactor FuncSrcRgb, - GalBlendFactor FuncDstRgb, - GalBlendFactor FuncSrcAlpha, - GalBlendFactor FuncDstAlpha) - { - GL.BlendEquationSeparate( - OGLEnumConverter.GetBlendEquation(EquationRgb), - OGLEnumConverter.GetBlendEquation(EquationAlpha)); - - GL.BlendFuncSeparate( - (BlendingFactorSrc)OGLEnumConverter.GetBlendFactor(FuncSrcRgb), - (BlendingFactorDest)OGLEnumConverter.GetBlendFactor(FuncDstRgb), - (BlendingFactorSrc)OGLEnumConverter.GetBlendFactor(FuncSrcAlpha), - (BlendingFactorDest)OGLEnumConverter.GetBlendFactor(FuncDstAlpha)); - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLCachedResource.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLCachedResource.cs deleted file mode 100644 index 01ebf98202..0000000000 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLCachedResource.cs +++ /dev/null @@ -1,186 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Ryujinx.Graphics.Gal.OpenGL -{ - class OGLCachedResource - { - public delegate void DeleteValue(T Value); - - private const int MaxTimeDelta = 5 * 60000; - private const int MaxRemovalsPerRun = 10; - - private struct CacheBucket - { - public T Value { get; private set; } - - public LinkedListNode Node { get; private set; } - - public long DataSize { get; private set; } - - public int Timestamp { get; private set; } - - public CacheBucket(T Value, long DataSize, LinkedListNode Node) - { - this.Value = Value; - this.DataSize = DataSize; - this.Node = Node; - - Timestamp = Environment.TickCount; - } - } - - private Dictionary Cache; - - private LinkedList SortedCache; - - private DeleteValue DeleteValueCallback; - - private Queue DeletePending; - - private bool Locked; - - public OGLCachedResource(DeleteValue DeleteValueCallback) - { - if (DeleteValueCallback == null) - { - throw new ArgumentNullException(nameof(DeleteValueCallback)); - } - - this.DeleteValueCallback = DeleteValueCallback; - - Cache = new Dictionary(); - - SortedCache = new LinkedList(); - - DeletePending = new Queue(); - } - - public void Lock() - { - Locked = true; - } - - public void Unlock() - { - Locked = false; - - while (DeletePending.TryDequeue(out T Value)) - { - DeleteValueCallback(Value); - } - - ClearCacheIfNeeded(); - } - - public void AddOrUpdate(long Key, T Value, long Size) - { - if (!Locked) - { - ClearCacheIfNeeded(); - } - - LinkedListNode Node = SortedCache.AddLast(Key); - - CacheBucket NewBucket = new CacheBucket(Value, Size, Node); - - if (Cache.TryGetValue(Key, out CacheBucket Bucket)) - { - if (Locked) - { - DeletePending.Enqueue(Bucket.Value); - } - else - { - DeleteValueCallback(Bucket.Value); - } - - SortedCache.Remove(Bucket.Node); - - Cache[Key] = NewBucket; - } - else - { - Cache.Add(Key, NewBucket); - } - } - - public bool TryGetValue(long Key, out T Value) - { - if (Cache.TryGetValue(Key, out CacheBucket Bucket)) - { - Value = Bucket.Value; - - SortedCache.Remove(Bucket.Node); - - LinkedListNode Node = SortedCache.AddLast(Key); - - Cache[Key] = new CacheBucket(Value, Bucket.DataSize, Node); - - return true; - } - - Value = default(T); - - return false; - } - - public bool TryGetSize(long Key, out long Size) - { - if (Cache.TryGetValue(Key, out CacheBucket Bucket)) - { - Size = Bucket.DataSize; - - return true; - } - - Size = 0; - - return false; - } - - private void ClearCacheIfNeeded() - { - int Timestamp = Environment.TickCount; - - int Count = 0; - - while (Count++ < MaxRemovalsPerRun) - { - LinkedListNode Node = SortedCache.First; - - if (Node == null) - { - break; - } - - CacheBucket Bucket = Cache[Node.Value]; - - int TimeDelta = RingDelta(Bucket.Timestamp, Timestamp); - - if ((uint)TimeDelta <= (uint)MaxTimeDelta) - { - break; - } - - SortedCache.Remove(Node); - - Cache.Remove(Node.Value); - - DeleteValueCallback(Bucket.Value); - } - } - - private int RingDelta(int Old, int New) - { - if ((uint)New < (uint)Old) - { - return New + (~Old + 1); - } - else - { - return New - Old; - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLEnumConverter.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLEnumConverter.cs deleted file mode 100644 index 8f189d2b08..0000000000 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLEnumConverter.cs +++ /dev/null @@ -1,267 +0,0 @@ -using OpenTK.Graphics.OpenGL; -using System; - -namespace Ryujinx.Graphics.Gal.OpenGL -{ - static class OGLEnumConverter - { - public static FrontFaceDirection GetFrontFace(GalFrontFace FrontFace) - { - switch (FrontFace) - { - case GalFrontFace.CW: return FrontFaceDirection.Cw; - case GalFrontFace.CCW: return FrontFaceDirection.Ccw; - } - - throw new ArgumentException(nameof(FrontFace)); - } - - public static CullFaceMode GetCullFace(GalCullFace CullFace) - { - switch (CullFace) - { - case GalCullFace.Front: return CullFaceMode.Front; - case GalCullFace.Back: return CullFaceMode.Back; - case GalCullFace.FrontAndBack: return CullFaceMode.FrontAndBack; - } - - throw new ArgumentException(nameof(CullFace)); - } - - public static StencilOp GetStencilOp(GalStencilOp Op) - { - switch (Op) - { - case GalStencilOp.Keep: return StencilOp.Keep; - case GalStencilOp.Zero: return StencilOp.Zero; - case GalStencilOp.Replace: return StencilOp.Replace; - case GalStencilOp.Incr: return StencilOp.Incr; - case GalStencilOp.Decr: return StencilOp.Decr; - case GalStencilOp.Invert: return StencilOp.Invert; - case GalStencilOp.IncrWrap: return StencilOp.IncrWrap; - case GalStencilOp.DecrWrap: return StencilOp.DecrWrap; - } - - throw new ArgumentException(nameof(Op)); - } - - public static DepthFunction GetDepthFunc(GalComparisonOp Func) - { - //Looks like the GPU can take it's own values (described in GalComparisonOp) and OpenGL values alike - if ((int)Func >= (int)DepthFunction.Never && - (int)Func <= (int)DepthFunction.Always) - { - return (DepthFunction)Func; - } - - switch (Func) - { - case GalComparisonOp.Never: return DepthFunction.Never; - case GalComparisonOp.Less: return DepthFunction.Less; - case GalComparisonOp.Equal: return DepthFunction.Equal; - case GalComparisonOp.Lequal: return DepthFunction.Lequal; - case GalComparisonOp.Greater: return DepthFunction.Greater; - case GalComparisonOp.NotEqual: return DepthFunction.Notequal; - case GalComparisonOp.Gequal: return DepthFunction.Gequal; - case GalComparisonOp.Always: return DepthFunction.Always; - } - - throw new ArgumentException(nameof(Func)); - } - - public static StencilFunction GetStencilFunc(GalComparisonOp Func) - { - //OGL comparison values match, it's just an enum cast - return (StencilFunction)GetDepthFunc(Func); - } - - public static DrawElementsType GetDrawElementsType(GalIndexFormat Format) - { - switch (Format) - { - case GalIndexFormat.Byte: return DrawElementsType.UnsignedByte; - case GalIndexFormat.Int16: return DrawElementsType.UnsignedShort; - case GalIndexFormat.Int32: return DrawElementsType.UnsignedInt; - } - - throw new ArgumentException(nameof(Format)); - } - - public static PrimitiveType GetPrimitiveType(GalPrimitiveType Type) - { - switch (Type) - { - case GalPrimitiveType.Points: return PrimitiveType.Points; - case GalPrimitiveType.Lines: return PrimitiveType.Lines; - case GalPrimitiveType.LineLoop: return PrimitiveType.LineLoop; - case GalPrimitiveType.LineStrip: return PrimitiveType.LineStrip; - case GalPrimitiveType.Triangles: return PrimitiveType.Triangles; - case GalPrimitiveType.TriangleStrip: return PrimitiveType.TriangleStrip; - case GalPrimitiveType.TriangleFan: return PrimitiveType.TriangleFan; - case GalPrimitiveType.Quads: return PrimitiveType.Quads; - case GalPrimitiveType.QuadStrip: return PrimitiveType.QuadStrip; - case GalPrimitiveType.Polygon: return PrimitiveType.Polygon; - case GalPrimitiveType.LinesAdjacency: return PrimitiveType.LinesAdjacency; - case GalPrimitiveType.LineStripAdjacency: return PrimitiveType.LineStripAdjacency; - case GalPrimitiveType.TrianglesAdjacency: return PrimitiveType.TrianglesAdjacency; - case GalPrimitiveType.TriangleStripAdjacency: return PrimitiveType.TriangleStripAdjacency; - case GalPrimitiveType.Patches: return PrimitiveType.Patches; - } - - throw new ArgumentException(nameof(Type)); - } - - public static ShaderType GetShaderType(GalShaderType Type) - { - switch (Type) - { - case GalShaderType.Vertex: return ShaderType.VertexShader; - case GalShaderType.TessControl: return ShaderType.TessControlShader; - case GalShaderType.TessEvaluation: return ShaderType.TessEvaluationShader; - case GalShaderType.Geometry: return ShaderType.GeometryShader; - case GalShaderType.Fragment: return ShaderType.FragmentShader; - } - - throw new ArgumentException(nameof(Type)); - } - - public static (PixelFormat, PixelType) GetTextureFormat(GalTextureFormat Format) - { - switch (Format) - { - case GalTextureFormat.R32G32B32A32: return (PixelFormat.Rgba, PixelType.Float); - case GalTextureFormat.R16G16B16A16: return (PixelFormat.Rgba, PixelType.HalfFloat); - case GalTextureFormat.A8B8G8R8: return (PixelFormat.Rgba, PixelType.UnsignedByte); - case GalTextureFormat.R32: return (PixelFormat.Red, PixelType.Float); - case GalTextureFormat.A1B5G5R5: return (PixelFormat.Rgba, PixelType.UnsignedShort5551); - case GalTextureFormat.B5G6R5: return (PixelFormat.Rgb, PixelType.UnsignedShort565); - case GalTextureFormat.G8R8: return (PixelFormat.Rg, PixelType.UnsignedByte); - case GalTextureFormat.R16: return (PixelFormat.Red, PixelType.HalfFloat); - case GalTextureFormat.R8: return (PixelFormat.Red, PixelType.UnsignedByte); - case GalTextureFormat.ZF32: return (PixelFormat.DepthComponent, PixelType.Float); - } - - throw new NotImplementedException(Format.ToString()); - } - - public static InternalFormat GetCompressedTextureFormat(GalTextureFormat Format) - { - switch (Format) - { - case GalTextureFormat.BC7U: return InternalFormat.CompressedRgbaBptcUnorm; - case GalTextureFormat.BC1: return InternalFormat.CompressedRgbaS3tcDxt1Ext; - case GalTextureFormat.BC2: return InternalFormat.CompressedRgbaS3tcDxt3Ext; - case GalTextureFormat.BC3: return InternalFormat.CompressedRgbaS3tcDxt5Ext; - case GalTextureFormat.BC4: return InternalFormat.CompressedRedRgtc1; - case GalTextureFormat.BC5: return InternalFormat.CompressedRgRgtc2; - } - - throw new NotImplementedException(Format.ToString()); - } - - public static All GetTextureSwizzle(GalTextureSource Source) - { - switch (Source) - { - case GalTextureSource.Zero: return All.Zero; - case GalTextureSource.Red: return All.Red; - case GalTextureSource.Green: return All.Green; - case GalTextureSource.Blue: return All.Blue; - case GalTextureSource.Alpha: return All.Alpha; - case GalTextureSource.OneInt: return All.One; - case GalTextureSource.OneFloat: return All.One; - } - - throw new ArgumentException(nameof(Source)); - } - - public static TextureWrapMode GetTextureWrapMode(GalTextureWrap Wrap) - { - switch (Wrap) - { - case GalTextureWrap.Repeat: return TextureWrapMode.Repeat; - case GalTextureWrap.MirroredRepeat: return TextureWrapMode.MirroredRepeat; - case GalTextureWrap.ClampToEdge: return TextureWrapMode.ClampToEdge; - case GalTextureWrap.ClampToBorder: return TextureWrapMode.ClampToBorder; - case GalTextureWrap.Clamp: return TextureWrapMode.Clamp; - - //TODO: Those needs extensions (and are currently wrong). - case GalTextureWrap.MirrorClampToEdge: return TextureWrapMode.ClampToEdge; - case GalTextureWrap.MirrorClampToBorder: return TextureWrapMode.ClampToBorder; - case GalTextureWrap.MirrorClamp: return TextureWrapMode.Clamp; - } - - throw new ArgumentException(nameof(Wrap)); - } - - public static TextureMinFilter GetTextureMinFilter( - GalTextureFilter MinFilter, - GalTextureMipFilter MipFilter) - { - //TODO: Mip (needs mipmap support first). - switch (MinFilter) - { - case GalTextureFilter.Nearest: return TextureMinFilter.Nearest; - case GalTextureFilter.Linear: return TextureMinFilter.Linear; - } - - throw new ArgumentException(nameof(MinFilter)); - } - - public static TextureMagFilter GetTextureMagFilter(GalTextureFilter Filter) - { - switch (Filter) - { - case GalTextureFilter.Nearest: return TextureMagFilter.Nearest; - case GalTextureFilter.Linear: return TextureMagFilter.Linear; - } - - throw new ArgumentException(nameof(Filter)); - } - - public static BlendEquationMode GetBlendEquation(GalBlendEquation BlendEquation) - { - switch (BlendEquation) - { - case GalBlendEquation.FuncAdd: return BlendEquationMode.FuncAdd; - case GalBlendEquation.FuncSubtract: return BlendEquationMode.FuncSubtract; - case GalBlendEquation.FuncReverseSubtract: return BlendEquationMode.FuncReverseSubtract; - case GalBlendEquation.Min: return BlendEquationMode.Min; - case GalBlendEquation.Max: return BlendEquationMode.Max; - } - - throw new ArgumentException(nameof(BlendEquation)); - } - - public static BlendingFactor GetBlendFactor(GalBlendFactor BlendFactor) - { - switch (BlendFactor) - { - case GalBlendFactor.Zero: return BlendingFactor.Zero; - case GalBlendFactor.One: return BlendingFactor.One; - case GalBlendFactor.SrcColor: return BlendingFactor.SrcColor; - case GalBlendFactor.OneMinusSrcColor: return BlendingFactor.OneMinusSrcColor; - case GalBlendFactor.DstColor: return BlendingFactor.DstColor; - case GalBlendFactor.OneMinusDstColor: return BlendingFactor.OneMinusDstColor; - case GalBlendFactor.SrcAlpha: return BlendingFactor.SrcAlpha; - case GalBlendFactor.OneMinusSrcAlpha: return BlendingFactor.OneMinusSrcAlpha; - case GalBlendFactor.DstAlpha: return BlendingFactor.DstAlpha; - case GalBlendFactor.OneMinusDstAlpha: return BlendingFactor.OneMinusDstAlpha; - case GalBlendFactor.OneMinusConstantColor: return BlendingFactor.OneMinusConstantColor; - case GalBlendFactor.ConstantAlpha: return BlendingFactor.ConstantAlpha; - case GalBlendFactor.OneMinusConstantAlpha: return BlendingFactor.OneMinusConstantAlpha; - case GalBlendFactor.SrcAlphaSaturate: return BlendingFactor.SrcAlphaSaturate; - case GalBlendFactor.Src1Color: return BlendingFactor.Src1Color; - case GalBlendFactor.OneMinusSrc1Color: return (BlendingFactor)BlendingFactorSrc.OneMinusSrc1Color; - case GalBlendFactor.Src1Alpha: return BlendingFactor.Src1Alpha; - case GalBlendFactor.OneMinusSrc1Alpha: return (BlendingFactor)BlendingFactorSrc.OneMinusSrc1Alpha; - - case GalBlendFactor.ConstantColor: - case GalBlendFactor.ConstantColorG80: - return BlendingFactor.ConstantColor; - } - - throw new ArgumentException(nameof(BlendFactor)); - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLFrameBuffer.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLFrameBuffer.cs deleted file mode 100644 index 305fa37d8f..0000000000 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLFrameBuffer.cs +++ /dev/null @@ -1,450 +0,0 @@ -using OpenTK; -using OpenTK.Graphics.OpenGL; -using System; -using System.Collections.Generic; - -namespace Ryujinx.Graphics.Gal.OpenGL -{ - public class OGLFrameBuffer : IGalFrameBuffer - { - private struct Rect - { - public int X { get; private set; } - public int Y { get; private set; } - public int Width { get; private set; } - public int Height { get; private set; } - - public Rect(int X, int Y, int Width, int Height) - { - this.X = X; - this.Y = Y; - this.Width = Width; - this.Height = Height; - } - } - - private class FrameBuffer - { - public int Width { get; set; } - public int Height { get; set; } - - public int Handle { get; private set; } - public int RbHandle { get; private set; } - public int TexHandle { get; private set; } - - public FrameBuffer(int Width, int Height) - { - this.Width = Width; - this.Height = Height; - - Handle = GL.GenFramebuffer(); - RbHandle = GL.GenRenderbuffer(); - TexHandle = GL.GenTexture(); - } - } - - private struct ShaderProgram - { - public int Handle; - public int VpHandle; - public int FpHandle; - } - - private Dictionary Fbs; - - private ShaderProgram Shader; - - private Rect Viewport; - private Rect Window; - - private bool IsInitialized; - - private int RawFbTexWidth; - private int RawFbTexHeight; - private int RawFbTexHandle; - - private int CurrFbHandle; - private int CurrTexHandle; - - private int VaoHandle; - private int VboHandle; - - public OGLFrameBuffer() - { - Fbs = new Dictionary(); - - Shader = new ShaderProgram(); - } - - public void Create(long Key, int Width, int Height) - { - //TODO: We should either use the original frame buffer size, - //or just remove the Width/Height arguments. - Width = Window.Width; - Height = Window.Height; - - if (Fbs.TryGetValue(Key, out FrameBuffer Fb)) - { - if (Fb.Width != Width || - Fb.Height != Height) - { - SetupTexture(Fb.TexHandle, Width, Height); - - Fb.Width = Width; - Fb.Height = Height; - } - - return; - } - - Fb = new FrameBuffer(Width, Height); - - SetupTexture(Fb.TexHandle, Width, Height); - - GL.BindFramebuffer(FramebufferTarget.Framebuffer, Fb.Handle); - - GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, Fb.RbHandle); - - GL.RenderbufferStorage( - RenderbufferTarget.Renderbuffer, - RenderbufferStorage.Depth24Stencil8, - Width, - Height); - - GL.FramebufferRenderbuffer( - FramebufferTarget.Framebuffer, - FramebufferAttachment.DepthStencilAttachment, - RenderbufferTarget.Renderbuffer, - Fb.RbHandle); - - GL.FramebufferTexture( - FramebufferTarget.Framebuffer, - FramebufferAttachment.ColorAttachment0, - Fb.TexHandle, - 0); - - GL.DrawBuffer(DrawBufferMode.ColorAttachment0); - - GL.Viewport(0, 0, Width, Height); - - Fbs.Add(Key, Fb); - } - - public void Bind(long Key) - { - if (Fbs.TryGetValue(Key, out FrameBuffer Fb)) - { - GL.BindFramebuffer(FramebufferTarget.Framebuffer, Fb.Handle); - - CurrFbHandle = Fb.Handle; - } - } - - public void BindTexture(long Key, int Index) - { - if (Fbs.TryGetValue(Key, out FrameBuffer Fb)) - { - GL.ActiveTexture(TextureUnit.Texture0 + Index); - - GL.BindTexture(TextureTarget.Texture2D, Fb.TexHandle); - } - } - - public void Set(long Key) - { - if (Fbs.TryGetValue(Key, out FrameBuffer Fb)) - { - CurrTexHandle = Fb.TexHandle; - } - } - - public void Set(byte[] Data, int Width, int Height) - { - if (RawFbTexHandle == 0) - { - RawFbTexHandle = GL.GenTexture(); - } - - if (RawFbTexWidth != Width || - RawFbTexHeight != Height) - { - SetupTexture(RawFbTexHandle, Width, Height); - - RawFbTexWidth = Width; - RawFbTexHeight = Height; - } - - GL.ActiveTexture(TextureUnit.Texture0); - - GL.BindTexture(TextureTarget.Texture2D, RawFbTexHandle); - - (PixelFormat Format, PixelType Type) = OGLEnumConverter.GetTextureFormat(GalTextureFormat.A8B8G8R8); - - GL.TexSubImage2D(TextureTarget.Texture2D, 0, 0, 0, Width, Height, Format, Type, Data); - - CurrTexHandle = RawFbTexHandle; - } - - public void SetTransform(float SX, float SY, float Rotate, float TX, float TY) - { - EnsureInitialized(); - - Matrix2 Transform; - - Transform = Matrix2.CreateScale(SX, SY); - Transform *= Matrix2.CreateRotation(Rotate); - - Vector2 Offs = new Vector2(TX, TY); - - int CurrentProgram = GL.GetInteger(GetPName.CurrentProgram); - - GL.UseProgram(Shader.Handle); - - int TransformUniformLocation = GL.GetUniformLocation(Shader.Handle, "transform"); - - GL.UniformMatrix2(TransformUniformLocation, false, ref Transform); - - int OffsetUniformLocation = GL.GetUniformLocation(Shader.Handle, "offset"); - - GL.Uniform2(OffsetUniformLocation, ref Offs); - - GL.UseProgram(CurrentProgram); - } - - public void SetWindowSize(int Width, int Height) - { - int CurrentProgram = GL.GetInteger(GetPName.CurrentProgram); - - GL.UseProgram(Shader.Handle); - - int WindowSizeUniformLocation = GL.GetUniformLocation(Shader.Handle, "window_size"); - - GL.Uniform2(WindowSizeUniformLocation, new Vector2(Width, Height)); - - GL.UseProgram(CurrentProgram); - - Window = new Rect(0, 0, Width, Height); - } - - public void SetViewport(int X, int Y, int Width, int Height) - { - Viewport = new Rect(X, Y, Width, Height); - - //TODO - } - - public void Render() - { - if (CurrTexHandle != 0) - { - EnsureInitialized(); - - //bool CullFaceEnable = GL.IsEnabled(EnableCap.CullFace); - - bool DepthTestEnable = GL.IsEnabled(EnableCap.DepthTest); - - bool StencilTestEnable = GL.IsEnabled(EnableCap.StencilTest); - - bool AlphaBlendEnable = GL.IsEnabled(EnableCap.Blend); - - //GL.Disable(EnableCap.CullFace); - - GL.Disable(EnableCap.DepthTest); - - GL.Disable(EnableCap.StencilTest); - - GL.Disable(EnableCap.Blend); - - GL.ActiveTexture(TextureUnit.Texture0); - - GL.BindTexture(TextureTarget.Texture2D, CurrTexHandle); - - int CurrentProgram = GL.GetInteger(GetPName.CurrentProgram); - - GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0); - - SetViewport(Window); - - GL.Clear( - ClearBufferMask.ColorBufferBit | - ClearBufferMask.DepthBufferBit); - - GL.BindVertexArray(VaoHandle); - - GL.UseProgram(Shader.Handle); - - GL.DrawArrays(PrimitiveType.TriangleStrip, 0, 4); - - //Restore the original state. - GL.BindFramebuffer(FramebufferTarget.Framebuffer, CurrFbHandle); - - GL.UseProgram(CurrentProgram); - - //if (CullFaceEnable) - //{ - // GL.Enable(EnableCap.CullFace); - //} - - if (DepthTestEnable) - { - GL.Enable(EnableCap.DepthTest); - } - - if (StencilTestEnable) - { - GL.Enable(EnableCap.StencilTest); - } - - if (AlphaBlendEnable) - { - GL.Enable(EnableCap.Blend); - } - - //GL.Viewport(0, 0, 1280, 720); - } - } - - public void GetBufferData(long Key, Action Callback) - { - if (Fbs.TryGetValue(Key, out FrameBuffer Fb)) - { - GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, Fb.Handle); - - byte[] Data = new byte[Fb.Width * Fb.Height * 4]; - - (PixelFormat Format, PixelType Type) = OGLEnumConverter.GetTextureFormat(GalTextureFormat.A8B8G8R8); - - GL.ReadPixels( - 0, - 0, - Fb.Width, - Fb.Height, - Format, - Type, - Data); - - Callback(Data); - - GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, CurrFbHandle); - } - } - - private void SetViewport(Rect Viewport) - { - GL.Viewport( - Viewport.X, - Viewport.Y, - Viewport.Width, - Viewport.Height); - } - - private void EnsureInitialized() - { - if (!IsInitialized) - { - IsInitialized = true; - - SetupShader(); - SetupVertex(); - } - } - - private void SetupShader() - { - Shader.VpHandle = GL.CreateShader(ShaderType.VertexShader); - Shader.FpHandle = GL.CreateShader(ShaderType.FragmentShader); - - string VpSource = EmbeddedResource.GetString("GlFbVtxShader"); - string FpSource = EmbeddedResource.GetString("GlFbFragShader"); - - GL.ShaderSource(Shader.VpHandle, VpSource); - GL.ShaderSource(Shader.FpHandle, FpSource); - GL.CompileShader(Shader.VpHandle); - GL.CompileShader(Shader.FpHandle); - - Shader.Handle = GL.CreateProgram(); - - GL.AttachShader(Shader.Handle, Shader.VpHandle); - GL.AttachShader(Shader.Handle, Shader.FpHandle); - GL.LinkProgram(Shader.Handle); - GL.UseProgram(Shader.Handle); - - Matrix2 Transform = Matrix2.Identity; - - int TexUniformLocation = GL.GetUniformLocation(Shader.Handle, "tex"); - - GL.Uniform1(TexUniformLocation, 0); - - int WindowSizeUniformLocation = GL.GetUniformLocation(Shader.Handle, "window_size"); - - GL.Uniform2(WindowSizeUniformLocation, new Vector2(1280.0f, 720.0f)); - - int TransformUniformLocation = GL.GetUniformLocation(Shader.Handle, "transform"); - - GL.UniformMatrix2(TransformUniformLocation, false, ref Transform); - } - - private void SetupVertex() - { - VaoHandle = GL.GenVertexArray(); - VboHandle = GL.GenBuffer(); - - float[] Buffer = new float[] - { - -1, 1, 0, 0, - 1, 1, 1, 0, - -1, -1, 0, 1, - 1, -1, 1, 1 - }; - - IntPtr Length = new IntPtr(Buffer.Length * 4); - - GL.BindBuffer(BufferTarget.ArrayBuffer, VboHandle); - GL.BufferData(BufferTarget.ArrayBuffer, Length, Buffer, BufferUsageHint.StreamDraw); - GL.BindBuffer(BufferTarget.ArrayBuffer, 0); - - GL.BindVertexArray(VaoHandle); - - GL.EnableVertexAttribArray(0); - - GL.BindBuffer(BufferTarget.ArrayBuffer, VboHandle); - - GL.VertexAttribPointer(0, 2, VertexAttribPointerType.Float, false, 16, 0); - - GL.EnableVertexAttribArray(1); - - GL.BindBuffer(BufferTarget.ArrayBuffer, VboHandle); - - GL.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, 16, 8); - } - - private void SetupTexture(int Handle, int Width, int Height) - { - GL.BindTexture(TextureTarget.Texture2D, Handle); - - const int MinFilter = (int)TextureMinFilter.Linear; - const int MagFilter = (int)TextureMagFilter.Linear; - - GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, MinFilter); - GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, MagFilter); - - (PixelFormat Format, PixelType Type) = OGLEnumConverter.GetTextureFormat(GalTextureFormat.A8B8G8R8); - - const PixelInternalFormat InternalFmt = PixelInternalFormat.Rgba; - - const int Level = 0; - const int Border = 0; - - GL.TexImage2D( - TextureTarget.Texture2D, - Level, - InternalFmt, - Width, - Height, - Border, - Format, - Type, - IntPtr.Zero); - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs deleted file mode 100644 index 0dc56966b3..0000000000 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs +++ /dev/null @@ -1,333 +0,0 @@ -using OpenTK.Graphics.OpenGL; -using System; -using System.Collections.Generic; - -namespace Ryujinx.Graphics.Gal.OpenGL -{ - public class OGLRasterizer : IGalRasterizer - { - private static Dictionary AttribElements = - new Dictionary() - { - { GalVertexAttribSize._32_32_32_32, 4 }, - { GalVertexAttribSize._32_32_32, 3 }, - { GalVertexAttribSize._16_16_16_16, 4 }, - { GalVertexAttribSize._32_32, 2 }, - { GalVertexAttribSize._16_16_16, 3 }, - { GalVertexAttribSize._8_8_8_8, 4 }, - { GalVertexAttribSize._16_16, 2 }, - { GalVertexAttribSize._32, 1 }, - { GalVertexAttribSize._8_8_8, 3 }, - { GalVertexAttribSize._8_8, 2 }, - { GalVertexAttribSize._16, 1 }, - { GalVertexAttribSize._8, 1 }, - { GalVertexAttribSize._10_10_10_2, 4 }, - { GalVertexAttribSize._11_11_10, 3 } - }; - - private static Dictionary AttribTypes = - new Dictionary() - { - { GalVertexAttribSize._32_32_32_32, VertexAttribPointerType.Int }, - { GalVertexAttribSize._32_32_32, VertexAttribPointerType.Int }, - { GalVertexAttribSize._16_16_16_16, VertexAttribPointerType.Short }, - { GalVertexAttribSize._32_32, VertexAttribPointerType.Int }, - { GalVertexAttribSize._16_16_16, VertexAttribPointerType.Short }, - { GalVertexAttribSize._8_8_8_8, VertexAttribPointerType.Byte }, - { GalVertexAttribSize._16_16, VertexAttribPointerType.Short }, - { GalVertexAttribSize._32, VertexAttribPointerType.Int }, - { GalVertexAttribSize._8_8_8, VertexAttribPointerType.Byte }, - { GalVertexAttribSize._8_8, VertexAttribPointerType.Byte }, - { GalVertexAttribSize._16, VertexAttribPointerType.Short }, - { GalVertexAttribSize._8, VertexAttribPointerType.Byte }, - { GalVertexAttribSize._10_10_10_2, VertexAttribPointerType.Int }, //? - { GalVertexAttribSize._11_11_10, VertexAttribPointerType.Int } //? - }; - - private int VaoHandle; - - private int[] VertexBuffers; - - private OGLCachedResource VboCache; - private OGLCachedResource IboCache; - - private struct IbInfo - { - public int Count; - public int ElemSizeLog2; - - public DrawElementsType Type; - } - - private IbInfo IndexBuffer; - - public OGLRasterizer() - { - VertexBuffers = new int[32]; - - VboCache = new OGLCachedResource(GL.DeleteBuffer); - IboCache = new OGLCachedResource(GL.DeleteBuffer); - - IndexBuffer = new IbInfo(); - } - - public void LockCaches() - { - VboCache.Lock(); - IboCache.Lock(); - } - - public void UnlockCaches() - { - VboCache.Unlock(); - IboCache.Unlock(); - } - - public void ClearBuffers(GalClearBufferFlags Flags) - { - ClearBufferMask Mask = ClearBufferMask.ColorBufferBit; - - GL.ColorMask( - Flags.HasFlag(GalClearBufferFlags.ColorRed), - Flags.HasFlag(GalClearBufferFlags.ColorGreen), - Flags.HasFlag(GalClearBufferFlags.ColorBlue), - Flags.HasFlag(GalClearBufferFlags.ColorAlpha)); - - if (Flags.HasFlag(GalClearBufferFlags.Depth)) - { - Mask |= ClearBufferMask.DepthBufferBit; - } - - if (Flags.HasFlag(GalClearBufferFlags.Stencil)) - { - Mask |= ClearBufferMask.StencilBufferBit; - } - - GL.Clear(Mask); - - GL.ColorMask(true, true, true, true); - } - - public bool IsVboCached(long Key, long DataSize) - { - return VboCache.TryGetSize(Key, out long Size) && Size == DataSize; - } - - public bool IsIboCached(long Key, long DataSize) - { - return IboCache.TryGetSize(Key, out long Size) && Size == DataSize; - } - - public void SetFrontFace(GalFrontFace FrontFace) - { - GL.FrontFace(OGLEnumConverter.GetFrontFace(FrontFace)); - } - - public void EnableCullFace() - { - GL.Enable(EnableCap.CullFace); - } - - public void DisableCullFace() - { - GL.Disable(EnableCap.CullFace); - } - - public void SetCullFace(GalCullFace CullFace) - { - GL.CullFace(OGLEnumConverter.GetCullFace(CullFace)); - } - - public void EnableDepthTest() - { - GL.Enable(EnableCap.DepthTest); - } - - public void DisableDepthTest() - { - GL.Disable(EnableCap.DepthTest); - } - - public void SetDepthFunction(GalComparisonOp Func) - { - GL.DepthFunc(OGLEnumConverter.GetDepthFunc(Func)); - } - - public void SetClearDepth(float Depth) - { - GL.ClearDepth(Depth); - } - - public void EnableStencilTest() - { - GL.Enable(EnableCap.StencilTest); - } - - public void DisableStencilTest() - { - GL.Disable(EnableCap.StencilTest); - } - - public void SetStencilFunction(bool IsFrontFace, GalComparisonOp Func, int Ref, int Mask) - { - GL.StencilFuncSeparate( - IsFrontFace ? StencilFace.Front : StencilFace.Back, - OGLEnumConverter.GetStencilFunc(Func), - Ref, - Mask); - } - - public void SetStencilOp(bool IsFrontFace, GalStencilOp Fail, GalStencilOp ZFail, GalStencilOp ZPass) - { - GL.StencilOpSeparate( - IsFrontFace ? StencilFace.Front : StencilFace.Back, - OGLEnumConverter.GetStencilOp(Fail), - OGLEnumConverter.GetStencilOp(ZFail), - OGLEnumConverter.GetStencilOp(ZPass)); - } - - public void SetStencilMask(bool IsFrontFace, int Mask) - { - GL.StencilMaskSeparate(IsFrontFace ? StencilFace.Front : StencilFace.Back, Mask); - } - - public void SetClearStencil(int Stencil) - { - GL.ClearStencil(Stencil); - } - - public void EnablePrimitiveRestart() - { - GL.Enable(EnableCap.PrimitiveRestart); - } - - public void DisablePrimitiveRestart() - { - GL.Disable(EnableCap.PrimitiveRestart); - } - - public void SetPrimitiveRestartIndex(uint Index) - { - GL.PrimitiveRestartIndex(Index); - } - - public void CreateVbo(long Key, byte[] Buffer) - { - int Handle = GL.GenBuffer(); - - VboCache.AddOrUpdate(Key, Handle, (uint)Buffer.Length); - - IntPtr Length = new IntPtr(Buffer.Length); - - GL.BindBuffer(BufferTarget.ArrayBuffer, Handle); - GL.BufferData(BufferTarget.ArrayBuffer, Length, Buffer, BufferUsageHint.StreamDraw); - } - - public void CreateIbo(long Key, byte[] Buffer) - { - int Handle = GL.GenBuffer(); - - IboCache.AddOrUpdate(Key, Handle, (uint)Buffer.Length); - - IntPtr Length = new IntPtr(Buffer.Length); - - GL.BindBuffer(BufferTarget.ElementArrayBuffer, Handle); - GL.BufferData(BufferTarget.ElementArrayBuffer, Length, Buffer, BufferUsageHint.StreamDraw); - } - - public void SetVertexArray(int Stride, long VboKey, GalVertexAttrib[] Attribs) - { - if (!VboCache.TryGetValue(VboKey, out int VboHandle)) - { - return; - } - - if (VaoHandle == 0) - { - VaoHandle = GL.GenVertexArray(); - } - - GL.BindVertexArray(VaoHandle); - - foreach (GalVertexAttrib Attrib in Attribs) - { - GL.EnableVertexAttribArray(Attrib.Index); - - GL.BindBuffer(BufferTarget.ArrayBuffer, VboHandle); - - bool Unsigned = - Attrib.Type == GalVertexAttribType.Unorm || - Attrib.Type == GalVertexAttribType.Uint || - Attrib.Type == GalVertexAttribType.Uscaled; - - bool Normalize = - Attrib.Type == GalVertexAttribType.Snorm || - Attrib.Type == GalVertexAttribType.Unorm; - - VertexAttribPointerType Type = 0; - - if (Attrib.Type == GalVertexAttribType.Float) - { - Type = VertexAttribPointerType.Float; - } - else - { - Type = AttribTypes[Attrib.Size] + (Unsigned ? 1 : 0); - } - - int Size = AttribElements[Attrib.Size]; - int Offset = Attrib.Offset; - - GL.VertexAttribPointer(Attrib.Index, Size, Type, Normalize, Stride, Offset); - } - } - - public void SetIndexArray(int Size, GalIndexFormat Format) - { - IndexBuffer.Type = OGLEnumConverter.GetDrawElementsType(Format); - - IndexBuffer.Count = Size >> (int)Format; - - IndexBuffer.ElemSizeLog2 = (int)Format; - } - - public void DrawArrays(int First, int PrimCount, GalPrimitiveType PrimType) - { - if (PrimCount == 0) - { - return; - } - - GL.BindVertexArray(VaoHandle); - - GL.DrawArrays(OGLEnumConverter.GetPrimitiveType(PrimType), First, PrimCount); - } - - public void DrawElements(long IboKey, int First, int VertexBase, GalPrimitiveType PrimType) - { - if (!IboCache.TryGetValue(IboKey, out int IboHandle)) - { - return; - } - - PrimitiveType Mode = OGLEnumConverter.GetPrimitiveType(PrimType); - - GL.BindVertexArray(VaoHandle); - - GL.BindBuffer(BufferTarget.ElementArrayBuffer, IboHandle); - - First <<= IndexBuffer.ElemSizeLog2; - - if (VertexBase != 0) - { - IntPtr Indices = new IntPtr(First); - - GL.DrawElementsBaseVertex(Mode, IndexBuffer.Count, IndexBuffer.Type, Indices, VertexBase); - } - else - { - GL.DrawElements(Mode, IndexBuffer.Count, IndexBuffer.Type, First); - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLRenderer.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLRenderer.cs deleted file mode 100644 index ca70d4f666..0000000000 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLRenderer.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using System.Collections.Concurrent; - -namespace Ryujinx.Graphics.Gal.OpenGL -{ - public class OGLRenderer : IGalRenderer - { - public IGalBlend Blend { get; private set; } - - public IGalFrameBuffer FrameBuffer { get; private set; } - - public IGalRasterizer Rasterizer { get; private set; } - - public IGalShader Shader { get; private set; } - - public IGalTexture Texture { get; private set; } - - private ConcurrentQueue ActionsQueue; - - public OGLRenderer() - { - Blend = new OGLBlend(); - - FrameBuffer = new OGLFrameBuffer(); - - Rasterizer = new OGLRasterizer(); - - Shader = new OGLShader(); - - Texture = new OGLTexture(); - - ActionsQueue = new ConcurrentQueue(); - } - - public void QueueAction(Action ActionMthd) - { - ActionsQueue.Enqueue(ActionMthd); - } - - public void RunActions() - { - int Count = ActionsQueue.Count; - - while (Count-- > 0 && ActionsQueue.TryDequeue(out Action RenderAction)) - { - RenderAction(); - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLShader.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLShader.cs deleted file mode 100644 index 3c5c874eaa..0000000000 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLShader.cs +++ /dev/null @@ -1,363 +0,0 @@ -using OpenTK.Graphics.OpenGL; -using Ryujinx.Graphics.Gal.Shader; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; - -namespace Ryujinx.Graphics.Gal.OpenGL -{ - public class OGLShader : IGalShader - { - private class ShaderStage : IDisposable - { - public int Handle { get; private set; } - - public bool IsCompiled { get; private set; } - - public GalShaderType Type { get; private set; } - - public string Code { get; private set; } - - public IEnumerable TextureUsage { get; private set; } - public IEnumerable UniformUsage { get; private set; } - - public ShaderStage( - GalShaderType Type, - string Code, - IEnumerable TextureUsage, - IEnumerable UniformUsage) - { - this.Type = Type; - this.Code = Code; - this.TextureUsage = TextureUsage; - this.UniformUsage = UniformUsage; - } - - public void Compile() - { - if (Handle == 0) - { - Handle = GL.CreateShader(OGLEnumConverter.GetShaderType(Type)); - - CompileAndCheck(Handle, Code); - } - } - - public void Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool Disposing) - { - if (Disposing && Handle != 0) - { - GL.DeleteShader(Handle); - - Handle = 0; - } - } - } - - private struct ShaderProgram - { - public ShaderStage Vertex; - public ShaderStage TessControl; - public ShaderStage TessEvaluation; - public ShaderStage Geometry; - public ShaderStage Fragment; - } - - const int ConstBuffersPerStage = 18; - - private ShaderProgram Current; - - private ConcurrentDictionary Stages; - - private Dictionary Programs; - - public int CurrentProgramHandle { get; private set; } - - private OGLStreamBuffer[][] ConstBuffers; - - public OGLShader() - { - Stages = new ConcurrentDictionary(); - - Programs = new Dictionary(); - - ConstBuffers = new OGLStreamBuffer[5][]; - - for (int i = 0; i < 5; i++) - { - ConstBuffers[i] = new OGLStreamBuffer[ConstBuffersPerStage]; - } - } - - public void Create(IGalMemory Memory, long Key, GalShaderType Type) - { - Stages.GetOrAdd(Key, (Stage) => ShaderStageFactory(Memory, Key, 0, false, Type)); - } - - public void Create(IGalMemory Memory, long VpAPos, long Key, GalShaderType Type) - { - Stages.GetOrAdd(Key, (Stage) => ShaderStageFactory(Memory, VpAPos, Key, true, Type)); - } - - private ShaderStage ShaderStageFactory( - IGalMemory Memory, - long Position, - long PositionB, - bool IsDualVp, - GalShaderType Type) - { - GlslProgram Program; - - GlslDecompiler Decompiler = new GlslDecompiler(); - - if (IsDualVp) - { - Program = Decompiler.Decompile( - Memory, - Position + 0x50, - PositionB + 0x50, - Type); - } - else - { - Program = Decompiler.Decompile(Memory, Position + 0x50, Type); - } - - return new ShaderStage( - Type, - Program.Code, - Program.Textures, - Program.Uniforms); - } - - public IEnumerable GetTextureUsage(long Key) - { - if (Stages.TryGetValue(Key, out ShaderStage Stage)) - { - return Stage.TextureUsage; - } - - return Enumerable.Empty(); - } - - public void SetConstBuffer(long Key, int Cbuf, byte[] Data) - { - if (Stages.TryGetValue(Key, out ShaderStage Stage)) - { - foreach (ShaderDeclInfo DeclInfo in Stage.UniformUsage.Where(x => x.Cbuf == Cbuf)) - { - OGLStreamBuffer Buffer = GetConstBuffer(Stage.Type, Cbuf); - - int Size = Math.Min(Data.Length, Buffer.Size); - - byte[] Destiny = Buffer.Map(Size); - - Array.Copy(Data, Destiny, Size); - - Buffer.Unmap(Size); - } - } - } - - public void EnsureTextureBinding(string UniformName, int Value) - { - BindProgram(); - - int Location = GL.GetUniformLocation(CurrentProgramHandle, UniformName); - - GL.Uniform1(Location, Value); - } - - public void SetFlip(float X, float Y) - { - BindProgram(); - - int Location = GL.GetUniformLocation(CurrentProgramHandle, GlslDecl.FlipUniformName); - - GL.Uniform2(Location, X, Y); - } - - public void Bind(long Key) - { - if (Stages.TryGetValue(Key, out ShaderStage Stage)) - { - Bind(Stage); - } - } - - private void Bind(ShaderStage Stage) - { - switch (Stage.Type) - { - case GalShaderType.Vertex: Current.Vertex = Stage; break; - case GalShaderType.TessControl: Current.TessControl = Stage; break; - case GalShaderType.TessEvaluation: Current.TessEvaluation = Stage; break; - case GalShaderType.Geometry: Current.Geometry = Stage; break; - case GalShaderType.Fragment: Current.Fragment = Stage; break; - } - } - - public void BindProgram() - { - if (Current.Vertex == null || - Current.Fragment == null) - { - return; - } - - if (!Programs.TryGetValue(Current, out int Handle)) - { - Handle = GL.CreateProgram(); - - AttachIfNotNull(Handle, Current.Vertex); - AttachIfNotNull(Handle, Current.TessControl); - AttachIfNotNull(Handle, Current.TessEvaluation); - AttachIfNotNull(Handle, Current.Geometry); - AttachIfNotNull(Handle, Current.Fragment); - - GL.LinkProgram(Handle); - - CheckProgramLink(Handle); - - BindUniformBlocks(Handle); - - Programs.Add(Current, Handle); - } - - GL.UseProgram(Handle); - - BindUniformBuffers(Handle); - - CurrentProgramHandle = Handle; - } - - private void AttachIfNotNull(int ProgramHandle, ShaderStage Stage) - { - if (Stage != null) - { - Stage.Compile(); - - GL.AttachShader(ProgramHandle, Stage.Handle); - } - } - - private void BindUniformBlocks(int ProgramHandle) - { - int FreeBinding = 0; - - int BindUniformBlocksIfNotNull(ShaderStage Stage) - { - if (Stage != null) - { - foreach (ShaderDeclInfo DeclInfo in Stage.UniformUsage) - { - int BlockIndex = GL.GetUniformBlockIndex(ProgramHandle, DeclInfo.Name); - - if (BlockIndex < 0) - { - //It is expected that its found, if it's not then driver might be in a malfunction - throw new InvalidOperationException(); - } - - GL.UniformBlockBinding(ProgramHandle, BlockIndex, FreeBinding); - - FreeBinding++; - } - } - - return FreeBinding; - } - - BindUniformBlocksIfNotNull(Current.Vertex); - BindUniformBlocksIfNotNull(Current.TessControl); - BindUniformBlocksIfNotNull(Current.TessEvaluation); - BindUniformBlocksIfNotNull(Current.Geometry); - BindUniformBlocksIfNotNull(Current.Fragment); - } - - private void BindUniformBuffers(int ProgramHandle) - { - int FreeBinding = 0; - - int BindUniformBuffersIfNotNull(ShaderStage Stage) - { - if (Stage != null) - { - foreach (ShaderDeclInfo DeclInfo in Stage.UniformUsage) - { - OGLStreamBuffer Buffer = GetConstBuffer(Stage.Type, DeclInfo.Cbuf); - - GL.BindBufferBase(BufferRangeTarget.UniformBuffer, FreeBinding, Buffer.Handle); - - FreeBinding++; - } - } - - return FreeBinding; - } - - BindUniformBuffersIfNotNull(Current.Vertex); - BindUniformBuffersIfNotNull(Current.TessControl); - BindUniformBuffersIfNotNull(Current.TessEvaluation); - BindUniformBuffersIfNotNull(Current.Geometry); - BindUniformBuffersIfNotNull(Current.Fragment); - } - - private OGLStreamBuffer GetConstBuffer(GalShaderType StageType, int Cbuf) - { - int StageIndex = (int)StageType; - - OGLStreamBuffer Buffer = ConstBuffers[StageIndex][Cbuf]; - - if (Buffer == null) - { - //Allocate a maximum of 64 KiB - int Size = Math.Min(GL.GetInteger(GetPName.MaxUniformBlockSize), 64 * 1024); - - Buffer = OGLStreamBuffer.Create(BufferTarget.UniformBuffer, Size); - - ConstBuffers[StageIndex][Cbuf] = Buffer; - } - - return Buffer; - } - - public static void CompileAndCheck(int Handle, string Code) - { - GL.ShaderSource(Handle, Code); - GL.CompileShader(Handle); - - CheckCompilation(Handle); - } - - private static void CheckCompilation(int Handle) - { - int Status = 0; - - GL.GetShader(Handle, ShaderParameter.CompileStatus, out Status); - - if (Status == 0) - { - throw new ShaderException(GL.GetShaderInfoLog(Handle)); - } - } - - private static void CheckProgramLink(int Handle) - { - int Status = 0; - - GL.GetProgram(Handle, GetProgramParameterName.LinkStatus, out Status); - - if (Status == 0) - { - throw new ShaderException(GL.GetProgramInfoLog(Handle)); - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLStreamBuffer.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLStreamBuffer.cs deleted file mode 100644 index 329c5b5df7..0000000000 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLStreamBuffer.cs +++ /dev/null @@ -1,113 +0,0 @@ -using System; -using OpenTK.Graphics.OpenGL; - -namespace Ryujinx.Graphics.Gal.OpenGL -{ - abstract class OGLStreamBuffer : IDisposable - { - public int Handle { get; protected set; } - - public int Size { get; protected set; } - - protected BufferTarget Target { get; private set; } - - private bool Mapped = false; - - public OGLStreamBuffer(BufferTarget Target, int MaxSize) - { - Handle = 0; - Mapped = false; - - this.Target = Target; - this.Size = MaxSize; - } - - public static OGLStreamBuffer Create(BufferTarget Target, int MaxSize) - { - //TODO: Query here for ARB_buffer_storage and use when available - return new SubDataBuffer(Target, MaxSize); - } - - public byte[] Map(int Size) - { - if (Handle == 0 || Mapped || Size > this.Size) - { - throw new InvalidOperationException(); - } - - byte[] Memory = InternMap(Size); - - Mapped = true; - - return Memory; - } - - public void Unmap(int UsedSize) - { - if (Handle == 0 || !Mapped) - { - throw new InvalidOperationException(); - } - - InternUnmap(UsedSize); - - Mapped = false; - } - - protected abstract byte[] InternMap(int Size); - - protected abstract void InternUnmap(int UsedSize); - - public void Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool Disposing) - { - if (Disposing && Handle != 0) - { - GL.DeleteBuffer(Handle); - - Handle = 0; - } - } - } - - class SubDataBuffer : OGLStreamBuffer - { - private byte[] Memory; - - public SubDataBuffer(BufferTarget Target, int MaxSize) - : base(Target, MaxSize) - { - Memory = new byte[MaxSize]; - - GL.GenBuffers(1, out int Handle); - - GL.BindBuffer(Target, Handle); - - GL.BufferData(Target, Size, IntPtr.Zero, BufferUsageHint.StreamDraw); - - this.Handle = Handle; - } - - protected override byte[] InternMap(int Size) - { - return Memory; - } - - protected override void InternUnmap(int UsedSize) - { - GL.BindBuffer(Target, Handle); - - unsafe - { - fixed (byte* MemoryPtr = Memory) - { - GL.BufferSubData(Target, IntPtr.Zero, UsedSize, (IntPtr)MemoryPtr); - } - } - } - } -} diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLTexture.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLTexture.cs deleted file mode 100644 index 5caca6ecde..0000000000 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLTexture.cs +++ /dev/null @@ -1,227 +0,0 @@ -using OpenTK.Graphics.OpenGL; -using Ryujinx.Graphics.Gal.Texture; -using System; - -namespace Ryujinx.Graphics.Gal.OpenGL -{ - public class OGLTexture : IGalTexture - { - private class TCE - { - public int Handle; - - public GalTexture Texture; - - public TCE(int Handle, GalTexture Texture) - { - this.Handle = Handle; - this.Texture = Texture; - } - } - - private OGLCachedResource TextureCache; - - public OGLTexture() - { - TextureCache = new OGLCachedResource(DeleteTexture); - } - - public void LockCache() - { - TextureCache.Lock(); - } - - public void UnlockCache() - { - TextureCache.Unlock(); - } - - private static void DeleteTexture(TCE CachedTexture) - { - GL.DeleteTexture(CachedTexture.Handle); - } - - public void Create(long Key, byte[] Data, GalTexture Texture) - { - int Handle = GL.GenTexture(); - - TextureCache.AddOrUpdate(Key, new TCE(Handle, Texture), (uint)Data.Length); - - GL.BindTexture(TextureTarget.Texture2D, Handle); - - const int Level = 0; //TODO: Support mipmap textures. - const int Border = 0; - - if (IsCompressedTextureFormat(Texture.Format)) - { - InternalFormat InternalFmt = OGLEnumConverter.GetCompressedTextureFormat(Texture.Format); - - GL.CompressedTexImage2D( - TextureTarget.Texture2D, - Level, - InternalFmt, - Texture.Width, - Texture.Height, - Border, - Data.Length, - Data); - } - else - { - if (Texture.Format >= GalTextureFormat.Astc2D4x4) - { - int TextureBlockWidth = GetAstcBlockWidth(Texture.Format); - int TextureBlockHeight = GetAstcBlockHeight(Texture.Format); - - Data = ASTCDecoder.DecodeToRGBA8888( - Data, - TextureBlockWidth, - TextureBlockHeight, 1, - Texture.Width, - Texture.Height, 1); - - Texture.Format = GalTextureFormat.A8B8G8R8; - } - - const PixelInternalFormat InternalFmt = PixelInternalFormat.Rgba; - - (PixelFormat Format, PixelType Type) = OGLEnumConverter.GetTextureFormat(Texture.Format); - - GL.TexImage2D( - TextureTarget.Texture2D, - Level, - InternalFmt, - Texture.Width, - Texture.Height, - Border, - Format, - Type, - Data); - } - - int SwizzleR = (int)OGLEnumConverter.GetTextureSwizzle(Texture.XSource); - int SwizzleG = (int)OGLEnumConverter.GetTextureSwizzle(Texture.YSource); - int SwizzleB = (int)OGLEnumConverter.GetTextureSwizzle(Texture.ZSource); - int SwizzleA = (int)OGLEnumConverter.GetTextureSwizzle(Texture.WSource); - - GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleR, SwizzleR); - GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleG, SwizzleG); - GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleB, SwizzleB); - GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleA, SwizzleA); - } - - private static int GetAstcBlockWidth(GalTextureFormat Format) - { - switch (Format) - { - case GalTextureFormat.Astc2D4x4: return 4; - case GalTextureFormat.Astc2D5x5: return 5; - case GalTextureFormat.Astc2D6x6: return 6; - case GalTextureFormat.Astc2D8x8: return 8; - case GalTextureFormat.Astc2D10x10: return 10; - case GalTextureFormat.Astc2D12x12: return 12; - case GalTextureFormat.Astc2D5x4: return 5; - case GalTextureFormat.Astc2D6x5: return 6; - case GalTextureFormat.Astc2D8x6: return 8; - case GalTextureFormat.Astc2D10x8: return 10; - case GalTextureFormat.Astc2D12x10: return 12; - case GalTextureFormat.Astc2D8x5: return 8; - case GalTextureFormat.Astc2D10x5: return 10; - case GalTextureFormat.Astc2D10x6: return 10; - } - - throw new ArgumentException(nameof(Format)); - } - - private static int GetAstcBlockHeight(GalTextureFormat Format) - { - switch (Format) - { - case GalTextureFormat.Astc2D4x4: return 4; - case GalTextureFormat.Astc2D5x5: return 5; - case GalTextureFormat.Astc2D6x6: return 6; - case GalTextureFormat.Astc2D8x8: return 8; - case GalTextureFormat.Astc2D10x10: return 10; - case GalTextureFormat.Astc2D12x12: return 12; - case GalTextureFormat.Astc2D5x4: return 4; - case GalTextureFormat.Astc2D6x5: return 5; - case GalTextureFormat.Astc2D8x6: return 6; - case GalTextureFormat.Astc2D10x8: return 8; - case GalTextureFormat.Astc2D12x10: return 10; - case GalTextureFormat.Astc2D8x5: return 5; - case GalTextureFormat.Astc2D10x5: return 5; - case GalTextureFormat.Astc2D10x6: return 6; - } - - throw new ArgumentException(nameof(Format)); - } - - public bool TryGetCachedTexture(long Key, long DataSize, out GalTexture Texture) - { - if (TextureCache.TryGetSize(Key, out long Size) && Size == DataSize) - { - if (TextureCache.TryGetValue(Key, out TCE CachedTexture)) - { - Texture = CachedTexture.Texture; - - return true; - } - } - - Texture = default(GalTexture); - - return false; - } - - public void Bind(long Key, int Index) - { - if (TextureCache.TryGetValue(Key, out TCE CachedTexture)) - { - GL.ActiveTexture(TextureUnit.Texture0 + Index); - - GL.BindTexture(TextureTarget.Texture2D, CachedTexture.Handle); - } - } - - public void SetSampler(GalTextureSampler Sampler) - { - int WrapS = (int)OGLEnumConverter.GetTextureWrapMode(Sampler.AddressU); - int WrapT = (int)OGLEnumConverter.GetTextureWrapMode(Sampler.AddressV); - - int MinFilter = (int)OGLEnumConverter.GetTextureMinFilter(Sampler.MinFilter, Sampler.MipFilter); - int MagFilter = (int)OGLEnumConverter.GetTextureMagFilter(Sampler.MagFilter); - - GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, WrapS); - GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, WrapT); - - GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, MinFilter); - GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, MagFilter); - - float[] Color = new float[] - { - Sampler.BorderColor.Red, - Sampler.BorderColor.Green, - Sampler.BorderColor.Blue, - Sampler.BorderColor.Alpha - }; - - GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureBorderColor, Color); - } - - private static bool IsCompressedTextureFormat(GalTextureFormat Format) - { - switch (Format) - { - case GalTextureFormat.BC7U: - case GalTextureFormat.BC1: - case GalTextureFormat.BC2: - case GalTextureFormat.BC3: - case GalTextureFormat.BC4: - case GalTextureFormat.BC5: - return true; - } - - return false; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/GlslDecl.cs b/Ryujinx.Graphics/Gal/Shader/GlslDecl.cs deleted file mode 100644 index d3284f9f55..0000000000 --- a/Ryujinx.Graphics/Gal/Shader/GlslDecl.cs +++ /dev/null @@ -1,302 +0,0 @@ -using System.Collections.Generic; - -namespace Ryujinx.Graphics.Gal.Shader -{ - class GlslDecl - { - public const int TessCoordAttrX = 0x2f0; - public const int TessCoordAttrY = 0x2f4; - public const int TessCoordAttrZ = 0x2f8; - public const int InstanceIdAttr = 0x2f8; - public const int VertexIdAttr = 0x2fc; - public const int FaceAttr = 0x3fc; - public const int GlPositionWAttr = 0x7c; - - public const int MaxUboSize = 1024; - - public const int GlPositionVec4Index = 7; - - public const int PositionOutAttrLocation = 15; - - private const int AttrStartIndex = 8; - private const int TexStartIndex = 8; - - public const string PositionOutAttrName = "position"; - - private const string TextureName = "tex"; - private const string UniformName = "c"; - - private const string AttrName = "attr"; - private const string InAttrName = "in_" + AttrName; - private const string OutAttrName = "out_" + AttrName; - - private const string GprName = "gpr"; - private const string PredName = "pred"; - - public const string FragmentOutputName = "FragColor"; - - public const string FlipUniformName = "flip"; - - public const string ProgramName = "program"; - public const string ProgramAName = ProgramName + "_a"; - public const string ProgramBName = ProgramName + "_b"; - - private string[] StagePrefixes = new string[] { "vp", "tcp", "tep", "gp", "fp" }; - - private string StagePrefix; - - private Dictionary m_Textures; - private Dictionary m_Uniforms; - - private Dictionary m_Attributes; - private Dictionary m_InAttributes; - private Dictionary m_OutAttributes; - - private Dictionary m_Gprs; - private Dictionary m_Preds; - - public IReadOnlyDictionary Textures => m_Textures; - public IReadOnlyDictionary Uniforms => m_Uniforms; - - public IReadOnlyDictionary Attributes => m_Attributes; - public IReadOnlyDictionary InAttributes => m_InAttributes; - public IReadOnlyDictionary OutAttributes => m_OutAttributes; - - public IReadOnlyDictionary Gprs => m_Gprs; - public IReadOnlyDictionary Preds => m_Preds; - - public GalShaderType ShaderType { get; private set; } - - private GlslDecl(GalShaderType ShaderType) - { - this.ShaderType = ShaderType; - - m_Uniforms = new Dictionary(); - m_Textures = new Dictionary(); - - m_Attributes = new Dictionary(); - m_InAttributes = new Dictionary(); - m_OutAttributes = new Dictionary(); - - m_Gprs = new Dictionary(); - m_Preds = new Dictionary(); - } - - public GlslDecl(ShaderIrBlock[] Blocks, GalShaderType ShaderType) : this(ShaderType) - { - StagePrefix = StagePrefixes[(int)ShaderType] + "_"; - - if (ShaderType == GalShaderType.Fragment) - { - m_Gprs.Add(0, new ShaderDeclInfo(FragmentOutputName, 0, 0, 4)); - } - - foreach (ShaderIrBlock Block in Blocks) - { - foreach (ShaderIrNode Node in Block.GetNodes()) - { - Traverse(null, Node); - } - } - } - - public static GlslDecl Merge(GlslDecl VpA, GlslDecl VpB) - { - GlslDecl Combined = new GlslDecl(GalShaderType.Vertex); - - Merge(Combined.m_Textures, VpA.m_Textures, VpB.m_Textures); - Merge(Combined.m_Uniforms, VpA.m_Uniforms, VpB.m_Uniforms); - - Merge(Combined.m_Attributes, VpA.m_Attributes, VpB.m_Attributes); - Merge(Combined.m_OutAttributes, VpA.m_OutAttributes, VpB.m_OutAttributes); - - Merge(Combined.m_Gprs, VpA.m_Gprs, VpB.m_Gprs); - Merge(Combined.m_Preds, VpA.m_Preds, VpB.m_Preds); - - //Merge input attributes. - foreach (KeyValuePair KV in VpA.m_InAttributes) - { - Combined.m_InAttributes.TryAdd(KV.Key, KV.Value); - } - - foreach (KeyValuePair KV in VpB.m_InAttributes) - { - //If Vertex Program A already writes to this attribute, - //then we don't need to add it as an input attribute since - //Vertex Program A will already have written to it anyway, - //and there's no guarantee that there is an input attribute - //for this slot. - if (!VpA.m_OutAttributes.ContainsKey(KV.Key)) - { - Combined.m_InAttributes.TryAdd(KV.Key, KV.Value); - } - } - - return Combined; - } - - private static void Merge( - Dictionary C, - Dictionary A, - Dictionary B) - { - foreach (KeyValuePair KV in A) - { - C.TryAdd(KV.Key, KV.Value); - } - - foreach (KeyValuePair KV in B) - { - C.TryAdd(KV.Key, KV.Value); - } - } - - private void Traverse(ShaderIrNode Parent, ShaderIrNode Node) - { - switch (Node) - { - case ShaderIrAsg Asg: - { - Traverse(Asg, Asg.Dst); - Traverse(Asg, Asg.Src); - - break; - } - - case ShaderIrCond Cond: - { - Traverse(Cond, Cond.Pred); - Traverse(Cond, Cond.Child); - - break; - } - - case ShaderIrOp Op: - { - Traverse(Op, Op.OperandA); - Traverse(Op, Op.OperandB); - Traverse(Op, Op.OperandC); - - if (Op.Inst == ShaderIrInst.Texq || - Op.Inst == ShaderIrInst.Texs || - Op.Inst == ShaderIrInst.Txlf) - { - int Handle = ((ShaderIrOperImm)Op.OperandC).Value; - - int Index = Handle - TexStartIndex; - - string Name = StagePrefix + TextureName + Index; - - m_Textures.TryAdd(Handle, new ShaderDeclInfo(Name, Handle)); - } - break; - } - - case ShaderIrOperCbuf Cbuf: - { - if (!m_Uniforms.ContainsKey(Cbuf.Index)) - { - string Name = StagePrefix + UniformName + Cbuf.Index; - - ShaderDeclInfo DeclInfo = new ShaderDeclInfo(Name, Cbuf.Pos, Cbuf.Index); - - m_Uniforms.Add(Cbuf.Index, DeclInfo); - } - break; - } - - case ShaderIrOperAbuf Abuf: - { - //This is a built-in input variable. - if (Abuf.Offs == VertexIdAttr || - Abuf.Offs == InstanceIdAttr || - Abuf.Offs == FaceAttr) - { - break; - } - - int Index = Abuf.Offs >> 4; - int Elem = (Abuf.Offs >> 2) & 3; - - int GlslIndex = Index - AttrStartIndex; - - if (GlslIndex < 0) - { - return; - } - - ShaderDeclInfo DeclInfo; - - if (Parent is ShaderIrAsg Asg && Asg.Dst == Node) - { - if (!m_OutAttributes.TryGetValue(Index, out DeclInfo)) - { - DeclInfo = new ShaderDeclInfo(OutAttrName + GlslIndex, GlslIndex); - - m_OutAttributes.Add(Index, DeclInfo); - } - } - else - { - if (!m_InAttributes.TryGetValue(Index, out DeclInfo)) - { - DeclInfo = new ShaderDeclInfo(InAttrName + GlslIndex, GlslIndex); - - m_InAttributes.Add(Index, DeclInfo); - } - } - - DeclInfo.Enlarge(Elem + 1); - - if (!m_Attributes.ContainsKey(Index)) - { - DeclInfo = new ShaderDeclInfo(AttrName + GlslIndex, GlslIndex, 0, 4); - - m_Attributes.Add(Index, DeclInfo); - } - break; - } - - case ShaderIrOperGpr Gpr: - { - if (!Gpr.IsConst && !HasName(m_Gprs, Gpr.Index)) - { - string Name = GprName + Gpr.Index; - - m_Gprs.TryAdd(Gpr.Index, new ShaderDeclInfo(Name, Gpr.Index)); - } - break; - } - - case ShaderIrOperPred Pred: - { - if (!Pred.IsConst && !HasName(m_Preds, Pred.Index)) - { - string Name = PredName + Pred.Index; - - m_Preds.TryAdd(Pred.Index, new ShaderDeclInfo(Name, Pred.Index)); - } - break; - } - } - } - - private bool HasName(Dictionary Decls, int Index) - { - //This is used to check if the dictionary already contains - //a entry for a vector at a given index position. - //Used to enable turning gprs into vectors. - int VecIndex = Index & ~3; - - if (Decls.TryGetValue(VecIndex, out ShaderDeclInfo DeclInfo)) - { - if (DeclInfo.Size > 1 && Index < VecIndex + DeclInfo.Size) - { - return true; - } - } - - return Decls.ContainsKey(Index); - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs b/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs deleted file mode 100644 index f3075a504e..0000000000 --- a/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs +++ /dev/null @@ -1,1128 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Text; - -namespace Ryujinx.Graphics.Gal.Shader -{ - public class GlslDecompiler - { - private delegate string GetInstExpr(ShaderIrOp Op); - - private Dictionary InstsExpr; - - private enum OperType - { - Bool, - F32, - I32 - } - - private const string IdentationStr = " "; - - private static string[] ElemTypes = new string[] { "float", "vec2", "vec3", "vec4" }; - - private GlslDecl Decl; - - private ShaderIrBlock[] Blocks, BlocksB; - - private StringBuilder SB; - - public GlslDecompiler() - { - InstsExpr = new Dictionary() - { - { ShaderIrInst.Abs, GetAbsExpr }, - { ShaderIrInst.Add, GetAddExpr }, - { ShaderIrInst.And, GetAndExpr }, - { ShaderIrInst.Asr, GetAsrExpr }, - { ShaderIrInst.Band, GetBandExpr }, - { ShaderIrInst.Bnot, GetBnotExpr }, - { ShaderIrInst.Bor, GetBorExpr }, - { ShaderIrInst.Bxor, GetBxorExpr }, - { ShaderIrInst.Ceil, GetCeilExpr }, - { ShaderIrInst.Ceq, GetCeqExpr }, - { ShaderIrInst.Cge, GetCgeExpr }, - { ShaderIrInst.Cgt, GetCgtExpr }, - { ShaderIrInst.Clamps, GetClampsExpr }, - { ShaderIrInst.Clampu, GetClampuExpr }, - { ShaderIrInst.Cle, GetCleExpr }, - { ShaderIrInst.Clt, GetCltExpr }, - { ShaderIrInst.Cne, GetCneExpr }, - { ShaderIrInst.Exit, GetExitExpr }, - { ShaderIrInst.Fabs, GetAbsExpr }, - { ShaderIrInst.Fadd, GetAddExpr }, - { ShaderIrInst.Fceq, GetCeqExpr }, - { ShaderIrInst.Fcequ, GetCequExpr }, - { ShaderIrInst.Fcge, GetCgeExpr }, - { ShaderIrInst.Fcgeu, GetCgeuExpr }, - { ShaderIrInst.Fcgt, GetCgtExpr }, - { ShaderIrInst.Fcgtu, GetCgtuExpr }, - { ShaderIrInst.Fclamp, GetFclampExpr }, - { ShaderIrInst.Fcle, GetCleExpr }, - { ShaderIrInst.Fcleu, GetCleuExpr }, - { ShaderIrInst.Fclt, GetCltExpr }, - { ShaderIrInst.Fcltu, GetCltuExpr }, - { ShaderIrInst.Fcnan, GetCnanExpr }, - { ShaderIrInst.Fcne, GetCneExpr }, - { ShaderIrInst.Fcneu, GetCneuExpr }, - { ShaderIrInst.Fcnum, GetCnumExpr }, - { ShaderIrInst.Fcos, GetFcosExpr }, - { ShaderIrInst.Fex2, GetFex2Expr }, - { ShaderIrInst.Ffma, GetFfmaExpr }, - { ShaderIrInst.Flg2, GetFlg2Expr }, - { ShaderIrInst.Floor, GetFloorExpr }, - { ShaderIrInst.Fmax, GetMaxExpr }, - { ShaderIrInst.Fmin, GetMinExpr }, - { ShaderIrInst.Fmul, GetMulExpr }, - { ShaderIrInst.Fneg, GetNegExpr }, - { ShaderIrInst.Frcp, GetFrcpExpr }, - { ShaderIrInst.Frsq, GetFrsqExpr }, - { ShaderIrInst.Fsin, GetFsinExpr }, - { ShaderIrInst.Fsqrt, GetFsqrtExpr }, - { ShaderIrInst.Ftos, GetFtosExpr }, - { ShaderIrInst.Ftou, GetFtouExpr }, - { ShaderIrInst.Ipa, GetIpaExpr }, - { ShaderIrInst.Kil, GetKilExpr }, - { ShaderIrInst.Lsl, GetLslExpr }, - { ShaderIrInst.Lsr, GetLsrExpr }, - { ShaderIrInst.Max, GetMaxExpr }, - { ShaderIrInst.Min, GetMinExpr }, - { ShaderIrInst.Mul, GetMulExpr }, - { ShaderIrInst.Neg, GetNegExpr }, - { ShaderIrInst.Not, GetNotExpr }, - { ShaderIrInst.Or, GetOrExpr }, - { ShaderIrInst.Stof, GetStofExpr }, - { ShaderIrInst.Sub, GetSubExpr }, - { ShaderIrInst.Texq, GetTexqExpr }, - { ShaderIrInst.Texs, GetTexsExpr }, - { ShaderIrInst.Trunc, GetTruncExpr }, - { ShaderIrInst.Txlf, GetTxlfExpr }, - { ShaderIrInst.Utof, GetUtofExpr }, - { ShaderIrInst.Xor, GetXorExpr } - }; - } - - public GlslProgram Decompile( - IGalMemory Memory, - long VpAPosition, - long VpBPosition, - GalShaderType ShaderType) - { - Blocks = ShaderDecoder.Decode(Memory, VpAPosition); - BlocksB = ShaderDecoder.Decode(Memory, VpBPosition); - - GlslDecl DeclVpA = new GlslDecl(Blocks, ShaderType); - GlslDecl DeclVpB = new GlslDecl(BlocksB, ShaderType); - - Decl = GlslDecl.Merge(DeclVpA, DeclVpB); - - return Decompile(); - } - - public GlslProgram Decompile(IGalMemory Memory, long Position, GalShaderType ShaderType) - { - Blocks = ShaderDecoder.Decode(Memory, Position); - BlocksB = null; - - Decl = new GlslDecl(Blocks, ShaderType); - - return Decompile(); - } - - private GlslProgram Decompile() - { - SB = new StringBuilder(); - - SB.AppendLine("#version 410 core"); - - PrintDeclTextures(); - PrintDeclUniforms(); - PrintDeclAttributes(); - PrintDeclInAttributes(); - PrintDeclOutAttributes(); - PrintDeclGprs(); - PrintDeclPreds(); - - if (BlocksB != null) - { - PrintBlockScope(Blocks[0], null, null, "void " + GlslDecl.ProgramAName + "()", IdentationStr); - - SB.AppendLine(); - - PrintBlockScope(BlocksB[0], null, null, "void " + GlslDecl.ProgramBName + "()", IdentationStr); - } - else - { - PrintBlockScope(Blocks[0], null, null, "void " + GlslDecl.ProgramName + "()", IdentationStr); - } - - SB.AppendLine(); - - PrintMain(); - - string GlslCode = SB.ToString(); - - return new GlslProgram( - GlslCode, - Decl.Textures.Values, - Decl.Uniforms.Values); - } - - private void PrintDeclTextures() - { - PrintDecls(Decl.Textures, "uniform sampler2D"); - } - - private void PrintDeclUniforms() - { - if (Decl.ShaderType == GalShaderType.Vertex) - { - SB.AppendLine("uniform vec2 " + GlslDecl.FlipUniformName + ";"); - } - - SB.AppendLine(); - - foreach (ShaderDeclInfo DeclInfo in Decl.Uniforms.Values.OrderBy(DeclKeySelector)) - { - SB.AppendLine($"layout (std140) uniform {DeclInfo.Name} {{"); - - SB.AppendLine($"{IdentationStr}vec4 {DeclInfo.Name}_data[{GlslDecl.MaxUboSize}];"); - - SB.AppendLine("};"); - } - - if (Decl.Uniforms.Count > 0) - { - SB.AppendLine(); - } - } - - private void PrintDeclAttributes() - { - PrintDecls(Decl.Attributes); - } - - private void PrintDeclInAttributes() - { - if (Decl.ShaderType == GalShaderType.Fragment) - { - SB.AppendLine("layout (location = " + GlslDecl.PositionOutAttrLocation + ") in vec4 " + GlslDecl.PositionOutAttrName + ";"); - } - - PrintDeclAttributes(Decl.InAttributes.Values, "in"); - } - - private void PrintDeclOutAttributes() - { - if (Decl.ShaderType == GalShaderType.Vertex) - { - SB.AppendLine("layout (location = " + GlslDecl.PositionOutAttrLocation + ") out vec4 " + GlslDecl.PositionOutAttrName + ";"); - } - - PrintDeclAttributes(Decl.OutAttributes.Values, "out"); - } - - private void PrintDeclAttributes(IEnumerable Decls, string InOut) - { - int Count = 0; - - foreach (ShaderDeclInfo DeclInfo in Decls.OrderBy(DeclKeySelector)) - { - if (DeclInfo.Index >= 0) - { - SB.AppendLine("layout (location = " + DeclInfo.Index + ") " + InOut + " " + GetDecl(DeclInfo) + ";"); - - Count++; - } - } - - if (Count > 0) - { - SB.AppendLine(); - } - } - - private void PrintDeclGprs() - { - PrintDecls(Decl.Gprs); - } - - private void PrintDeclPreds() - { - PrintDecls(Decl.Preds, "bool"); - } - - private void PrintDecls(IReadOnlyDictionary Dict, string CustomType = null) - { - foreach (ShaderDeclInfo DeclInfo in Dict.Values.OrderBy(DeclKeySelector)) - { - string Name; - - if (CustomType != null) - { - Name = CustomType + " " + DeclInfo.Name + ";"; - } - else if (DeclInfo.Name == GlslDecl.FragmentOutputName) - { - Name = "layout (location = 0) out " + GetDecl(DeclInfo) + ";" + Environment.NewLine; - } - else - { - Name = GetDecl(DeclInfo) + ";"; - } - - SB.AppendLine(Name); - } - - if (Dict.Count > 0) - { - SB.AppendLine(); - } - } - - private int DeclKeySelector(ShaderDeclInfo DeclInfo) - { - return DeclInfo.Cbuf << 24 | DeclInfo.Index; - } - - private string GetDecl(ShaderDeclInfo DeclInfo) - { - return ElemTypes[DeclInfo.Size - 1] + " " + DeclInfo.Name; - } - - private void PrintMain() - { - SB.AppendLine("void main() {"); - - foreach (KeyValuePair KV in Decl.InAttributes) - { - if (!Decl.Attributes.TryGetValue(KV.Key, out ShaderDeclInfo Attr)) - { - continue; - } - - ShaderDeclInfo DeclInfo = KV.Value; - - string Swizzle = ".xyzw".Substring(0, DeclInfo.Size + 1); - - SB.AppendLine(IdentationStr + Attr.Name + Swizzle + " = " + DeclInfo.Name + ";"); - } - - if (BlocksB != null) - { - SB.AppendLine(IdentationStr + GlslDecl.ProgramAName + "();"); - SB.AppendLine(IdentationStr + GlslDecl.ProgramBName + "();"); - } - else - { - SB.AppendLine(IdentationStr + GlslDecl.ProgramName + "();"); - } - - foreach (KeyValuePair KV in Decl.OutAttributes) - { - if (!Decl.Attributes.TryGetValue(KV.Key, out ShaderDeclInfo Attr)) - { - continue; - } - - ShaderDeclInfo DeclInfo = KV.Value; - - string Swizzle = ".xyzw".Substring(0, DeclInfo.Size + 1); - - SB.AppendLine(IdentationStr + DeclInfo.Name + " = " + Attr.Name + Swizzle + ";"); - } - - if (Decl.ShaderType == GalShaderType.Vertex) - { - SB.AppendLine(IdentationStr + "gl_Position.xy *= " + GlslDecl.FlipUniformName + ";"); - - SB.AppendLine(IdentationStr + GlslDecl.PositionOutAttrName + " = gl_Position;"); - SB.AppendLine(IdentationStr + GlslDecl.PositionOutAttrName + ".w = 1;"); - } - - SB.AppendLine("}"); - } - - private void PrintBlockScope( - ShaderIrBlock Block, - ShaderIrBlock EndBlock, - ShaderIrBlock LoopBlock, - string ScopeName, - string Identation, - bool IsDoWhile = false) - { - string UpIdent = Identation.Substring(0, Identation.Length - IdentationStr.Length); - - if (IsDoWhile) - { - SB.AppendLine(UpIdent + "do {"); - } - else - { - SB.AppendLine(UpIdent + ScopeName + " {"); - } - - while (Block != null && Block != EndBlock) - { - ShaderIrNode[] Nodes = Block.GetNodes(); - - Block = PrintNodes(Block, EndBlock, LoopBlock, Identation, Nodes); - } - - if (IsDoWhile) - { - SB.AppendLine(UpIdent + "} " + ScopeName + ";"); - } - else - { - SB.AppendLine(UpIdent + "}"); - } - } - - private ShaderIrBlock PrintNodes( - ShaderIrBlock Block, - ShaderIrBlock EndBlock, - ShaderIrBlock LoopBlock, - string Identation, - params ShaderIrNode[] Nodes) - { - /* - * Notes about control flow and if-else/loop generation: - * The code assumes that the program has sane control flow, - * that is, there's no jumps to a location after another jump or - * jump target (except for the end of an if-else block), and backwards - * jumps to a location before the last loop dominator. - * Such cases needs to be transformed on a step before the GLSL code - * generation to ensure that we have sane graphs to work with. - * TODO: Such transformation is not yet implemented. - */ - string NewIdent = Identation + IdentationStr; - - ShaderIrBlock LoopTail = GetLoopTailBlock(Block); - - if (LoopTail != null && LoopBlock != Block) - { - //Shoock! kuma shock! We have a loop here! - //The entire sequence needs to be inside a do-while block. - ShaderIrBlock LoopEnd = GetDownBlock(LoopTail); - - PrintBlockScope(Block, LoopEnd, Block, "while (false)", NewIdent, IsDoWhile: true); - - return LoopEnd; - } - - foreach (ShaderIrNode Node in Nodes) - { - if (Node is ShaderIrCond Cond) - { - string IfExpr = GetSrcExpr(Cond.Pred, true); - - if (Cond.Not) - { - IfExpr = "!(" + IfExpr + ")"; - } - - if (Cond.Child is ShaderIrOp Op && Op.Inst == ShaderIrInst.Bra) - { - //Branch is a loop branch and would result in infinite recursion. - if (Block.Branch.Position <= Block.Position) - { - SB.AppendLine(Identation + "if (" + IfExpr + ") {"); - - SB.AppendLine(Identation + IdentationStr + "continue;"); - - SB.AppendLine(Identation + "}"); - - continue; - } - - string SubScopeName = "if (!" + IfExpr + ")"; - - PrintBlockScope(Block.Next, Block.Branch, LoopBlock, SubScopeName, NewIdent); - - ShaderIrBlock IfElseEnd = GetUpBlock(Block.Branch).Branch; - - if (IfElseEnd?.Position > Block.Branch.Position) - { - PrintBlockScope(Block.Branch, IfElseEnd, LoopBlock, "else", NewIdent); - - return IfElseEnd; - } - - return Block.Branch; - } - else - { - SB.AppendLine(Identation + "if (" + IfExpr + ") {"); - - PrintNodes(Block, EndBlock, LoopBlock, NewIdent, Cond.Child); - - SB.AppendLine(Identation + "}"); - } - } - else if (Node is ShaderIrAsg Asg) - { - if (IsValidOutOper(Asg.Dst)) - { - string Expr = GetSrcExpr(Asg.Src, true); - - Expr = GetExprWithCast(Asg.Dst, Asg.Src, Expr); - - SB.AppendLine(Identation + GetDstOperName(Asg.Dst) + " = " + Expr + ";"); - } - } - else if (Node is ShaderIrOp Op) - { - if (Op.Inst == ShaderIrInst.Bra) - { - if (Block.Branch.Position <= Block.Position) - { - SB.AppendLine(Identation + "continue;"); - } - - continue; - } - - SB.AppendLine(Identation + GetSrcExpr(Op, true) + ";"); - } - else if (Node is ShaderIrCmnt Cmnt) - { - SB.AppendLine(Identation + "// " + Cmnt.Comment); - } - else - { - throw new InvalidOperationException(); - } - } - - return Block.Next; - } - - private ShaderIrBlock GetUpBlock(ShaderIrBlock Block) - { - return Blocks.FirstOrDefault(x => x.EndPosition == Block.Position); - } - - private ShaderIrBlock GetDownBlock(ShaderIrBlock Block) - { - return Blocks.FirstOrDefault(x => x.Position == Block.EndPosition); - } - - private ShaderIrBlock GetLoopTailBlock(ShaderIrBlock LoopHead) - { - ShaderIrBlock Tail = null; - - foreach (ShaderIrBlock Block in LoopHead.Sources) - { - if (Block.Position >= LoopHead.Position) - { - if (Tail == null || Tail.Position < Block.Position) - { - Tail = Block; - } - } - } - - return Tail; - } - - private bool IsValidOutOper(ShaderIrNode Node) - { - if (Node is ShaderIrOperGpr Gpr && Gpr.IsConst) - { - return false; - } - else if (Node is ShaderIrOperPred Pred && Pred.IsConst) - { - return false; - } - - return true; - } - - private string GetDstOperName(ShaderIrNode Node) - { - if (Node is ShaderIrOperAbuf Abuf) - { - return GetOutAbufName(Abuf); - } - else if (Node is ShaderIrOperGpr Gpr) - { - return GetName(Gpr); - } - else if (Node is ShaderIrOperPred Pred) - { - return GetName(Pred); - } - - throw new ArgumentException(nameof(Node)); - } - - private string GetSrcExpr(ShaderIrNode Node, bool Entry = false) - { - switch (Node) - { - case ShaderIrOperAbuf Abuf: return GetName (Abuf); - case ShaderIrOperCbuf Cbuf: return GetName (Cbuf); - case ShaderIrOperGpr Gpr: return GetName (Gpr); - case ShaderIrOperImm Imm: return GetValue(Imm); - case ShaderIrOperImmf Immf: return GetValue(Immf); - case ShaderIrOperPred Pred: return GetName (Pred); - - case ShaderIrOp Op: - string Expr; - - if (InstsExpr.TryGetValue(Op.Inst, out GetInstExpr GetExpr)) - { - Expr = GetExpr(Op); - } - else - { - throw new NotImplementedException(Op.Inst.ToString()); - } - - if (!Entry && NeedsParentheses(Op)) - { - Expr = "(" + Expr + ")"; - } - - return Expr; - - default: throw new ArgumentException(nameof(Node)); - } - } - - private static bool NeedsParentheses(ShaderIrOp Op) - { - switch (Op.Inst) - { - case ShaderIrInst.Frcp: - return true; - - case ShaderIrInst.Ipa: - case ShaderIrInst.Texq: - case ShaderIrInst.Texs: - case ShaderIrInst.Txlf: - return false; - } - - return Op.OperandB != null || - Op.OperandC != null; - } - - private string GetName(ShaderIrOperCbuf Cbuf) - { - if (!Decl.Uniforms.TryGetValue(Cbuf.Index, out ShaderDeclInfo DeclInfo)) - { - throw new InvalidOperationException(); - } - - if (Cbuf.Offs != null) - { - string Offset = "floatBitsToInt(" + GetSrcExpr(Cbuf.Offs) + ")"; - - string Index = "(" + Cbuf.Pos * 4 + " + " + Offset + ")"; - - return $"{DeclInfo.Name}_data[{Index} / 16][({Index} / 4) % 4]"; - } - else - { - return $"{DeclInfo.Name}_data[{Cbuf.Pos / 4}][{Cbuf.Pos % 4}]"; - } - } - - private string GetOutAbufName(ShaderIrOperAbuf Abuf) - { - return GetAttrTempName(Abuf); - } - - private string GetName(ShaderIrOperAbuf Abuf) - { - //Handle special scalar read-only attributes here. - if (Decl.ShaderType == GalShaderType.Vertex) - { - switch (Abuf.Offs) - { - case GlslDecl.VertexIdAttr: return "gl_VertexID"; - case GlslDecl.InstanceIdAttr: return "gl_InstanceID"; - } - } - else if (Decl.ShaderType == GalShaderType.TessEvaluation) - { - switch (Abuf.Offs) - { - case GlslDecl.TessCoordAttrX: return "gl_TessCoord.x"; - case GlslDecl.TessCoordAttrY: return "gl_TessCoord.y"; - case GlslDecl.TessCoordAttrZ: return "gl_TessCoord.z"; - } - } - else if (Decl.ShaderType == GalShaderType.Fragment) - { - switch (Abuf.Offs) - { - //Note: It's a guess that Maxwell's face is 1 when gl_FrontFacing == true - case GlslDecl.FaceAttr: return "(gl_FrontFacing ? 1 : 0)"; - } - } - - return GetAttrTempName(Abuf); - } - - private string GetAttrTempName(ShaderIrOperAbuf Abuf) - { - int Index = Abuf.Offs >> 4; - int Elem = (Abuf.Offs >> 2) & 3; - - string Swizzle = "." + GetAttrSwizzle(Elem); - - if (!Decl.Attributes.TryGetValue(Index, out ShaderDeclInfo DeclInfo)) - { - //Handle special vec4 attributes here - //(for example, index 7 is aways gl_Position). - if (Index == GlslDecl.GlPositionVec4Index) - { - string Name = - Decl.ShaderType != GalShaderType.Vertex && - Decl.ShaderType != GalShaderType.Geometry ? GlslDecl.PositionOutAttrName : "gl_Position"; - - return Name + Swizzle; - } - - throw new InvalidOperationException(); - } - - return DeclInfo.Name + Swizzle; - } - - private string GetName(ShaderIrOperGpr Gpr) - { - return Gpr.IsConst ? "0" : GetNameWithSwizzle(Decl.Gprs, Gpr.Index); - } - - private string GetValue(ShaderIrOperImm Imm) - { - //Only use hex is the value is too big and would likely be hard to read as int. - if (Imm.Value > 0xfff || - Imm.Value < -0xfff) - { - return "0x" + Imm.Value.ToString("x8", CultureInfo.InvariantCulture); - } - else - { - return Imm.Value.ToString(CultureInfo.InvariantCulture); - } - } - - private string GetValue(ShaderIrOperImmf Immf) - { - return Immf.Value.ToString(CultureInfo.InvariantCulture); - } - - private string GetName(ShaderIrOperPred Pred) - { - return Pred.IsConst ? "true" : GetNameWithSwizzle(Decl.Preds, Pred.Index); - } - - private string GetNameWithSwizzle(IReadOnlyDictionary Dict, int Index) - { - int VecIndex = Index >> 2; - - if (Dict.TryGetValue(VecIndex, out ShaderDeclInfo DeclInfo)) - { - if (DeclInfo.Size > 1 && Index < VecIndex + DeclInfo.Size) - { - return DeclInfo.Name + "." + GetAttrSwizzle(Index & 3); - } - } - - if (!Dict.TryGetValue(Index, out DeclInfo)) - { - throw new InvalidOperationException(); - } - - return DeclInfo.Name; - } - - private string GetAttrSwizzle(int Elem) - { - return "xyzw".Substring(Elem, 1); - } - - private string GetAbsExpr(ShaderIrOp Op) => GetUnaryCall(Op, "abs"); - - private string GetAddExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "+"); - - private string GetAndExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "&"); - - private string GetAsrExpr(ShaderIrOp Op) => GetBinaryExpr(Op, ">>"); - - private string GetBandExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "&&"); - - private string GetBnotExpr(ShaderIrOp Op) => GetUnaryExpr(Op, "!"); - - private string GetBorExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "||"); - - private string GetBxorExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "^^"); - - private string GetCeilExpr(ShaderIrOp Op) => GetUnaryCall(Op, "ceil"); - - private string GetClampsExpr(ShaderIrOp Op) - { - return "clamp(" + GetOperExpr(Op, Op.OperandA) + ", " + - GetOperExpr(Op, Op.OperandB) + ", " + - GetOperExpr(Op, Op.OperandC) + ")"; - } - - private string GetClampuExpr(ShaderIrOp Op) - { - return "int(clamp(uint(" + GetOperExpr(Op, Op.OperandA) + "), " + - "uint(" + GetOperExpr(Op, Op.OperandB) + "), " + - "uint(" + GetOperExpr(Op, Op.OperandC) + ")))"; - } - - private string GetCeqExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "=="); - - private string GetCequExpr(ShaderIrOp Op) => GetBinaryExprWithNaN(Op, "=="); - - private string GetCgeExpr(ShaderIrOp Op) => GetBinaryExpr(Op, ">="); - - private string GetCgeuExpr(ShaderIrOp Op) => GetBinaryExprWithNaN(Op, ">="); - - private string GetCgtExpr(ShaderIrOp Op) => GetBinaryExpr(Op, ">"); - - private string GetCgtuExpr(ShaderIrOp Op) => GetBinaryExprWithNaN(Op, ">"); - - private string GetCleExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "<="); - - private string GetCleuExpr(ShaderIrOp Op) => GetBinaryExprWithNaN(Op, "<="); - - private string GetCltExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "<"); - - private string GetCltuExpr(ShaderIrOp Op) => GetBinaryExprWithNaN(Op, "<"); - - private string GetCnanExpr(ShaderIrOp Op) => GetUnaryCall(Op, "isnan"); - - private string GetCneExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "!="); - - private string GetCneuExpr(ShaderIrOp Op) => GetBinaryExprWithNaN(Op, "!="); - - private string GetCnumExpr(ShaderIrOp Op) => GetUnaryCall(Op, "!isnan"); - - private string GetExitExpr(ShaderIrOp Op) => "return"; - - private string GetFcosExpr(ShaderIrOp Op) => GetUnaryCall(Op, "cos"); - - private string GetFex2Expr(ShaderIrOp Op) => GetUnaryCall(Op, "exp2"); - - private string GetFfmaExpr(ShaderIrOp Op) => GetTernaryExpr(Op, "*", "+"); - - private string GetFclampExpr(ShaderIrOp Op) => GetTernaryCall(Op, "clamp"); - - private string GetFlg2Expr(ShaderIrOp Op) => GetUnaryCall(Op, "log2"); - - private string GetFloorExpr(ShaderIrOp Op) => GetUnaryCall(Op, "floor"); - - private string GetFrcpExpr(ShaderIrOp Op) => GetUnaryExpr(Op, "1 / "); - - private string GetFrsqExpr(ShaderIrOp Op) => GetUnaryCall(Op, "inversesqrt"); - - private string GetFsinExpr(ShaderIrOp Op) => GetUnaryCall(Op, "sin"); - - private string GetFsqrtExpr(ShaderIrOp Op) => GetUnaryCall(Op, "sqrt"); - - private string GetFtosExpr(ShaderIrOp Op) - { - return "int(" + GetOperExpr(Op, Op.OperandA) + ")"; - } - - private string GetFtouExpr(ShaderIrOp Op) - { - return "int(uint(" + GetOperExpr(Op, Op.OperandA) + "))"; - } - - private string GetIpaExpr(ShaderIrOp Op) => GetSrcExpr(Op.OperandA); - - private string GetKilExpr(ShaderIrOp Op) => "discard"; - - private string GetLslExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "<<"); - private string GetLsrExpr(ShaderIrOp Op) - { - return "int(uint(" + GetOperExpr(Op, Op.OperandA) + ") >> " + - GetOperExpr(Op, Op.OperandB) + ")"; - } - - private string GetMaxExpr(ShaderIrOp Op) => GetBinaryCall(Op, "max"); - private string GetMinExpr(ShaderIrOp Op) => GetBinaryCall(Op, "min"); - - private string GetMulExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "*"); - - private string GetNegExpr(ShaderIrOp Op) => GetUnaryExpr(Op, "-"); - - private string GetNotExpr(ShaderIrOp Op) => GetUnaryExpr(Op, "~"); - - private string GetOrExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "|"); - - private string GetStofExpr(ShaderIrOp Op) - { - return "float(" + GetOperExpr(Op, Op.OperandA) + ")"; - } - - private string GetSubExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "-"); - - private string GetTexqExpr(ShaderIrOp Op) - { - ShaderIrMetaTexq Meta = (ShaderIrMetaTexq)Op.MetaData; - - string Ch = "xyzw".Substring(Meta.Elem, 1); - - if (Meta.Info == ShaderTexqInfo.Dimension) - { - string Sampler = GetTexSamplerName(Op); - - string Lod = GetOperExpr(Op, Op.OperandA); //??? - - return "textureSize(" + Sampler + ", " + Lod + ")." + Ch; - } - else - { - throw new NotImplementedException(Meta.Info.ToString()); - } - } - - private string GetTexsExpr(ShaderIrOp Op) - { - ShaderIrMetaTex Meta = (ShaderIrMetaTex)Op.MetaData; - - string Sampler = GetTexSamplerName(Op); - - string Coords = GetTexSamplerCoords(Op); - - string Ch = "rgba".Substring(Meta.Elem, 1); - - return "texture(" + Sampler + ", " + Coords + ")." + Ch; - } - - private string GetTxlfExpr(ShaderIrOp Op) - { - ShaderIrMetaTex Meta = (ShaderIrMetaTex)Op.MetaData; - - string Sampler = GetTexSamplerName(Op); - - string Coords = GetITexSamplerCoords(Op); - - string Ch = "rgba".Substring(Meta.Elem, 1); - - return "texelFetch(" + Sampler + ", " + Coords + ", 0)." + Ch; - } - - private string GetTruncExpr(ShaderIrOp Op) => GetUnaryCall(Op, "trunc"); - - private string GetUtofExpr(ShaderIrOp Op) - { - return "float(uint(" + GetOperExpr(Op, Op.OperandA) + "))"; - } - - private string GetXorExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "^"); - - private string GetUnaryCall(ShaderIrOp Op, string FuncName) - { - return FuncName + "(" + GetOperExpr(Op, Op.OperandA) + ")"; - } - - private string GetBinaryCall(ShaderIrOp Op, string FuncName) - { - return FuncName + "(" + GetOperExpr(Op, Op.OperandA) + ", " + - GetOperExpr(Op, Op.OperandB) + ")"; - } - - private string GetTernaryCall(ShaderIrOp Op, string FuncName) - { - return FuncName + "(" + GetOperExpr(Op, Op.OperandA) + ", " + - GetOperExpr(Op, Op.OperandB) + ", " + - GetOperExpr(Op, Op.OperandC) + ")"; - } - - private string GetUnaryExpr(ShaderIrOp Op, string Opr) - { - return Opr + GetOperExpr(Op, Op.OperandA); - } - - private string GetBinaryExpr(ShaderIrOp Op, string Opr) - { - return GetOperExpr(Op, Op.OperandA) + " " + Opr + " " + - GetOperExpr(Op, Op.OperandB); - } - - private string GetBinaryExprWithNaN(ShaderIrOp Op, string Opr) - { - string A = GetOperExpr(Op, Op.OperandA); - string B = GetOperExpr(Op, Op.OperandB); - - string NaNCheck = - " || isnan(" + A + ")" + - " || isnan(" + B + ")"; - - return A + " " + Opr + " " + B + NaNCheck; - } - - private string GetTernaryExpr(ShaderIrOp Op, string Opr1, string Opr2) - { - return GetOperExpr(Op, Op.OperandA) + " " + Opr1 + " " + - GetOperExpr(Op, Op.OperandB) + " " + Opr2 + " " + - GetOperExpr(Op, Op.OperandC); - } - - private string GetTexSamplerName(ShaderIrOp Op) - { - ShaderIrOperImm Node = (ShaderIrOperImm)Op.OperandC; - - int Handle = ((ShaderIrOperImm)Op.OperandC).Value; - - if (!Decl.Textures.TryGetValue(Handle, out ShaderDeclInfo DeclInfo)) - { - throw new InvalidOperationException(); - } - - return DeclInfo.Name; - } - - private string GetTexSamplerCoords(ShaderIrOp Op) - { - return "vec2(" + GetOperExpr(Op, Op.OperandA) + ", " + - GetOperExpr(Op, Op.OperandB) + ")"; - } - - private string GetITexSamplerCoords(ShaderIrOp Op) - { - return "ivec2(" + GetOperExpr(Op, Op.OperandA) + ", " + - GetOperExpr(Op, Op.OperandB) + ")"; - } - - private string GetOperExpr(ShaderIrOp Op, ShaderIrNode Oper) - { - return GetExprWithCast(Op, Oper, GetSrcExpr(Oper)); - } - - private static string GetExprWithCast(ShaderIrNode Dst, ShaderIrNode Src, string Expr) - { - //Note: The "DstType" (of the cast) is the type that the operation - //uses on the source operands, while the "SrcType" is the destination - //type of the operand result (if it is a operation) or just the type - //of the variable for registers/uniforms/attributes. - OperType DstType = GetSrcNodeType(Dst); - OperType SrcType = GetDstNodeType(Src); - - if (DstType != SrcType) - { - //Check for invalid casts - //(like bool to int/float and others). - if (SrcType != OperType.F32 && - SrcType != OperType.I32) - { - throw new InvalidOperationException(); - } - - switch (Src) - { - case ShaderIrOperGpr Gpr: - { - //When the Gpr is ZR, just return the 0 value directly, - //since the float encoding for 0 is 0. - if (Gpr.IsConst) - { - return "0"; - } - break; - } - - case ShaderIrOperImm Imm: - { - //For integer immediates being used as float, - //it's better (for readability) to just return the float value. - if (DstType == OperType.F32) - { - float Value = BitConverter.Int32BitsToSingle(Imm.Value); - - if (!float.IsNaN(Value) && !float.IsInfinity(Value)) - { - return Value.ToString(CultureInfo.InvariantCulture); - } - } - break; - } - } - - switch (DstType) - { - case OperType.F32: Expr = "intBitsToFloat(" + Expr + ")"; break; - case OperType.I32: Expr = "floatBitsToInt(" + Expr + ")"; break; - } - } - - return Expr; - } - - private static OperType GetDstNodeType(ShaderIrNode Node) - { - //Special case instructions with the result type different - //from the input types (like integer <-> float conversion) here. - if (Node is ShaderIrOp Op) - { - switch (Op.Inst) - { - case ShaderIrInst.Stof: - case ShaderIrInst.Txlf: - case ShaderIrInst.Utof: - return OperType.F32; - - case ShaderIrInst.Ftos: - case ShaderIrInst.Ftou: - return OperType.I32; - } - } - - return GetSrcNodeType(Node); - } - - private static OperType GetSrcNodeType(ShaderIrNode Node) - { - switch (Node) - { - case ShaderIrOperAbuf Abuf: - return Abuf.Offs == GlslDecl.VertexIdAttr || - Abuf.Offs == GlslDecl.InstanceIdAttr || - Abuf.Offs == GlslDecl.FaceAttr - ? OperType.I32 - : OperType.F32; - - case ShaderIrOperCbuf Cbuf: return OperType.F32; - case ShaderIrOperGpr Gpr: return OperType.F32; - case ShaderIrOperImm Imm: return OperType.I32; - case ShaderIrOperImmf Immf: return OperType.F32; - case ShaderIrOperPred Pred: return OperType.Bool; - - case ShaderIrOp Op: - if (Op.Inst > ShaderIrInst.B_Start && - Op.Inst < ShaderIrInst.B_End) - { - return OperType.Bool; - } - else if (Op.Inst > ShaderIrInst.F_Start && - Op.Inst < ShaderIrInst.F_End) - { - return OperType.F32; - } - else if (Op.Inst > ShaderIrInst.I_Start && - Op.Inst < ShaderIrInst.I_End) - { - return OperType.I32; - } - break; - } - - throw new ArgumentException(nameof(Node)); - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/GlslProgram.cs b/Ryujinx.Graphics/Gal/Shader/GlslProgram.cs deleted file mode 100644 index a7af05aef9..0000000000 --- a/Ryujinx.Graphics/Gal/Shader/GlslProgram.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Collections.Generic; - -namespace Ryujinx.Graphics.Gal.Shader -{ - public struct GlslProgram - { - public string Code { get; private set; } - - public IEnumerable Textures { get; private set; } - public IEnumerable Uniforms { get; private set; } - - public GlslProgram( - string Code, - IEnumerable Textures, - IEnumerable Uniforms) - { - this.Code = Code; - this.Textures = Textures; - this.Uniforms = Uniforms; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecode.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecode.cs deleted file mode 100644 index ef0fd78bd3..0000000000 --- a/Ryujinx.Graphics/Gal/Shader/ShaderDecode.cs +++ /dev/null @@ -1,4 +0,0 @@ -namespace Ryujinx.Graphics.Gal.Shader -{ - delegate void ShaderDecodeFunc(ShaderIrBlock Block, long OpCode); -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeAlu.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeAlu.cs deleted file mode 100644 index 2e022fbd44..0000000000 --- a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeAlu.cs +++ /dev/null @@ -1,913 +0,0 @@ -using System; - -using static Ryujinx.Graphics.Gal.Shader.ShaderDecodeHelper; - -namespace Ryujinx.Graphics.Gal.Shader -{ - static partial class ShaderDecode - { - public static void Bfe_C(ShaderIrBlock Block, long OpCode) - { - EmitBfe(Block, OpCode, ShaderOper.CR); - } - - public static void Bfe_I(ShaderIrBlock Block, long OpCode) - { - EmitBfe(Block, OpCode, ShaderOper.Imm); - } - - public static void Bfe_R(ShaderIrBlock Block, long OpCode) - { - EmitBfe(Block, OpCode, ShaderOper.RR); - } - - public static void Fadd_C(ShaderIrBlock Block, long OpCode) - { - EmitAluBinaryF(Block, OpCode, ShaderOper.CR, ShaderIrInst.Fadd); - } - - public static void Fadd_I(ShaderIrBlock Block, long OpCode) - { - EmitAluBinaryF(Block, OpCode, ShaderOper.Immf, ShaderIrInst.Fadd); - } - - public static void Fadd_I32(ShaderIrBlock Block, long OpCode) - { - ShaderIrNode OperA = GetOperGpr8 (OpCode); - ShaderIrNode OperB = GetOperImmf32_20(OpCode); - - bool NegB = ((OpCode >> 53) & 1) != 0; - bool AbsA = ((OpCode >> 54) & 1) != 0; - bool NegA = ((OpCode >> 56) & 1) != 0; - bool AbsB = ((OpCode >> 57) & 1) != 0; - - OperA = GetAluFabsFneg(OperA, AbsA, NegA); - OperB = GetAluFabsFneg(OperB, AbsB, NegB); - - ShaderIrOp Op = new ShaderIrOp(ShaderIrInst.Fadd, OperA, OperB); - - Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Op), OpCode)); - } - - public static void Fadd_R(ShaderIrBlock Block, long OpCode) - { - EmitAluBinaryF(Block, OpCode, ShaderOper.RR, ShaderIrInst.Fadd); - } - - public static void Ffma_CR(ShaderIrBlock Block, long OpCode) - { - EmitFfma(Block, OpCode, ShaderOper.CR); - } - - public static void Ffma_I(ShaderIrBlock Block, long OpCode) - { - EmitFfma(Block, OpCode, ShaderOper.Immf); - } - - public static void Ffma_RC(ShaderIrBlock Block, long OpCode) - { - EmitFfma(Block, OpCode, ShaderOper.RC); - } - - public static void Ffma_RR(ShaderIrBlock Block, long OpCode) - { - EmitFfma(Block, OpCode, ShaderOper.RR); - } - - public static void Fmnmx_C(ShaderIrBlock Block, long OpCode) - { - EmitFmnmx(Block, OpCode, ShaderOper.CR); - } - - public static void Fmnmx_I(ShaderIrBlock Block, long OpCode) - { - EmitFmnmx(Block, OpCode, ShaderOper.Immf); - } - - public static void Fmnmx_R(ShaderIrBlock Block, long OpCode) - { - EmitFmnmx(Block, OpCode, ShaderOper.RR); - } - - public static void Fmul_I32(ShaderIrBlock Block, long OpCode) - { - ShaderIrNode OperA = GetOperGpr8 (OpCode); - ShaderIrNode OperB = GetOperImmf32_20(OpCode); - - ShaderIrOp Op = new ShaderIrOp(ShaderIrInst.Fmul, OperA, OperB); - - Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Op), OpCode)); - } - - public static void Fmul_C(ShaderIrBlock Block, long OpCode) - { - EmitAluBinaryF(Block, OpCode, ShaderOper.CR, ShaderIrInst.Fmul); - } - - public static void Fmul_I(ShaderIrBlock Block, long OpCode) - { - EmitAluBinaryF(Block, OpCode, ShaderOper.Immf, ShaderIrInst.Fmul); - } - - public static void Fmul_R(ShaderIrBlock Block, long OpCode) - { - EmitAluBinaryF(Block, OpCode, ShaderOper.RR, ShaderIrInst.Fmul); - } - - public static void Fset_C(ShaderIrBlock Block, long OpCode) - { - EmitFset(Block, OpCode, ShaderOper.CR); - } - - public static void Fset_I(ShaderIrBlock Block, long OpCode) - { - EmitFset(Block, OpCode, ShaderOper.Immf); - } - - public static void Fset_R(ShaderIrBlock Block, long OpCode) - { - EmitFset(Block, OpCode, ShaderOper.RR); - } - - public static void Fsetp_C(ShaderIrBlock Block, long OpCode) - { - EmitFsetp(Block, OpCode, ShaderOper.CR); - } - - public static void Fsetp_I(ShaderIrBlock Block, long OpCode) - { - EmitFsetp(Block, OpCode, ShaderOper.Immf); - } - - public static void Fsetp_R(ShaderIrBlock Block, long OpCode) - { - EmitFsetp(Block, OpCode, ShaderOper.RR); - } - - public static void Imnmx_C(ShaderIrBlock Block, long OpCode) - { - EmitImnmx(Block, OpCode, ShaderOper.CR); - } - - public static void Imnmx_I(ShaderIrBlock Block, long OpCode) - { - EmitImnmx(Block, OpCode, ShaderOper.Imm); - } - - public static void Imnmx_R(ShaderIrBlock Block, long OpCode) - { - EmitImnmx(Block, OpCode, ShaderOper.RR); - } - - public static void Ipa(ShaderIrBlock Block, long OpCode) - { - ShaderIrNode OperA = GetOperAbuf28(OpCode); - ShaderIrNode OperB = GetOperGpr20 (OpCode); - - ShaderIrOp Op = new ShaderIrOp(ShaderIrInst.Ipa, OperA, OperB); - - Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Op), OpCode)); - } - - public static void Iscadd_C(ShaderIrBlock Block, long OpCode) - { - EmitIscadd(Block, OpCode, ShaderOper.CR); - } - - public static void Iscadd_I(ShaderIrBlock Block, long OpCode) - { - EmitIscadd(Block, OpCode, ShaderOper.Imm); - } - - public static void Iscadd_R(ShaderIrBlock Block, long OpCode) - { - EmitIscadd(Block, OpCode, ShaderOper.RR); - } - - public static void Isetp_C(ShaderIrBlock Block, long OpCode) - { - EmitIsetp(Block, OpCode, ShaderOper.CR); - } - - public static void Isetp_I(ShaderIrBlock Block, long OpCode) - { - EmitIsetp(Block, OpCode, ShaderOper.Imm); - } - - public static void Isetp_R(ShaderIrBlock Block, long OpCode) - { - EmitIsetp(Block, OpCode, ShaderOper.RR); - } - - public static void Lop_I32(ShaderIrBlock Block, long OpCode) - { - int SubOp = (int)(OpCode >> 53) & 3; - - bool InvA = ((OpCode >> 55) & 1) != 0; - bool InvB = ((OpCode >> 56) & 1) != 0; - - ShaderIrInst Inst = 0; - - switch (SubOp) - { - case 0: Inst = ShaderIrInst.And; break; - case 1: Inst = ShaderIrInst.Or; break; - case 2: Inst = ShaderIrInst.Xor; break; - } - - ShaderIrNode OperA = GetAluNot(GetOperGpr8(OpCode), InvA); - - //SubOp == 3 is pass, used by the not instruction - //which just moves the inverted register value. - if (SubOp < 3) - { - ShaderIrNode OperB = GetAluNot(GetOperImm32_20(OpCode), InvB); - - ShaderIrOp Op = new ShaderIrOp(Inst, OperA, OperB); - - Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Op), OpCode)); - } - else - { - Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), OperA), OpCode)); - } - } - - public static void Mufu(ShaderIrBlock Block, long OpCode) - { - int SubOp = (int)(OpCode >> 20) & 0xf; - - bool AbsA = ((OpCode >> 46) & 1) != 0; - bool NegA = ((OpCode >> 48) & 1) != 0; - - ShaderIrInst Inst = 0; - - switch (SubOp) - { - case 0: Inst = ShaderIrInst.Fcos; break; - case 1: Inst = ShaderIrInst.Fsin; break; - case 2: Inst = ShaderIrInst.Fex2; break; - case 3: Inst = ShaderIrInst.Flg2; break; - case 4: Inst = ShaderIrInst.Frcp; break; - case 5: Inst = ShaderIrInst.Frsq; break; - case 8: Inst = ShaderIrInst.Fsqrt; break; - - default: throw new NotImplementedException(SubOp.ToString()); - } - - ShaderIrNode OperA = GetOperGpr8(OpCode); - - ShaderIrOp Op = new ShaderIrOp(Inst, GetAluFabsFneg(OperA, AbsA, NegA)); - - Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Op), OpCode)); - } - - public static void Psetp(ShaderIrBlock Block, long OpCode) - { - bool NegA = ((OpCode >> 15) & 1) != 0; - bool NegB = ((OpCode >> 32) & 1) != 0; - bool NegP = ((OpCode >> 42) & 1) != 0; - - ShaderIrInst LopInst = GetBLop24(OpCode); - - ShaderIrNode OperA = GetOperPred12(OpCode); - ShaderIrNode OperB = GetOperPred29(OpCode); - - if (NegA) - { - OperA = new ShaderIrOp(ShaderIrInst.Bnot, OperA); - } - - if (NegB) - { - OperB = new ShaderIrOp(ShaderIrInst.Bnot, OperB); - } - - ShaderIrOp Op = new ShaderIrOp(LopInst, OperA, OperB); - - ShaderIrOperPred P0Node = GetOperPred3 (OpCode); - ShaderIrOperPred P1Node = GetOperPred0 (OpCode); - ShaderIrOperPred P2Node = GetOperPred39(OpCode); - - Block.AddNode(GetPredNode(new ShaderIrAsg(P0Node, Op), OpCode)); - - LopInst = GetBLop45(OpCode); - - if (LopInst == ShaderIrInst.Band && P1Node.IsConst && P2Node.IsConst) - { - return; - } - - ShaderIrNode P2NNode = P2Node; - - if (NegP) - { - P2NNode = new ShaderIrOp(ShaderIrInst.Bnot, P2NNode); - } - - Op = new ShaderIrOp(ShaderIrInst.Bnot, P0Node); - - Op = new ShaderIrOp(LopInst, Op, P2NNode); - - Block.AddNode(GetPredNode(new ShaderIrAsg(P1Node, Op), OpCode)); - - Op = new ShaderIrOp(LopInst, P0Node, P2NNode); - - Block.AddNode(GetPredNode(new ShaderIrAsg(P0Node, Op), OpCode)); - } - - public static void Rro_C(ShaderIrBlock Block, long OpCode) - { - EmitRro(Block, OpCode, ShaderOper.CR); - } - - public static void Rro_I(ShaderIrBlock Block, long OpCode) - { - EmitRro(Block, OpCode, ShaderOper.Immf); - } - - public static void Rro_R(ShaderIrBlock Block, long OpCode) - { - EmitRro(Block, OpCode, ShaderOper.RR); - } - - public static void Shl_C(ShaderIrBlock Block, long OpCode) - { - EmitAluBinary(Block, OpCode, ShaderOper.CR, ShaderIrInst.Lsl); - } - - public static void Shl_I(ShaderIrBlock Block, long OpCode) - { - EmitAluBinary(Block, OpCode, ShaderOper.Imm, ShaderIrInst.Lsl); - } - - public static void Shl_R(ShaderIrBlock Block, long OpCode) - { - EmitAluBinary(Block, OpCode, ShaderOper.RR, ShaderIrInst.Lsl); - } - - public static void Shr_C(ShaderIrBlock Block, long OpCode) - { - EmitAluBinary(Block, OpCode, ShaderOper.CR, GetShrInst(OpCode)); - } - - public static void Shr_I(ShaderIrBlock Block, long OpCode) - { - EmitAluBinary(Block, OpCode, ShaderOper.Imm, GetShrInst(OpCode)); - } - - public static void Shr_R(ShaderIrBlock Block, long OpCode) - { - EmitAluBinary(Block, OpCode, ShaderOper.RR, GetShrInst(OpCode)); - } - - private static ShaderIrInst GetShrInst(long OpCode) - { - bool Signed = ((OpCode >> 48) & 1) != 0; - - return Signed ? ShaderIrInst.Asr : ShaderIrInst.Lsr; - } - - public static void Xmad_CR(ShaderIrBlock Block, long OpCode) - { - EmitXmad(Block, OpCode, ShaderOper.CR); - } - - public static void Xmad_I(ShaderIrBlock Block, long OpCode) - { - EmitXmad(Block, OpCode, ShaderOper.Imm); - } - - public static void Xmad_RC(ShaderIrBlock Block, long OpCode) - { - EmitXmad(Block, OpCode, ShaderOper.RC); - } - - public static void Xmad_RR(ShaderIrBlock Block, long OpCode) - { - EmitXmad(Block, OpCode, ShaderOper.RR); - } - - private static void EmitAluBinary( - ShaderIrBlock Block, - long OpCode, - ShaderOper Oper, - ShaderIrInst Inst) - { - ShaderIrNode OperA = GetOperGpr8(OpCode), OperB; - - switch (Oper) - { - case ShaderOper.CR: OperB = GetOperCbuf34 (OpCode); break; - case ShaderOper.Imm: OperB = GetOperImm19_20(OpCode); break; - case ShaderOper.RR: OperB = GetOperGpr20 (OpCode); break; - - default: throw new ArgumentException(nameof(Oper)); - } - - ShaderIrNode Op = new ShaderIrOp(Inst, OperA, OperB); - - Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Op), OpCode)); - } - - private static void EmitAluBinaryF( - ShaderIrBlock Block, - long OpCode, - ShaderOper Oper, - ShaderIrInst Inst) - { - bool NegB = ((OpCode >> 45) & 1) != 0; - bool AbsA = ((OpCode >> 46) & 1) != 0; - bool NegA = ((OpCode >> 48) & 1) != 0; - bool AbsB = ((OpCode >> 49) & 1) != 0; - - ShaderIrNode OperA = GetOperGpr8(OpCode), OperB; - - if (Inst == ShaderIrInst.Fadd) - { - OperA = GetAluFabsFneg(OperA, AbsA, NegA); - } - - switch (Oper) - { - case ShaderOper.CR: OperB = GetOperCbuf34 (OpCode); break; - case ShaderOper.Immf: OperB = GetOperImmf19_20(OpCode); break; - case ShaderOper.RR: OperB = GetOperGpr20 (OpCode); break; - - default: throw new ArgumentException(nameof(Oper)); - } - - OperB = GetAluFabsFneg(OperB, AbsB, NegB); - - ShaderIrNode Op = new ShaderIrOp(Inst, OperA, OperB); - - Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Op), OpCode)); - } - - private static void EmitBfe(ShaderIrBlock Block, long OpCode, ShaderOper Oper) - { - //TODO: Handle the case where position + length - //is greater than the word size, in this case the sign bit - //needs to be replicated to fill the remaining space. - bool NegB = ((OpCode >> 48) & 1) != 0; - bool NegA = ((OpCode >> 49) & 1) != 0; - - ShaderIrNode OperA = GetOperGpr8(OpCode), OperB; - - switch (Oper) - { - case ShaderOper.CR: OperB = GetOperCbuf34 (OpCode); break; - case ShaderOper.Imm: OperB = GetOperImm19_20(OpCode); break; - case ShaderOper.RR: OperB = GetOperGpr20 (OpCode); break; - - default: throw new ArgumentException(nameof(Oper)); - } - - ShaderIrNode Op; - - bool Signed = ((OpCode >> 48) & 1) != 0; //? - - if (OperB is ShaderIrOperImm PosLen) - { - int Position = (PosLen.Value >> 0) & 0xff; - int Length = (PosLen.Value >> 8) & 0xff; - - int LSh = 32 - (Position + Length); - - ShaderIrInst RightShift = Signed - ? ShaderIrInst.Asr - : ShaderIrInst.Lsr; - - Op = new ShaderIrOp(ShaderIrInst.Lsl, OperA, new ShaderIrOperImm(LSh)); - Op = new ShaderIrOp(RightShift, Op, new ShaderIrOperImm(LSh + Position)); - } - else - { - ShaderIrOperImm Shift = new ShaderIrOperImm(8); - ShaderIrOperImm Mask = new ShaderIrOperImm(0xff); - - ShaderIrNode OpPos, OpLen; - - OpPos = new ShaderIrOp(ShaderIrInst.And, OperB, Mask); - OpLen = new ShaderIrOp(ShaderIrInst.Lsr, OperB, Shift); - OpLen = new ShaderIrOp(ShaderIrInst.And, OpLen, Mask); - - Op = new ShaderIrOp(ShaderIrInst.Lsr, OperA, OpPos); - - Op = ExtendTo32(Op, Signed, OpLen); - } - - Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Op), OpCode)); - } - - private static void EmitFfma(ShaderIrBlock Block, long OpCode, ShaderOper Oper) - { - bool NegB = ((OpCode >> 48) & 1) != 0; - bool NegC = ((OpCode >> 49) & 1) != 0; - - ShaderIrNode OperA = GetOperGpr8(OpCode), OperB, OperC; - - switch (Oper) - { - case ShaderOper.CR: OperB = GetOperCbuf34 (OpCode); break; - case ShaderOper.Immf: OperB = GetOperImmf19_20(OpCode); break; - case ShaderOper.RC: OperB = GetOperGpr39 (OpCode); break; - case ShaderOper.RR: OperB = GetOperGpr20 (OpCode); break; - - default: throw new ArgumentException(nameof(Oper)); - } - - OperB = GetAluFneg(OperB, NegB); - - if (Oper == ShaderOper.RC) - { - OperC = GetAluFneg(GetOperCbuf34(OpCode), NegC); - } - else - { - OperC = GetAluFneg(GetOperGpr39(OpCode), NegC); - } - - ShaderIrOp Op = new ShaderIrOp(ShaderIrInst.Ffma, OperA, OperB, OperC); - - Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Op), OpCode)); - } - - private static void EmitIscadd(ShaderIrBlock Block, long OpCode, ShaderOper Oper) - { - bool NegB = ((OpCode >> 48) & 1) != 0; - bool NegA = ((OpCode >> 49) & 1) != 0; - - ShaderIrNode OperA = GetOperGpr8(OpCode), OperB; - - ShaderIrOperImm Scale = GetOperImm5_39(OpCode); - - switch (Oper) - { - case ShaderOper.CR: OperB = GetOperCbuf34 (OpCode); break; - case ShaderOper.Imm: OperB = GetOperImm19_20(OpCode); break; - case ShaderOper.RR: OperB = GetOperGpr20 (OpCode); break; - - default: throw new ArgumentException(nameof(Oper)); - } - - OperA = GetAluIneg(OperA, NegA); - OperB = GetAluIneg(OperB, NegB); - - ShaderIrOp ScaleOp = new ShaderIrOp(ShaderIrInst.Lsl, OperA, Scale); - ShaderIrOp AddOp = new ShaderIrOp(ShaderIrInst.Add, OperB, ScaleOp); - - Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), AddOp), OpCode)); - } - - private static void EmitFmnmx(ShaderIrBlock Block, long OpCode, ShaderOper Oper) - { - EmitMnmx(Block, OpCode, true, Oper); - } - - private static void EmitImnmx(ShaderIrBlock Block, long OpCode, ShaderOper Oper) - { - EmitMnmx(Block, OpCode, false, Oper); - } - - private static void EmitMnmx(ShaderIrBlock Block, long OpCode, bool IsFloat, ShaderOper Oper) - { - bool NegB = ((OpCode >> 45) & 1) != 0; - bool AbsA = ((OpCode >> 46) & 1) != 0; - bool NegA = ((OpCode >> 48) & 1) != 0; - bool AbsB = ((OpCode >> 49) & 1) != 0; - - ShaderIrNode OperA = GetOperGpr8(OpCode), OperB; - - if (IsFloat) - { - OperA = GetAluFabsFneg(OperA, AbsA, NegA); - } - else - { - OperA = GetAluIabsIneg(OperA, AbsA, NegA); - } - - switch (Oper) - { - case ShaderOper.CR: OperB = GetOperCbuf34 (OpCode); break; - case ShaderOper.Imm: OperB = GetOperImm19_20 (OpCode); break; - case ShaderOper.Immf: OperB = GetOperImmf19_20(OpCode); break; - case ShaderOper.RR: OperB = GetOperGpr20 (OpCode); break; - - default: throw new ArgumentException(nameof(Oper)); - } - - if (IsFloat) - { - OperB = GetAluFabsFneg(OperB, AbsB, NegB); - } - else - { - OperB = GetAluIabsIneg(OperB, AbsB, NegB); - } - - ShaderIrOperPred Pred = GetOperPred39(OpCode); - - ShaderIrOp Op; - - ShaderIrInst MaxInst = IsFloat ? ShaderIrInst.Fmax : ShaderIrInst.Max; - ShaderIrInst MinInst = IsFloat ? ShaderIrInst.Fmin : ShaderIrInst.Min; - - if (Pred.IsConst) - { - bool IsMax = ((OpCode >> 42) & 1) != 0; - - Op = new ShaderIrOp(IsMax - ? MaxInst - : MinInst, OperA, OperB); - - Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Op), OpCode)); - } - else - { - ShaderIrNode PredN = GetOperPred39N(OpCode); - - ShaderIrOp OpMax = new ShaderIrOp(MaxInst, OperA, OperB); - ShaderIrOp OpMin = new ShaderIrOp(MinInst, OperA, OperB); - - ShaderIrAsg AsgMax = new ShaderIrAsg(GetOperGpr0(OpCode), OpMax); - ShaderIrAsg AsgMin = new ShaderIrAsg(GetOperGpr0(OpCode), OpMin); - - Block.AddNode(GetPredNode(new ShaderIrCond(PredN, AsgMax, Not: true), OpCode)); - Block.AddNode(GetPredNode(new ShaderIrCond(PredN, AsgMin, Not: false), OpCode)); - } - } - - private static void EmitRro(ShaderIrBlock Block, long OpCode, ShaderOper Oper) - { - //Note: this is a range reduction instruction and is supposed to - //be used with Mufu, here it just moves the value and ignores the operation. - bool NegA = ((OpCode >> 45) & 1) != 0; - bool AbsA = ((OpCode >> 49) & 1) != 0; - - ShaderIrNode OperA; - - switch (Oper) - { - case ShaderOper.CR: OperA = GetOperCbuf34 (OpCode); break; - case ShaderOper.Immf: OperA = GetOperImmf19_20(OpCode); break; - case ShaderOper.RR: OperA = GetOperGpr20 (OpCode); break; - - default: throw new ArgumentException(nameof(Oper)); - } - - OperA = GetAluFabsFneg(OperA, AbsA, NegA); - - Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), OperA), OpCode)); - } - - private static void EmitFset(ShaderIrBlock Block, long OpCode, ShaderOper Oper) - { - EmitSet(Block, OpCode, true, Oper); - } - - private static void EmitIset(ShaderIrBlock Block, long OpCode, ShaderOper Oper) - { - EmitSet(Block, OpCode, false, Oper); - } - - private static void EmitSet(ShaderIrBlock Block, long OpCode, bool IsFloat, ShaderOper Oper) - { - bool NegA = ((OpCode >> 43) & 1) != 0; - bool AbsB = ((OpCode >> 44) & 1) != 0; - bool BoolFloat = ((OpCode >> 52) & 1) != 0; - bool NegB = ((OpCode >> 53) & 1) != 0; - bool AbsA = ((OpCode >> 54) & 1) != 0; - - ShaderIrNode OperA = GetOperGpr8(OpCode), OperB; - - switch (Oper) - { - case ShaderOper.CR: OperB = GetOperCbuf34 (OpCode); break; - case ShaderOper.Imm: OperB = GetOperImm19_20 (OpCode); break; - case ShaderOper.Immf: OperB = GetOperImmf19_20(OpCode); break; - case ShaderOper.RR: OperB = GetOperGpr20 (OpCode); break; - - default: throw new ArgumentException(nameof(Oper)); - } - - ShaderIrInst CmpInst; - - if (IsFloat) - { - OperA = GetAluFabsFneg(OperA, AbsA, NegA); - OperB = GetAluFabsFneg(OperB, AbsB, NegB); - - CmpInst = GetCmpF(OpCode); - } - else - { - CmpInst = GetCmp(OpCode); - } - - ShaderIrOp Op = new ShaderIrOp(CmpInst, OperA, OperB); - - ShaderIrInst LopInst = GetBLop45(OpCode); - - ShaderIrOperPred PNode = GetOperPred39(OpCode); - - ShaderIrNode Imm0, Imm1; - - if (BoolFloat) - { - Imm0 = new ShaderIrOperImmf(0); - Imm1 = new ShaderIrOperImmf(1); - } - else - { - Imm0 = new ShaderIrOperImm(0); - Imm1 = new ShaderIrOperImm(-1); - } - - ShaderIrNode Asg0 = new ShaderIrAsg(GetOperGpr0(OpCode), Imm0); - ShaderIrNode Asg1 = new ShaderIrAsg(GetOperGpr0(OpCode), Imm1); - - if (LopInst != ShaderIrInst.Band || !PNode.IsConst) - { - ShaderIrOp Op2 = new ShaderIrOp(LopInst, Op, PNode); - - Asg0 = new ShaderIrCond(Op2, Asg0, Not: true); - Asg1 = new ShaderIrCond(Op2, Asg1, Not: false); - } - else - { - Asg0 = new ShaderIrCond(Op, Asg0, Not: true); - Asg1 = new ShaderIrCond(Op, Asg1, Not: false); - } - - Block.AddNode(GetPredNode(Asg0, OpCode)); - Block.AddNode(GetPredNode(Asg1, OpCode)); - } - - private static void EmitFsetp(ShaderIrBlock Block, long OpCode, ShaderOper Oper) - { - EmitSetp(Block, OpCode, true, Oper); - } - - private static void EmitIsetp(ShaderIrBlock Block, long OpCode, ShaderOper Oper) - { - EmitSetp(Block, OpCode, false, Oper); - } - - private static void EmitSetp(ShaderIrBlock Block, long OpCode, bool IsFloat, ShaderOper Oper) - { - bool AbsA = ((OpCode >> 7) & 1) != 0; - bool NegP = ((OpCode >> 42) & 1) != 0; - bool NegA = ((OpCode >> 43) & 1) != 0; - bool AbsB = ((OpCode >> 44) & 1) != 0; - - ShaderIrNode OperA = GetOperGpr8(OpCode), OperB; - - switch (Oper) - { - case ShaderOper.CR: OperB = GetOperCbuf34 (OpCode); break; - case ShaderOper.Imm: OperB = GetOperImm19_20 (OpCode); break; - case ShaderOper.Immf: OperB = GetOperImmf19_20(OpCode); break; - case ShaderOper.RR: OperB = GetOperGpr20 (OpCode); break; - - default: throw new ArgumentException(nameof(Oper)); - } - - ShaderIrInst CmpInst; - - if (IsFloat) - { - OperA = GetAluFabsFneg(OperA, AbsA, NegA); - OperB = GetAluFabs (OperB, AbsB); - - CmpInst = GetCmpF(OpCode); - } - else - { - CmpInst = GetCmp(OpCode); - } - - ShaderIrOp Op = new ShaderIrOp(CmpInst, OperA, OperB); - - ShaderIrOperPred P0Node = GetOperPred3 (OpCode); - ShaderIrOperPred P1Node = GetOperPred0 (OpCode); - ShaderIrOperPred P2Node = GetOperPred39(OpCode); - - Block.AddNode(GetPredNode(new ShaderIrAsg(P0Node, Op), OpCode)); - - ShaderIrInst LopInst = GetBLop45(OpCode); - - if (LopInst == ShaderIrInst.Band && P1Node.IsConst && P2Node.IsConst) - { - return; - } - - ShaderIrNode P2NNode = P2Node; - - if (NegP) - { - P2NNode = new ShaderIrOp(ShaderIrInst.Bnot, P2NNode); - } - - Op = new ShaderIrOp(ShaderIrInst.Bnot, P0Node); - - Op = new ShaderIrOp(LopInst, Op, P2NNode); - - Block.AddNode(GetPredNode(new ShaderIrAsg(P1Node, Op), OpCode)); - - Op = new ShaderIrOp(LopInst, P0Node, P2NNode); - - Block.AddNode(GetPredNode(new ShaderIrAsg(P0Node, Op), OpCode)); - } - - private static void EmitXmad(ShaderIrBlock Block, long OpCode, ShaderOper Oper) - { - //TODO: Confirm SignAB/C, it is just a guess. - //TODO: Implement Mode 3 (CSFU), what it does? - bool SignAB = ((OpCode >> 48) & 1) != 0; - bool SignC = ((OpCode >> 49) & 1) != 0; - bool HighB = ((OpCode >> 52) & 1) != 0; - bool HighA = ((OpCode >> 53) & 1) != 0; - - int Mode = (int)(OpCode >> 50) & 7; - - ShaderIrNode OperA = GetOperGpr8(OpCode), OperB, OperC; - - ShaderIrOperImm Imm16 = new ShaderIrOperImm(16); - ShaderIrOperImm ImmMsk = new ShaderIrOperImm(0xffff); - - ShaderIrInst ShiftAB = SignAB ? ShaderIrInst.Asr : ShaderIrInst.Lsr; - ShaderIrInst ShiftC = SignC ? ShaderIrInst.Asr : ShaderIrInst.Lsr; - - if (HighA) - { - OperA = new ShaderIrOp(ShiftAB, OperA, Imm16); - } - - switch (Oper) - { - case ShaderOper.CR: OperB = GetOperCbuf34 (OpCode); break; - case ShaderOper.Imm: OperB = GetOperImm19_20(OpCode); break; - case ShaderOper.RC: OperB = GetOperGpr39 (OpCode); break; - case ShaderOper.RR: OperB = GetOperGpr20 (OpCode); break; - - default: throw new ArgumentException(nameof(Oper)); - } - - bool ProductShiftLeft = false, Merge = false; - - if (Oper == ShaderOper.RC) - { - OperC = GetOperCbuf34(OpCode); - } - else - { - OperC = GetOperGpr39(OpCode); - - ProductShiftLeft = ((OpCode >> 36) & 1) != 0; - Merge = ((OpCode >> 37) & 1) != 0; - } - - switch (Mode) - { - //CLO. - case 1: OperC = ExtendTo32(OperC, SignC, 16); break; - - //CHI. - case 2: OperC = new ShaderIrOp(ShiftC, OperC, Imm16); break; - } - - ShaderIrNode OperBH = OperB; - - if (HighB) - { - OperBH = new ShaderIrOp(ShiftAB, OperBH, Imm16); - } - - ShaderIrOp MulOp = new ShaderIrOp(ShaderIrInst.Mul, OperA, OperBH); - - if (ProductShiftLeft) - { - MulOp = new ShaderIrOp(ShaderIrInst.Lsl, MulOp, Imm16); - } - - ShaderIrOp AddOp = new ShaderIrOp(ShaderIrInst.Add, MulOp, OperC); - - if (Merge) - { - AddOp = new ShaderIrOp(ShaderIrInst.And, AddOp, ImmMsk); - OperB = new ShaderIrOp(ShaderIrInst.Lsl, OperB, Imm16); - AddOp = new ShaderIrOp(ShaderIrInst.Or, AddOp, OperB); - } - - if (Mode == 4) - { - OperB = new ShaderIrOp(ShaderIrInst.Lsl, OperB, Imm16); - AddOp = new ShaderIrOp(ShaderIrInst.Or, AddOp, OperB); - } - - Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), AddOp), OpCode)); - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeFlow.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeFlow.cs deleted file mode 100644 index 8d0925a321..0000000000 --- a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeFlow.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; - -using static Ryujinx.Graphics.Gal.Shader.ShaderDecodeHelper; - -namespace Ryujinx.Graphics.Gal.Shader -{ - static partial class ShaderDecode - { - public static void Bra(ShaderIrBlock Block, long OpCode) - { - if ((OpCode & 0x20) != 0) - { - //This reads the target offset from the constant buffer. - //Almost impossible to support with GLSL. - throw new NotImplementedException(); - } - - int Target = ((int)(OpCode >> 20) << 8) >> 8; - - ShaderIrOperImm Imm = new ShaderIrOperImm(Target); - - Block.AddNode(GetPredNode(new ShaderIrOp(ShaderIrInst.Bra, Imm), OpCode)); - } - - public static void Exit(ShaderIrBlock Block, long OpCode) - { - int CCode = (int)OpCode & 0x1f; - - //TODO: Figure out what the other condition codes mean... - if (CCode == 0xf) - { - Block.AddNode(GetPredNode(new ShaderIrOp(ShaderIrInst.Exit), OpCode)); - } - - } - - public static void Kil(ShaderIrBlock Block, long OpCode) - { - Block.AddNode(GetPredNode(new ShaderIrOp(ShaderIrInst.Kil), OpCode)); - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeHelper.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeHelper.cs deleted file mode 100644 index a8ad5ec2ec..0000000000 --- a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeHelper.cs +++ /dev/null @@ -1,312 +0,0 @@ -using System; - -namespace Ryujinx.Graphics.Gal.Shader -{ - static class ShaderDecodeHelper - { - public static ShaderIrOperAbuf[] GetOperAbuf20(long OpCode) - { - int Abuf = (int)(OpCode >> 20) & 0x3ff; - int Reg = (int)(OpCode >> 39) & 0xff; - int Size = (int)(OpCode >> 47) & 3; - - ShaderIrOperAbuf[] Opers = new ShaderIrOperAbuf[Size + 1]; - - for (int Index = 0; Index <= Size; Index++) - { - Opers[Index] = new ShaderIrOperAbuf(Abuf + Index * 4, Reg); - } - - return Opers; - } - - public static ShaderIrOperAbuf GetOperAbuf28(long OpCode) - { - int Abuf = (int)(OpCode >> 28) & 0x3ff; - int Reg = (int)(OpCode >> 39) & 0xff; - - return new ShaderIrOperAbuf(Abuf, Reg); - } - - public static ShaderIrOperCbuf GetOperCbuf34(long OpCode) - { - return new ShaderIrOperCbuf( - (int)(OpCode >> 34) & 0x1f, - (int)(OpCode >> 20) & 0x3fff); - } - - public static ShaderIrOperCbuf GetOperCbuf36(long OpCode) - { - return new ShaderIrOperCbuf( - (int)(OpCode >> 36) & 0x1f, - (int)(OpCode >> 22) & 0x3fff, GetOperGpr8(OpCode)); - } - - public static ShaderIrOperGpr GetOperGpr8(long OpCode) - { - return new ShaderIrOperGpr((int)(OpCode >> 8) & 0xff); - } - - public static ShaderIrOperGpr GetOperGpr20(long OpCode) - { - return new ShaderIrOperGpr((int)(OpCode >> 20) & 0xff); - } - - public static ShaderIrOperGpr GetOperGpr39(long OpCode) - { - return new ShaderIrOperGpr((int)(OpCode >> 39) & 0xff); - } - - public static ShaderIrOperGpr GetOperGpr0(long OpCode) - { - return new ShaderIrOperGpr((int)(OpCode >> 0) & 0xff); - } - - public static ShaderIrOperGpr GetOperGpr28(long OpCode) - { - return new ShaderIrOperGpr((int)(OpCode >> 28) & 0xff); - } - - public static ShaderIrOperImm GetOperImm5_39(long OpCode) - { - return new ShaderIrOperImm((int)(OpCode >> 39) & 0x1f); - } - - public static ShaderIrOperImm GetOperImm13_36(long OpCode) - { - return new ShaderIrOperImm((int)(OpCode >> 36) & 0x1fff); - } - - public static ShaderIrOperImm GetOperImm32_20(long OpCode) - { - return new ShaderIrOperImm((int)(OpCode >> 20)); - } - - public static ShaderIrOperImmf GetOperImmf32_20(long OpCode) - { - return new ShaderIrOperImmf(BitConverter.Int32BitsToSingle((int)(OpCode >> 20))); - } - - public static ShaderIrOperImm GetOperImm19_20(long OpCode) - { - int Value = (int)(OpCode >> 20) & 0x7ffff; - - bool Neg = ((OpCode >> 56) & 1) != 0; - - if (Neg) - { - Value = -Value; - } - - return new ShaderIrOperImm((int)Value); - } - - public static ShaderIrOperImmf GetOperImmf19_20(long OpCode) - { - uint Imm = (uint)(OpCode >> 20) & 0x7ffff; - - bool Neg = ((OpCode >> 56) & 1) != 0; - - Imm <<= 12; - - if (Neg) - { - Imm |= 0x80000000; - } - - float Value = BitConverter.Int32BitsToSingle((int)Imm); - - return new ShaderIrOperImmf(Value); - } - - public static ShaderIrOperPred GetOperPred0(long OpCode) - { - return new ShaderIrOperPred((int)(OpCode >> 0) & 7); - } - - public static ShaderIrOperPred GetOperPred3(long OpCode) - { - return new ShaderIrOperPred((int)(OpCode >> 3) & 7); - } - - public static ShaderIrOperPred GetOperPred12(long OpCode) - { - return new ShaderIrOperPred((int)(OpCode >> 12) & 7); - } - - public static ShaderIrOperPred GetOperPred29(long OpCode) - { - return new ShaderIrOperPred((int)(OpCode >> 29) & 7); - } - - public static ShaderIrNode GetOperPred39N(long OpCode) - { - ShaderIrNode Node = GetOperPred39(OpCode); - - if (((OpCode >> 42) & 1) != 0) - { - Node = new ShaderIrOp(ShaderIrInst.Bnot, Node); - } - - return Node; - } - - public static ShaderIrOperPred GetOperPred39(long OpCode) - { - return new ShaderIrOperPred((int)(OpCode >> 39) & 7); - } - - public static ShaderIrInst GetCmp(long OpCode) - { - switch ((int)(OpCode >> 49) & 7) - { - case 1: return ShaderIrInst.Clt; - case 2: return ShaderIrInst.Ceq; - case 3: return ShaderIrInst.Cle; - case 4: return ShaderIrInst.Cgt; - case 5: return ShaderIrInst.Cne; - case 6: return ShaderIrInst.Cge; - } - - throw new ArgumentException(nameof(OpCode)); - } - - public static ShaderIrInst GetCmpF(long OpCode) - { - switch ((int)(OpCode >> 48) & 0xf) - { - case 0x1: return ShaderIrInst.Fclt; - case 0x2: return ShaderIrInst.Fceq; - case 0x3: return ShaderIrInst.Fcle; - case 0x4: return ShaderIrInst.Fcgt; - case 0x5: return ShaderIrInst.Fcne; - case 0x6: return ShaderIrInst.Fcge; - case 0x7: return ShaderIrInst.Fcnum; - case 0x8: return ShaderIrInst.Fcnan; - case 0x9: return ShaderIrInst.Fcltu; - case 0xa: return ShaderIrInst.Fcequ; - case 0xb: return ShaderIrInst.Fcleu; - case 0xc: return ShaderIrInst.Fcgtu; - case 0xd: return ShaderIrInst.Fcneu; - case 0xe: return ShaderIrInst.Fcgeu; - } - - throw new ArgumentException(nameof(OpCode)); - } - - public static ShaderIrInst GetBLop45(long OpCode) - { - switch ((int)(OpCode >> 45) & 3) - { - case 0: return ShaderIrInst.Band; - case 1: return ShaderIrInst.Bor; - case 2: return ShaderIrInst.Bxor; - } - - throw new ArgumentException(nameof(OpCode)); - } - - public static ShaderIrInst GetBLop24(long OpCode) - { - switch ((int)(OpCode >> 24) & 3) - { - case 0: return ShaderIrInst.Band; - case 1: return ShaderIrInst.Bor; - case 2: return ShaderIrInst.Bxor; - } - - throw new ArgumentException(nameof(OpCode)); - } - - public static ShaderIrNode GetPredNode(ShaderIrNode Node, long OpCode) - { - ShaderIrOperPred Pred = GetPredNode(OpCode); - - if (Pred.Index != ShaderIrOperPred.UnusedIndex) - { - bool Inv = ((OpCode >> 19) & 1) != 0; - - Node = new ShaderIrCond(Pred, Node, Inv); - } - - return Node; - } - - private static ShaderIrOperPred GetPredNode(long OpCode) - { - int Pred = (int)(OpCode >> 16) & 0xf; - - if (Pred != 0xf) - { - Pred &= 7; - } - - return new ShaderIrOperPred(Pred); - } - - public static ShaderIrNode GetAluFabsFneg(ShaderIrNode Node, bool Abs, bool Neg) - { - return GetAluFneg(GetAluFabs(Node, Abs), Neg); - } - - public static ShaderIrNode GetAluFabs(ShaderIrNode Node, bool Abs) - { - return Abs ? new ShaderIrOp(ShaderIrInst.Fabs, Node) : Node; - } - - public static ShaderIrNode GetAluFneg(ShaderIrNode Node, bool Neg) - { - return Neg ? new ShaderIrOp(ShaderIrInst.Fneg, Node) : Node; - } - - public static ShaderIrNode GetAluIabsIneg(ShaderIrNode Node, bool Abs, bool Neg) - { - return GetAluIneg(GetAluIabs(Node, Abs), Neg); - } - - public static ShaderIrNode GetAluIabs(ShaderIrNode Node, bool Abs) - { - return Abs ? new ShaderIrOp(ShaderIrInst.Abs, Node) : Node; - } - - public static ShaderIrNode GetAluIneg(ShaderIrNode Node, bool Neg) - { - return Neg ? new ShaderIrOp(ShaderIrInst.Neg, Node) : Node; - } - - public static ShaderIrNode GetAluNot(ShaderIrNode Node, bool Not) - { - return Not ? new ShaderIrOp(ShaderIrInst.Not, Node) : Node; - } - - public static ShaderIrNode ExtendTo32(ShaderIrNode Node, bool Signed, int Size) - { - int Shift = 32 - Size; - - ShaderIrInst RightShift = Signed - ? ShaderIrInst.Asr - : ShaderIrInst.Lsr; - - Node = new ShaderIrOp(ShaderIrInst.Lsl, Node, new ShaderIrOperImm(Shift)); - Node = new ShaderIrOp(RightShift, Node, new ShaderIrOperImm(Shift)); - - return Node; - } - - public static ShaderIrNode ExtendTo32(ShaderIrNode Node, bool Signed, ShaderIrNode Size) - { - ShaderIrOperImm WordSize = new ShaderIrOperImm(32); - - ShaderIrOp Shift = new ShaderIrOp(ShaderIrInst.Sub, WordSize, Size); - - ShaderIrInst RightShift = Signed - ? ShaderIrInst.Asr - : ShaderIrInst.Lsr; - - Node = new ShaderIrOp(ShaderIrInst.Lsl, Node, Shift); - Node = new ShaderIrOp(RightShift, Node, Shift); - - return Node; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMem.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMem.cs deleted file mode 100644 index 083b0c63a1..0000000000 --- a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMem.cs +++ /dev/null @@ -1,242 +0,0 @@ -using System; - -using static Ryujinx.Graphics.Gal.Shader.ShaderDecodeHelper; - -namespace Ryujinx.Graphics.Gal.Shader -{ - static partial class ShaderDecode - { - private const int TempRegStart = 0x100; - - private const int ____ = 0x0; - private const int R___ = 0x1; - private const int _G__ = 0x2; - private const int RG__ = 0x3; - private const int __B_ = 0x4; - private const int RGB_ = 0x7; - private const int ___A = 0x8; - private const int R__A = 0x9; - private const int _G_A = 0xa; - private const int RG_A = 0xb; - private const int __BA = 0xc; - private const int R_BA = 0xd; - private const int _GBA = 0xe; - private const int RGBA = 0xf; - - private static int[,] MaskLut = new int[,] - { - { ____, ____, ____, ____, ____, ____, ____, ____ }, - { R___, _G__, __B_, ___A, RG__, ____, ____, ____ }, - { R___, _G__, __B_, ___A, RG__, R__A, _G_A, __BA }, - { RGB_, RG_A, R_BA, _GBA, RGBA, ____, ____, ____ } - }; - - public static void Ld_A(ShaderIrBlock Block, long OpCode) - { - ShaderIrNode[] Opers = GetOperAbuf20(OpCode); - - int Index = 0; - - foreach (ShaderIrNode OperA in Opers) - { - ShaderIrOperGpr OperD = GetOperGpr0(OpCode); - - OperD.Index += Index++; - - Block.AddNode(GetPredNode(new ShaderIrAsg(OperD, OperA), OpCode)); - } - } - - public static void Ld_C(ShaderIrBlock Block, long OpCode) - { - int Type = (int)(OpCode >> 48) & 7; - - if (Type > 5) - { - throw new InvalidOperationException(); - } - - int Count = Type == 5 ? 2 : 1; - - for (int Index = 0; Index < Count; Index++) - { - ShaderIrOperCbuf OperA = GetOperCbuf36(OpCode); - ShaderIrOperGpr OperD = GetOperGpr0 (OpCode); - - OperA.Pos += Index; - OperD.Index += Index; - - ShaderIrNode Node = OperA; - - if (Type < 4) - { - //This is a 8 or 16 bits type. - bool Signed = (Type & 1) != 0; - - int Size = 8 << (Type >> 1); - - Node = ExtendTo32(Node, Signed, Size); - } - - Block.AddNode(GetPredNode(new ShaderIrAsg(OperD, Node), OpCode)); - } - } - - public static void St_A(ShaderIrBlock Block, long OpCode) - { - ShaderIrNode[] Opers = GetOperAbuf20(OpCode); - - int Index = 0; - - foreach (ShaderIrNode OperA in Opers) - { - ShaderIrOperGpr OperD = GetOperGpr0(OpCode); - - OperD.Index += Index++; - - Block.AddNode(GetPredNode(new ShaderIrAsg(OperA, OperD), OpCode)); - } - } - - public static void Texq(ShaderIrBlock Block, long OpCode) - { - ShaderIrNode OperD = GetOperGpr0(OpCode); - ShaderIrNode OperA = GetOperGpr8(OpCode); - - ShaderTexqInfo Info = (ShaderTexqInfo)((OpCode >> 22) & 0x1f); - - ShaderIrMetaTexq Meta0 = new ShaderIrMetaTexq(Info, 0); - ShaderIrMetaTexq Meta1 = new ShaderIrMetaTexq(Info, 1); - - ShaderIrNode OperC = GetOperImm13_36(OpCode); - - ShaderIrOp Op0 = new ShaderIrOp(ShaderIrInst.Texq, OperA, null, OperC, Meta0); - ShaderIrOp Op1 = new ShaderIrOp(ShaderIrInst.Texq, OperA, null, OperC, Meta1); - - Block.AddNode(GetPredNode(new ShaderIrAsg(OperD, Op0), OpCode)); - Block.AddNode(GetPredNode(new ShaderIrAsg(OperA, Op1), OpCode)); //Is this right? - } - - public static void Tex(ShaderIrBlock Block, long OpCode) - { - //TODO: Support other formats. - ShaderIrOperGpr[] Coords = new ShaderIrOperGpr[2]; - - for (int Index = 0; Index < Coords.Length; Index++) - { - Coords[Index] = GetOperGpr8(OpCode); - - Coords[Index].Index += Index; - - if (Coords[Index].Index > ShaderIrOperGpr.ZRIndex) - { - Coords[Index].Index = ShaderIrOperGpr.ZRIndex; - } - } - - int ChMask = (int)(OpCode >> 31) & 0xf; - - ShaderIrNode OperC = GetOperImm13_36(OpCode); - - for (int Ch = 0; Ch < 4; Ch++) - { - ShaderIrOperGpr Dst = new ShaderIrOperGpr(TempRegStart + Ch); - - ShaderIrMetaTex Meta = new ShaderIrMetaTex(Ch); - - ShaderIrOp Op = new ShaderIrOp(ShaderIrInst.Texs, Coords[0], Coords[1], OperC, Meta); - - Block.AddNode(GetPredNode(new ShaderIrAsg(Dst, Op), OpCode)); - } - - int RegInc = 0; - - for (int Ch = 0; Ch < 4; Ch++) - { - if (!IsChannelUsed(ChMask, Ch)) - { - continue; - } - - ShaderIrOperGpr Src = new ShaderIrOperGpr(TempRegStart + Ch); - - ShaderIrOperGpr Dst = GetOperGpr0(OpCode); - - Dst.Index += RegInc++; - - if (Dst.Index >= ShaderIrOperGpr.ZRIndex) - { - continue; - } - - Block.AddNode(GetPredNode(new ShaderIrAsg(Dst, Src), OpCode)); - } - } - - public static void Texs(ShaderIrBlock Block, long OpCode) - { - EmitTex(Block, OpCode, ShaderIrInst.Texs); - } - - public static void Tlds(ShaderIrBlock Block, long OpCode) - { - EmitTex(Block, OpCode, ShaderIrInst.Txlf); - } - - private static void EmitTex(ShaderIrBlock Block, long OpCode, ShaderIrInst Inst) - { - //TODO: Support other formats. - ShaderIrNode OperA = GetOperGpr8 (OpCode); - ShaderIrNode OperB = GetOperGpr20 (OpCode); - ShaderIrNode OperC = GetOperImm13_36(OpCode); - - int LutIndex; - - LutIndex = GetOperGpr0(OpCode).Index != ShaderIrOperGpr.ZRIndex ? 1 : 0; - LutIndex |= GetOperGpr28(OpCode).Index != ShaderIrOperGpr.ZRIndex ? 2 : 0; - - int ChMask = MaskLut[LutIndex, (OpCode >> 50) & 7]; - - for (int Ch = 0; Ch < 4; Ch++) - { - ShaderIrOperGpr Dst = new ShaderIrOperGpr(TempRegStart + Ch); - - ShaderIrMetaTex Meta = new ShaderIrMetaTex(Ch); - - ShaderIrOp Op = new ShaderIrOp(Inst, OperA, OperB, OperC, Meta); - - Block.AddNode(GetPredNode(new ShaderIrAsg(Dst, Op), OpCode)); - } - - int RegInc = 0; - - for (int Ch = 0; Ch < 4; Ch++) - { - if (!IsChannelUsed(ChMask, Ch)) - { - continue; - } - - ShaderIrOperGpr Src = new ShaderIrOperGpr(TempRegStart + Ch); - - ShaderIrOperGpr Dst = (RegInc >> 1) != 0 - ? GetOperGpr28(OpCode) - : GetOperGpr0 (OpCode); - - Dst.Index += RegInc++ & 1; - - if (Dst.Index >= ShaderIrOperGpr.ZRIndex) - { - continue; - } - - Block.AddNode(GetPredNode(new ShaderIrAsg(Dst, Src), OpCode)); - } - } - - private static bool IsChannelUsed(int ChMask, int Ch) - { - return (ChMask & (1 << Ch)) != 0; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMove.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMove.cs deleted file mode 100644 index dfcea905ad..0000000000 --- a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMove.cs +++ /dev/null @@ -1,374 +0,0 @@ -using System; - -using static Ryujinx.Graphics.Gal.Shader.ShaderDecodeHelper; - -namespace Ryujinx.Graphics.Gal.Shader -{ - static partial class ShaderDecode - { - private enum IntType - { - U8 = 0, - U16 = 1, - U32 = 2, - U64 = 3, - S8 = 4, - S16 = 5, - S32 = 6, - S64 = 7 - } - - private enum FloatType - { - F16 = 1, - F32 = 2, - F64 = 3 - } - - public static void F2f_C(ShaderIrBlock Block, long OpCode) - { - EmitF2f(Block, OpCode, ShaderOper.CR); - } - - public static void F2f_I(ShaderIrBlock Block, long OpCode) - { - EmitF2f(Block, OpCode, ShaderOper.Immf); - } - - public static void F2f_R(ShaderIrBlock Block, long OpCode) - { - EmitF2f(Block, OpCode, ShaderOper.RR); - } - - public static void F2i_C(ShaderIrBlock Block, long OpCode) - { - EmitF2i(Block, OpCode, ShaderOper.CR); - } - - public static void F2i_I(ShaderIrBlock Block, long OpCode) - { - EmitF2i(Block, OpCode, ShaderOper.Immf); - } - - public static void F2i_R(ShaderIrBlock Block, long OpCode) - { - EmitF2i(Block, OpCode, ShaderOper.RR); - } - - public static void I2f_C(ShaderIrBlock Block, long OpCode) - { - EmitI2f(Block, OpCode, ShaderOper.CR); - } - - public static void I2f_I(ShaderIrBlock Block, long OpCode) - { - EmitI2f(Block, OpCode, ShaderOper.Imm); - } - - public static void I2f_R(ShaderIrBlock Block, long OpCode) - { - EmitI2f(Block, OpCode, ShaderOper.RR); - } - - public static void I2i_C(ShaderIrBlock Block, long OpCode) - { - EmitI2i(Block, OpCode, ShaderOper.CR); - } - - public static void I2i_I(ShaderIrBlock Block, long OpCode) - { - EmitI2i(Block, OpCode, ShaderOper.Imm); - } - - public static void I2i_R(ShaderIrBlock Block, long OpCode) - { - EmitI2i(Block, OpCode, ShaderOper.RR); - } - - public static void Mov_C(ShaderIrBlock Block, long OpCode) - { - ShaderIrOperCbuf Cbuf = GetOperCbuf34(OpCode); - - Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Cbuf), OpCode)); - } - - public static void Mov_I(ShaderIrBlock Block, long OpCode) - { - ShaderIrOperImm Imm = GetOperImm19_20(OpCode); - - Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Imm), OpCode)); - } - - public static void Mov_I32(ShaderIrBlock Block, long OpCode) - { - ShaderIrOperImm Imm = GetOperImm32_20(OpCode); - - Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Imm), OpCode)); - } - - public static void Mov_R(ShaderIrBlock Block, long OpCode) - { - ShaderIrOperGpr Gpr = GetOperGpr20(OpCode); - - Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Gpr), OpCode)); - } - - private static void EmitF2f(ShaderIrBlock Block, long OpCode, ShaderOper Oper) - { - bool NegA = ((OpCode >> 45) & 1) != 0; - bool AbsA = ((OpCode >> 49) & 1) != 0; - - ShaderIrNode OperA; - - switch (Oper) - { - case ShaderOper.CR: OperA = GetOperCbuf34 (OpCode); break; - case ShaderOper.Immf: OperA = GetOperImmf19_20(OpCode); break; - case ShaderOper.RR: OperA = GetOperGpr20 (OpCode); break; - - default: throw new ArgumentException(nameof(Oper)); - } - - OperA = GetAluFabsFneg(OperA, AbsA, NegA); - - ShaderIrInst RoundInst = GetRoundInst(OpCode); - - if (RoundInst != ShaderIrInst.Invalid) - { - OperA = new ShaderIrOp(RoundInst, OperA); - } - - Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), OperA), OpCode)); - } - - private static void EmitF2i(ShaderIrBlock Block, long OpCode, ShaderOper Oper) - { - IntType Type = GetIntType(OpCode); - - if (Type == IntType.U64 || - Type == IntType.S64) - { - //TODO: 64-bits support. - //Note: GLSL doesn't support 64-bits integers. - throw new NotImplementedException(); - } - - bool NegA = ((OpCode >> 45) & 1) != 0; - bool AbsA = ((OpCode >> 49) & 1) != 0; - - ShaderIrNode OperA; - - switch (Oper) - { - case ShaderOper.CR: OperA = GetOperCbuf34 (OpCode); break; - case ShaderOper.Immf: OperA = GetOperImmf19_20(OpCode); break; - case ShaderOper.RR: OperA = GetOperGpr20 (OpCode); break; - - default: throw new ArgumentException(nameof(Oper)); - } - - OperA = GetAluFabsFneg(OperA, AbsA, NegA); - - ShaderIrInst RoundInst = GetRoundInst(OpCode); - - if (RoundInst != ShaderIrInst.Invalid) - { - OperA = new ShaderIrOp(RoundInst, OperA); - } - - bool Signed = Type >= IntType.S8; - - int Size = 8 << ((int)Type & 3); - - if (Size < 32) - { - uint Mask = uint.MaxValue >> (32 - Size); - - float CMin = 0; - float CMax = Mask; - - if (Signed) - { - uint HalfMask = Mask >> 1; - - CMin -= HalfMask + 1; - CMax = HalfMask; - } - - ShaderIrOperImmf IMin = new ShaderIrOperImmf(CMin); - ShaderIrOperImmf IMax = new ShaderIrOperImmf(CMax); - - OperA = new ShaderIrOp(ShaderIrInst.Fclamp, OperA, IMin, IMax); - } - - ShaderIrInst Inst = Signed - ? ShaderIrInst.Ftos - : ShaderIrInst.Ftou; - - ShaderIrNode Op = new ShaderIrOp(Inst, OperA); - - Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Op), OpCode)); - } - - private static void EmitI2f(ShaderIrBlock Block, long OpCode, ShaderOper Oper) - { - IntType Type = GetIntType(OpCode); - - if (Type == IntType.U64 || - Type == IntType.S64) - { - //TODO: 64-bits support. - //Note: GLSL doesn't support 64-bits integers. - throw new NotImplementedException(); - } - - int Sel = (int)(OpCode >> 41) & 3; - - bool NegA = ((OpCode >> 45) & 1) != 0; - bool AbsA = ((OpCode >> 49) & 1) != 0; - - ShaderIrNode OperA; - - switch (Oper) - { - case ShaderOper.CR: OperA = GetOperCbuf34 (OpCode); break; - case ShaderOper.Imm: OperA = GetOperImm19_20(OpCode); break; - case ShaderOper.RR: OperA = GetOperGpr20 (OpCode); break; - - default: throw new ArgumentException(nameof(Oper)); - } - - OperA = GetAluIabsIneg(OperA, AbsA, NegA); - - bool Signed = Type >= IntType.S8; - - int Shift = Sel * 8; - - int Size = 8 << ((int)Type & 3); - - if (Shift != 0) - { - OperA = new ShaderIrOp(ShaderIrInst.Asr, OperA, new ShaderIrOperImm(Shift)); - } - - if (Size < 32) - { - OperA = ExtendTo32(OperA, Signed, Size); - } - - ShaderIrInst Inst = Signed - ? ShaderIrInst.Stof - : ShaderIrInst.Utof; - - ShaderIrNode Op = new ShaderIrOp(Inst, OperA); - - Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Op), OpCode)); - } - - private static void EmitI2i(ShaderIrBlock Block, long OpCode, ShaderOper Oper) - { - IntType Type = GetIntType(OpCode); - - if (Type == IntType.U64 || - Type == IntType.S64) - { - //TODO: 64-bits support. - //Note: GLSL doesn't support 64-bits integers. - throw new NotImplementedException(); - } - - int Sel = (int)(OpCode >> 41) & 3; - - bool NegA = ((OpCode >> 45) & 1) != 0; - bool AbsA = ((OpCode >> 49) & 1) != 0; - bool SatA = ((OpCode >> 50) & 1) != 0; - - ShaderIrNode OperA; - - switch (Oper) - { - case ShaderOper.CR: OperA = GetOperCbuf34 (OpCode); break; - case ShaderOper.Immf: OperA = GetOperImmf19_20(OpCode); break; - case ShaderOper.RR: OperA = GetOperGpr20 (OpCode); break; - - default: throw new ArgumentException(nameof(Oper)); - } - - OperA = GetAluIabsIneg(OperA, AbsA, NegA); - - bool Signed = Type >= IntType.S8; - - int Shift = Sel * 8; - - int Size = 8 << ((int)Type & 3); - - if (Shift != 0) - { - OperA = new ShaderIrOp(ShaderIrInst.Asr, OperA, new ShaderIrOperImm(Shift)); - } - - if (Size < 32) - { - uint Mask = uint.MaxValue >> (32 - Size); - - if (SatA) - { - uint CMin = 0; - uint CMax = Mask; - - if (Signed) - { - uint HalfMask = Mask >> 1; - - CMin -= HalfMask + 1; - CMax = HalfMask; - } - - ShaderIrOperImm IMin = new ShaderIrOperImm((int)CMin); - ShaderIrOperImm IMax = new ShaderIrOperImm((int)CMax); - - OperA = new ShaderIrOp(Signed - ? ShaderIrInst.Clamps - : ShaderIrInst.Clampu, OperA, IMin, IMax); - } - else - { - OperA = ExtendTo32(OperA, Signed, Size); - } - } - - Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), OperA), OpCode)); - } - - private static IntType GetIntType(long OpCode) - { - bool Signed = ((OpCode >> 13) & 1) != 0; - - IntType Type = (IntType)((OpCode >> 10) & 3); - - if (Signed) - { - Type += (int)IntType.S8; - } - - return Type; - } - - private static FloatType GetFloatType(long OpCode) - { - return (FloatType)((OpCode >> 8) & 3); - } - - private static ShaderIrInst GetRoundInst(long OpCode) - { - switch ((OpCode >> 39) & 3) - { - case 1: return ShaderIrInst.Floor; - case 2: return ShaderIrInst.Ceil; - case 3: return ShaderIrInst.Trunc; - } - - return ShaderIrInst.Invalid; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecoder.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecoder.cs deleted file mode 100644 index 85522ff950..0000000000 --- a/Ryujinx.Graphics/Gal/Shader/ShaderDecoder.cs +++ /dev/null @@ -1,204 +0,0 @@ -using System.Collections.Generic; - -namespace Ryujinx.Graphics.Gal.Shader -{ - static class ShaderDecoder - { - private const bool AddDbgComments = true; - - public static ShaderIrBlock[] Decode(IGalMemory Memory, long Start) - { - Dictionary Visited = new Dictionary(); - Dictionary VisitedEnd = new Dictionary(); - - Queue Blocks = new Queue(); - - ShaderIrBlock Enqueue(long Position, ShaderIrBlock Source = null) - { - if (!Visited.TryGetValue(Position, out ShaderIrBlock Output)) - { - Output = new ShaderIrBlock(Position); - - Blocks.Enqueue(Output); - - Visited.Add(Position, Output); - } - - if (Source != null) - { - Output.Sources.Add(Source); - } - - return Output; - } - - ShaderIrBlock Entry = Enqueue(Start); - - while (Blocks.Count > 0) - { - ShaderIrBlock Current = Blocks.Dequeue(); - - FillBlock(Memory, Current); - - //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 - //or end of shader, Next is null. - if (Current.Nodes.Count > 0) - { - ShaderIrNode LastNode = Current.GetLastNode(); - - ShaderIrOp Op = GetInnermostOp(LastNode); - - if (Op?.Inst == ShaderIrInst.Bra) - { - int Offset = ((ShaderIrOperImm)Op.OperandA).Value; - - long Target = Current.EndPosition + Offset; - - Current.Branch = Enqueue(Target, Current); - } - - if (NodeHasNext(LastNode)) - { - Current.Next = Enqueue(Current.EndPosition); - } - } - - //If we have on the graph two blocks with the same end position, - //then we need to split the bigger block and have two small blocks, - //the end position of the bigger "Current" block should then be == to - //the position of the "Smaller" block. - while (VisitedEnd.TryGetValue(Current.EndPosition, out ShaderIrBlock Smaller)) - { - if (Current.Position > Smaller.Position) - { - ShaderIrBlock Temp = Smaller; - - Smaller = Current; - Current = Temp; - } - - Current.EndPosition = Smaller.Position; - Current.Next = Smaller; - Current.Branch = null; - - Current.Nodes.RemoveRange( - Current.Nodes.Count - Smaller.Nodes.Count, - Smaller.Nodes.Count); - - VisitedEnd[Smaller.EndPosition] = Smaller; - } - - VisitedEnd.Add(Current.EndPosition, Current); - } - - //Make and sort Graph blocks array by position. - ShaderIrBlock[] Graph = new ShaderIrBlock[Visited.Count]; - - while (Visited.Count > 0) - { - ulong FirstPos = ulong.MaxValue; - - foreach (ShaderIrBlock Block in Visited.Values) - { - if (FirstPos > (ulong)Block.Position) - FirstPos = (ulong)Block.Position; - } - - ShaderIrBlock Current = Visited[(long)FirstPos]; - - do - { - Graph[Graph.Length - Visited.Count] = Current; - - Visited.Remove(Current.Position); - - Current = Current.Next; - } - while (Current != null); - } - - return Graph; - } - - private static void FillBlock(IGalMemory Memory, ShaderIrBlock Block) - { - long Position = Block.Position; - - do - { - //Ignore scheduling instructions, which are written every 32 bytes. - if ((Position & 0x1f) == 0) - { - Position += 8; - - continue; - } - - uint Word0 = (uint)Memory.ReadInt32(Position + 0); - uint Word1 = (uint)Memory.ReadInt32(Position + 4); - - Position += 8; - - long OpCode = Word0 | (long)Word1 << 32; - - ShaderDecodeFunc Decode = ShaderOpCodeTable.GetDecoder(OpCode); - - if (AddDbgComments) - { - string DbgOpCode = $"0x{(Position - 8):x16}: 0x{OpCode:x16} "; - - DbgOpCode += (Decode?.Method.Name ?? "???"); - - if (Decode == ShaderDecode.Bra) - { - int Offset = ((int)(OpCode >> 20) << 8) >> 8; - - long Target = Position + Offset; - - DbgOpCode += " (0x" + Target.ToString("x16") + ")"; - } - - Block.AddNode(new ShaderIrCmnt(DbgOpCode)); - } - - if (Decode == null) - { - continue; - } - - Decode(Block, OpCode); - } - while (!IsFlowChange(Block.GetLastNode())); - - Block.EndPosition = Position; - } - - private static bool IsFlowChange(ShaderIrNode Node) - { - return !NodeHasNext(GetInnermostOp(Node)); - } - - private static ShaderIrOp GetInnermostOp(ShaderIrNode Node) - { - if (Node is ShaderIrCond Cond) - { - Node = Cond.Child; - } - - return Node is ShaderIrOp Op ? Op : null; - } - - private static bool NodeHasNext(ShaderIrNode Node) - { - if (!(Node is ShaderIrOp Op)) - { - return true; - } - - return Op.Inst != ShaderIrInst.Exit && - Op.Inst != ShaderIrInst.Bra; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrAsg.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrAsg.cs deleted file mode 100644 index 00f8f6a5e5..0000000000 --- a/Ryujinx.Graphics/Gal/Shader/ShaderIrAsg.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Ryujinx.Graphics.Gal.Shader -{ - class ShaderIrAsg : ShaderIrNode - { - public ShaderIrNode Dst { get; set; } - public ShaderIrNode Src { get; set; } - - public ShaderIrAsg(ShaderIrNode Dst, ShaderIrNode Src) - { - this.Dst = Dst; - this.Src = Src; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrBlock.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrBlock.cs deleted file mode 100644 index 50e563b846..0000000000 --- a/Ryujinx.Graphics/Gal/Shader/ShaderIrBlock.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System.Collections.Generic; - -namespace Ryujinx.Graphics.Gal.Shader -{ - class ShaderIrBlock - { - public long Position { get; set; } - public long EndPosition { get; set; } - - public ShaderIrBlock Next { get; set; } - public ShaderIrBlock Branch { get; set; } - - public List Sources { get; private set; } - - public List Nodes { get; private set; } - - public ShaderIrBlock(long Position) - { - this.Position = Position; - - Sources = new List(); - - Nodes = new List(); - } - - public void AddNode(ShaderIrNode Node) - { - Nodes.Add(Node); - } - - public ShaderIrNode[] GetNodes() - { - return Nodes.ToArray(); - } - - public ShaderIrNode GetLastNode() - { - if (Nodes.Count > 0) - { - return Nodes[Nodes.Count - 1]; - } - - return null; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrCmnt.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrCmnt.cs deleted file mode 100644 index 03031ec5b7..0000000000 --- a/Ryujinx.Graphics/Gal/Shader/ShaderIrCmnt.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Ryujinx.Graphics.Gal.Shader -{ - class ShaderIrCmnt : ShaderIrNode - { - public string Comment { get; private set; } - - public ShaderIrCmnt(string Comment) - { - this.Comment = Comment; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrCond.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrCond.cs deleted file mode 100644 index 8fb01660cf..0000000000 --- a/Ryujinx.Graphics/Gal/Shader/ShaderIrCond.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Ryujinx.Graphics.Gal.Shader -{ - class ShaderIrCond : ShaderIrNode - { - public ShaderIrNode Pred { get; set; } - public ShaderIrNode Child { get; set; } - - public bool Not { get; private set; } - - public ShaderIrCond(ShaderIrNode Pred, ShaderIrNode Child, bool Not) - { - this.Pred = Pred; - this.Child = Child; - this.Not = Not; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrInst.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrInst.cs deleted file mode 100644 index 9841f58ff0..0000000000 --- a/Ryujinx.Graphics/Gal/Shader/ShaderIrInst.cs +++ /dev/null @@ -1,87 +0,0 @@ -namespace Ryujinx.Graphics.Gal.Shader -{ - enum ShaderIrInst - { - Invalid, - - B_Start, - Band, - Bnot, - Bor, - Bxor, - B_End, - - F_Start, - Ceil, - - Fabs, - Fadd, - Fceq, - Fcequ, - Fcge, - Fcgeu, - Fcgt, - Fcgtu, - Fclamp, - Fcle, - Fcleu, - Fclt, - Fcltu, - Fcnan, - Fcne, - Fcneu, - Fcnum, - Fcos, - Fex2, - Ffma, - Flg2, - Floor, - Fmax, - Fmin, - Fmul, - Fneg, - Frcp, - Frsq, - Fsin, - Fsqrt, - Ftos, - Ftou, - Ipa, - Texs, - Trunc, - F_End, - - I_Start, - Abs, - Add, - And, - Asr, - Ceq, - Cge, - Cgt, - Clamps, - Clampu, - Cle, - Clt, - Cne, - Lsl, - Lsr, - Max, - Min, - Mul, - Neg, - Not, - Or, - Stof, - Sub, - Texq, - Txlf, - Utof, - Xor, - I_End, - - Bra, - Exit, - Kil - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrMeta.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrMeta.cs deleted file mode 100644 index afb7503be8..0000000000 --- a/Ryujinx.Graphics/Gal/Shader/ShaderIrMeta.cs +++ /dev/null @@ -1,4 +0,0 @@ -namespace Ryujinx.Graphics.Gal.Shader -{ - class ShaderIrMeta { } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrMetaTex.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrMetaTex.cs deleted file mode 100644 index 82f3bb774a..0000000000 --- a/Ryujinx.Graphics/Gal/Shader/ShaderIrMetaTex.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Ryujinx.Graphics.Gal.Shader -{ - class ShaderIrMetaTex : ShaderIrMeta - { - public int Elem { get; private set; } - - public ShaderIrMetaTex(int Elem) - { - this.Elem = Elem; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrMetaTexq.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrMetaTexq.cs deleted file mode 100644 index 92871137f6..0000000000 --- a/Ryujinx.Graphics/Gal/Shader/ShaderIrMetaTexq.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Ryujinx.Graphics.Gal.Shader -{ - class ShaderIrMetaTexq : ShaderIrMeta - { - public ShaderTexqInfo Info { get; private set; } - - public int Elem { get; private set; } - - public ShaderIrMetaTexq(ShaderTexqInfo Info, int Elem) - { - this.Info = Info; - this.Elem = Elem; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrNode.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrNode.cs deleted file mode 100644 index 2648164a11..0000000000 --- a/Ryujinx.Graphics/Gal/Shader/ShaderIrNode.cs +++ /dev/null @@ -1,4 +0,0 @@ -namespace Ryujinx.Graphics.Gal.Shader -{ - class ShaderIrNode { } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrOp.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrOp.cs deleted file mode 100644 index 12a6123c31..0000000000 --- a/Ryujinx.Graphics/Gal/Shader/ShaderIrOp.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace Ryujinx.Graphics.Gal.Shader -{ - class ShaderIrOp : ShaderIrNode - { - public ShaderIrInst Inst { get; private set; } - public ShaderIrNode OperandA { get; set; } - public ShaderIrNode OperandB { get; set; } - public ShaderIrNode OperandC { get; set; } - public ShaderIrMeta MetaData { get; set; } - - public ShaderIrOp( - ShaderIrInst Inst, - ShaderIrNode OperandA = null, - ShaderIrNode OperandB = null, - ShaderIrNode OperandC = null, - ShaderIrMeta MetaData = null) - { - this.Inst = Inst; - this.OperandA = OperandA; - this.OperandB = OperandB; - this.OperandC = OperandC; - this.MetaData = MetaData; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperAbuf.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperAbuf.cs deleted file mode 100644 index fa612de76a..0000000000 --- a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperAbuf.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Ryujinx.Graphics.Gal.Shader -{ - class ShaderIrOperAbuf : ShaderIrNode - { - public int Offs { get; private set; } - public int GprIndex { get; private set; } - - public ShaderIrOperAbuf(int Offs, int GprIndex) - { - this.Offs = Offs; - this.GprIndex = GprIndex; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperCbuf.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperCbuf.cs deleted file mode 100644 index b040c5c63e..0000000000 --- a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperCbuf.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Ryujinx.Graphics.Gal.Shader -{ - class ShaderIrOperCbuf : ShaderIrNode - { - public int Index { get; private set; } - public int Pos { get; set; } - - public ShaderIrNode Offs { get; private set; } - - public ShaderIrOperCbuf(int Index, int Pos, ShaderIrNode Offs = null) - { - this.Index = Index; - this.Pos = Pos; - this.Offs = Offs; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperGpr.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperGpr.cs deleted file mode 100644 index 5c69d6a67a..0000000000 --- a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperGpr.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace Ryujinx.Graphics.Gal.Shader -{ - class ShaderIrOperGpr : ShaderIrNode - { - public const int ZRIndex = 0xff; - - public bool IsConst => Index == ZRIndex; - - public int Index { get; set; } - - public ShaderIrOperGpr(int Index) - { - this.Index = Index; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperImm.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperImm.cs deleted file mode 100644 index ba2c2c9b24..0000000000 --- a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperImm.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Ryujinx.Graphics.Gal.Shader -{ - class ShaderIrOperImm : ShaderIrNode - { - public int Value { get; private set; } - - public ShaderIrOperImm(int Value) - { - this.Value = Value; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperImmf.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperImmf.cs deleted file mode 100644 index 3c27e48361..0000000000 --- a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperImmf.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Ryujinx.Graphics.Gal.Shader -{ - class ShaderIrOperImmf : ShaderIrNode - { - public float Value { get; private set; } - - public ShaderIrOperImmf(float Value) - { - this.Value = Value; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperPred.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperPred.cs deleted file mode 100644 index 74cca0efef..0000000000 --- a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperPred.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Ryujinx.Graphics.Gal.Shader -{ - class ShaderIrOperPred : ShaderIrNode - { - public const int UnusedIndex = 0x7; - public const int NeverExecute = 0xf; - - public bool IsConst => Index >= UnusedIndex; - - public int Index { get; set; } - - public ShaderIrOperPred(int Index) - { - this.Index = Index; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderOpCodeTable.cs b/Ryujinx.Graphics/Gal/Shader/ShaderOpCodeTable.cs deleted file mode 100644 index b4f51e5084..0000000000 --- a/Ryujinx.Graphics/Gal/Shader/ShaderOpCodeTable.cs +++ /dev/null @@ -1,163 +0,0 @@ -using System; - -namespace Ryujinx.Graphics.Gal.Shader -{ - static class ShaderOpCodeTable - { - private const int EncodingBits = 14; - - private class ShaderDecodeEntry - { - public ShaderDecodeFunc Func; - - public int XBits; - - public ShaderDecodeEntry(ShaderDecodeFunc Func, int XBits) - { - this.Func = Func; - this.XBits = XBits; - } - } - - private static ShaderDecodeEntry[] OpCodes; - - static ShaderOpCodeTable() - { - OpCodes = new ShaderDecodeEntry[1 << EncodingBits]; - -#region Instructions - Set("0100110000000x", ShaderDecode.Bfe_C); - Set("0011100x00000x", ShaderDecode.Bfe_I); - Set("0101110000000x", ShaderDecode.Bfe_R); - Set("111000100100xx", ShaderDecode.Bra); - Set("111000110000xx", ShaderDecode.Exit); - Set("0100110010101x", ShaderDecode.F2f_C); - Set("0011100x10101x", ShaderDecode.F2f_I); - Set("0101110010101x", ShaderDecode.F2f_R); - Set("0100110010110x", ShaderDecode.F2i_C); - Set("0011100x10110x", ShaderDecode.F2i_I); - Set("0101110010110x", ShaderDecode.F2i_R); - Set("0100110001011x", ShaderDecode.Fadd_C); - Set("0011100x01011x", ShaderDecode.Fadd_I); - Set("000010xxxxxxxx", ShaderDecode.Fadd_I32); - Set("0101110001011x", ShaderDecode.Fadd_R); - Set("010010011xxxxx", ShaderDecode.Ffma_CR); - Set("0011001x1xxxxx", ShaderDecode.Ffma_I); - Set("010100011xxxxx", ShaderDecode.Ffma_RC); - Set("010110011xxxxx", ShaderDecode.Ffma_RR); - Set("0100110001101x", ShaderDecode.Fmul_C); - Set("0011100x01101x", ShaderDecode.Fmul_I); - Set("00011110xxxxxx", ShaderDecode.Fmul_I32); - Set("0101110001101x", ShaderDecode.Fmul_R); - Set("0100110001100x", ShaderDecode.Fmnmx_C); - Set("0011100x01100x", ShaderDecode.Fmnmx_I); - Set("0101110001100x", ShaderDecode.Fmnmx_R); - Set("0100100xxxxxxx", ShaderDecode.Fset_C); - Set("0011000xxxxxxx", ShaderDecode.Fset_I); - Set("01011000xxxxxx", ShaderDecode.Fset_R); - Set("010010111011xx", ShaderDecode.Fsetp_C); - Set("0011011x1011xx", ShaderDecode.Fsetp_I); - Set("010110111011xx", ShaderDecode.Fsetp_R); - Set("0100110010111x", ShaderDecode.I2f_C); - Set("0011100x10111x", ShaderDecode.I2f_I); - Set("0101110010111x", ShaderDecode.I2f_R); - Set("0100110011100x", ShaderDecode.I2i_C); - Set("0011100x11100x", ShaderDecode.I2i_I); - Set("0101110011100x", ShaderDecode.I2i_R); - Set("0100110000100x", ShaderDecode.Imnmx_C); - Set("0011100x00100x", ShaderDecode.Imnmx_I); - Set("0101110000100x", ShaderDecode.Imnmx_R); - Set("11100000xxxxxx", ShaderDecode.Ipa); - Set("0100110000011x", ShaderDecode.Iscadd_C); - Set("0011100x00011x", ShaderDecode.Iscadd_I); - Set("0101110000011x", ShaderDecode.Iscadd_R); - Set("010010110110xx", ShaderDecode.Isetp_C); - Set("0011011x0110xx", ShaderDecode.Isetp_I); - Set("010110110110xx", ShaderDecode.Isetp_R); - Set("111000110011xx", ShaderDecode.Kil); - Set("1110111111011x", ShaderDecode.Ld_A); - Set("1110111110010x", ShaderDecode.Ld_C); - Set("000001xxxxxxxx", ShaderDecode.Lop_I32); - Set("0100110010011x", ShaderDecode.Mov_C); - Set("0011100x10011x", ShaderDecode.Mov_I); - Set("000000010000xx", ShaderDecode.Mov_I32); - Set("0101110010011x", ShaderDecode.Mov_R); - Set("0101000010000x", ShaderDecode.Mufu); - Set("0101000010010x", ShaderDecode.Psetp); - Set("0100110010010x", ShaderDecode.Rro_C); - Set("0011100x10010x", ShaderDecode.Rro_I); - Set("0101110010010x", ShaderDecode.Rro_R); - Set("0100110001001x", ShaderDecode.Shl_C); - Set("0011100x01001x", ShaderDecode.Shl_I); - Set("0101110001001x", ShaderDecode.Shl_R); - Set("0100110000101x", ShaderDecode.Shr_C); - Set("0011100x00101x", ShaderDecode.Shr_I); - Set("0101110000101x", ShaderDecode.Shr_R); - Set("1110111111110x", ShaderDecode.St_A); - Set("110000xxxx111x", ShaderDecode.Tex); - Set("1101111101001x", ShaderDecode.Texq); - Set("1101100xxxxxxx", ShaderDecode.Texs); - Set("1101101xxxxxxx", ShaderDecode.Tlds); - Set("0100111xxxxxxx", ShaderDecode.Xmad_CR); - Set("0011011x00xxxx", ShaderDecode.Xmad_I); - Set("010100010xxxxx", ShaderDecode.Xmad_RC); - Set("0101101100xxxx", ShaderDecode.Xmad_RR); -#endregion - } - - private static void Set(string Encoding, ShaderDecodeFunc Func) - { - if (Encoding.Length != EncodingBits) - { - throw new ArgumentException(nameof(Encoding)); - } - - int Bit = Encoding.Length - 1; - int Value = 0; - int XMask = 0; - int XBits = 0; - - int[] XPos = new int[Encoding.Length]; - - for (int Index = 0; Index < Encoding.Length; Index++, Bit--) - { - char Chr = Encoding[Index]; - - if (Chr == '1') - { - Value |= 1 << Bit; - } - else if (Chr == 'x') - { - XMask |= 1 << Bit; - - XPos[XBits++] = Bit; - } - } - - XMask = ~XMask; - - ShaderDecodeEntry Entry = new ShaderDecodeEntry(Func, XBits); - - for (int Index = 0; Index < (1 << XBits); Index++) - { - Value &= XMask; - - for (int X = 0; X < XBits; X++) - { - Value |= ((Index >> X) & 1) << XPos[X]; - } - - if (OpCodes[Value] == null || OpCodes[Value].XBits > XBits) - { - OpCodes[Value] = Entry; - } - } - } - - public static ShaderDecodeFunc GetDecoder(long OpCode) - { - return OpCodes[(ulong)OpCode >> (64 - EncodingBits)]?.Func; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderOper.cs b/Ryujinx.Graphics/Gal/Shader/ShaderOper.cs deleted file mode 100644 index aa48548282..0000000000 --- a/Ryujinx.Graphics/Gal/Shader/ShaderOper.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Ryujinx.Graphics.Gal.Shader -{ - enum ShaderOper - { - CR, - Imm, - Immf, - RC, - RR - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderTexqInfo.cs b/Ryujinx.Graphics/Gal/Shader/ShaderTexqInfo.cs deleted file mode 100644 index 9158662ccd..0000000000 --- a/Ryujinx.Graphics/Gal/Shader/ShaderTexqInfo.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Ryujinx.Graphics.Gal.Shader -{ - enum ShaderTexqInfo - { - Dimension = 1, - TextureType = 2, - SamplePos = 5, - Filter = 16, - Lod = 18, - Wrap = 20, - BorderColor = 22 - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/ShaderDeclInfo.cs b/Ryujinx.Graphics/Gal/ShaderDeclInfo.cs deleted file mode 100644 index d400850c86..0000000000 --- a/Ryujinx.Graphics/Gal/ShaderDeclInfo.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace Ryujinx.Graphics.Gal -{ - public class ShaderDeclInfo - { - public string Name { get; private set; } - - public int Index { get; private set; } - public int Cbuf { get; private set; } - public int Size { get; private set; } - - public ShaderDeclInfo(string Name, int Index, int Cbuf = 0, int Size = 1) - { - this.Name = Name; - this.Index = Index; - this.Cbuf = Cbuf; - this.Size = Size; - } - - internal void Enlarge(int NewSize) - { - if (Size < NewSize) - { - Size = NewSize; - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/ShaderException.cs b/Ryujinx.Graphics/Gal/ShaderException.cs deleted file mode 100644 index 9bc87ff3db..0000000000 --- a/Ryujinx.Graphics/Gal/ShaderException.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; - -namespace Ryujinx.Graphics.Gal -{ - class ShaderException : Exception - { - public ShaderException() : base() { } - - public ShaderException(string Message) : base(Message) { } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Texture/ASTCDecoder.cs b/Ryujinx.Graphics/Gal/Texture/ASTCDecoder.cs deleted file mode 100644 index da1b9ef410..0000000000 --- a/Ryujinx.Graphics/Gal/Texture/ASTCDecoder.cs +++ /dev/null @@ -1,1384 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; - -namespace Ryujinx.Graphics.Gal.Texture -{ - public class ASTCDecoderException : Exception - { - public ASTCDecoderException(string ExMsg) : base(ExMsg) { } - } - - //https://github.com/GammaUNC/FasTC/blob/master/ASTCEncoder/src/Decompressor.cpp - public static class ASTCDecoder - { - struct TexelWeightParams - { - public int Width; - public int Height; - public bool DualPlane; - public int MaxWeight; - public bool Error; - public bool VoidExtentLDR; - public bool VoidExtentHDR; - - public int GetPackedBitSize() - { - // How many indices do we have? - int Indices = Height * Width; - - if (DualPlane) - { - Indices *= 2; - } - - IntegerEncoded IntEncoded = IntegerEncoded.CreateEncoding(MaxWeight); - - return IntEncoded.GetBitLength(Indices); - } - - public int GetNumWeightValues() - { - int Ret = Width * Height; - - if (DualPlane) - { - Ret *= 2; - } - - return Ret; - } - } - - public static byte[] DecodeToRGBA8888( - byte[] InputBuffer, - int BlockX, - int BlockY, - int BlockZ, - int X, - int Y, - int Z) - { - using (MemoryStream InputStream = new MemoryStream(InputBuffer)) - { - BinaryReader BinReader = new BinaryReader(InputStream); - - if (BlockX > 12 || BlockY > 12) - { - throw new ASTCDecoderException("Block size unsupported!"); - } - - if (BlockZ != 1 || Z != 1) - { - throw new ASTCDecoderException("3D compressed textures unsupported!"); - } - - using (MemoryStream OutputStream = new MemoryStream()) - { - int BlockIndex = 0; - - for (int j = 0; j < Y; j += BlockY) - { - for (int i = 0; i < X; i += BlockX) - { - int[] DecompressedData = new int[144]; - - DecompressBlock(BinReader.ReadBytes(0x10), DecompressedData, BlockX, BlockY); - - int DecompressedWidth = Math.Min(BlockX, X - i); - int DecompressedHeight = Math.Min(BlockY, Y - j); - int BaseOffsets = (j * X + i) * 4; - - for (int jj = 0; jj < DecompressedHeight; jj++) - { - OutputStream.Seek(BaseOffsets + jj * X * 4, SeekOrigin.Begin); - - byte[] OutputBuffer = new byte[DecompressedData.Length * sizeof(int)]; - Buffer.BlockCopy(DecompressedData, 0, OutputBuffer, 0, OutputBuffer.Length); - - OutputStream.Write(OutputBuffer, jj * BlockX * 4, DecompressedWidth * 4); - } - - BlockIndex++; - } - } - - return OutputStream.ToArray(); - } - } - } - - public static bool DecompressBlock( - byte[] InputBuffer, - int[] OutputBuffer, - int BlockWidth, - int BlockHeight) - { - BitArrayStream BitStream = new BitArrayStream(new BitArray(InputBuffer)); - TexelWeightParams TexelParams = DecodeBlockInfo(BitStream); - - if (TexelParams.Error) - { - throw new ASTCDecoderException("Invalid block mode"); - } - - if (TexelParams.VoidExtentLDR) - { - FillVoidExtentLDR(BitStream, OutputBuffer, BlockWidth, BlockHeight); - - return true; - } - - if (TexelParams.VoidExtentHDR) - { - throw new ASTCDecoderException("HDR void extent blocks are unsupported!"); - } - - if (TexelParams.Width > BlockWidth) - { - throw new ASTCDecoderException("Texel weight grid width should be smaller than block width"); - } - - if (TexelParams.Height > BlockHeight) - { - throw new ASTCDecoderException("Texel weight grid height should be smaller than block height"); - } - - // Read num partitions - int NumberPartitions = BitStream.ReadBits(2) + 1; - Debug.Assert(NumberPartitions <= 4); - - if (NumberPartitions == 4 && TexelParams.DualPlane) - { - throw new ASTCDecoderException("Dual plane mode is incompatible with four partition blocks"); - } - - // Based on the number of partitions, read the color endpoint mode for - // each partition. - - // Determine partitions, partition index, and color endpoint modes - int PlaneIndices = -1; - int PartitionIndex; - uint[] ColorEndpointMode = { 0, 0, 0, 0 }; - - BitArrayStream ColorEndpointStream = new BitArrayStream(new BitArray(16 * 8)); - - // Read extra config data... - uint BaseColorEndpointMode = 0; - - if (NumberPartitions == 1) - { - ColorEndpointMode[0] = (uint)BitStream.ReadBits(4); - PartitionIndex = 0; - } - else - { - PartitionIndex = BitStream.ReadBits(10); - BaseColorEndpointMode = (uint)BitStream.ReadBits(6); - } - - uint BaseMode = (BaseColorEndpointMode & 3); - - // Remaining bits are color endpoint data... - int NumberWeightBits = TexelParams.GetPackedBitSize(); - int RemainingBits = 128 - NumberWeightBits - BitStream.Position; - - // Consider extra bits prior to texel data... - uint ExtraColorEndpointModeBits = 0; - - if (BaseMode != 0) - { - switch (NumberPartitions) - { - case 2: ExtraColorEndpointModeBits += 2; break; - case 3: ExtraColorEndpointModeBits += 5; break; - case 4: ExtraColorEndpointModeBits += 8; break; - default: Debug.Assert(false); break; - } - } - - RemainingBits -= (int)ExtraColorEndpointModeBits; - - // Do we have a dual plane situation? - int PlaneSelectorBits = 0; - - if (TexelParams.DualPlane) - { - PlaneSelectorBits = 2; - } - - RemainingBits -= PlaneSelectorBits; - - // Read color data... - int ColorDataBits = RemainingBits; - - while (RemainingBits > 0) - { - int NumberBits = Math.Min(RemainingBits, 8); - int Bits = BitStream.ReadBits(NumberBits); - ColorEndpointStream.WriteBits(Bits, NumberBits); - RemainingBits -= 8; - } - - // Read the plane selection bits - PlaneIndices = BitStream.ReadBits(PlaneSelectorBits); - - // Read the rest of the CEM - if (BaseMode != 0) - { - uint ExtraColorEndpointMode = (uint)BitStream.ReadBits((int)ExtraColorEndpointModeBits); - uint TempColorEndpointMode = (ExtraColorEndpointMode << 6) | BaseColorEndpointMode; - TempColorEndpointMode >>= 2; - - bool[] C = new bool[4]; - - for (int i = 0; i < NumberPartitions; i++) - { - C[i] = (TempColorEndpointMode & 1) != 0; - TempColorEndpointMode >>= 1; - } - - byte[] M = new byte[4]; - - for (int i = 0; i < NumberPartitions; i++) - { - M[i] = (byte)(TempColorEndpointMode & 3); - TempColorEndpointMode >>= 2; - Debug.Assert(M[i] <= 3); - } - - for (int i = 0; i < NumberPartitions; i++) - { - ColorEndpointMode[i] = BaseMode; - if (!(C[i])) ColorEndpointMode[i] -= 1; - ColorEndpointMode[i] <<= 2; - ColorEndpointMode[i] |= M[i]; - } - } - else if (NumberPartitions > 1) - { - uint TempColorEndpointMode = BaseColorEndpointMode >> 2; - - for (uint i = 0; i < NumberPartitions; i++) - { - ColorEndpointMode[i] = TempColorEndpointMode; - } - } - - // Make sure everything up till here is sane. - for (int i = 0; i < NumberPartitions; i++) - { - Debug.Assert(ColorEndpointMode[i] < 16); - } - Debug.Assert(BitStream.Position + TexelParams.GetPackedBitSize() == 128); - - // Decode both color data and texel weight data - int[] ColorValues = new int[32]; // Four values * two endpoints * four maximum partitions - DecodeColorValues(ColorValues, ColorEndpointStream.ToByteArray(), ColorEndpointMode, NumberPartitions, ColorDataBits); - - ASTCPixel[][] EndPoints = new ASTCPixel[4][]; - EndPoints[0] = new ASTCPixel[2]; - EndPoints[1] = new ASTCPixel[2]; - EndPoints[2] = new ASTCPixel[2]; - EndPoints[3] = new ASTCPixel[2]; - - int ColorValuesPosition = 0; - - for (int i = 0; i < NumberPartitions; i++) - { - ComputeEndpoints(EndPoints[i], ColorValues, ColorEndpointMode[i], ref ColorValuesPosition); - } - - // Read the texel weight data. - byte[] TexelWeightData = (byte[])InputBuffer.Clone(); - - // Reverse everything - for (int i = 0; i < 8; i++) - { - byte a = ReverseByte(TexelWeightData[i]); - byte b = ReverseByte(TexelWeightData[15 - i]); - - TexelWeightData[i] = b; - TexelWeightData[15 - i] = a; - } - - // Make sure that higher non-texel bits are set to zero - int ClearByteStart = (TexelParams.GetPackedBitSize() >> 3) + 1; - TexelWeightData[ClearByteStart - 1] &= (byte)((1 << (TexelParams.GetPackedBitSize() % 8)) - 1); - - int cLen = 16 - ClearByteStart; - for (int i = ClearByteStart; i < ClearByteStart + cLen; i++) TexelWeightData[i] = 0; - - List TexelWeightValues = new List(); - BitArrayStream WeightBitStream = new BitArrayStream(new BitArray(TexelWeightData)); - - IntegerEncoded.DecodeIntegerSequence(TexelWeightValues, WeightBitStream, TexelParams.MaxWeight, TexelParams.GetNumWeightValues()); - - // Blocks can be at most 12x12, so we can have as many as 144 weights - int[][] Weights = new int[2][]; - Weights[0] = new int[144]; - Weights[1] = new int[144]; - - UnquantizeTexelWeights(Weights, TexelWeightValues, TexelParams, BlockWidth, BlockHeight); - - // Now that we have endpoints and weights, we can interpolate and generate - // the proper decoding... - for (int j = 0; j < BlockHeight; j++) - { - for (int i = 0; i < BlockWidth; i++) - { - int Partition = Select2DPartition(PartitionIndex, i, j, NumberPartitions, ((BlockHeight * BlockWidth) < 32)); - Debug.Assert(Partition < NumberPartitions); - - ASTCPixel Pixel = new ASTCPixel(0, 0, 0, 0); - for (int Component = 0; Component < 4; Component++) - { - int Component0 = EndPoints[Partition][0].GetComponent(Component); - Component0 = BitArrayStream.Replicate(Component0, 8, 16); - int Component1 = EndPoints[Partition][1].GetComponent(Component); - Component1 = BitArrayStream.Replicate(Component1, 8, 16); - - int Plane = 0; - - if (TexelParams.DualPlane && (((PlaneIndices + 1) & 3) == Component)) - { - Plane = 1; - } - - int Weight = Weights[Plane][j * BlockWidth + i]; - int FinalComponent = (Component0 * (64 - Weight) + Component1 * Weight + 32) / 64; - - if (FinalComponent == 65535) - { - Pixel.SetComponent(Component, 255); - } - else - { - double FinalComponentFloat = FinalComponent; - Pixel.SetComponent(Component, (int)(255.0 * (FinalComponentFloat / 65536.0) + 0.5)); - } - } - - OutputBuffer[j * BlockWidth + i] = Pixel.Pack(); - } - } - - return true; - } - - private static int Select2DPartition(int Seed, int X, int Y, int PartitionCount, bool IsSmallBlock) - { - return SelectPartition(Seed, X, Y, 0, PartitionCount, IsSmallBlock); - } - - private static int SelectPartition(int Seed, int X, int Y, int Z, int PartitionCount, bool IsSmallBlock) - { - if (PartitionCount == 1) - { - return 0; - } - - if (IsSmallBlock) - { - X <<= 1; - Y <<= 1; - Z <<= 1; - } - - Seed += (PartitionCount - 1) * 1024; - - int RightNum = Hash52((uint)Seed); - byte Seed01 = (byte)(RightNum & 0xF); - byte Seed02 = (byte)((RightNum >> 4) & 0xF); - byte Seed03 = (byte)((RightNum >> 8) & 0xF); - byte Seed04 = (byte)((RightNum >> 12) & 0xF); - byte Seed05 = (byte)((RightNum >> 16) & 0xF); - byte Seed06 = (byte)((RightNum >> 20) & 0xF); - byte Seed07 = (byte)((RightNum >> 24) & 0xF); - byte Seed08 = (byte)((RightNum >> 28) & 0xF); - byte Seed09 = (byte)((RightNum >> 18) & 0xF); - byte Seed10 = (byte)((RightNum >> 22) & 0xF); - byte Seed11 = (byte)((RightNum >> 26) & 0xF); - byte Seed12 = (byte)(((RightNum >> 30) | (RightNum << 2)) & 0xF); - - Seed01 *= Seed01; Seed02 *= Seed02; - Seed03 *= Seed03; Seed04 *= Seed04; - Seed05 *= Seed05; Seed06 *= Seed06; - Seed07 *= Seed07; Seed08 *= Seed08; - Seed09 *= Seed09; Seed10 *= Seed10; - Seed11 *= Seed11; Seed12 *= Seed12; - - int SeedHash1, SeedHash2, SeedHash3; - - if ((Seed & 1) != 0) - { - SeedHash1 = (Seed & 2) != 0 ? 4 : 5; - SeedHash2 = (PartitionCount == 3) ? 6 : 5; - } - else - { - SeedHash1 = (PartitionCount == 3) ? 6 : 5; - SeedHash2 = (Seed & 2) != 0 ? 4 : 5; - } - - SeedHash3 = (Seed & 0x10) != 0 ? SeedHash1 : SeedHash2; - - Seed01 >>= SeedHash1; Seed02 >>= SeedHash2; Seed03 >>= SeedHash1; Seed04 >>= SeedHash2; - Seed05 >>= SeedHash1; Seed06 >>= SeedHash2; Seed07 >>= SeedHash1; Seed08 >>= SeedHash2; - Seed09 >>= SeedHash3; Seed10 >>= SeedHash3; Seed11 >>= SeedHash3; Seed12 >>= SeedHash3; - - int a = Seed01 * X + Seed02 * Y + Seed11 * Z + (RightNum >> 14); - int b = Seed03 * X + Seed04 * Y + Seed12 * Z + (RightNum >> 10); - int c = Seed05 * X + Seed06 * Y + Seed09 * Z + (RightNum >> 6); - int d = Seed07 * X + Seed08 * Y + Seed10 * Z + (RightNum >> 2); - - a &= 0x3F; b &= 0x3F; c &= 0x3F; d &= 0x3F; - - if (PartitionCount < 4) d = 0; - if (PartitionCount < 3) c = 0; - - if (a >= b && a >= c && a >= d) return 0; - else if (b >= c && b >= d) return 1; - else if (c >= d) return 2; - return 3; - } - - static int Hash52(uint Val) - { - Val ^= Val >> 15; Val -= Val << 17; Val += Val << 7; Val += Val << 4; - Val ^= Val >> 5; Val += Val << 16; Val ^= Val >> 7; Val ^= Val >> 3; - Val ^= Val << 6; Val ^= Val >> 17; - - return (int)Val; - } - - static void UnquantizeTexelWeights( - int[][] OutputBuffer, - List Weights, - TexelWeightParams TexelParams, - int BlockWidth, - int BlockHeight) - { - int WeightIndices = 0; - int[][] Unquantized = new int[2][]; - Unquantized[0] = new int[144]; - Unquantized[1] = new int[144]; - - for (int i = 0; i < Weights.Count; i++) - { - Unquantized[0][WeightIndices] = UnquantizeTexelWeight(Weights[i]); - - if (TexelParams.DualPlane) - { - i++; - Unquantized[1][WeightIndices] = UnquantizeTexelWeight(Weights[i]); - - if (i == Weights.Count) - { - break; - } - } - - if (++WeightIndices >= (TexelParams.Width * TexelParams.Height)) break; - } - - // Do infill if necessary (Section C.2.18) ... - int Ds = (1024 + (BlockWidth / 2)) / (BlockWidth - 1); - int Dt = (1024 + (BlockHeight / 2)) / (BlockHeight - 1); - - int PlaneScale = TexelParams.DualPlane ? 2 : 1; - - for (int Plane = 0; Plane < PlaneScale; Plane++) - { - for (int t = 0; t < BlockHeight; t++) - { - for (int s = 0; s < BlockWidth; s++) - { - int cs = Ds * s; - int ct = Dt * t; - - int gs = (cs * (TexelParams.Width - 1) + 32) >> 6; - int gt = (ct * (TexelParams.Height - 1) + 32) >> 6; - - int js = gs >> 4; - int fs = gs & 0xF; - - int jt = gt >> 4; - int ft = gt & 0x0F; - - int w11 = (fs * ft + 8) >> 4; - int w10 = ft - w11; - int w01 = fs - w11; - int w00 = 16 - fs - ft + w11; - - int v0 = js + jt * TexelParams.Width; - - int p00 = 0; - int p01 = 0; - int p10 = 0; - int p11 = 0; - - if (v0 < (TexelParams.Width * TexelParams.Height)) - { - p00 = Unquantized[Plane][v0]; - } - - if (v0 + 1 < (TexelParams.Width * TexelParams.Height)) - { - p01 = Unquantized[Plane][v0 + 1]; - } - - if (v0 + TexelParams.Width < (TexelParams.Width * TexelParams.Height)) - { - p10 = Unquantized[Plane][v0 + TexelParams.Width]; - } - - if (v0 + TexelParams.Width + 1 < (TexelParams.Width * TexelParams.Height)) - { - p11 = Unquantized[Plane][v0 + TexelParams.Width + 1]; - } - - OutputBuffer[Plane][t * BlockWidth + s] = (p00 * w00 + p01 * w01 + p10 * w10 + p11 * w11 + 8) >> 4; - } - } - } - } - - static int UnquantizeTexelWeight(IntegerEncoded IntEncoded) - { - int BitValue = IntEncoded.BitValue; - int BitLength = IntEncoded.NumberBits; - - int A = BitArrayStream.Replicate(BitValue & 1, 1, 7); - int B = 0, C = 0, D = 0; - - int Result = 0; - - switch (IntEncoded.GetEncoding()) - { - case IntegerEncoded.EIntegerEncoding.JustBits: - Result = BitArrayStream.Replicate(BitValue, BitLength, 6); - break; - - case IntegerEncoded.EIntegerEncoding.Trit: - { - D = IntEncoded.TritValue; - Debug.Assert(D < 3); - - switch (BitLength) - { - case 0: - { - int[] Results = { 0, 32, 63 }; - Result = Results[D]; - - break; - } - - case 1: - { - C = 50; - break; - } - - case 2: - { - C = 23; - int b = (BitValue >> 1) & 1; - B = (b << 6) | (b << 2) | b; - - break; - } - - case 3: - { - C = 11; - int cb = (BitValue >> 1) & 3; - B = (cb << 5) | cb; - - break; - } - - default: - throw new ASTCDecoderException("Invalid trit encoding for texel weight"); - } - - break; - } - - case IntegerEncoded.EIntegerEncoding.Quint: - { - D = IntEncoded.QuintValue; - Debug.Assert(D < 5); - - switch (BitLength) - { - case 0: - { - int[] Results = { 0, 16, 32, 47, 63 }; - Result = Results[D]; - - break; - } - - case 1: - { - C = 28; - - break; - } - - case 2: - { - C = 13; - int b = (BitValue >> 1) & 1; - B = (b << 6) | (b << 1); - - break; - } - - default: - throw new ASTCDecoderException("Invalid quint encoding for texel weight"); - } - - break; - } - } - - if (IntEncoded.GetEncoding() != IntegerEncoded.EIntegerEncoding.JustBits && BitLength > 0) - { - // Decode the value... - Result = D * C + B; - Result ^= A; - Result = (A & 0x20) | (Result >> 2); - } - - Debug.Assert(Result < 64); - - // Change from [0,63] to [0,64] - if (Result > 32) - { - Result += 1; - } - - return Result; - } - - static byte ReverseByte(byte b) - { - // Taken from http://graphics.stanford.edu/~seander/bithacks.html#ReverseByteWith64Bits - return (byte)((((b) * 0x80200802L) & 0x0884422110L) * 0x0101010101L >> 32); - } - - static uint[] ReadUintColorValues(int Number, int[] ColorValues, ref int ColorValuesPosition) - { - uint[] Ret = new uint[Number]; - - for (int i = 0; i < Number; i++) - { - Ret[i] = (uint)ColorValues[ColorValuesPosition++]; - } - - return Ret; - } - - static int[] ReadIntColorValues(int Number, int[] ColorValues, ref int ColorValuesPosition) - { - int[] Ret = new int[Number]; - - for (int i = 0; i < Number; i++) - { - Ret[i] = ColorValues[ColorValuesPosition++]; - } - - return Ret; - } - - static void ComputeEndpoints( - ASTCPixel[] EndPoints, - int[] ColorValues, - uint ColorEndpointMode, - ref int ColorValuesPosition) - { - switch (ColorEndpointMode) - { - case 0: - { - uint[] Val = ReadUintColorValues(2, ColorValues, ref ColorValuesPosition); - - EndPoints[0] = new ASTCPixel(0xFF, (short)Val[0], (short)Val[0], (short)Val[0]); - EndPoints[1] = new ASTCPixel(0xFF, (short)Val[1], (short)Val[1], (short)Val[1]); - - break; - } - - - case 1: - { - uint[] Val = ReadUintColorValues(2, ColorValues, ref ColorValuesPosition); - int L0 = (int)((Val[0] >> 2) | (Val[1] & 0xC0)); - int L1 = (int)Math.Max(L0 + (Val[1] & 0x3F), 0xFFU); - - EndPoints[0] = new ASTCPixel(0xFF, (short)L0, (short)L0, (short)L0); - EndPoints[1] = new ASTCPixel(0xFF, (short)L1, (short)L1, (short)L1); - - break; - } - - case 4: - { - uint[] Val = ReadUintColorValues(4, ColorValues, ref ColorValuesPosition); - - EndPoints[0] = new ASTCPixel((short)Val[2], (short)Val[0], (short)Val[0], (short)Val[0]); - EndPoints[1] = new ASTCPixel((short)Val[3], (short)Val[1], (short)Val[1], (short)Val[1]); - - break; - } - - case 5: - { - int[] Val = ReadIntColorValues(4, ColorValues, ref ColorValuesPosition); - - BitArrayStream.BitTransferSigned(ref Val[1], ref Val[0]); - BitArrayStream.BitTransferSigned(ref Val[3], ref Val[2]); - - EndPoints[0] = new ASTCPixel((short)Val[2], (short)Val[0], (short)Val[0], (short)Val[0]); - EndPoints[1] = new ASTCPixel((short)(Val[2] + Val[3]), (short)(Val[0] + Val[1]), (short)(Val[0] + Val[1]), (short)(Val[0] + Val[1])); - - EndPoints[0].ClampByte(); - EndPoints[1].ClampByte(); - - break; - } - - case 6: - { - uint[] Val = ReadUintColorValues(4, ColorValues, ref ColorValuesPosition); - - EndPoints[0] = new ASTCPixel(0xFF, (short)(Val[0] * Val[3] >> 8), (short)(Val[1] * Val[3] >> 8), (short)(Val[2] * Val[3] >> 8)); - EndPoints[1] = new ASTCPixel(0xFF, (short)Val[0], (short)Val[1], (short)Val[2]); - - break; - } - - case 8: - { - uint[] Val = ReadUintColorValues(6, ColorValues, ref ColorValuesPosition); - - if (Val[1] + Val[3] + Val[5] >= Val[0] + Val[2] + Val[4]) - { - EndPoints[0] = new ASTCPixel(0xFF, (short)Val[0], (short)Val[2], (short)Val[4]); - EndPoints[1] = new ASTCPixel(0xFF, (short)Val[1], (short)Val[3], (short)Val[5]); - } - else - { - EndPoints[0] = ASTCPixel.BlueContract(0xFF, (short)Val[1], (short)Val[3], (short)Val[5]); - EndPoints[1] = ASTCPixel.BlueContract(0xFF, (short)Val[0], (short)Val[2], (short)Val[4]); - } - - break; - } - - case 9: - { - int[] Val = ReadIntColorValues(6, ColorValues, ref ColorValuesPosition); - - BitArrayStream.BitTransferSigned(ref Val[1], ref Val[0]); - BitArrayStream.BitTransferSigned(ref Val[3], ref Val[2]); - BitArrayStream.BitTransferSigned(ref Val[5], ref Val[4]); - - if (Val[1] + Val[3] + Val[5] >= 0) - { - EndPoints[0] = new ASTCPixel(0xFF, (short)Val[0], (short)Val[2], (short)Val[4]); - EndPoints[1] = new ASTCPixel(0xFF, (short)(Val[0] + Val[1]), (short)(Val[2] + Val[3]), (short)(Val[4] + Val[5])); - } - else - { - EndPoints[0] = ASTCPixel.BlueContract(0xFF, Val[0] + Val[1], Val[2] + Val[3], Val[4] + Val[5]); - EndPoints[1] = ASTCPixel.BlueContract(0xFF, Val[0], Val[2], Val[4]); - } - - EndPoints[0].ClampByte(); - EndPoints[1].ClampByte(); - - break; - } - - case 10: - { - uint[] Val = ReadUintColorValues(6, ColorValues, ref ColorValuesPosition); - - EndPoints[0] = new ASTCPixel((short)Val[4], (short)(Val[0] * Val[3] >> 8), (short)(Val[1] * Val[3] >> 8), (short)(Val[2] * Val[3] >> 8)); - EndPoints[1] = new ASTCPixel((short)Val[5], (short)Val[0], (short)Val[1], (short)Val[2]); - - break; - } - - case 12: - { - uint[] Val = ReadUintColorValues(8, ColorValues, ref ColorValuesPosition); - - if (Val[1] + Val[3] + Val[5] >= Val[0] + Val[2] + Val[4]) - { - EndPoints[0] = new ASTCPixel((short)Val[6], (short)Val[0], (short)Val[2], (short)Val[4]); - EndPoints[1] = new ASTCPixel((short)Val[7], (short)Val[1], (short)Val[3], (short)Val[5]); - } - else - { - EndPoints[0] = ASTCPixel.BlueContract((short)Val[7], (short)Val[1], (short)Val[3], (short)Val[5]); - EndPoints[1] = ASTCPixel.BlueContract((short)Val[6], (short)Val[0], (short)Val[2], (short)Val[4]); - } - - break; - } - - case 13: - { - int[] Val = ReadIntColorValues(8, ColorValues, ref ColorValuesPosition); - - BitArrayStream.BitTransferSigned(ref Val[1], ref Val[0]); - BitArrayStream.BitTransferSigned(ref Val[3], ref Val[2]); - BitArrayStream.BitTransferSigned(ref Val[5], ref Val[4]); - BitArrayStream.BitTransferSigned(ref Val[7], ref Val[6]); - - if (Val[1] + Val[3] + Val[5] >= 0) - { - EndPoints[0] = new ASTCPixel((short)Val[6], (short)Val[0], (short)Val[2], (short)Val[4]); - EndPoints[1] = new ASTCPixel((short)(Val[7] + Val[6]), (short)(Val[0] + Val[1]), (short)(Val[2] + Val[3]), (short)(Val[4] + Val[5])); - } - else - { - EndPoints[0] = ASTCPixel.BlueContract(Val[6] + Val[7], Val[0] + Val[1], Val[2] + Val[3], Val[4] + Val[5]); - EndPoints[1] = ASTCPixel.BlueContract(Val[6], Val[0], Val[2], Val[4]); - } - - EndPoints[0].ClampByte(); - EndPoints[1].ClampByte(); - - break; - } - - default: - throw new ASTCDecoderException("Unsupported color endpoint mode (is it HDR?)"); - } - } - - static void DecodeColorValues( - int[] OutputValues, - byte[] InputData, - uint[] Modes, - int NumberPartitions, - int NumberBitsForColorData) - { - // First figure out how many color values we have - int NumberValues = 0; - - for (int i = 0; i < NumberPartitions; i++) - { - NumberValues += (int)((Modes[i] >> 2) + 1) << 1; - } - - // Then based on the number of values and the remaining number of bits, - // figure out the max value for each of them... - int Range = 256; - - while (--Range > 0) - { - IntegerEncoded IntEncoded = IntegerEncoded.CreateEncoding(Range); - int BitLength = IntEncoded.GetBitLength(NumberValues); - - if (BitLength <= NumberBitsForColorData) - { - // Find the smallest possible range that matches the given encoding - while (--Range > 0) - { - IntegerEncoded NewIntEncoded = IntegerEncoded.CreateEncoding(Range); - if (!NewIntEncoded.MatchesEncoding(IntEncoded)) - { - break; - } - } - - // Return to last matching range. - Range++; - break; - } - } - - // We now have enough to decode our integer sequence. - List IntegerEncodedSequence = new List(); - BitArrayStream ColorBitStream = new BitArrayStream(new BitArray(InputData)); - - IntegerEncoded.DecodeIntegerSequence(IntegerEncodedSequence, ColorBitStream, Range, NumberValues); - - // Once we have the decoded values, we need to dequantize them to the 0-255 range - // This procedure is outlined in ASTC spec C.2.13 - int OutputIndices = 0; - - foreach (IntegerEncoded IntEncoded in IntegerEncodedSequence) - { - int BitLength = IntEncoded.NumberBits; - int BitValue = IntEncoded.BitValue; - - Debug.Assert(BitLength >= 1); - - int A = 0, B = 0, C = 0, D = 0; - // A is just the lsb replicated 9 times. - A = BitArrayStream.Replicate(BitValue & 1, 1, 9); - - switch (IntEncoded.GetEncoding()) - { - case IntegerEncoded.EIntegerEncoding.JustBits: - { - OutputValues[OutputIndices++] = BitArrayStream.Replicate(BitValue, BitLength, 8); - - break; - } - - case IntegerEncoded.EIntegerEncoding.Trit: - { - D = IntEncoded.TritValue; - - switch (BitLength) - { - case 1: - { - C = 204; - - break; - } - - case 2: - { - C = 93; - // B = b000b0bb0 - int b = (BitValue >> 1) & 1; - B = (b << 8) | (b << 4) | (b << 2) | (b << 1); - - break; - } - - case 3: - { - C = 44; - // B = cb000cbcb - int cb = (BitValue >> 1) & 3; - B = (cb << 7) | (cb << 2) | cb; - - break; - } - - - case 4: - { - C = 22; - // B = dcb000dcb - int dcb = (BitValue >> 1) & 7; - B = (dcb << 6) | dcb; - - break; - } - - case 5: - { - C = 11; - // B = edcb000ed - int edcb = (BitValue >> 1) & 0xF; - B = (edcb << 5) | (edcb >> 2); - - break; - } - - case 6: - { - C = 5; - // B = fedcb000f - int fedcb = (BitValue >> 1) & 0x1F; - B = (fedcb << 4) | (fedcb >> 4); - - break; - } - - default: - throw new ASTCDecoderException("Unsupported trit encoding for color values!"); - } - - break; - } - - case IntegerEncoded.EIntegerEncoding.Quint: - { - D = IntEncoded.QuintValue; - - switch (BitLength) - { - case 1: - { - C = 113; - - break; - } - - case 2: - { - C = 54; - // B = b0000bb00 - int b = (BitValue >> 1) & 1; - B = (b << 8) | (b << 3) | (b << 2); - - break; - } - - case 3: - { - C = 26; - // B = cb0000cbc - int cb = (BitValue >> 1) & 3; - B = (cb << 7) | (cb << 1) | (cb >> 1); - - break; - } - - case 4: - { - C = 13; - // B = dcb0000dc - int dcb = (BitValue >> 1) & 7; - B = (dcb << 6) | (dcb >> 1); - - break; - } - - case 5: - { - C = 6; - // B = edcb0000e - int edcb = (BitValue >> 1) & 0xF; - B = (edcb << 5) | (edcb >> 3); - - break; - } - - default: - throw new ASTCDecoderException("Unsupported quint encoding for color values!"); - } - break; - } - } - - if (IntEncoded.GetEncoding() != IntegerEncoded.EIntegerEncoding.JustBits) - { - int T = D * C + B; - T ^= A; - T = (A & 0x80) | (T >> 2); - - OutputValues[OutputIndices++] = T; - } - } - - // Make sure that each of our values is in the proper range... - for (int i = 0; i < NumberValues; i++) - { - Debug.Assert(OutputValues[i] <= 255); - } - } - - static void FillVoidExtentLDR(BitArrayStream BitStream, int[] OutputBuffer, int BlockWidth, int BlockHeight) - { - // Don't actually care about the void extent, just read the bits... - for (int i = 0; i < 4; ++i) - { - BitStream.ReadBits(13); - } - - // Decode the RGBA components and renormalize them to the range [0, 255] - ushort R = (ushort)BitStream.ReadBits(16); - ushort G = (ushort)BitStream.ReadBits(16); - ushort B = (ushort)BitStream.ReadBits(16); - ushort A = (ushort)BitStream.ReadBits(16); - - int RGBA = (R >> 8) | (G & 0xFF00) | ((B) & 0xFF00) << 8 | ((A) & 0xFF00) << 16; - - for (int j = 0; j < BlockHeight; j++) - { - for (int i = 0; i < BlockWidth; i++) - { - OutputBuffer[j * BlockWidth + i] = RGBA; - } - } - } - - static TexelWeightParams DecodeBlockInfo(BitArrayStream BitStream) - { - TexelWeightParams TexelParams = new TexelWeightParams(); - - // Read the entire block mode all at once - ushort ModeBits = (ushort)BitStream.ReadBits(11); - - // Does this match the void extent block mode? - if ((ModeBits & 0x01FF) == 0x1FC) - { - if ((ModeBits & 0x200) != 0) - { - TexelParams.VoidExtentHDR = true; - } - else - { - TexelParams.VoidExtentLDR = true; - } - - // Next two bits must be one. - if ((ModeBits & 0x400) == 0 || BitStream.ReadBits(1) == 0) - { - TexelParams.Error = true; - } - - return TexelParams; - } - - // First check if the last four bits are zero - if ((ModeBits & 0xF) == 0) - { - TexelParams.Error = true; - return TexelParams; - } - - // If the last two bits are zero, then if bits - // [6-8] are all ones, this is also reserved. - if ((ModeBits & 0x3) == 0 && (ModeBits & 0x1C0) == 0x1C0) - { - TexelParams.Error = true; - - return TexelParams; - } - - // Otherwise, there is no error... Figure out the layout - // of the block mode. Layout is determined by a number - // between 0 and 9 corresponding to table C.2.8 of the - // ASTC spec. - int Layout = 0; - - if ((ModeBits & 0x1) != 0 || (ModeBits & 0x2) != 0) - { - // layout is in [0-4] - if ((ModeBits & 0x8) != 0) - { - // layout is in [2-4] - if ((ModeBits & 0x4) != 0) - { - // layout is in [3-4] - if ((ModeBits & 0x100) != 0) - { - Layout = 4; - } - else - { - Layout = 3; - } - } - else - { - Layout = 2; - } - } - else - { - // layout is in [0-1] - if ((ModeBits & 0x4) != 0) - { - Layout = 1; - } - else - { - Layout = 0; - } - } - } - else - { - // layout is in [5-9] - if ((ModeBits & 0x100) != 0) - { - // layout is in [7-9] - if ((ModeBits & 0x80) != 0) - { - // layout is in [7-8] - Debug.Assert((ModeBits & 0x40) == 0); - - if ((ModeBits & 0x20) != 0) - { - Layout = 8; - } - else - { - Layout = 7; - } - } - else - { - Layout = 9; - } - } - else - { - // layout is in [5-6] - if ((ModeBits & 0x80) != 0) - { - Layout = 6; - } - else - { - Layout = 5; - } - } - } - - Debug.Assert(Layout < 10); - - // Determine R - int R = (ModeBits >> 4) & 1; - if (Layout < 5) - { - R |= (ModeBits & 0x3) << 1; - } - else - { - R |= (ModeBits & 0xC) >> 1; - } - - Debug.Assert(2 <= R && R <= 7); - - // Determine width & height - switch (Layout) - { - case 0: - { - int A = (ModeBits >> 5) & 0x3; - int B = (ModeBits >> 7) & 0x3; - - TexelParams.Width = B + 4; - TexelParams.Height = A + 2; - - break; - } - - case 1: - { - int A = (ModeBits >> 5) & 0x3; - int B = (ModeBits >> 7) & 0x3; - - TexelParams.Width = B + 8; - TexelParams.Height = A + 2; - - break; - } - - case 2: - { - int A = (ModeBits >> 5) & 0x3; - int B = (ModeBits >> 7) & 0x3; - - TexelParams.Width = A + 2; - TexelParams.Height = B + 8; - - break; - } - - case 3: - { - int A = (ModeBits >> 5) & 0x3; - int B = (ModeBits >> 7) & 0x1; - - TexelParams.Width = A + 2; - TexelParams.Height = B + 6; - - break; - } - - case 4: - { - int A = (ModeBits >> 5) & 0x3; - int B = (ModeBits >> 7) & 0x1; - - TexelParams.Width = B + 2; - TexelParams.Height = A + 2; - - break; - } - - case 5: - { - int A = (ModeBits >> 5) & 0x3; - - TexelParams.Width = 12; - TexelParams.Height = A + 2; - - break; - } - - case 6: - { - int A = (ModeBits >> 5) & 0x3; - - TexelParams.Width = A + 2; - TexelParams.Height = 12; - - break; - } - - case 7: - { - TexelParams.Width = 6; - TexelParams.Height = 10; - - break; - } - - case 8: - { - TexelParams.Width = 10; - TexelParams.Height = 6; - break; - } - - case 9: - { - int A = (ModeBits >> 5) & 0x3; - int B = (ModeBits >> 9) & 0x3; - - TexelParams.Width = A + 6; - TexelParams.Height = B + 6; - - break; - } - - default: - //Don't know this layout... - TexelParams.Error = true; - break; - } - - // Determine whether or not we're using dual planes - // and/or high precision layouts. - bool D = ((Layout != 9) && ((ModeBits & 0x400) != 0)); - bool H = (Layout != 9) && ((ModeBits & 0x200) != 0); - - if (H) - { - int[] MaxWeights = { 9, 11, 15, 19, 23, 31 }; - TexelParams.MaxWeight = MaxWeights[R - 2]; - } - else - { - int[] MaxWeights = { 1, 2, 3, 4, 5, 7 }; - TexelParams.MaxWeight = MaxWeights[R - 2]; - } - - TexelParams.DualPlane = D; - - return TexelParams; - } - } -} diff --git a/Ryujinx.Graphics/Gal/Texture/ASTCPixel.cs b/Ryujinx.Graphics/Gal/Texture/ASTCPixel.cs deleted file mode 100644 index 4a2998186c..0000000000 --- a/Ryujinx.Graphics/Gal/Texture/ASTCPixel.cs +++ /dev/null @@ -1,138 +0,0 @@ -using System; -using System.Diagnostics; - -namespace Ryujinx.Graphics.Gal.Texture -{ - class ASTCPixel - { - public short R { get; set; } - public short G { get; set; } - public short B { get; set; } - public short A { get; set; } - - byte[] BitDepth = new byte[4]; - - public ASTCPixel(short _A, short _R, short _G, short _B) - { - A = _A; - R = _R; - G = _G; - B = _B; - - for (int i = 0; i < 4; i++) - BitDepth[i] = 8; - } - - public void ClampByte() - { - R = Math.Min(Math.Max(R, (short)0), (short)255); - G = Math.Min(Math.Max(G, (short)0), (short)255); - B = Math.Min(Math.Max(B, (short)0), (short)255); - A = Math.Min(Math.Max(A, (short)0), (short)255); - } - - public short GetComponent(int Index) - { - switch(Index) - { - case 0: return A; - case 1: return R; - case 2: return G; - case 3: return B; - } - - return 0; - } - - public void SetComponent(int Index, int Value) - { - switch (Index) - { - case 0: - A = (short)Value; - break; - case 1: - R = (short)Value; - break; - case 2: - G = (short)Value; - break; - case 3: - B = (short)Value; - break; - } - } - - public void ChangeBitDepth(byte[] Depth) - { - for(int i = 0; i< 4; i++) - { - int Value = ChangeBitDepth(GetComponent(i), BitDepth[i], Depth[i]); - - SetComponent(i, Value); - BitDepth[i] = Depth[i]; - } - } - - short ChangeBitDepth(short Value, byte OldDepth, byte NewDepth) - { - Debug.Assert(NewDepth <= 8); - Debug.Assert(OldDepth <= 8); - - if (OldDepth == NewDepth) - { - // Do nothing - return Value; - } - else if (OldDepth == 0 && NewDepth != 0) - { - return (short)((1 << NewDepth) - 1); - } - else if (NewDepth > OldDepth) - { - return (short)BitArrayStream.Replicate(Value, OldDepth, NewDepth); - } - else - { - // oldDepth > newDepth - if (NewDepth == 0) - { - return 0xFF; - } - else - { - byte BitsWasted = (byte)(OldDepth - NewDepth); - short TempValue = Value; - - TempValue = (short)((TempValue + (1 << (BitsWasted - 1))) >> BitsWasted); - TempValue = Math.Min(Math.Max((short)0, TempValue), (short)((1 << NewDepth) - 1)); - - return (byte)(TempValue); - } - } - } - - public int Pack() - { - ASTCPixel NewPixel = new ASTCPixel(A, R, G, B); - byte[] eightBitDepth = { 8, 8, 8, 8 }; - - NewPixel.ChangeBitDepth(eightBitDepth); - - return (byte)NewPixel.A << 24 | - (byte)NewPixel.B << 16 | - (byte)NewPixel.G << 8 | - (byte)NewPixel.R << 0; - } - - // Adds more precision to the blue channel as described - // in C.2.14 - public static ASTCPixel BlueContract(int a, int r, int g, int b) - { - return new ASTCPixel((short)(a), - (short)((r + b) >> 1), - (short)((g + b) >> 1), - (short)(b)); - } - } -} diff --git a/Ryujinx.Graphics/Gal/Texture/BitArrayStream.cs b/Ryujinx.Graphics/Gal/Texture/BitArrayStream.cs deleted file mode 100644 index eb2204c4bf..0000000000 --- a/Ryujinx.Graphics/Gal/Texture/BitArrayStream.cs +++ /dev/null @@ -1,120 +0,0 @@ -using System; -using System.Collections; - -namespace Ryujinx.Graphics.Gal.Texture -{ - public class BitArrayStream - { - public BitArray BitsArray; - public int Position { get; private set; } - - public BitArrayStream(BitArray BitArray) - { - BitsArray = BitArray; - Position = 0; - } - - public short ReadBits(int Length) - { - int RetValue = 0; - for (int i = Position; i < Position + Length; i++) - { - if (BitsArray[i]) - { - RetValue |= 1 << (i - Position); - } - } - - Position += Length; - return (short)RetValue; - } - - public int ReadBits(int Start, int End) - { - int RetValue = 0; - for (int i = Start; i <= End; i++) - { - if (BitsArray[i]) - { - RetValue |= 1 << (i - Start); - } - } - - return RetValue; - } - - public int ReadBit(int Index) - { - return Convert.ToInt32(BitsArray[Index]); - } - - public void WriteBits(int Value, int Length) - { - for (int i = Position; i < Position + Length; i++) - { - BitsArray[i] = ((Value >> (i - Position)) & 1) != 0; - } - - Position += Length; - } - - public byte[] ToByteArray() - { - byte[] RetArray = new byte[(BitsArray.Length + 7) / 8]; - BitsArray.CopyTo(RetArray, 0); - return RetArray; - } - - public static int Replicate(int Value, int NumberBits, int ToBit) - { - if (NumberBits == 0) return 0; - if (ToBit == 0) return 0; - - int TempValue = Value & ((1 << NumberBits) - 1); - int RetValue = TempValue; - int ResLength = NumberBits; - - while (ResLength < ToBit) - { - int Comp = 0; - if (NumberBits > ToBit - ResLength) - { - int NewShift = ToBit - ResLength; - Comp = NumberBits - NewShift; - NumberBits = NewShift; - } - RetValue <<= NumberBits; - RetValue |= TempValue >> Comp; - ResLength += NumberBits; - } - return RetValue; - } - - public static int PopCnt(int Number) - { - int Counter; - for (Counter = 0; Number != 0; Counter++) - { - Number &= Number - 1; - } - return Counter; - } - - public static void Swap(ref T lhs, ref T rhs) - { - T Temp = lhs; - lhs = rhs; - rhs = Temp; - } - - // Transfers a bit as described in C.2.14 - public static void BitTransferSigned(ref int a, ref int b) - { - b >>= 1; - b |= a & 0x80; - a >>= 1; - a &= 0x3F; - if ((a & 0x20) != 0) a -= 0x40; - } - } -} diff --git a/Ryujinx.Graphics/Gal/Texture/IntegerEncoded.cs b/Ryujinx.Graphics/Gal/Texture/IntegerEncoded.cs deleted file mode 100644 index 0adabe17e2..0000000000 --- a/Ryujinx.Graphics/Gal/Texture/IntegerEncoded.cs +++ /dev/null @@ -1,269 +0,0 @@ -using System.Collections; -using System.Collections.Generic; - -namespace Ryujinx.Graphics.Gal.Texture -{ - public struct IntegerEncoded - { - public enum EIntegerEncoding - { - JustBits, - Quint, - Trit - } - - EIntegerEncoding Encoding; - public int NumberBits { get; private set; } - public int BitValue { get; private set; } - public int TritValue { get; private set; } - public int QuintValue { get; private set; } - - public IntegerEncoded(EIntegerEncoding _Encoding, int NumBits) - { - Encoding = _Encoding; - NumberBits = NumBits; - BitValue = 0; - TritValue = 0; - QuintValue = 0; - } - - public bool MatchesEncoding(IntegerEncoded Other) - { - return Encoding == Other.Encoding && NumberBits == Other.NumberBits; - } - - public EIntegerEncoding GetEncoding() - { - return Encoding; - } - - public int GetBitLength(int NumberVals) - { - int TotalBits = NumberBits * NumberVals; - if (Encoding == EIntegerEncoding.Trit) - { - TotalBits += (NumberVals * 8 + 4) / 5; - } - else if (Encoding == EIntegerEncoding.Quint) - { - TotalBits += (NumberVals * 7 + 2) / 3; - } - return TotalBits; - } - - public static IntegerEncoded CreateEncoding(int MaxVal) - { - while (MaxVal > 0) - { - int Check = MaxVal + 1; - - // Is maxVal a power of two? - if ((Check & (Check - 1)) == 0) - { - return new IntegerEncoded(EIntegerEncoding.JustBits, BitArrayStream.PopCnt(MaxVal)); - } - - // Is maxVal of the type 3*2^n - 1? - if ((Check % 3 == 0) && ((Check / 3) & ((Check / 3) - 1)) == 0) - { - return new IntegerEncoded(EIntegerEncoding.Trit, BitArrayStream.PopCnt(Check / 3 - 1)); - } - - // Is maxVal of the type 5*2^n - 1? - if ((Check % 5 == 0) && ((Check / 5) & ((Check / 5) - 1)) == 0) - { - return new IntegerEncoded(EIntegerEncoding.Quint, BitArrayStream.PopCnt(Check / 5 - 1)); - } - - // Apparently it can't be represented with a bounded integer sequence... - // just iterate. - MaxVal--; - } - - return new IntegerEncoded(EIntegerEncoding.JustBits, 0); - } - - public static void DecodeTritBlock( - BitArrayStream BitStream, - List ListIntegerEncoded, - int NumberBitsPerValue) - { - // Implement the algorithm in section C.2.12 - int[] m = new int[5]; - int[] t = new int[5]; - int T; - - // Read the trit encoded block according to - // table C.2.14 - m[0] = BitStream.ReadBits(NumberBitsPerValue); - T = BitStream.ReadBits(2); - m[1] = BitStream.ReadBits(NumberBitsPerValue); - T |= BitStream.ReadBits(2) << 2; - m[2] = BitStream.ReadBits(NumberBitsPerValue); - T |= BitStream.ReadBits(1) << 4; - m[3] = BitStream.ReadBits(NumberBitsPerValue); - T |= BitStream.ReadBits(2) << 5; - m[4] = BitStream.ReadBits(NumberBitsPerValue); - T |= BitStream.ReadBits(1) << 7; - - int C = 0; - - BitArrayStream Tb = new BitArrayStream(new BitArray(new int[] { T })); - if (Tb.ReadBits(2, 4) == 7) - { - C = (Tb.ReadBits(5, 7) << 2) | Tb.ReadBits(0, 1); - t[4] = t[3] = 2; - } - else - { - C = Tb.ReadBits(0, 4); - if (Tb.ReadBits(5, 6) == 3) - { - t[4] = 2; - t[3] = Tb.ReadBit(7); - } - else - { - t[4] = Tb.ReadBit(7); - t[3] = Tb.ReadBits(5, 6); - } - } - - BitArrayStream Cb = new BitArrayStream(new BitArray(new int[] { C })); - if (Cb.ReadBits(0, 1) == 3) - { - t[2] = 2; - t[1] = Cb.ReadBit(4); - t[0] = (Cb.ReadBit(3) << 1) | (Cb.ReadBit(2) & ~Cb.ReadBit(3)); - } - else if (Cb.ReadBits(2, 3) == 3) - { - t[2] = 2; - t[1] = 2; - t[0] = Cb.ReadBits(0, 1); - } - else - { - t[2] = Cb.ReadBit(4); - t[1] = Cb.ReadBits(2, 3); - t[0] = (Cb.ReadBit(1) << 1) | (Cb.ReadBit(0) & ~Cb.ReadBit(1)); - } - - for (int i = 0; i < 5; i++) - { - IntegerEncoded IntEncoded = new IntegerEncoded(EIntegerEncoding.Trit, NumberBitsPerValue) - { - BitValue = m[i], - TritValue = t[i] - }; - ListIntegerEncoded.Add(IntEncoded); - } - } - - public static void DecodeQuintBlock( - BitArrayStream BitStream, - List ListIntegerEncoded, - int NumberBitsPerValue) - { - // Implement the algorithm in section C.2.12 - int[] m = new int[3]; - int[] q = new int[3]; - int Q; - - // Read the trit encoded block according to - // table C.2.15 - m[0] = BitStream.ReadBits(NumberBitsPerValue); - Q = BitStream.ReadBits(3); - m[1] = BitStream.ReadBits(NumberBitsPerValue); - Q |= BitStream.ReadBits(2) << 3; - m[2] = BitStream.ReadBits(NumberBitsPerValue); - Q |= BitStream.ReadBits(2) << 5; - - BitArrayStream Qb = new BitArrayStream(new BitArray(new int[] { Q })); - if (Qb.ReadBits(1, 2) == 3 && Qb.ReadBits(5, 6) == 0) - { - q[0] = q[1] = 4; - q[2] = (Qb.ReadBit(0) << 2) | ((Qb.ReadBit(4) & ~Qb.ReadBit(0)) << 1) | (Qb.ReadBit(3) & ~Qb.ReadBit(0)); - } - else - { - int C = 0; - if (Qb.ReadBits(1, 2) == 3) - { - q[2] = 4; - C = (Qb.ReadBits(3, 4) << 3) | ((~Qb.ReadBits(5, 6) & 3) << 1) | Qb.ReadBit(0); - } - else - { - q[2] = Qb.ReadBits(5, 6); - C = Qb.ReadBits(0, 4); - } - - BitArrayStream Cb = new BitArrayStream(new BitArray(new int[] { C })); - if (Cb.ReadBits(0, 2) == 5) - { - q[1] = 4; - q[0] = Cb.ReadBits(3, 4); - } - else - { - q[1] = Cb.ReadBits(3, 4); - q[0] = Cb.ReadBits(0, 2); - } - } - - for (int i = 0; i < 3; i++) - { - IntegerEncoded IntEncoded = new IntegerEncoded(EIntegerEncoding.Quint, NumberBitsPerValue) - { - BitValue = m[i], - QuintValue = q[i] - }; - ListIntegerEncoded.Add(IntEncoded); - } - } - - public static void DecodeIntegerSequence( - List DecodeIntegerSequence, - BitArrayStream BitStream, - int MaxRange, - int NumberValues) - { - // Determine encoding parameters - IntegerEncoded IntEncoded = CreateEncoding(MaxRange); - - // Start decoding - int NumberValuesDecoded = 0; - while (NumberValuesDecoded < NumberValues) - { - switch (IntEncoded.GetEncoding()) - { - case EIntegerEncoding.Quint: - { - DecodeQuintBlock(BitStream, DecodeIntegerSequence, IntEncoded.NumberBits); - NumberValuesDecoded += 3; - - break; - } - - case EIntegerEncoding.Trit: - { - DecodeTritBlock(BitStream, DecodeIntegerSequence, IntEncoded.NumberBits); - NumberValuesDecoded += 5; - - break; - } - - case EIntegerEncoding.JustBits: - { - IntEncoded.BitValue = BitStream.ReadBits(IntEncoded.NumberBits); - DecodeIntegerSequence.Add(IntEncoded); - NumberValuesDecoded++; - - break; - } - } - } - } - } -} diff --git a/Ryujinx.Graphics/Ryujinx.Graphics.csproj b/Ryujinx.Graphics/Ryujinx.Graphics.csproj deleted file mode 100644 index d0fad10765..0000000000 --- a/Ryujinx.Graphics/Ryujinx.Graphics.csproj +++ /dev/null @@ -1,33 +0,0 @@ - - - - netcoreapp2.1 - win10-x64;osx-x64;linux-x64 - - - - true - - - - true - - - - - - - - - - - - - GlFbVtxShader - - - GlFbFragShader - - - - diff --git a/Ryujinx.HLE/DeviceMemory.cs b/Ryujinx.HLE/DeviceMemory.cs new file mode 100644 index 0000000000..38864bc2d7 --- /dev/null +++ b/Ryujinx.HLE/DeviceMemory.cs @@ -0,0 +1,194 @@ +using ARMeilleure.Memory; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE +{ + class DeviceMemory : IDisposable + { + public const long RamSize = 4L * 1024 * 1024 * 1024; + + public IntPtr RamPointer { get; } + + private unsafe byte* _ramPtr; + + public unsafe DeviceMemory() + { + RamPointer = MemoryManagement.AllocateWriteTracked(RamSize); + + _ramPtr = (byte*)RamPointer; + } + + public sbyte ReadSByte(long position) + { + return (sbyte)ReadByte(position); + } + + public short ReadInt16(long position) + { + return (short)ReadUInt16(position); + } + + public int ReadInt32(long position) + { + return (int)ReadUInt32(position); + } + + public long ReadInt64(long position) + { + return (long)ReadUInt64(position); + } + + public unsafe byte ReadByte(long position) + { + return *(_ramPtr + position); + } + + public unsafe ushort ReadUInt16(long position) + { + return *((ushort*)(_ramPtr + position)); + } + + public unsafe uint ReadUInt32(long position) + { + return *((uint*)(_ramPtr + position)); + } + + public unsafe ulong ReadUInt64(long position) + { + return *((ulong*)(_ramPtr + position)); + } + + public unsafe T ReadStruct(long position) + { + return Marshal.PtrToStructure((IntPtr)(_ramPtr + position)); + } + + public void WriteSByte(long position, sbyte value) + { + WriteByte(position, (byte)value); + } + + public void WriteInt16(long position, short value) + { + WriteUInt16(position, (ushort)value); + } + + public void WriteInt32(long position, int value) + { + WriteUInt32(position, (uint)value); + } + + public void WriteInt64(long position, long value) + { + WriteUInt64(position, (ulong)value); + } + + public unsafe void WriteByte(long position, byte value) + { + *(_ramPtr + position) = value; + } + + public unsafe void WriteUInt16(long position, ushort value) + { + *((ushort*)(_ramPtr + position)) = value; + } + + public unsafe void WriteUInt32(long position, uint value) + { + *((uint*)(_ramPtr + position)) = value; + } + + public unsafe void WriteUInt64(long position, ulong value) + { + *((ulong*)(_ramPtr + position)) = value; + } + + public unsafe void WriteStruct(long position, T value) + { + Marshal.StructureToPtr(value, (IntPtr)(_ramPtr + position), false); + } + + public void FillWithZeros(long position, int size) + { + int size8 = size & ~(8 - 1); + + for (int offs = 0; offs < size8; offs += 8) + { + WriteInt64(position + offs, 0); + } + + for (int offs = size8; offs < (size - size8); offs++) + { + WriteByte(position + offs, 0); + } + } + + public void Set(ulong address, byte value, ulong size) + { + if (address + size < address) + { + throw new ArgumentOutOfRangeException(nameof(size)); + } + + if (address + size > RamSize) + { + throw new ArgumentOutOfRangeException(nameof(address)); + } + + ulong size8 = size & ~7UL; + + ulong valueRep = (ulong)value * 0x0101010101010101; + + for (ulong offs = 0; offs < size8; offs += 8) + { + WriteUInt64((long)(address + offs), valueRep); + } + + for (ulong offs = size8; offs < (size - size8); offs++) + { + WriteByte((long)(address + offs), value); + } + } + + public void Copy(ulong dst, ulong src, ulong size) + { + if (dst + size < dst || src + size < src) + { + throw new ArgumentOutOfRangeException(nameof(size)); + } + + if (dst + size > RamSize) + { + throw new ArgumentOutOfRangeException(nameof(dst)); + } + + if (src + size > RamSize) + { + throw new ArgumentOutOfRangeException(nameof(src)); + } + + ulong size8 = size & ~7UL; + + for (ulong offs = 0; offs < size8; offs += 8) + { + WriteUInt64((long)(dst + offs), ReadUInt64((long)(src + offs))); + } + + for (ulong offs = size8; offs < (size - size8); offs++) + { + WriteByte((long)(dst + offs), ReadByte((long)(src + offs))); + } + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + MemoryManagement.Free(RamPointer); + } + } +} diff --git a/Ryujinx.HLE/OsHle/Exceptions/GuestBrokeExecutionException.cs b/Ryujinx.HLE/Exceptions/GuestBrokeExecutionException.cs similarity index 85% rename from Ryujinx.HLE/OsHle/Exceptions/GuestBrokeExecutionException.cs rename to Ryujinx.HLE/Exceptions/GuestBrokeExecutionException.cs index 2ed7f19e3b..fe41b02a69 100644 --- a/Ryujinx.HLE/OsHle/Exceptions/GuestBrokeExecutionException.cs +++ b/Ryujinx.HLE/Exceptions/GuestBrokeExecutionException.cs @@ -1,6 +1,6 @@ using System; -namespace Ryujinx.HLE.OsHle.Exceptions +namespace Ryujinx.HLE.Exceptions { public class GuestBrokeExecutionException : Exception { diff --git a/Ryujinx.HLE/Exceptions/InternalServiceException.cs b/Ryujinx.HLE/Exceptions/InternalServiceException.cs new file mode 100644 index 0000000000..b940c51c89 --- /dev/null +++ b/Ryujinx.HLE/Exceptions/InternalServiceException.cs @@ -0,0 +1,9 @@ +using System; + +namespace Ryujinx.HLE.Exceptions +{ + class InternalServiceException: Exception + { + public InternalServiceException(string message) : base(message) { } + } +} diff --git a/Ryujinx.HLE/Exceptions/InvalidFirmwarePackageException.cs b/Ryujinx.HLE/Exceptions/InvalidFirmwarePackageException.cs new file mode 100644 index 0000000000..bddd827a01 --- /dev/null +++ b/Ryujinx.HLE/Exceptions/InvalidFirmwarePackageException.cs @@ -0,0 +1,9 @@ +using System; + +namespace Ryujinx.HLE.Exceptions +{ + class InvalidFirmwarePackageException : Exception + { + public InvalidFirmwarePackageException(string message) : base(message) { } + } +} diff --git a/Ryujinx.HLE/Exceptions/InvalidNpdmException.cs b/Ryujinx.HLE/Exceptions/InvalidNpdmException.cs new file mode 100644 index 0000000000..c4036ea0d0 --- /dev/null +++ b/Ryujinx.HLE/Exceptions/InvalidNpdmException.cs @@ -0,0 +1,9 @@ +using System; + +namespace Ryujinx.HLE.Exceptions +{ + public class InvalidNpdmException : Exception + { + public InvalidNpdmException(string message) : base(message) { } + } +} diff --git a/Ryujinx.HLE/Exceptions/InvalidSystemResourceException.cs b/Ryujinx.HLE/Exceptions/InvalidSystemResourceException.cs new file mode 100644 index 0000000000..3c63e0641e --- /dev/null +++ b/Ryujinx.HLE/Exceptions/InvalidSystemResourceException.cs @@ -0,0 +1,9 @@ +using System; + +namespace Ryujinx.HLE.Exceptions +{ + public class InvalidSystemResourceException : Exception + { + public InvalidSystemResourceException(string message) : base(message) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/Exceptions/ServiceNotImplementedException.cs b/Ryujinx.HLE/Exceptions/ServiceNotImplementedException.cs new file mode 100644 index 0000000000..1be7609bb3 --- /dev/null +++ b/Ryujinx.HLE/Exceptions/ServiceNotImplementedException.cs @@ -0,0 +1,168 @@ +using Ryujinx.Common; +using Ryujinx.HLE.HOS; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Services; +using System; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Runtime.Serialization; +using System.Text; + +namespace Ryujinx.HLE.Exceptions +{ + [Serializable] + internal class ServiceNotImplementedException : Exception + { + public ServiceCtx Context { get; } + public IpcMessage Request { get; } + + public ServiceNotImplementedException(ServiceCtx context) + : this(context, "The service call is not implemented.") + { } + + public ServiceNotImplementedException(ServiceCtx context, string message) + : base(message) + { + Context = context; + Request = context.Request; + } + + public ServiceNotImplementedException(ServiceCtx context, string message, Exception inner) + : base(message, inner) + { + Context = context; + Request = context.Request; + } + + protected ServiceNotImplementedException(SerializationInfo info, StreamingContext context) + : base(info, context) + { } + + public override string Message + { + get + { + return base.Message + + Environment.NewLine + + Environment.NewLine + + BuildMessage(); + } + } + + private string BuildMessage() + { + StringBuilder sb = new StringBuilder(); + + // Print the IPC command details (service name, command ID, and handler) + (Type callingType, MethodBase callingMethod) = WalkStackTrace(new StackTrace(this)); + + if (callingType != null && callingMethod != null) + { + var ipcService = Context.Session.Service; + var ipcCommands = ipcService.Commands; + + // Find the handler for the method called + var ipcHandler = ipcCommands.FirstOrDefault(x => x.Value as MethodBase == callingMethod); + var ipcCommandId = ipcHandler.Key; + var ipcMethod = ipcHandler.Value; + + if (ipcMethod != null) + { + sb.AppendLine($"Service Command: {ipcService.GetType().FullName}: {ipcCommandId} ({ipcMethod.Name})"); + sb.AppendLine(); + } + } + + sb.AppendLine("Guest Stack Trace:"); + sb.AppendLine(Context.Thread.GetGuestStackTrace()); + + // Print buffer information + if (Request.PtrBuff.Count > 0 || + Request.SendBuff.Count > 0 || + Request.ReceiveBuff.Count > 0 || + Request.ExchangeBuff.Count > 0 || + Request.RecvListBuff.Count > 0) + { + sb.AppendLine("Buffer Information:"); + + if (Request.PtrBuff.Count > 0) + { + sb.AppendLine("\tPtrBuff:"); + + foreach (var buff in Request.PtrBuff) + { + sb.AppendLine($"\t[{buff.Index}] Position: 0x{buff.Position:x16} Size: 0x{buff.Size:x16}"); + } + } + + if (Request.SendBuff.Count > 0) + { + sb.AppendLine("\tSendBuff:"); + + foreach (var buff in Request.SendBuff) + { + sb.AppendLine($"\tPosition: 0x{buff.Position:x16} Size: 0x{buff.Size:x16} Flags: {buff.Flags}"); + } + } + + if (Request.ReceiveBuff.Count > 0) + { + sb.AppendLine("\tReceiveBuff:"); + + foreach (var buff in Request.ReceiveBuff) + { + sb.AppendLine($"\tPosition: 0x{buff.Position:x16} Size: 0x{buff.Size:x16} Flags: {buff.Flags}"); + } + } + + if (Request.ExchangeBuff.Count > 0) + { + sb.AppendLine("\tExchangeBuff:"); + + foreach (var buff in Request.ExchangeBuff) + { + sb.AppendLine($"\tPosition: 0x{buff.Position:x16} Size: 0x{buff.Size:x16} Flags: {buff.Flags}"); + } + } + + if (Request.RecvListBuff.Count > 0) + { + sb.AppendLine("\tRecvListBuff:"); + + foreach (var buff in Request.RecvListBuff) + { + sb.AppendLine($"\tPosition: 0x{buff.Position:x16} Size: 0x{buff.Size:x16}"); + } + } + + sb.AppendLine(); + } + + sb.AppendLine("Raw Request Data:"); + sb.Append(HexUtils.HexTable(Request.RawData)); + + return sb.ToString(); + } + + private (Type, MethodBase) WalkStackTrace(StackTrace trace) + { + int i = 0; + + StackFrame frame; + // Find the IIpcService method that threw this exception + while ((frame = trace.GetFrame(i++)) != null) + { + var method = frame.GetMethod(); + var declType = method.DeclaringType; + + if (typeof(IIpcService).IsAssignableFrom(declType)) + { + return (declType, method); + } + } + + return (null, null); + } + } +} diff --git a/Ryujinx.HLE/OsHle/Exceptions/UndefinedInstructionException.cs b/Ryujinx.HLE/Exceptions/UndefinedInstructionException.cs similarity index 60% rename from Ryujinx.HLE/OsHle/Exceptions/UndefinedInstructionException.cs rename to Ryujinx.HLE/Exceptions/UndefinedInstructionException.cs index d9f0b8cf9c..dfbd6c272b 100644 --- a/Ryujinx.HLE/OsHle/Exceptions/UndefinedInstructionException.cs +++ b/Ryujinx.HLE/Exceptions/UndefinedInstructionException.cs @@ -1,6 +1,6 @@ using System; -namespace Ryujinx.HLE.OsHle.Exceptions +namespace Ryujinx.HLE.Exceptions { public class UndefinedInstructionException : Exception { @@ -8,6 +8,6 @@ namespace Ryujinx.HLE.OsHle.Exceptions public UndefinedInstructionException() : base() { } - public UndefinedInstructionException(long Position, int OpCode) : base(string.Format(ExMsg, Position, OpCode)) { } + public UndefinedInstructionException(ulong address, int opCode) : base(string.Format(ExMsg, address, opCode)) { } } } \ No newline at end of file diff --git a/Ryujinx.HLE/FileSystem/Content/ContentManager.cs b/Ryujinx.HLE/FileSystem/Content/ContentManager.cs new file mode 100644 index 0000000000..680ebd522e --- /dev/null +++ b/Ryujinx.HLE/FileSystem/Content/ContentManager.cs @@ -0,0 +1,888 @@ +using LibHac; +using LibHac.Fs; +using LibHac.FsSystem; +using LibHac.FsSystem.NcaUtils; +using LibHac.Ncm; +using Ryujinx.HLE.Exceptions; +using Ryujinx.HLE.HOS.Services.Time; +using Ryujinx.HLE.Utilities; +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; + +namespace Ryujinx.HLE.FileSystem.Content +{ + internal class ContentManager + { + private const ulong SystemVersionTitleId = 0x0100000000000809; + private const ulong SystemUpdateTitleId = 0x0100000000000816; + + private Dictionary> _locationEntries; + + private Dictionary _sharedFontTitleDictionary; + private Dictionary _sharedFontFilenameDictionary; + + private SortedDictionary<(ulong titleId, NcaContentType type), string> _contentDictionary; + + private Switch _device; + + private readonly object _lock = new object(); + + public ContentManager(Switch device) + { + _contentDictionary = new SortedDictionary<(ulong, NcaContentType), string>(); + _locationEntries = new Dictionary>(); + + _sharedFontTitleDictionary = new Dictionary + { + { "FontStandard", 0x0100000000000811 }, + { "FontChineseSimplified", 0x0100000000000814 }, + { "FontExtendedChineseSimplified", 0x0100000000000814 }, + { "FontKorean", 0x0100000000000812 }, + { "FontChineseTraditional", 0x0100000000000813 }, + { "FontNintendoExtended", 0x0100000000000810 } + }; + + _sharedFontFilenameDictionary = new Dictionary + { + { "FontStandard", "nintendo_udsg-r_std_003.bfttf" }, + { "FontChineseSimplified", "nintendo_udsg-r_org_zh-cn_003.bfttf" }, + { "FontExtendedChineseSimplified", "nintendo_udsg-r_ext_zh-cn_003.bfttf" }, + { "FontKorean", "nintendo_udsg-r_ko_003.bfttf" }, + { "FontChineseTraditional", "nintendo_udjxh-db_zh-tw_003.bfttf" }, + { "FontNintendoExtended", "nintendo_ext_003.bfttf" } + }; + + _device = device; + } + + public void LoadEntries(bool ignoreMissingFonts = false) + { + lock (_lock) + { + _contentDictionary = new SortedDictionary<(ulong, NcaContentType), string>(); + _locationEntries = new Dictionary>(); + + foreach (StorageId storageId in Enum.GetValues(typeof(StorageId))) + { + string contentDirectory = null; + string contentPathString = null; + string registeredDirectory = null; + + try + { + contentPathString = LocationHelper.GetContentRoot(storageId); + contentDirectory = LocationHelper.GetRealPath(_device.FileSystem, contentPathString); + registeredDirectory = Path.Combine(contentDirectory, "registered"); + } + catch (NotSupportedException) + { + continue; + } + + Directory.CreateDirectory(registeredDirectory); + + LinkedList locationList = new LinkedList(); + + void AddEntry(LocationEntry entry) + { + locationList.AddLast(entry); + } + + foreach (string directoryPath in Directory.EnumerateDirectories(registeredDirectory)) + { + if (Directory.GetFiles(directoryPath).Length > 0) + { + string ncaName = new DirectoryInfo(directoryPath).Name.Replace(".nca", string.Empty); + + using (FileStream ncaFile = File.OpenRead(Directory.GetFiles(directoryPath)[0])) + { + Nca nca = new Nca(_device.System.KeySet, ncaFile.AsStorage()); + + string switchPath = contentPathString + ":/" + ncaFile.Name.Replace(contentDirectory, string.Empty).TrimStart(Path.DirectorySeparatorChar); + + // Change path format to switch's + switchPath = switchPath.Replace('\\', '/'); + + LocationEntry entry = new LocationEntry(switchPath, + 0, + (long)nca.Header.TitleId, + nca.Header.ContentType); + + AddEntry(entry); + + _contentDictionary.Add((nca.Header.TitleId, nca.Header.ContentType), ncaName); + } + } + } + + foreach (string filePath in Directory.EnumerateFiles(contentDirectory)) + { + if (Path.GetExtension(filePath) == ".nca") + { + string ncaName = Path.GetFileNameWithoutExtension(filePath); + + using (FileStream ncaFile = new FileStream(filePath, FileMode.Open, FileAccess.Read)) + { + Nca nca = new Nca(_device.System.KeySet, ncaFile.AsStorage()); + + string switchPath = contentPathString + ":/" + filePath.Replace(contentDirectory, string.Empty).TrimStart(Path.DirectorySeparatorChar); + + // Change path format to switch's + switchPath = switchPath.Replace('\\', '/'); + + LocationEntry entry = new LocationEntry(switchPath, + 0, + (long)nca.Header.TitleId, + nca.Header.ContentType); + + AddEntry(entry); + + _contentDictionary.Add((nca.Header.TitleId, nca.Header.ContentType), ncaName); + } + } + } + + if (_locationEntries.ContainsKey(storageId) && _locationEntries[storageId]?.Count == 0) + { + _locationEntries.Remove(storageId); + } + + if (!_locationEntries.ContainsKey(storageId)) + { + _locationEntries.Add(storageId, locationList); + } + } + + TimeManager.Instance.InitializeTimeZone(_device); + + _device.System.Font.Initialize(this, ignoreMissingFonts); + } + } + + public void ClearEntry(long titleId, NcaContentType contentType, StorageId storageId) + { + lock (_lock) + { + RemoveLocationEntry(titleId, contentType, storageId); + } + } + + public void RefreshEntries(StorageId storageId, int flag) + { + lock (_lock) + { + LinkedList locationList = _locationEntries[storageId]; + LinkedListNode locationEntry = locationList.First; + + while (locationEntry != null) + { + LinkedListNode nextLocationEntry = locationEntry.Next; + + if (locationEntry.Value.Flag == flag) + { + locationList.Remove(locationEntry.Value); + } + + locationEntry = nextLocationEntry; + } + } + } + + public bool HasNca(string ncaId, StorageId storageId) + { + lock (_lock) + { + if (_contentDictionary.ContainsValue(ncaId)) + { + var content = _contentDictionary.FirstOrDefault(x => x.Value == ncaId); + long titleId = (long)content.Key.Item1; + + NcaContentType contentType = content.Key.type; + StorageId storage = GetInstalledStorage(titleId, contentType, storageId); + + return storage == storageId; + } + } + + return false; + } + + public UInt128 GetInstalledNcaId(long titleId, NcaContentType contentType) + { + lock (_lock) + { + if (_contentDictionary.ContainsKey(((ulong)titleId, contentType))) + { + return new UInt128(_contentDictionary[((ulong)titleId, contentType)]); + } + } + + return new UInt128(); + } + + public StorageId GetInstalledStorage(long titleId, NcaContentType contentType, StorageId storageId) + { + lock (_lock) + { + LocationEntry locationEntry = GetLocation(titleId, contentType, storageId); + + return locationEntry.ContentPath != null ? + LocationHelper.GetStorageId(locationEntry.ContentPath) : StorageId.None; + } + } + + public string GetInstalledContentPath(long titleId, StorageId storageId, NcaContentType contentType) + { + lock (_lock) + { + LocationEntry locationEntry = GetLocation(titleId, contentType, storageId); + + if (VerifyContentType(locationEntry, contentType)) + { + return locationEntry.ContentPath; + } + } + + return string.Empty; + } + + public void RedirectLocation(LocationEntry newEntry, StorageId storageId) + { + lock (_lock) + { + LocationEntry locationEntry = GetLocation(newEntry.TitleId, newEntry.ContentType, storageId); + + if (locationEntry.ContentPath != null) + { + RemoveLocationEntry(newEntry.TitleId, newEntry.ContentType, storageId); + } + + AddLocationEntry(newEntry, storageId); + } + } + + private bool VerifyContentType(LocationEntry locationEntry, NcaContentType contentType) + { + if (locationEntry.ContentPath == null) + { + return false; + } + + string installedPath = _device.FileSystem.SwitchPathToSystemPath(locationEntry.ContentPath); + + if (!string.IsNullOrWhiteSpace(installedPath)) + { + if (File.Exists(installedPath)) + { + using (FileStream file = new FileStream(installedPath, FileMode.Open, FileAccess.Read)) + { + Nca nca = new Nca(_device.System.KeySet, file.AsStorage()); + bool contentCheck = nca.Header.ContentType == contentType; + + return contentCheck; + } + } + } + + return false; + } + + private void AddLocationEntry(LocationEntry entry, StorageId storageId) + { + LinkedList locationList = null; + + if (_locationEntries.ContainsKey(storageId)) + { + locationList = _locationEntries[storageId]; + } + + if (locationList != null) + { + if (locationList.Contains(entry)) + { + locationList.Remove(entry); + } + + locationList.AddLast(entry); + } + } + + private void RemoveLocationEntry(long titleId, NcaContentType contentType, StorageId storageId) + { + LinkedList locationList = null; + + if (_locationEntries.ContainsKey(storageId)) + { + locationList = _locationEntries[storageId]; + } + + if (locationList != null) + { + LocationEntry entry = + locationList.ToList().Find(x => x.TitleId == titleId && x.ContentType == contentType); + + if (entry.ContentPath != null) + { + locationList.Remove(entry); + } + } + } + + public bool TryGetFontTitle(string fontName, out long titleId) + { + return _sharedFontTitleDictionary.TryGetValue(fontName, out titleId); + } + + public bool TryGetFontFilename(string fontName, out string filename) + { + return _sharedFontFilenameDictionary.TryGetValue(fontName, out filename); + } + + private LocationEntry GetLocation(long titleId, NcaContentType contentType, StorageId storageId) + { + LinkedList locationList = _locationEntries[storageId]; + + return locationList.ToList().Find(x => x.TitleId == titleId && x.ContentType == contentType); + } + + public void InstallFirmware(string firmwareSource) + { + string contentPathString = LocationHelper.GetContentRoot(StorageId.NandSystem); + string contentDirectory = LocationHelper.GetRealPath(_device.FileSystem, contentPathString); + string registeredDirectory = Path.Combine(contentDirectory, "registered"); + string temporaryDirectory = Path.Combine(contentDirectory, "temp"); + + if (Directory.Exists(temporaryDirectory)) + { + Directory.Delete(temporaryDirectory, true); + } + + if (Directory.Exists(firmwareSource)) + { + InstallFromDirectory(firmwareSource, temporaryDirectory); + FinishInstallation(temporaryDirectory, registeredDirectory); + + return; + } + + if (!File.Exists(firmwareSource)) + { + throw new FileNotFoundException("Firmware file does not exist."); + } + + FileInfo info = new FileInfo(firmwareSource); + + using (FileStream file = File.OpenRead(firmwareSource)) + { + switch (info.Extension) + { + case ".zip": + using (ZipArchive archive = ZipFile.OpenRead(firmwareSource)) + { + InstallFromZip(archive, temporaryDirectory); + } + break; + case ".xci": + Xci xci = new Xci(_device.System.KeySet, file.AsStorage()); + InstallFromCart(xci, temporaryDirectory); + break; + default: + throw new InvalidFirmwarePackageException("Input file is not a valid firmware package"); + } + + FinishInstallation(temporaryDirectory, registeredDirectory); + } + } + + private void FinishInstallation(string temporaryDirectory, string registeredDirectory) + { + if (Directory.Exists(registeredDirectory)) + { + new DirectoryInfo(registeredDirectory).Delete(true); + } + + Directory.Move(temporaryDirectory, registeredDirectory); + + LoadEntries(); + } + + private void InstallFromDirectory(string firmwareDirectory, string temporaryDirectory) + { + InstallFromPartition(new LocalFileSystem(firmwareDirectory), temporaryDirectory); + } + + private void InstallFromPartition(IFileSystem filesystem, string temporaryDirectory) + { + foreach (var entry in filesystem.EnumerateEntries("/", "*.nca")) + { + Nca nca = new Nca(_device.System.KeySet, OpenPossibleFragmentedFile(filesystem, entry.FullPath, OpenMode.Read).AsStorage()); + + SaveNca(nca, entry.Name.Remove(entry.Name.IndexOf('.')), temporaryDirectory); + } + } + + private void InstallFromCart(Xci gameCard, string temporaryDirectory) + { + if (gameCard.HasPartition(XciPartitionType.Update)) + { + XciPartition partition = gameCard.OpenPartition(XciPartitionType.Update); + + InstallFromPartition(partition, temporaryDirectory); + } + else + { + throw new Exception("Update not found in xci file."); + } + } + + private void InstallFromZip(ZipArchive archive, string temporaryDirectory) + { + using (archive) + { + foreach (var entry in archive.Entries) + { + if (entry.FullName.EndsWith(".nca") || entry.FullName.EndsWith(".nca/00")) + { + // Clean up the name and get the NcaId + + string[] pathComponents = entry.FullName.Replace(".cnmt", "").Split('/'); + + string ncaId = pathComponents[pathComponents.Length - 1]; + + // If this is a fragmented nca, we need to get the previous element.GetZip + if (ncaId.Equals("00")) + { + ncaId = pathComponents[pathComponents.Length - 2]; + } + + if (ncaId.Contains(".nca")) + { + string newPath = Path.Combine(temporaryDirectory, ncaId); + + Directory.CreateDirectory(newPath); + + entry.ExtractToFile(Path.Combine(newPath, "00")); + } + } + } + } + } + + public void SaveNca(Nca nca, string ncaId, string temporaryDirectory) + { + string newPath = Path.Combine(temporaryDirectory, ncaId + ".nca"); + + Directory.CreateDirectory(newPath); + + using (FileStream file = File.Create(Path.Combine(newPath, "00"))) + { + nca.BaseStorage.AsStream().CopyTo(file); + } + } + + private IFile OpenPossibleFragmentedFile(IFileSystem filesystem, string path, OpenMode mode) + { + IFile file; + + if (filesystem.FileExists($"{path}/00")) + { + filesystem.OpenFile(out file, $"{path}/00", mode); + } + else + { + filesystem.OpenFile(out file, path, mode); + } + + return file; + } + + private Stream GetZipStream(ZipArchiveEntry entry) + { + MemoryStream dest = new MemoryStream(); + + Stream src = entry.Open(); + + src.CopyTo(dest); + src.Dispose(); + + return dest; + } + + public SystemVersion VerifyFirmwarePackage(string firmwarePackage) + { + Dictionary> updateNcas = new Dictionary>(); + + if (Directory.Exists(firmwarePackage)) + { + return VerifyAndGetVersionDirectory(firmwarePackage); + } + + if (!File.Exists(firmwarePackage)) + { + throw new FileNotFoundException("Firmware file does not exist."); + } + + FileInfo info = new FileInfo(firmwarePackage); + + using (FileStream file = File.OpenRead(firmwarePackage)) + { + switch (info.Extension) + { + case ".zip": + using (ZipArchive archive = ZipFile.OpenRead(firmwarePackage)) + { + return VerifyAndGetVersionZip(archive); + } + case ".xci": + Xci xci = new Xci(_device.System.KeySet, file.AsStorage()); + + if (xci.HasPartition(XciPartitionType.Update)) + { + XciPartition partition = xci.OpenPartition(XciPartitionType.Update); + + return VerifyAndGetVersion(partition); + } + else + { + throw new InvalidFirmwarePackageException("Update not found in xci file."); + } + default: + break; + } + } + + SystemVersion VerifyAndGetVersionDirectory(string firmwareDirectory) + { + return VerifyAndGetVersion(new LocalFileSystem(firmwareDirectory)); + } + + SystemVersion VerifyAndGetVersionZip(ZipArchive archive) + { + SystemVersion systemVersion = null; + + foreach (var entry in archive.Entries) + { + if (entry.FullName.EndsWith(".nca") || entry.FullName.EndsWith(".nca/00")) + { + using (Stream ncaStream = GetZipStream(entry)) + { + IStorage storage = ncaStream.AsStorage(); + + Nca nca = new Nca(_device.System.KeySet, storage); + + if (updateNcas.ContainsKey(nca.Header.TitleId)) + { + updateNcas[nca.Header.TitleId].Add((nca.Header.ContentType, entry.FullName)); + } + else + { + updateNcas.Add(nca.Header.TitleId, new List<(NcaContentType, string)>()); + updateNcas[nca.Header.TitleId].Add((nca.Header.ContentType, entry.FullName)); + } + } + } + } + + if (updateNcas.ContainsKey(SystemUpdateTitleId)) + { + var ncaEntry = updateNcas[SystemUpdateTitleId]; + + string metaPath = ncaEntry.Find(x => x.type == NcaContentType.Meta).path; + + CnmtContentMetaEntry[] metaEntries = null; + + var fileEntry = archive.GetEntry(metaPath); + + using (Stream ncaStream = GetZipStream(fileEntry)) + { + Nca metaNca = new Nca(_device.System.KeySet, ncaStream.AsStorage()); + + IFileSystem fs = metaNca.OpenFileSystem(NcaSectionType.Data, _device.System.FsIntegrityCheckLevel); + + string cnmtPath = fs.EnumerateEntries("/", "*.cnmt").Single().FullPath; + + if (fs.OpenFile(out IFile metaFile, cnmtPath, OpenMode.Read).IsSuccess()) + { + var meta = new Cnmt(metaFile.AsStream()); + + if (meta.Type == ContentMetaType.SystemUpdate) + { + metaEntries = meta.MetaEntries; + + updateNcas.Remove(SystemUpdateTitleId); + }; + } + } + + if (metaEntries == null) + { + throw new FileNotFoundException("System update title was not found in the firmware package."); + } + + if (updateNcas.ContainsKey(SystemVersionTitleId)) + { + string versionEntry = updateNcas[SystemVersionTitleId].Find(x => x.type != NcaContentType.Meta).path; + + using (Stream ncaStream = GetZipStream(archive.GetEntry(versionEntry))) + { + Nca nca = new Nca(_device.System.KeySet, ncaStream.AsStorage()); + + var romfs = nca.OpenFileSystem(NcaSectionType.Data, _device.System.FsIntegrityCheckLevel); + + if (romfs.OpenFile(out IFile systemVersionFile, "/file", OpenMode.Read).IsSuccess()) + { + systemVersion = new SystemVersion(systemVersionFile.AsStream()); + } + } + } + + foreach (CnmtContentMetaEntry metaEntry in metaEntries) + { + if (updateNcas.TryGetValue(metaEntry.TitleId, out ncaEntry)) + { + metaPath = ncaEntry.Find(x => x.type == NcaContentType.Meta).path; + + string contentPath = ncaEntry.Find(x => x.type != NcaContentType.Meta).path; + + // Nintendo in 9.0.0, removed PPC and only kept the meta nca of it. + // This is a perfect valid case, so we should just ignore the missing content nca and continue. + if (contentPath == null) + { + updateNcas.Remove(metaEntry.TitleId); + + continue; + } + + ZipArchiveEntry metaZipEntry = archive.GetEntry(metaPath); + ZipArchiveEntry contentZipEntry = archive.GetEntry(contentPath); + + using (Stream metaNcaStream = GetZipStream(metaZipEntry)) + { + using (Stream contentNcaStream = GetZipStream(contentZipEntry)) + { + Nca metaNca = new Nca(_device.System.KeySet, metaNcaStream.AsStorage()); + + IFileSystem fs = metaNca.OpenFileSystem(NcaSectionType.Data, _device.System.FsIntegrityCheckLevel); + + string cnmtPath = fs.EnumerateEntries("/", "*.cnmt").Single().FullPath; + + if (fs.OpenFile(out IFile metaFile, cnmtPath, OpenMode.Read).IsSuccess()) + { + var meta = new Cnmt(metaFile.AsStream()); + + IStorage contentStorage = contentNcaStream.AsStorage(); + if (contentStorage.GetSize(out long size).IsSuccess()) + { + byte[] contentData = new byte[size]; + + Span content = new Span(contentData); + + contentStorage.Read(0, content); + + Span hash = new Span(new byte[32]); + + LibHac.Crypto.Sha256.GenerateSha256Hash(content, hash); + + if (LibHac.Util.ArraysEqual(hash.ToArray(), meta.ContentEntries[0].Hash)) + { + updateNcas.Remove(metaEntry.TitleId); + } + } + } + } + } + } + } + + if (updateNcas.Count > 0) + { + string extraNcas = string.Empty; + + foreach (var entry in updateNcas) + { + foreach (var nca in entry.Value) + { + extraNcas += nca.path + Environment.NewLine; + } + } + + throw new InvalidFirmwarePackageException($"Firmware package contains unrelated archives. Please remove these paths: {Environment.NewLine}{extraNcas}"); + } + } + else + { + throw new FileNotFoundException("System update title was not found in the firmware package."); + } + + return systemVersion; + } + + SystemVersion VerifyAndGetVersion(IFileSystem filesystem) + { + SystemVersion systemVersion = null; + + CnmtContentMetaEntry[] metaEntries = null; + + foreach (var entry in filesystem.EnumerateEntries("/", "*.nca")) + { + IStorage ncaStorage = OpenPossibleFragmentedFile(filesystem, entry.FullPath, OpenMode.Read).AsStorage(); + + Nca nca = new Nca(_device.System.KeySet, ncaStorage); + + if (nca.Header.TitleId == SystemUpdateTitleId && nca.Header.ContentType == NcaContentType.Meta) + { + IFileSystem fs = nca.OpenFileSystem(NcaSectionType.Data, _device.System.FsIntegrityCheckLevel); + + string cnmtPath = fs.EnumerateEntries("/", "*.cnmt").Single().FullPath; + + if (fs.OpenFile(out IFile metaFile, cnmtPath, OpenMode.Read).IsSuccess()) + { + var meta = new Cnmt(metaFile.AsStream()); + + if (meta.Type == ContentMetaType.SystemUpdate) + { + metaEntries = meta.MetaEntries; + } + }; + + continue; + } + else if (nca.Header.TitleId == SystemVersionTitleId && nca.Header.ContentType == NcaContentType.Data) + { + var romfs = nca.OpenFileSystem(NcaSectionType.Data, _device.System.FsIntegrityCheckLevel); + + if (romfs.OpenFile(out IFile systemVersionFile, "/file", OpenMode.Read).IsSuccess()) + { + systemVersion = new SystemVersion(systemVersionFile.AsStream()); + } + } + + if (updateNcas.ContainsKey(nca.Header.TitleId)) + { + updateNcas[nca.Header.TitleId].Add((nca.Header.ContentType, entry.FullPath)); + } + else + { + updateNcas.Add(nca.Header.TitleId, new List<(NcaContentType, string)>()); + updateNcas[nca.Header.TitleId].Add((nca.Header.ContentType, entry.FullPath)); + } + + ncaStorage.Dispose(); + } + + if (metaEntries == null) + { + throw new FileNotFoundException("System update title was not found in the firmware package."); + } + + foreach (CnmtContentMetaEntry metaEntry in metaEntries) + { + if (updateNcas.TryGetValue(metaEntry.TitleId, out var ncaEntry)) + { + var metaNcaEntry = ncaEntry.Find(x => x.type == NcaContentType.Meta); + string contentPath = ncaEntry.Find(x => x.type != NcaContentType.Meta).path; + + // Nintendo in 9.0.0, removed PPC and only kept the meta nca of it. + // This is a perfect valid case, so we should just ignore the missing content nca and continue. + if (contentPath == null) + { + updateNcas.Remove(metaEntry.TitleId); + + continue; + } + + IStorage metaStorage = OpenPossibleFragmentedFile(filesystem, metaNcaEntry.path, OpenMode.Read).AsStorage(); + IStorage contentStorage = OpenPossibleFragmentedFile(filesystem, contentPath, OpenMode.Read).AsStorage(); + + Nca metaNca = new Nca(_device.System.KeySet, metaStorage); + + IFileSystem fs = metaNca.OpenFileSystem(NcaSectionType.Data, _device.System.FsIntegrityCheckLevel); + + string cnmtPath = fs.EnumerateEntries("/", "*.cnmt").Single().FullPath; + + if (fs.OpenFile(out IFile metaFile, cnmtPath, OpenMode.Read).IsSuccess()) + { + var meta = new Cnmt(metaFile.AsStream()); + + if (contentStorage.GetSize(out long size).IsSuccess()) + { + byte[] contentData = new byte[size]; + + Span content = new Span(contentData); + + contentStorage.Read(0, content); + + Span hash = new Span(new byte[32]); + + LibHac.Crypto.Sha256.GenerateSha256Hash(content, hash); + + if (LibHac.Util.ArraysEqual(hash.ToArray(), meta.ContentEntries[0].Hash)) + { + updateNcas.Remove(metaEntry.TitleId); + } + } + } + } + } + + if (updateNcas.Count > 0) + { + string extraNcas = string.Empty; + + foreach (var entry in updateNcas) + { + foreach (var nca in entry.Value) + { + extraNcas += nca.path + Environment.NewLine; + } + } + + throw new InvalidFirmwarePackageException($"Firmware package contains unrelated archives. Please remove these paths: {Environment.NewLine}{extraNcas}"); + } + + return systemVersion; + } + + return null; + } + + public SystemVersion GetCurrentFirmwareVersion() + { + LoadEntries(true); + + lock (_lock) + { + var locationEnties = _locationEntries[StorageId.NandSystem]; + + foreach (var entry in locationEnties) + { + if (entry.ContentType == NcaContentType.Data) + { + var path = _device.FileSystem.SwitchPathToSystemPath(entry.ContentPath); + + using (FileStream fileStream = File.OpenRead(path)) + { + Nca nca = new Nca(_device.System.KeySet, fileStream.AsStorage()); + + if (nca.Header.TitleId == SystemVersionTitleId && nca.Header.ContentType == NcaContentType.Data) + { + var romfs = nca.OpenFileSystem(NcaSectionType.Data, _device.System.FsIntegrityCheckLevel); + + if (romfs.OpenFile(out IFile systemVersionFile, "/file", OpenMode.Read).IsSuccess()) + { + return new SystemVersion(systemVersionFile.AsStream()); + } + } + + } + } + } + } + + return null; + } + } +} diff --git a/Ryujinx.HLE/FileSystem/Content/ContentPath.cs b/Ryujinx.HLE/FileSystem/Content/ContentPath.cs new file mode 100644 index 0000000000..1e2c8ab32a --- /dev/null +++ b/Ryujinx.HLE/FileSystem/Content/ContentPath.cs @@ -0,0 +1,19 @@ +namespace Ryujinx.HLE.FileSystem.Content +{ + static class ContentPath + { + public const string SystemContent = "@SystemContent"; + public const string UserContent = "@UserContent"; + public const string SdCardContent = "@SdCardContent"; + public const string SdCard = "@SdCard"; + public const string CalibFile = "@CalibFile"; + public const string Safe = "@Safe"; + public const string User = "@User"; + public const string System = "@System"; + public const string Host = "@Host"; + public const string GamecardApp = "@GcApp"; + public const string GamecardContents = "@GcS00000001"; + public const string GamecardUpdate = "@upp"; + public const string RegisteredUpdate = "@RegUpdate"; + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/FileSystem/Content/LocationEntry.cs b/Ryujinx.HLE/FileSystem/Content/LocationEntry.cs new file mode 100644 index 0000000000..15485148e0 --- /dev/null +++ b/Ryujinx.HLE/FileSystem/Content/LocationEntry.cs @@ -0,0 +1,25 @@ +using LibHac.FsSystem.NcaUtils; + +namespace Ryujinx.HLE.FileSystem.Content +{ + public struct LocationEntry + { + public string ContentPath { get; private set; } + public int Flag { get; private set; } + public long TitleId { get; private set; } + public NcaContentType ContentType { get; private set; } + + public LocationEntry(string contentPath, int flag, long titleId, NcaContentType contentType) + { + ContentPath = contentPath; + Flag = flag; + TitleId = titleId; + ContentType = contentType; + } + + public void SetFlag(int flag) + { + Flag = flag; + } + } +} diff --git a/Ryujinx.HLE/FileSystem/Content/LocationHelper.cs b/Ryujinx.HLE/FileSystem/Content/LocationHelper.cs new file mode 100644 index 0000000000..c522b053ba --- /dev/null +++ b/Ryujinx.HLE/FileSystem/Content/LocationHelper.cs @@ -0,0 +1,91 @@ +using System; +using System.IO; + +using static Ryujinx.HLE.FileSystem.VirtualFileSystem; + +namespace Ryujinx.HLE.FileSystem.Content +{ + internal static class LocationHelper + { + public static string GetRealPath(VirtualFileSystem fileSystem, string switchContentPath) + { + string basePath = fileSystem.GetBasePath(); + + switch (switchContentPath) + { + case ContentPath.SystemContent: + return Path.Combine(basePath, SystemNandPath, "Contents"); + case ContentPath.UserContent: + return Path.Combine(basePath, UserNandPath, "Contents"); + case ContentPath.SdCardContent: + return Path.Combine(fileSystem.GetSdCardPath(), "Nintendo", "Contents"); + case ContentPath.System: + return Path.Combine(basePath, SystemNandPath); + case ContentPath.User: + return Path.Combine(basePath, UserNandPath); + default: + throw new NotSupportedException($"Content Path `{switchContentPath}` is not supported."); + } + } + + public static string GetContentPath(ContentStorageId contentStorageId) + { + switch (contentStorageId) + { + case ContentStorageId.NandSystem: + return ContentPath.SystemContent; + case ContentStorageId.NandUser: + return ContentPath.UserContent; + case ContentStorageId.SdCard: + return ContentPath.SdCardContent; + default: + throw new NotSupportedException($"Content Storage `{contentStorageId}` is not supported."); + } + } + + public static string GetContentRoot(StorageId storageId) + { + switch (storageId) + { + case StorageId.NandSystem: + return ContentPath.SystemContent; + case StorageId.NandUser: + return ContentPath.UserContent; + case StorageId.SdCard: + return ContentPath.SdCardContent; + default: + throw new NotSupportedException($"Storage Id `{storageId}` is not supported."); + } + } + + public static StorageId GetStorageId(string contentPathString) + { + string cleanedPath = contentPathString.Split(':')[0]; + + switch (cleanedPath) + { + case ContentPath.SystemContent: + case ContentPath.System: + return StorageId.NandSystem; + + case ContentPath.UserContent: + case ContentPath.User: + return StorageId.NandUser; + + case ContentPath.SdCardContent: + return StorageId.SdCard; + + case ContentPath.Host: + return StorageId.Host; + + case ContentPath.GamecardApp: + case ContentPath.GamecardContents: + case ContentPath.GamecardUpdate: + return StorageId.GameCard; + + default: + return StorageId.None; + } + } + } +} diff --git a/Ryujinx.HLE/FileSystem/Content/StorageId.cs b/Ryujinx.HLE/FileSystem/Content/StorageId.cs new file mode 100644 index 0000000000..4ff3dd6574 --- /dev/null +++ b/Ryujinx.HLE/FileSystem/Content/StorageId.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.FileSystem.Content +{ + public enum ContentStorageId + { + NandSystem, + NandUser, + SdCard + } +} diff --git a/Ryujinx.HLE/FileSystem/Content/SystemVersion.cs b/Ryujinx.HLE/FileSystem/Content/SystemVersion.cs new file mode 100644 index 0000000000..08ec351255 --- /dev/null +++ b/Ryujinx.HLE/FileSystem/Content/SystemVersion.cs @@ -0,0 +1,41 @@ +using System; +using System.IO; +using System.Text; + +namespace Ryujinx.HLE.FileSystem.Content +{ + public class SystemVersion + { + public byte Major { get; } + public byte Minor { get; } + public byte Micro { get; } + public byte RevisionMajor { get; } + public byte RevisionMinor { get; } + public string PlatformString { get; } + public string Hex { get; } + public string VersionString { get; } + public string VersionTitle { get; } + + public SystemVersion(Stream systemVersionFile) + { + using (BinaryReader reader = new BinaryReader(systemVersionFile)) + { + Major = reader.ReadByte(); + Minor = reader.ReadByte(); + Micro = reader.ReadByte(); + + reader.ReadByte(); // Padding + + RevisionMajor = reader.ReadByte(); + RevisionMinor = reader.ReadByte(); + + reader.ReadBytes(2); // Padding + + PlatformString = Encoding.ASCII.GetString(reader.ReadBytes(0x20)).TrimEnd('\0'); + Hex = Encoding.ASCII.GetString(reader.ReadBytes(0x40)).TrimEnd('\0'); + VersionString = Encoding.ASCII.GetString(reader.ReadBytes(0x18)).TrimEnd('\0'); + VersionTitle = Encoding.ASCII.GetString(reader.ReadBytes(0x80)).TrimEnd('\0'); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/FileSystem/Content/TitleType.cs b/Ryujinx.HLE/FileSystem/Content/TitleType.cs new file mode 100644 index 0000000000..6ad26c9cdd --- /dev/null +++ b/Ryujinx.HLE/FileSystem/Content/TitleType.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.HLE.FileSystem.Content +{ + enum TitleType + { + SystemPrograms = 0x01, + SystemDataArchive = 0x02, + SystemUpdate = 0x03, + FirmwarePackageA = 0x04, + FirmwarePackageB = 0x05, + RegularApplication = 0x80, + Update = 0x81, + AddOnContent = 0x82, + DeltaTitle = 0x83 + } +} diff --git a/Ryujinx.HLE/FileSystem/SaveDataType.cs b/Ryujinx.HLE/FileSystem/SaveDataType.cs new file mode 100644 index 0000000000..2207fc23b8 --- /dev/null +++ b/Ryujinx.HLE/FileSystem/SaveDataType.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.HLE.FileSystem +{ + enum SaveDataType : byte + { + SystemSaveData, + SaveData, + BcatDeliveryCacheStorage, + DeviceSaveData, + TemporaryStorage, + CacheStorage + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/FileSystem/SaveHelper.cs b/Ryujinx.HLE/FileSystem/SaveHelper.cs new file mode 100644 index 0000000000..b9abcb8555 --- /dev/null +++ b/Ryujinx.HLE/FileSystem/SaveHelper.cs @@ -0,0 +1,45 @@ +using LibHac.Fs; +using LibHac.FsSystem; +using Ryujinx.HLE.HOS; +using System.IO; + +namespace Ryujinx.HLE.FileSystem +{ + static class SaveHelper + { + public static IFileSystem OpenSystemSaveData(ServiceCtx context, ulong saveId) + { + SaveInfo saveInfo = new SaveInfo(0, (long)saveId, SaveDataType.SystemSaveData, SaveSpaceId.NandSystem); + string savePath = context.Device.FileSystem.GetSavePath(context, saveInfo, false); + + if (File.Exists(savePath)) + { + string tempDirectoryPath = $"{savePath}_temp"; + + Directory.CreateDirectory(tempDirectoryPath); + + IFileSystem outputFolder = new LocalFileSystem(tempDirectoryPath); + + using (LocalStorage systemSaveData = new LocalStorage(savePath, FileAccess.Read, FileMode.Open)) + { + IFileSystem saveFs = new LibHac.FsSystem.Save.SaveDataFileSystem(context.Device.System.KeySet, systemSaveData, IntegrityCheckLevel.None, false); + + saveFs.CopyDirectory(outputFolder, "/", "/"); + } + + File.Delete(savePath); + + Directory.Move(tempDirectoryPath, savePath); + } + else + { + if (!Directory.Exists(savePath)) + { + Directory.CreateDirectory(savePath); + } + } + + return new LocalFileSystem(savePath); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/FileSystem/SaveInfo.cs b/Ryujinx.HLE/FileSystem/SaveInfo.cs new file mode 100644 index 0000000000..0fc355758e --- /dev/null +++ b/Ryujinx.HLE/FileSystem/SaveInfo.cs @@ -0,0 +1,27 @@ +using Ryujinx.HLE.Utilities; + +namespace Ryujinx.HLE.FileSystem +{ + struct SaveInfo + { + public ulong TitleId { get; private set; } + public long SaveId { get; private set; } + public SaveDataType SaveDataType { get; private set; } + public SaveSpaceId SaveSpaceId { get; private set; } + public UInt128 UserId { get; private set; } + + public SaveInfo( + ulong titleId, + long saveId, + SaveDataType saveDataType, + SaveSpaceId saveSpaceId, + UInt128 userId = new UInt128()) + { + TitleId = titleId; + SaveId = saveId; + SaveDataType = saveDataType; + SaveSpaceId = saveSpaceId; + UserId = userId; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/FileSystem/SaveSpaceId.cs b/Ryujinx.HLE/FileSystem/SaveSpaceId.cs new file mode 100644 index 0000000000..d51922df1a --- /dev/null +++ b/Ryujinx.HLE/FileSystem/SaveSpaceId.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.FileSystem +{ + enum SaveSpaceId + { + NandSystem, + NandUser, + SdCard, + TemporaryStorage + } +} diff --git a/Ryujinx.HLE/FileSystem/StorageId.cs b/Ryujinx.HLE/FileSystem/StorageId.cs new file mode 100644 index 0000000000..1ef38e0167 --- /dev/null +++ b/Ryujinx.HLE/FileSystem/StorageId.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.HLE.FileSystem +{ + internal enum StorageId + { + None, + Host, + GameCard, + NandSystem, + NandUser, + SdCard + } +} diff --git a/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs b/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs new file mode 100644 index 0000000000..257a55a22f --- /dev/null +++ b/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs @@ -0,0 +1,199 @@ +using Ryujinx.HLE.FileSystem.Content; +using Ryujinx.HLE.HOS; +using System; +using System.IO; + +namespace Ryujinx.HLE.FileSystem +{ + public class VirtualFileSystem : IDisposable + { + public const string BasePath = "Ryujinx"; + public const string NandPath = "bis"; + public const string SdCardPath = "sdcard"; + public const string SystemPath = "system"; + + public static string SafeNandPath = Path.Combine(NandPath, "safe"); + public static string SystemNandPath = Path.Combine(NandPath, "system"); + public static string UserNandPath = Path.Combine(NandPath, "user"); + + public Stream RomFs { get; private set; } + + public void LoadRomFs(string fileName) + { + RomFs = new FileStream(fileName, FileMode.Open, FileAccess.Read); + } + + public void SetRomFs(Stream romfsStream) + { + RomFs?.Close(); + RomFs = romfsStream; + } + + public string GetFullPath(string basePath, string fileName) + { + if (fileName.StartsWith("//")) + { + fileName = fileName.Substring(2); + } + else if (fileName.StartsWith('/')) + { + fileName = fileName.Substring(1); + } + else + { + return null; + } + + string fullPath = Path.GetFullPath(Path.Combine(basePath, fileName)); + + if (!fullPath.StartsWith(GetBasePath())) + { + return null; + } + + return fullPath; + } + + public string GetSdCardPath() => MakeFullPath(SdCardPath); + + public string GetNandPath() => MakeFullPath(NandPath); + + public string GetSystemPath() => MakeFullPath(SystemPath); + + internal string GetSavePath(ServiceCtx context, SaveInfo saveInfo, bool isDirectory = true) + { + string saveUserPath = ""; + string baseSavePath = NandPath; + ulong currentTitleId = saveInfo.TitleId; + + switch (saveInfo.SaveSpaceId) + { + case SaveSpaceId.NandUser: baseSavePath = UserNandPath; break; + case SaveSpaceId.NandSystem: baseSavePath = SystemNandPath; break; + case SaveSpaceId.SdCard: baseSavePath = Path.Combine(SdCardPath, "Nintendo"); break; + } + + baseSavePath = Path.Combine(baseSavePath, "save"); + + if (saveInfo.TitleId == 0 && saveInfo.SaveDataType == SaveDataType.SaveData) + { + currentTitleId = context.Process.TitleId; + } + + if (saveInfo.SaveSpaceId == SaveSpaceId.NandUser) + { + saveUserPath = saveInfo.UserId.IsNull ? "savecommon" : saveInfo.UserId.ToString(); + } + + string savePath = Path.Combine(baseSavePath, + saveInfo.SaveId.ToString("x16"), + saveUserPath, + saveInfo.SaveDataType == SaveDataType.SaveData ? currentTitleId.ToString("x16") : string.Empty); + + return MakeFullPath(savePath, isDirectory); + } + + public string GetFullPartitionPath(string partitionPath) + { + return MakeFullPath(partitionPath); + } + + public string SwitchPathToSystemPath(string switchPath) + { + string[] parts = switchPath.Split(":"); + + if (parts.Length != 2) + { + return null; + } + + return GetFullPath(MakeFullPath(parts[0]), parts[1]); + } + + public string SystemPathToSwitchPath(string systemPath) + { + string baseSystemPath = GetBasePath() + Path.DirectorySeparatorChar; + + if (systemPath.StartsWith(baseSystemPath)) + { + string rawPath = systemPath.Replace(baseSystemPath, ""); + int firstSeparatorOffset = rawPath.IndexOf(Path.DirectorySeparatorChar); + + if (firstSeparatorOffset == -1) + { + return $"{rawPath}:/"; + } + + string basePath = rawPath.Substring(0, firstSeparatorOffset); + string fileName = rawPath.Substring(firstSeparatorOffset + 1); + + return $"{basePath}:/{fileName}"; + } + return null; + } + + private string MakeFullPath(string path, bool isDirectory = true) + { + // Handles Common Switch Content Paths + switch (path) + { + case ContentPath.SdCard: + case "@Sdcard": + path = SdCardPath; + break; + case ContentPath.User: + path = UserNandPath; + break; + case ContentPath.System: + path = SystemNandPath; + break; + case ContentPath.SdCardContent: + path = Path.Combine(SdCardPath, "Nintendo", "Contents"); + break; + case ContentPath.UserContent: + path = Path.Combine(UserNandPath, "Contents"); + break; + case ContentPath.SystemContent: + path = Path.Combine(SystemNandPath, "Contents"); + break; + } + + string fullPath = Path.Combine(GetBasePath(), path); + + if (isDirectory) + { + if (!Directory.Exists(fullPath)) + { + Directory.CreateDirectory(fullPath); + } + } + + return fullPath; + } + + public DriveInfo GetDrive() + { + return new DriveInfo(Path.GetPathRoot(GetBasePath())); + } + + public string GetBasePath() + { + string appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); + + return Path.Combine(appDataPath, BasePath); + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + RomFs?.Dispose(); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/Gpu/Engines/INvGpuEngine.cs b/Ryujinx.HLE/Gpu/Engines/INvGpuEngine.cs deleted file mode 100644 index 068878a988..0000000000 --- a/Ryujinx.HLE/Gpu/Engines/INvGpuEngine.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Ryujinx.HLE.Gpu.Memory; - -namespace Ryujinx.HLE.Gpu.Engines -{ - interface INvGpuEngine - { - int[] Registers { get; } - - void CallMethod(NvGpuVmm Vmm, NvGpuPBEntry PBEntry); - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/Gpu/Engines/MacroInterpreter.cs b/Ryujinx.HLE/Gpu/Engines/MacroInterpreter.cs deleted file mode 100644 index aef2eb4c8a..0000000000 --- a/Ryujinx.HLE/Gpu/Engines/MacroInterpreter.cs +++ /dev/null @@ -1,434 +0,0 @@ -using Ryujinx.HLE.Gpu.Exceptions; -using Ryujinx.HLE.Gpu.Memory; -using System; -using System.Collections.Generic; - -namespace Ryujinx.HLE.Gpu.Engines -{ - class MacroInterpreter - { - private const int MaxCallCountPerRun = 500; - - private int CallCount; - - private enum AssignmentOperation - { - IgnoreAndFetch = 0, - Move = 1, - MoveAndSetMaddr = 2, - FetchAndSend = 3, - MoveAndSend = 4, - FetchAndSetMaddr = 5, - MoveAndSetMaddrThenFetchAndSend = 6, - MoveAndSetMaddrThenSendHigh = 7 - } - - private enum AluOperation - { - AluReg = 0, - AddImmediate = 1, - BitfieldReplace = 2, - BitfieldExtractLslImm = 3, - BitfieldExtractLslReg = 4, - ReadImmediate = 5 - } - - private enum AluRegOperation - { - Add = 0, - AddWithCarry = 1, - Subtract = 2, - SubtractWithBorrow = 3, - BitwiseExclusiveOr = 8, - BitwiseOr = 9, - BitwiseAnd = 10, - BitwiseAndNot = 11, - BitwiseNotAnd = 12 - } - - private NvGpuFifo PFifo; - private INvGpuEngine Engine; - - public Queue Fifo { get; private set; } - - private int[] Gprs; - - private int MethAddr; - private int MethIncr; - - private bool Carry; - - private int OpCode; - - private int PipeOp; - - private int Pc; - - public MacroInterpreter(NvGpuFifo PFifo, INvGpuEngine Engine) - { - this.PFifo = PFifo; - this.Engine = Engine; - - Fifo = new Queue(); - - Gprs = new int[8]; - } - - public void Execute(NvGpuVmm Vmm, int[] Mme, int Position, int Param) - { - Reset(); - - Gprs[1] = Param; - - Pc = Position; - - FetchOpCode(Mme); - - while (Step(Vmm, Mme)); - - //Due to the delay slot, we still need to execute - //one more instruction before we actually exit. - Step(Vmm, Mme); - } - - private void Reset() - { - for (int Index = 0; Index < Gprs.Length; Index++) - { - Gprs[Index] = 0; - } - - MethAddr = 0; - MethIncr = 0; - - Carry = false; - - CallCount = 0; - } - - private bool Step(NvGpuVmm Vmm, int[] Mme) - { - int BaseAddr = Pc - 1; - - FetchOpCode(Mme); - - if ((OpCode & 7) < 7) - { - //Operation produces a value. - AssignmentOperation AsgOp = (AssignmentOperation)((OpCode >> 4) & 7); - - int Result = GetAluResult(); - - switch (AsgOp) - { - //Fetch parameter and ignore result. - case AssignmentOperation.IgnoreAndFetch: - { - SetDstGpr(FetchParam()); - - break; - } - - //Move result. - case AssignmentOperation.Move: - { - SetDstGpr(Result); - - break; - } - - //Move result and use as Method Address. - case AssignmentOperation.MoveAndSetMaddr: - { - SetDstGpr(Result); - - SetMethAddr(Result); - - break; - } - - //Fetch parameter and send result. - case AssignmentOperation.FetchAndSend: - { - SetDstGpr(FetchParam()); - - Send(Vmm, Result); - - break; - } - - //Move and send result. - case AssignmentOperation.MoveAndSend: - { - SetDstGpr(Result); - - Send(Vmm, Result); - - break; - } - - //Fetch parameter and use result as Method Address. - case AssignmentOperation.FetchAndSetMaddr: - { - SetDstGpr(FetchParam()); - - SetMethAddr(Result); - - break; - } - - //Move result and use as Method Address, then fetch and send paramter. - case AssignmentOperation.MoveAndSetMaddrThenFetchAndSend: - { - SetDstGpr(Result); - - SetMethAddr(Result); - - Send(Vmm, FetchParam()); - - break; - } - - //Move result and use as Method Address, then send bits 17:12 of result. - case AssignmentOperation.MoveAndSetMaddrThenSendHigh: - { - SetDstGpr(Result); - - SetMethAddr(Result); - - Send(Vmm, (Result >> 12) & 0x3f); - - break; - } - } - } - else - { - //Branch. - bool OnNotZero = ((OpCode >> 4) & 1) != 0; - - bool Taken = OnNotZero - ? GetGprA() != 0 - : GetGprA() == 0; - - if (Taken) - { - Pc = BaseAddr + GetImm(); - - bool NoDelays = (OpCode & 0x20) != 0; - - if (NoDelays) - { - FetchOpCode(Mme); - } - - return true; - } - } - - bool Exit = (OpCode & 0x80) != 0; - - return !Exit; - } - - private void FetchOpCode(int[] Mme) - { - OpCode = PipeOp; - - PipeOp = Mme[Pc++]; - } - - private int GetAluResult() - { - AluOperation Op = (AluOperation)(OpCode & 7); - - switch (Op) - { - case AluOperation.AluReg: - { - AluRegOperation AluOp = (AluRegOperation)((OpCode >> 17) & 0x1f); - - return GetAluResult(AluOp, GetGprA(), GetGprB()); - } - - case AluOperation.AddImmediate: - { - return GetGprA() + GetImm(); - } - - case AluOperation.BitfieldReplace: - case AluOperation.BitfieldExtractLslImm: - case AluOperation.BitfieldExtractLslReg: - { - int BfSrcBit = (OpCode >> 17) & 0x1f; - int BfSize = (OpCode >> 22) & 0x1f; - int BfDstBit = (OpCode >> 27) & 0x1f; - - int BfMask = (1 << BfSize) - 1; - - int Dst = GetGprA(); - int Src = GetGprB(); - - switch (Op) - { - case AluOperation.BitfieldReplace: - { - Src = (int)((uint)Src >> BfSrcBit) & BfMask; - - Dst &= ~(BfMask << BfDstBit); - - Dst |= Src << BfDstBit; - - return Dst; - } - - case AluOperation.BitfieldExtractLslImm: - { - Src = (int)((uint)Src >> Dst) & BfMask; - - return Src << BfDstBit; - } - - case AluOperation.BitfieldExtractLslReg: - { - Src = (int)((uint)Src >> BfSrcBit) & BfMask; - - return Src << Dst; - } - } - - break; - } - - case AluOperation.ReadImmediate: - { - return Read(GetGprA() + GetImm()); - } - } - - throw new ArgumentException(nameof(OpCode)); - } - - private int GetAluResult(AluRegOperation AluOp, int A, int B) - { - switch (AluOp) - { - case AluRegOperation.Add: - { - ulong Result = (ulong)A + (ulong)B; - - Carry = Result > 0xffffffff; - - return (int)Result; - } - - case AluRegOperation.AddWithCarry: - { - ulong Result = (ulong)A + (ulong)B + (Carry ? 1UL : 0UL); - - Carry = Result > 0xffffffff; - - return (int)Result; - } - - case AluRegOperation.Subtract: - { - ulong Result = (ulong)A - (ulong)B; - - Carry = Result < 0x100000000; - - return (int)Result; - } - - case AluRegOperation.SubtractWithBorrow: - { - ulong Result = (ulong)A - (ulong)B - (Carry ? 0UL : 1UL); - - Carry = Result < 0x100000000; - - return (int)Result; - } - - case AluRegOperation.BitwiseExclusiveOr: return A ^ B; - case AluRegOperation.BitwiseOr: return A | B; - case AluRegOperation.BitwiseAnd: return A & B; - case AluRegOperation.BitwiseAndNot: return A & ~B; - case AluRegOperation.BitwiseNotAnd: return ~(A & B); - } - - throw new ArgumentOutOfRangeException(nameof(AluOp)); - } - - private int GetImm() - { - //Note: The immediate is signed, the sign-extension is intended here. - return OpCode >> 14; - } - - private void SetMethAddr(int Value) - { - MethAddr = (Value >> 0) & 0xfff; - MethIncr = (Value >> 12) & 0x3f; - } - - private void SetDstGpr(int Value) - { - Gprs[(OpCode >> 8) & 7] = Value; - } - - private int GetGprA() - { - return GetGprValue((OpCode >> 11) & 7); - } - - private int GetGprB() - { - return GetGprValue((OpCode >> 14) & 7); - } - - private int GetGprValue(int Index) - { - return Index != 0 ? Gprs[Index] : 0; - } - - private int FetchParam() - { - int Value; - - //If we don't have any parameters in the FIFO, - //keep running the PFIFO engine until it writes the parameters. - while (!Fifo.TryDequeue(out Value)) - { - if (!PFifo.Step()) - { - return 0; - } - } - - return Value; - } - - private int Read(int Reg) - { - return Engine.Registers[Reg]; - } - - private void Send(NvGpuVmm Vmm, int Value) - { - //This is an artificial limit that prevents excessive calls - //to VertexEndGl since that triggers rendering, and in the - //case that something is bugged and causes an absurd amount of - //draw calls, this prevents the system from freezing (and throws instead). - if (MethAddr == 0x585 && ++CallCount > MaxCallCountPerRun) - { - GpuExceptionHelper.ThrowCallCoundExceeded(); - } - - NvGpuPBEntry PBEntry = new NvGpuPBEntry(MethAddr, 0, Value); - - Engine.CallMethod(Vmm, PBEntry); - - MethAddr += MethIncr; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/Gpu/Engines/NvGpuEngine.cs b/Ryujinx.HLE/Gpu/Engines/NvGpuEngine.cs deleted file mode 100644 index f9d6342cf4..0000000000 --- a/Ryujinx.HLE/Gpu/Engines/NvGpuEngine.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Ryujinx.HLE.Gpu.Engines -{ - enum NvGpuEngine - { - _2d = 0x902d, - _3d = 0xb197, - Compute = 0xb1c0, - Kepler = 0xa140, - Dma = 0xb0b5 - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/Gpu/Engines/NvGpuEngine2d.cs b/Ryujinx.HLE/Gpu/Engines/NvGpuEngine2d.cs deleted file mode 100644 index f150b3f5e9..0000000000 --- a/Ryujinx.HLE/Gpu/Engines/NvGpuEngine2d.cs +++ /dev/null @@ -1,170 +0,0 @@ -using Ryujinx.Graphics.Gal; -using Ryujinx.HLE.Gpu.Memory; -using Ryujinx.HLE.Gpu.Texture; -using System.Collections.Generic; - -namespace Ryujinx.HLE.Gpu.Engines -{ - class NvGpuEngine2d : INvGpuEngine - { - private enum CopyOperation - { - SrcCopyAnd, - RopAnd, - Blend, - SrcCopy, - Rop, - SrcCopyPremult, - BlendPremult - } - - public int[] Registers { get; private set; } - - private NvGpu Gpu; - - private Dictionary Methods; - - public NvGpuEngine2d(NvGpu Gpu) - { - this.Gpu = Gpu; - - Registers = new int[0xe00]; - - Methods = new Dictionary(); - - void AddMethod(int Meth, int Count, int Stride, NvGpuMethod Method) - { - while (Count-- > 0) - { - Methods.Add(Meth, Method); - - Meth += Stride; - } - } - - AddMethod(0xb5, 1, 1, TextureCopy); - } - - public void CallMethod(NvGpuVmm Vmm, NvGpuPBEntry PBEntry) - { - if (Methods.TryGetValue(PBEntry.Method, out NvGpuMethod Method)) - { - Method(Vmm, PBEntry); - } - else - { - WriteRegister(PBEntry); - } - } - - private void TextureCopy(NvGpuVmm Vmm, NvGpuPBEntry PBEntry) - { - CopyOperation Operation = (CopyOperation)ReadRegister(NvGpuEngine2dReg.CopyOperation); - - bool SrcLinear = ReadRegister(NvGpuEngine2dReg.SrcLinear) != 0; - int SrcWidth = ReadRegister(NvGpuEngine2dReg.SrcWidth); - int SrcHeight = ReadRegister(NvGpuEngine2dReg.SrcHeight); - - bool DstLinear = ReadRegister(NvGpuEngine2dReg.DstLinear) != 0; - int DstWidth = ReadRegister(NvGpuEngine2dReg.DstWidth); - int DstHeight = ReadRegister(NvGpuEngine2dReg.DstHeight); - int DstPitch = ReadRegister(NvGpuEngine2dReg.DstPitch); - int DstBlkDim = ReadRegister(NvGpuEngine2dReg.DstBlockDimensions); - - TextureSwizzle DstSwizzle = DstLinear - ? TextureSwizzle.Pitch - : TextureSwizzle.BlockLinear; - - int DstBlockHeight = 1 << ((DstBlkDim >> 4) & 0xf); - - long Key = Vmm.GetPhysicalAddress(MakeInt64From2xInt32(NvGpuEngine2dReg.SrcAddress)); - - long SrcAddress = MakeInt64From2xInt32(NvGpuEngine2dReg.SrcAddress); - long DstAddress = MakeInt64From2xInt32(NvGpuEngine2dReg.DstAddress); - - bool IsFbTexture = Gpu.Engine3d.IsFrameBufferPosition(Key); - - if (IsFbTexture && DstLinear) - { - DstSwizzle = TextureSwizzle.BlockLinear; - } - - TextureInfo DstTexture = new TextureInfo( - DstAddress, - DstWidth, - DstHeight, - DstBlockHeight, - DstBlockHeight, - DstSwizzle, - GalTextureFormat.A8B8G8R8); - - if (IsFbTexture) - { - //TODO: Change this when the correct frame buffer resolution is used. - //Currently, the frame buffer size is hardcoded to 1280x720. - SrcWidth = 1280; - SrcHeight = 720; - - Gpu.Renderer.FrameBuffer.GetBufferData(Key, (byte[] Buffer) => - { - CopyTexture( - Vmm, - DstTexture, - Buffer, - SrcWidth, - SrcHeight); - }); - } - else - { - long Size = SrcWidth * SrcHeight * 4; - - byte[] Buffer = Vmm.ReadBytes(SrcAddress, Size); - - CopyTexture( - Vmm, - DstTexture, - Buffer, - SrcWidth, - SrcHeight); - } - } - - private void CopyTexture( - NvGpuVmm Vmm, - TextureInfo Texture, - byte[] Buffer, - int Width, - int Height) - { - TextureWriter.Write(Vmm, Texture, Buffer, Width, Height); - } - - private long MakeInt64From2xInt32(NvGpuEngine2dReg Reg) - { - return - (long)Registers[(int)Reg + 0] << 32 | - (uint)Registers[(int)Reg + 1]; - } - - private void WriteRegister(NvGpuPBEntry PBEntry) - { - int ArgsCount = PBEntry.Arguments.Count; - - if (ArgsCount > 0) - { - Registers[PBEntry.Method] = PBEntry.Arguments[ArgsCount - 1]; - } - } - - private int ReadRegister(NvGpuEngine2dReg Reg) - { - return Registers[(int)Reg]; - } - - private void WriteRegister(NvGpuEngine2dReg Reg, int Value) - { - Registers[(int)Reg] = Value; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/Gpu/Engines/NvGpuEngine2dReg.cs b/Ryujinx.HLE/Gpu/Engines/NvGpuEngine2dReg.cs deleted file mode 100644 index 29d66d463e..0000000000 --- a/Ryujinx.HLE/Gpu/Engines/NvGpuEngine2dReg.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace Ryujinx.HLE.Gpu.Engines -{ - enum NvGpuEngine2dReg - { - DstFormat = 0x80, - DstLinear = 0x81, - DstBlockDimensions = 0x82, - DstDepth = 0x83, - DstLayer = 0x84, - DstPitch = 0x85, - DstWidth = 0x86, - DstHeight = 0x87, - DstAddress = 0x88, - SrcFormat = 0x8c, - SrcLinear = 0x8d, - SrcBlockDimensions = 0x8e, - SrcDepth = 0x8f, - SrcLayer = 0x90, - SrcPitch = 0x91, - SrcWidth = 0x92, - SrcHeight = 0x93, - SrcAddress = 0x94, - CopyOperation = 0xab - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/Gpu/Engines/NvGpuEngine3d.cs b/Ryujinx.HLE/Gpu/Engines/NvGpuEngine3d.cs deleted file mode 100644 index 2bacd71b36..0000000000 --- a/Ryujinx.HLE/Gpu/Engines/NvGpuEngine3d.cs +++ /dev/null @@ -1,764 +0,0 @@ -using Ryujinx.Graphics.Gal; -using Ryujinx.HLE.Gpu.Memory; -using Ryujinx.HLE.Gpu.Texture; -using System; -using System.Collections.Generic; - -namespace Ryujinx.HLE.Gpu.Engines -{ - class NvGpuEngine3d : INvGpuEngine - { - public int[] Registers { get; private set; } - - private NvGpu Gpu; - - private Dictionary Methods; - - private struct ConstBuffer - { - public bool Enabled; - public long Position; - public int Size; - } - - private ConstBuffer[][] ConstBuffers; - - private HashSet FrameBuffers; - - public NvGpuEngine3d(NvGpu Gpu) - { - this.Gpu = Gpu; - - Registers = new int[0xe00]; - - Methods = new Dictionary(); - - void AddMethod(int Meth, int Count, int Stride, NvGpuMethod Method) - { - while (Count-- > 0) - { - Methods.Add(Meth, Method); - - Meth += Stride; - } - } - - AddMethod(0x585, 1, 1, VertexEndGl); - AddMethod(0x674, 1, 1, ClearBuffers); - AddMethod(0x6c3, 1, 1, QueryControl); - AddMethod(0x8e4, 16, 1, CbData); - AddMethod(0x904, 5, 8, CbBind); - - ConstBuffers = new ConstBuffer[6][]; - - for (int Index = 0; Index < ConstBuffers.Length; Index++) - { - ConstBuffers[Index] = new ConstBuffer[18]; - } - - FrameBuffers = new HashSet(); - } - - public void CallMethod(NvGpuVmm Vmm, NvGpuPBEntry PBEntry) - { - if (Methods.TryGetValue(PBEntry.Method, out NvGpuMethod Method)) - { - Method(Vmm, PBEntry); - } - else - { - WriteRegister(PBEntry); - } - } - - private void VertexEndGl(NvGpuVmm Vmm, NvGpuPBEntry PBEntry) - { - LockCaches(); - - SetFrameBuffer(Vmm, 0); - - long[] Keys = UploadShaders(Vmm); - - Gpu.Renderer.Shader.BindProgram(); - - //Note: Uncomment SetFrontFace SetCullFace when flipping issues are solved - //SetFrontFace(); - //SetCullFace(); - SetDepth(); - SetStencil(); - SetAlphaBlending(); - SetPrimitiveRestart(); - - UploadTextures(Vmm, Keys); - UploadUniforms(Vmm); - UploadVertexArrays(Vmm); - - UnlockCaches(); - } - - private void LockCaches() - { - Gpu.Renderer.Rasterizer.LockCaches(); - Gpu.Renderer.Texture.LockCache(); - } - - private void UnlockCaches() - { - Gpu.Renderer.Rasterizer.UnlockCaches(); - Gpu.Renderer.Texture.UnlockCache(); - } - - private void ClearBuffers(NvGpuVmm Vmm, NvGpuPBEntry PBEntry) - { - int Arg0 = PBEntry.Arguments[0]; - - int FbIndex = (Arg0 >> 6) & 0xf; - - GalClearBufferFlags Flags = (GalClearBufferFlags)(Arg0 & 0x3f); - - SetFrameBuffer(Vmm, FbIndex); - - Gpu.Renderer.Rasterizer.ClearBuffers(Flags); - } - - private void SetFrameBuffer(NvGpuVmm Vmm, int FbIndex) - { - long VA = MakeInt64From2xInt32(NvGpuEngine3dReg.FrameBufferNAddress + FbIndex * 0x10); - - long Key = Vmm.GetPhysicalAddress(VA); - - FrameBuffers.Add(Key); - - int Width = ReadRegister(NvGpuEngine3dReg.FrameBufferNWidth + FbIndex * 0x10); - int Height = ReadRegister(NvGpuEngine3dReg.FrameBufferNHeight + FbIndex * 0x10); - - //Note: Using the Width/Height results seems to give incorrect results. - //Maybe the size of all frame buffers is hardcoded to screen size? This seems unlikely. - Gpu.Renderer.FrameBuffer.Create(Key, 1280, 720); - Gpu.Renderer.FrameBuffer.Bind(Key); - } - - private long[] UploadShaders(NvGpuVmm Vmm) - { - long[] Keys = new long[5]; - - long BasePosition = MakeInt64From2xInt32(NvGpuEngine3dReg.ShaderAddress); - - int Index = 1; - - int VpAControl = ReadRegister(NvGpuEngine3dReg.ShaderNControl); - - bool VpAEnable = (VpAControl & 1) != 0; - - if (VpAEnable) - { - //Note: The maxwell supports 2 vertex programs, usually - //only VP B is used, but in some cases VP A is also used. - //In this case, it seems to function as an extra vertex - //shader stage. - //The graphics abstraction layer has a special overload for this - //case, which should merge the two shaders into one vertex shader. - int VpAOffset = ReadRegister(NvGpuEngine3dReg.ShaderNOffset); - int VpBOffset = ReadRegister(NvGpuEngine3dReg.ShaderNOffset + 0x10); - - long VpAPos = BasePosition + (uint)VpAOffset; - long VpBPos = BasePosition + (uint)VpBOffset; - - Gpu.Renderer.Shader.Create(Vmm, VpAPos, VpBPos, GalShaderType.Vertex); - Gpu.Renderer.Shader.Bind(VpBPos); - - Index = 2; - } - - for (; Index < 6; Index++) - { - int Control = ReadRegister(NvGpuEngine3dReg.ShaderNControl + Index * 0x10); - int Offset = ReadRegister(NvGpuEngine3dReg.ShaderNOffset + Index * 0x10); - - //Note: Vertex Program (B) is always enabled. - bool Enable = (Control & 1) != 0 || Index == 1; - - if (!Enable) - { - continue; - } - - long Key = BasePosition + (uint)Offset; - - GalShaderType ShaderType = GetTypeFromProgram(Index); - - Keys[(int)ShaderType] = Key; - - Gpu.Renderer.Shader.Create(Vmm, Key, ShaderType); - Gpu.Renderer.Shader.Bind(Key); - } - - float SignX = GetFlipSign(NvGpuEngine3dReg.ViewportScaleX); - float SignY = GetFlipSign(NvGpuEngine3dReg.ViewportScaleY); - - Gpu.Renderer.Shader.SetFlip(SignX, SignY); - - return Keys; - } - - private static GalShaderType GetTypeFromProgram(int Program) - { - switch (Program) - { - case 0: - case 1: return GalShaderType.Vertex; - case 2: return GalShaderType.TessControl; - case 3: return GalShaderType.TessEvaluation; - case 4: return GalShaderType.Geometry; - case 5: return GalShaderType.Fragment; - } - - throw new ArgumentOutOfRangeException(nameof(Program)); - } - - private void SetFrontFace() - { - float SignX = GetFlipSign(NvGpuEngine3dReg.ViewportScaleX); - float SignY = GetFlipSign(NvGpuEngine3dReg.ViewportScaleY); - - GalFrontFace FrontFace = (GalFrontFace)ReadRegister(NvGpuEngine3dReg.FrontFace); - - //Flipping breaks facing. Flipping front facing too fixes it - if (SignX != SignY) - { - switch (FrontFace) - { - case GalFrontFace.CW: - FrontFace = GalFrontFace.CCW; - break; - - case GalFrontFace.CCW: - FrontFace = GalFrontFace.CW; - break; - } - } - - Gpu.Renderer.Rasterizer.SetFrontFace(FrontFace); - } - - private void SetCullFace() - { - bool Enable = (ReadRegister(NvGpuEngine3dReg.CullFaceEnable) & 1) != 0; - - if (Enable) - { - Gpu.Renderer.Rasterizer.EnableCullFace(); - } - else - { - Gpu.Renderer.Rasterizer.DisableCullFace(); - } - - if (!Enable) - { - return; - } - - GalCullFace CullFace = (GalCullFace)ReadRegister(NvGpuEngine3dReg.CullFace); - - Gpu.Renderer.Rasterizer.SetCullFace(CullFace); - } - - private void SetDepth() - { - float ClearDepth = ReadRegisterFloat(NvGpuEngine3dReg.ClearDepth); - - Gpu.Renderer.Rasterizer.SetClearDepth(ClearDepth); - - bool Enable = (ReadRegister(NvGpuEngine3dReg.DepthTestEnable) & 1) != 0; - - if (Enable) - { - Gpu.Renderer.Rasterizer.EnableDepthTest(); - } - else - { - Gpu.Renderer.Rasterizer.DisableDepthTest(); - } - - if (!Enable) - { - return; - } - - GalComparisonOp Func = (GalComparisonOp)ReadRegister(NvGpuEngine3dReg.DepthTestFunction); - - Gpu.Renderer.Rasterizer.SetDepthFunction(Func); - } - - private void SetStencil() - { - int ClearStencil = ReadRegister(NvGpuEngine3dReg.ClearStencil); - - Gpu.Renderer.Rasterizer.SetClearStencil(ClearStencil); - - bool Enable = (ReadRegister(NvGpuEngine3dReg.StencilEnable) & 1) != 0; - - if (Enable) - { - Gpu.Renderer.Rasterizer.EnableStencilTest(); - } - else - { - Gpu.Renderer.Rasterizer.DisableStencilTest(); - } - - if (!Enable) - { - return; - } - - void SetFaceStencil( - bool IsFrontFace, - NvGpuEngine3dReg Func, - NvGpuEngine3dReg FuncRef, - NvGpuEngine3dReg FuncMask, - NvGpuEngine3dReg OpFail, - NvGpuEngine3dReg OpZFail, - NvGpuEngine3dReg OpZPass, - NvGpuEngine3dReg Mask) - { - Gpu.Renderer.Rasterizer.SetStencilFunction( - IsFrontFace, - (GalComparisonOp)ReadRegister(Func), - ReadRegister(FuncRef), - ReadRegister(FuncMask)); - - Gpu.Renderer.Rasterizer.SetStencilOp( - IsFrontFace, - (GalStencilOp)ReadRegister(OpFail), - (GalStencilOp)ReadRegister(OpZFail), - (GalStencilOp)ReadRegister(OpZPass)); - - Gpu.Renderer.Rasterizer.SetStencilMask(IsFrontFace, ReadRegister(Mask)); - } - - SetFaceStencil(false, - NvGpuEngine3dReg.StencilBackFuncFunc, - NvGpuEngine3dReg.StencilBackFuncRef, - NvGpuEngine3dReg.StencilBackFuncMask, - NvGpuEngine3dReg.StencilBackOpFail, - NvGpuEngine3dReg.StencilBackOpZFail, - NvGpuEngine3dReg.StencilBackOpZPass, - NvGpuEngine3dReg.StencilBackMask); - - SetFaceStencil(true, - NvGpuEngine3dReg.StencilFrontFuncFunc, - NvGpuEngine3dReg.StencilFrontFuncRef, - NvGpuEngine3dReg.StencilFrontFuncMask, - NvGpuEngine3dReg.StencilFrontOpFail, - NvGpuEngine3dReg.StencilFrontOpZFail, - NvGpuEngine3dReg.StencilFrontOpZPass, - NvGpuEngine3dReg.StencilFrontMask); - } - - private void SetAlphaBlending() - { - //TODO: Support independent blend properly. - bool Enable = (ReadRegister(NvGpuEngine3dReg.IBlendNEnable) & 1) != 0; - - if (Enable) - { - Gpu.Renderer.Blend.Enable(); - } - else - { - Gpu.Renderer.Blend.Disable(); - } - - if (!Enable) - { - //If blend is not enabled, then the other values have no effect. - //Note that if it is disabled, the register may contain invalid values. - return; - } - - bool BlendSeparateAlpha = (ReadRegister(NvGpuEngine3dReg.IBlendNSeparateAlpha) & 1) != 0; - - GalBlendEquation EquationRgb = (GalBlendEquation)ReadRegister(NvGpuEngine3dReg.IBlendNEquationRgb); - - GalBlendFactor FuncSrcRgb = (GalBlendFactor)ReadRegister(NvGpuEngine3dReg.IBlendNFuncSrcRgb); - GalBlendFactor FuncDstRgb = (GalBlendFactor)ReadRegister(NvGpuEngine3dReg.IBlendNFuncDstRgb); - - if (BlendSeparateAlpha) - { - GalBlendEquation EquationAlpha = (GalBlendEquation)ReadRegister(NvGpuEngine3dReg.IBlendNEquationAlpha); - - GalBlendFactor FuncSrcAlpha = (GalBlendFactor)ReadRegister(NvGpuEngine3dReg.IBlendNFuncSrcAlpha); - GalBlendFactor FuncDstAlpha = (GalBlendFactor)ReadRegister(NvGpuEngine3dReg.IBlendNFuncDstAlpha); - - Gpu.Renderer.Blend.SetSeparate( - EquationRgb, - EquationAlpha, - FuncSrcRgb, - FuncDstRgb, - FuncSrcAlpha, - FuncDstAlpha); - } - else - { - Gpu.Renderer.Blend.Set(EquationRgb, FuncSrcRgb, FuncDstRgb); - } - } - - private void SetPrimitiveRestart() - { - bool Enable = (ReadRegister(NvGpuEngine3dReg.PrimRestartEnable) & 1) != 0; - - if (Enable) - { - Gpu.Renderer.Rasterizer.EnablePrimitiveRestart(); - } - else - { - Gpu.Renderer.Rasterizer.DisablePrimitiveRestart(); - } - - if (!Enable) - { - return; - } - - uint Index = (uint)ReadRegister(NvGpuEngine3dReg.PrimRestartIndex); - - Gpu.Renderer.Rasterizer.SetPrimitiveRestartIndex(Index); - } - - private void UploadTextures(NvGpuVmm Vmm, long[] Keys) - { - long BaseShPosition = MakeInt64From2xInt32(NvGpuEngine3dReg.ShaderAddress); - - int TextureCbIndex = ReadRegister(NvGpuEngine3dReg.TextureCbIndex); - - //Note: On the emulator renderer, Texture Unit 0 is - //reserved for drawing the frame buffer. - int TexIndex = 1; - - for (int Index = 0; Index < Keys.Length; Index++) - { - foreach (ShaderDeclInfo DeclInfo in Gpu.Renderer.Shader.GetTextureUsage(Keys[Index])) - { - long Position = ConstBuffers[Index][TextureCbIndex].Position; - - UploadTexture(Vmm, Position, TexIndex, DeclInfo.Index); - - Gpu.Renderer.Shader.EnsureTextureBinding(DeclInfo.Name, TexIndex); - - TexIndex++; - } - } - } - - private void UploadTexture(NvGpuVmm Vmm, long BasePosition, int TexIndex, int HndIndex) - { - long Position = BasePosition + HndIndex * 4; - - int TextureHandle = Vmm.ReadInt32(Position); - - if (TextureHandle == 0) - { - //TODO: Is this correct? - //Some games like puyo puyo will have 0 handles. - //It may be just normal behaviour or a bug caused by sync issues. - //The game does initialize the value properly after through. - return; - } - - int TicIndex = (TextureHandle >> 0) & 0xfffff; - int TscIndex = (TextureHandle >> 20) & 0xfff; - - long TicPosition = MakeInt64From2xInt32(NvGpuEngine3dReg.TexHeaderPoolOffset); - long TscPosition = MakeInt64From2xInt32(NvGpuEngine3dReg.TexSamplerPoolOffset); - - TicPosition += TicIndex * 0x20; - TscPosition += TscIndex * 0x20; - - GalTextureSampler Sampler = TextureFactory.MakeSampler(Gpu, Vmm, TscPosition); - - long Key = Vmm.ReadInt64(TicPosition + 4) & 0xffffffffffff; - - Key = Vmm.GetPhysicalAddress(Key); - - if (IsFrameBufferPosition(Key)) - { - //This texture is a frame buffer texture, - //we shouldn't read anything from memory and bind - //the frame buffer texture instead, since we're not - //really writing anything to memory. - Gpu.Renderer.FrameBuffer.BindTexture(Key, TexIndex); - } - else - { - GalTexture NewTexture = TextureFactory.MakeTexture(Vmm, TicPosition); - - long Size = (uint)TextureHelper.GetTextureSize(NewTexture); - - bool HasCachedTexture = false; - - if (Gpu.Renderer.Texture.TryGetCachedTexture(Key, Size, out GalTexture Texture)) - { - if (NewTexture.Equals(Texture) && !Vmm.IsRegionModified(Key, Size, NvGpuBufferType.Texture)) - { - Gpu.Renderer.Texture.Bind(Key, TexIndex); - - HasCachedTexture = true; - } - } - - if (!HasCachedTexture) - { - byte[] Data = TextureFactory.GetTextureData(Vmm, TicPosition); - - Gpu.Renderer.Texture.Create(Key, Data, NewTexture); - } - - Gpu.Renderer.Texture.Bind(Key, TexIndex); - } - - Gpu.Renderer.Texture.SetSampler(Sampler); - } - - private void UploadUniforms(NvGpuVmm Vmm) - { - long BasePosition = MakeInt64From2xInt32(NvGpuEngine3dReg.ShaderAddress); - - for (int Index = 0; Index < 5; Index++) - { - int Control = ReadRegister(NvGpuEngine3dReg.ShaderNControl + (Index + 1) * 0x10); - int Offset = ReadRegister(NvGpuEngine3dReg.ShaderNOffset + (Index + 1) * 0x10); - - //Note: Vertex Program (B) is always enabled. - bool Enable = (Control & 1) != 0 || Index == 0; - - if (!Enable) - { - continue; - } - - for (int Cbuf = 0; Cbuf < ConstBuffers[Index].Length; Cbuf++) - { - ConstBuffer Cb = ConstBuffers[Index][Cbuf]; - - if (Cb.Enabled) - { - byte[] Data = Vmm.ReadBytes(Cb.Position, (uint)Cb.Size); - - Gpu.Renderer.Shader.SetConstBuffer(BasePosition + (uint)Offset, Cbuf, Data); - } - } - } - } - - private void UploadVertexArrays(NvGpuVmm Vmm) - { - long IndexPosition = MakeInt64From2xInt32(NvGpuEngine3dReg.IndexArrayAddress); - - long IboKey = Vmm.GetPhysicalAddress(IndexPosition); - - int IndexEntryFmt = ReadRegister(NvGpuEngine3dReg.IndexArrayFormat); - int IndexFirst = ReadRegister(NvGpuEngine3dReg.IndexBatchFirst); - int IndexCount = ReadRegister(NvGpuEngine3dReg.IndexBatchCount); - - GalIndexFormat IndexFormat = (GalIndexFormat)IndexEntryFmt; - - int IndexEntrySize = 1 << IndexEntryFmt; - - if (IndexEntrySize > 4) - { - throw new InvalidOperationException(); - } - - if (IndexCount != 0) - { - int IbSize = IndexCount * IndexEntrySize; - - bool IboCached = Gpu.Renderer.Rasterizer.IsIboCached(IboKey, (uint)IbSize); - - if (!IboCached || Vmm.IsRegionModified(IboKey, (uint)IbSize, NvGpuBufferType.Index)) - { - byte[] Data = Vmm.ReadBytes(IndexPosition, (uint)IbSize); - - Gpu.Renderer.Rasterizer.CreateIbo(IboKey, Data); - } - - Gpu.Renderer.Rasterizer.SetIndexArray(IbSize, IndexFormat); - } - - List[] Attribs = new List[32]; - - for (int Attr = 0; Attr < 16; Attr++) - { - int Packed = ReadRegister(NvGpuEngine3dReg.VertexAttribNFormat + Attr); - - int ArrayIndex = Packed & 0x1f; - - if (Attribs[ArrayIndex] == null) - { - Attribs[ArrayIndex] = new List(); - } - - Attribs[ArrayIndex].Add(new GalVertexAttrib( - Attr, - ((Packed >> 6) & 0x1) != 0, - (Packed >> 7) & 0x3fff, - (GalVertexAttribSize)((Packed >> 21) & 0x3f), - (GalVertexAttribType)((Packed >> 27) & 0x7), - ((Packed >> 31) & 0x1) != 0)); - } - - int VertexFirst = ReadRegister(NvGpuEngine3dReg.VertexArrayFirst); - int VertexCount = ReadRegister(NvGpuEngine3dReg.VertexArrayCount); - - int PrimCtrl = ReadRegister(NvGpuEngine3dReg.VertexBeginGl); - - for (int Index = 0; Index < 32; Index++) - { - if (Attribs[Index] == null) - { - continue; - } - - int Control = ReadRegister(NvGpuEngine3dReg.VertexArrayNControl + Index * 4); - - bool Enable = (Control & 0x1000) != 0; - - long VertexPosition = MakeInt64From2xInt32(NvGpuEngine3dReg.VertexArrayNAddress + Index * 4); - long VertexEndPos = MakeInt64From2xInt32(NvGpuEngine3dReg.VertexArrayNEndAddr + Index * 2); - - if (!Enable) - { - continue; - } - - long VboKey = Vmm.GetPhysicalAddress(VertexPosition); - - int Stride = Control & 0xfff; - - long VbSize = (VertexEndPos - VertexPosition) + 1; - - bool VboCached = Gpu.Renderer.Rasterizer.IsVboCached(VboKey, VbSize); - - if (!VboCached || Vmm.IsRegionModified(VboKey, VbSize, NvGpuBufferType.Vertex)) - { - byte[] Data = Vmm.ReadBytes(VertexPosition, VbSize); - - Gpu.Renderer.Rasterizer.CreateVbo(VboKey, Data); - } - - Gpu.Renderer.Rasterizer.SetVertexArray(Stride, VboKey, Attribs[Index].ToArray()); - } - - GalPrimitiveType PrimType = (GalPrimitiveType)(PrimCtrl & 0xffff); - - if (IndexCount != 0) - { - int VertexBase = ReadRegister(NvGpuEngine3dReg.VertexArrayElemBase); - - Gpu.Renderer.Rasterizer.DrawElements(IboKey, IndexFirst, VertexBase, PrimType); - } - else - { - Gpu.Renderer.Rasterizer.DrawArrays(VertexFirst, VertexCount, PrimType); - } - } - - private void QueryControl(NvGpuVmm Vmm, NvGpuPBEntry PBEntry) - { - long Position = MakeInt64From2xInt32(NvGpuEngine3dReg.QueryAddress); - - int Seq = Registers[(int)NvGpuEngine3dReg.QuerySequence]; - int Ctrl = Registers[(int)NvGpuEngine3dReg.QueryControl]; - - int Mode = Ctrl & 3; - - if (Mode == 0) - { - //Write mode. - Vmm.WriteInt32(Position, Seq); - } - - WriteRegister(PBEntry); - } - - private void CbData(NvGpuVmm Vmm, NvGpuPBEntry PBEntry) - { - long Position = MakeInt64From2xInt32(NvGpuEngine3dReg.ConstBufferAddress); - - int Offset = ReadRegister(NvGpuEngine3dReg.ConstBufferOffset); - - foreach (int Arg in PBEntry.Arguments) - { - Vmm.WriteInt32(Position + Offset, Arg); - - Offset += 4; - } - - WriteRegister(NvGpuEngine3dReg.ConstBufferOffset, Offset); - } - - private void CbBind(NvGpuVmm Vmm, NvGpuPBEntry PBEntry) - { - int Stage = (PBEntry.Method - 0x904) >> 3; - - int Index = PBEntry.Arguments[0]; - - bool Enabled = (Index & 1) != 0; - - Index = (Index >> 4) & 0x1f; - - long Position = MakeInt64From2xInt32(NvGpuEngine3dReg.ConstBufferAddress); - - ConstBuffers[Stage][Index].Position = Position; - ConstBuffers[Stage][Index].Enabled = Enabled; - - ConstBuffers[Stage][Index].Size = ReadRegister(NvGpuEngine3dReg.ConstBufferSize); - } - - private float GetFlipSign(NvGpuEngine3dReg Reg) - { - return MathF.Sign(ReadRegisterFloat(Reg)); - } - - private long MakeInt64From2xInt32(NvGpuEngine3dReg Reg) - { - return - (long)Registers[(int)Reg + 0] << 32 | - (uint)Registers[(int)Reg + 1]; - } - - private void WriteRegister(NvGpuPBEntry PBEntry) - { - int ArgsCount = PBEntry.Arguments.Count; - - if (ArgsCount > 0) - { - Registers[PBEntry.Method] = PBEntry.Arguments[ArgsCount - 1]; - } - } - - private int ReadRegister(NvGpuEngine3dReg Reg) - { - return Registers[(int)Reg]; - } - - private float ReadRegisterFloat(NvGpuEngine3dReg Reg) - { - return BitConverter.Int32BitsToSingle(ReadRegister(Reg)); - } - - private void WriteRegister(NvGpuEngine3dReg Reg, int Value) - { - Registers[(int)Reg] = Value; - } - - public bool IsFrameBufferPosition(long Position) - { - return FrameBuffers.Contains(Position); - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/Gpu/Engines/NvGpuEngine3dReg.cs b/Ryujinx.HLE/Gpu/Engines/NvGpuEngine3dReg.cs deleted file mode 100644 index 3de2885ef2..0000000000 --- a/Ryujinx.HLE/Gpu/Engines/NvGpuEngine3dReg.cs +++ /dev/null @@ -1,86 +0,0 @@ -namespace Ryujinx.HLE.Gpu.Engines -{ - enum NvGpuEngine3dReg - { - FrameBufferNAddress = 0x200, - FrameBufferNWidth = 0x202, - FrameBufferNHeight = 0x203, - FrameBufferNFormat = 0x204, - ViewportScaleX = 0x280, - ViewportScaleY = 0x281, - ViewportScaleZ = 0x282, - ViewportTranslateX = 0x283, - ViewportTranslateY = 0x284, - ViewportTranslateZ = 0x285, - VertexArrayFirst = 0x35d, - VertexArrayCount = 0x35e, - ClearDepth = 0x364, - ClearStencil = 0x368, - StencilBackFuncRef = 0x3d5, - StencilBackMask = 0x3d6, - StencilBackFuncMask = 0x3d7, - VertexAttribNFormat = 0x458, - DepthTestEnable = 0x4b3, - IBlendEnable = 0x4b9, - DepthTestFunction = 0x4c3, - BlendSeparateAlpha = 0x4cf, - BlendEquationRgb = 0x4d0, - BlendFuncSrcRgb = 0x4d1, - BlendFuncDstRgb = 0x4d2, - BlendEquationAlpha = 0x4d3, - BlendFuncSrcAlpha = 0x4d4, - BlendFuncDstAlpha = 0x4d6, - BlendEnableMaster = 0x4d7, - IBlendNEnable = 0x4d8, - StencilEnable = 0x4e0, - StencilFrontOpFail = 0x4e1, - StencilFrontOpZFail = 0x4e2, - StencilFrontOpZPass = 0x4e3, - StencilFrontFuncFunc = 0x4e4, - StencilFrontFuncRef = 0x4e5, - StencilFrontFuncMask = 0x4e6, - StencilFrontMask = 0x4e7, - VertexArrayElemBase = 0x50d, - TexHeaderPoolOffset = 0x55d, - TexSamplerPoolOffset = 0x557, - StencilTwoSideEnable = 0x565, - StencilBackOpFail = 0x566, - StencilBackOpZFail = 0x567, - StencilBackOpZPass = 0x568, - StencilBackFuncFunc = 0x569, - ShaderAddress = 0x582, - VertexBeginGl = 0x586, - PrimRestartEnable = 0x591, - PrimRestartIndex = 0x592, - IndexArrayAddress = 0x5f2, - IndexArrayEndAddr = 0x5f4, - IndexArrayFormat = 0x5f6, - IndexBatchFirst = 0x5f7, - IndexBatchCount = 0x5f8, - CullFaceEnable = 0x646, - FrontFace = 0x647, - CullFace = 0x648, - QueryAddress = 0x6c0, - QuerySequence = 0x6c2, - QueryControl = 0x6c3, - VertexArrayNControl = 0x700, - VertexArrayNAddress = 0x701, - VertexArrayNDivisor = 0x703, - IBlendNSeparateAlpha = 0x780, - IBlendNEquationRgb = 0x781, - IBlendNFuncSrcRgb = 0x782, - IBlendNFuncDstRgb = 0x783, - IBlendNEquationAlpha = 0x784, - IBlendNFuncSrcAlpha = 0x785, - IBlendNFuncDstAlpha = 0x786, - VertexArrayNEndAddr = 0x7c0, - ShaderNControl = 0x800, - ShaderNOffset = 0x801, - ShaderNMaxGprs = 0x803, - ShaderNType = 0x804, - ConstBufferSize = 0x8e0, - ConstBufferAddress = 0x8e1, - ConstBufferOffset = 0x8e3, - TextureCbIndex = 0x982 - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/Gpu/Engines/NvGpuEngineDma.cs b/Ryujinx.HLE/Gpu/Engines/NvGpuEngineDma.cs deleted file mode 100644 index 7e355e8de9..0000000000 --- a/Ryujinx.HLE/Gpu/Engines/NvGpuEngineDma.cs +++ /dev/null @@ -1,143 +0,0 @@ -using Ryujinx.HLE.Gpu.Memory; -using Ryujinx.HLE.Gpu.Texture; -using System.Collections.Generic; - -namespace Ryujinx.HLE.Gpu.Engines -{ - class NvGpuEngineDma : INvGpuEngine - { - public int[] Registers { get; private set; } - - private NvGpu Gpu; - - private Dictionary Methods; - - public NvGpuEngineDma(NvGpu Gpu) - { - this.Gpu = Gpu; - - Registers = new int[0x1d6]; - - Methods = new Dictionary(); - - void AddMethod(int Meth, int Count, int Stride, NvGpuMethod Method) - { - while (Count-- > 0) - { - Methods.Add(Meth, Method); - - Meth += Stride; - } - } - - AddMethod(0xc0, 1, 1, Execute); - } - - public void CallMethod(NvGpuVmm Vmm, NvGpuPBEntry PBEntry) - { - if (Methods.TryGetValue(PBEntry.Method, out NvGpuMethod Method)) - { - Method(Vmm, PBEntry); - } - else - { - WriteRegister(PBEntry); - } - } - - private void Execute(NvGpuVmm Vmm, NvGpuPBEntry PBEntry) - { - int Control = PBEntry.Arguments[0]; - - bool SrcLinear = ((Control >> 7) & 1) != 0; - bool DstLinear = ((Control >> 8) & 1) != 0; - - long SrcAddress = MakeInt64From2xInt32(NvGpuEngineDmaReg.SrcAddress); - long DstAddress = MakeInt64From2xInt32(NvGpuEngineDmaReg.DstAddress); - - int SrcPitch = ReadRegister(NvGpuEngineDmaReg.SrcPitch); - int DstPitch = ReadRegister(NvGpuEngineDmaReg.DstPitch); - - int DstBlkDim = ReadRegister(NvGpuEngineDmaReg.DstBlkDim); - int DstSizeX = ReadRegister(NvGpuEngineDmaReg.DstSizeX); - int DstSizeY = ReadRegister(NvGpuEngineDmaReg.DstSizeY); - int DstSizeZ = ReadRegister(NvGpuEngineDmaReg.DstSizeZ); - int DstPosXY = ReadRegister(NvGpuEngineDmaReg.DstPosXY); - int DstPosZ = ReadRegister(NvGpuEngineDmaReg.DstPosZ); - - int SrcBlkDim = ReadRegister(NvGpuEngineDmaReg.SrcBlkDim); - int SrcSizeX = ReadRegister(NvGpuEngineDmaReg.SrcSizeX); - int SrcSizeY = ReadRegister(NvGpuEngineDmaReg.SrcSizeY); - int SrcSizeZ = ReadRegister(NvGpuEngineDmaReg.SrcSizeZ); - int SrcPosXY = ReadRegister(NvGpuEngineDmaReg.SrcPosXY); - int SrcPosZ = ReadRegister(NvGpuEngineDmaReg.SrcPosZ); - - int DstPosX = (DstPosXY >> 0) & 0xffff; - int DstPosY = (DstPosXY >> 16) & 0xffff; - - int SrcPosX = (SrcPosXY >> 0) & 0xffff; - int SrcPosY = (SrcPosXY >> 16) & 0xffff; - - int SrcBlockHeight = 1 << ((SrcBlkDim >> 4) & 0xf); - int DstBlockHeight = 1 << ((DstBlkDim >> 4) & 0xf); - - ISwizzle SrcSwizzle; - - if (SrcLinear) - { - SrcSwizzle = new LinearSwizzle(SrcPitch, 1); - } - else - { - SrcSwizzle = new BlockLinearSwizzle(SrcSizeX, 1, SrcBlockHeight); - } - - ISwizzle DstSwizzle; - - if (DstLinear) - { - DstSwizzle = new LinearSwizzle(DstPitch, 1); - } - else - { - DstSwizzle = new BlockLinearSwizzle(DstSizeX, 1, DstBlockHeight); - } - - for (int Y = 0; Y < DstSizeY; Y++) - for (int X = 0; X < DstSizeX; X++) - { - long SrcOffset = SrcAddress + (uint)SrcSwizzle.GetSwizzleOffset(X, Y); - long DstOffset = DstAddress + (uint)DstSwizzle.GetSwizzleOffset(X, Y); - - Vmm.WriteByte(DstOffset, Vmm.ReadByte(SrcOffset)); - } - } - - private long MakeInt64From2xInt32(NvGpuEngineDmaReg Reg) - { - return - (long)Registers[(int)Reg + 0] << 32 | - (uint)Registers[(int)Reg + 1]; - } - - private void WriteRegister(NvGpuPBEntry PBEntry) - { - int ArgsCount = PBEntry.Arguments.Count; - - if (ArgsCount > 0) - { - Registers[PBEntry.Method] = PBEntry.Arguments[ArgsCount - 1]; - } - } - - private int ReadRegister(NvGpuEngineDmaReg Reg) - { - return Registers[(int)Reg]; - } - - private void WriteRegister(NvGpuEngineDmaReg Reg, int Value) - { - Registers[(int)Reg] = Value; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/Gpu/Engines/NvGpuEngineDmaReg.cs b/Ryujinx.HLE/Gpu/Engines/NvGpuEngineDmaReg.cs deleted file mode 100644 index 835a822d16..0000000000 --- a/Ryujinx.HLE/Gpu/Engines/NvGpuEngineDmaReg.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace Ryujinx.HLE.Gpu.Engines -{ - enum NvGpuEngineDmaReg - { - SrcAddress = 0x100, - DstAddress = 0x102, - SrcPitch = 0x104, - DstPitch = 0x105, - DstBlkDim = 0x1c3, - DstSizeX = 0x1c4, - DstSizeY = 0x1c5, - DstSizeZ = 0x1c6, - DstPosZ = 0x1c7, - DstPosXY = 0x1c8, - SrcBlkDim = 0x1ca, - SrcSizeX = 0x1cb, - SrcSizeY = 0x1cc, - SrcSizeZ = 0x1cd, - SrcPosZ = 0x1ce, - SrcPosXY = 0x1cf - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/Gpu/Engines/NvGpuFifo.cs b/Ryujinx.HLE/Gpu/Engines/NvGpuFifo.cs deleted file mode 100644 index 0bc682a70f..0000000000 --- a/Ryujinx.HLE/Gpu/Engines/NvGpuFifo.cs +++ /dev/null @@ -1,183 +0,0 @@ -using Ryujinx.HLE.Gpu.Memory; -using System.Collections.Concurrent; - -namespace Ryujinx.HLE.Gpu.Engines -{ - class NvGpuFifo - { - private const int MacrosCount = 0x80; - private const int MacroIndexMask = MacrosCount - 1; - - //Note: The size of the macro memory is unknown, we just make - //a guess here and use 256kb as the size. Increase if needed. - private const int MmeWords = 256 * 256; - - private NvGpu Gpu; - - private ConcurrentQueue<(NvGpuVmm, NvGpuPBEntry)> BufferQueue; - - private NvGpuEngine[] SubChannels; - - private struct CachedMacro - { - public int Position { get; private set; } - - private MacroInterpreter Interpreter; - - public CachedMacro(NvGpuFifo PFifo, INvGpuEngine Engine, int Position) - { - this.Position = Position; - - Interpreter = new MacroInterpreter(PFifo, Engine); - } - - public void PushParam(int Param) - { - Interpreter?.Fifo.Enqueue(Param); - } - - public void Execute(NvGpuVmm Vmm, int[] Mme, int Param) - { - Interpreter?.Execute(Vmm, Mme, Position, Param); - } - } - - private int CurrMacroPosition; - private int CurrMacroBindIndex; - - private CachedMacro[] Macros; - - private int[] Mme; - - public NvGpuFifo(NvGpu Gpu) - { - this.Gpu = Gpu; - - BufferQueue = new ConcurrentQueue<(NvGpuVmm, NvGpuPBEntry)>(); - - SubChannels = new NvGpuEngine[8]; - - Macros = new CachedMacro[MacrosCount]; - - Mme = new int[MmeWords]; - } - - public void PushBuffer(NvGpuVmm Vmm, NvGpuPBEntry[] Buffer) - { - foreach (NvGpuPBEntry PBEntry in Buffer) - { - BufferQueue.Enqueue((Vmm, PBEntry)); - } - } - - public void DispatchCalls() - { - while (Step()); - } - - public bool Step() - { - if (BufferQueue.TryDequeue(out (NvGpuVmm Vmm, NvGpuPBEntry PBEntry) Tuple)) - { - CallMethod(Tuple.Vmm, Tuple.PBEntry); - - return true; - } - - return false; - } - - private void CallMethod(NvGpuVmm Vmm, NvGpuPBEntry PBEntry) - { - if (PBEntry.Method < 0x80) - { - switch ((NvGpuFifoMeth)PBEntry.Method) - { - case NvGpuFifoMeth.BindChannel: - { - NvGpuEngine Engine = (NvGpuEngine)PBEntry.Arguments[0]; - - SubChannels[PBEntry.SubChannel] = Engine; - - break; - } - - case NvGpuFifoMeth.SetMacroUploadAddress: - { - CurrMacroPosition = PBEntry.Arguments[0]; - - break; - } - - case NvGpuFifoMeth.SendMacroCodeData: - { - foreach (int Arg in PBEntry.Arguments) - { - Mme[CurrMacroPosition++] = Arg; - } - break; - } - - case NvGpuFifoMeth.SetMacroBindingIndex: - { - CurrMacroBindIndex = PBEntry.Arguments[0]; - - break; - } - - case NvGpuFifoMeth.BindMacro: - { - int Position = PBEntry.Arguments[0]; - - Macros[CurrMacroBindIndex] = new CachedMacro(this, Gpu.Engine3d, Position); - - break; - } - } - } - else - { - switch (SubChannels[PBEntry.SubChannel]) - { - case NvGpuEngine._2d: Call2dMethod (Vmm, PBEntry); break; - case NvGpuEngine._3d: Call3dMethod (Vmm, PBEntry); break; - case NvGpuEngine.Dma: CallDmaMethod(Vmm, PBEntry); break; - } - } - } - - private void Call2dMethod(NvGpuVmm Vmm, NvGpuPBEntry PBEntry) - { - Gpu.Engine2d.CallMethod(Vmm, PBEntry); - } - - private void Call3dMethod(NvGpuVmm Vmm, NvGpuPBEntry PBEntry) - { - if (PBEntry.Method < 0xe00) - { - Gpu.Engine3d.CallMethod(Vmm, PBEntry); - } - else - { - int MacroIndex = (PBEntry.Method >> 1) & MacroIndexMask; - - if ((PBEntry.Method & 1) != 0) - { - foreach (int Arg in PBEntry.Arguments) - { - Macros[MacroIndex].PushParam(Arg); - } - } - else - { - Macros[MacroIndex].Execute(Vmm, Mme, PBEntry.Arguments[0]); - } - } - } - - private void CallDmaMethod(NvGpuVmm Vmm, NvGpuPBEntry PBEntry) - { - Gpu.EngineDma.CallMethod(Vmm, PBEntry); - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/Gpu/Engines/NvGpuMethod.cs b/Ryujinx.HLE/Gpu/Engines/NvGpuMethod.cs deleted file mode 100644 index 04c92f2a93..0000000000 --- a/Ryujinx.HLE/Gpu/Engines/NvGpuMethod.cs +++ /dev/null @@ -1,6 +0,0 @@ -using Ryujinx.HLE.Gpu.Memory; - -namespace Ryujinx.HLE.Gpu.Engines -{ - delegate void NvGpuMethod(NvGpuVmm Vmm, NvGpuPBEntry PBEntry); -} \ No newline at end of file diff --git a/Ryujinx.HLE/Gpu/Exceptions/GpuException.cs b/Ryujinx.HLE/Gpu/Exceptions/GpuException.cs deleted file mode 100644 index c0bce5a52c..0000000000 --- a/Ryujinx.HLE/Gpu/Exceptions/GpuException.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; - -namespace Ryujinx.HLE.Gpu.Exceptions -{ - class GpuException : Exception - { - public GpuException() : base() { } - - public GpuException(string ExMsg) : base(ExMsg) { } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/Gpu/Exceptions/GpuExceptionHelper.cs b/Ryujinx.HLE/Gpu/Exceptions/GpuExceptionHelper.cs deleted file mode 100644 index aeab9a291a..0000000000 --- a/Ryujinx.HLE/Gpu/Exceptions/GpuExceptionHelper.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Ryujinx.HLE.Gpu.Exceptions -{ - static class GpuExceptionHelper - { - private const string CallCountExceeded = "Method call count exceeded the limit allowed per run!"; - - public static void ThrowCallCoundExceeded() - { - throw new GpuException(CallCountExceeded); - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/Gpu/Memory/NvGpuBufferType.cs b/Ryujinx.HLE/Gpu/Memory/NvGpuBufferType.cs deleted file mode 100644 index 469cd6cd0c..0000000000 --- a/Ryujinx.HLE/Gpu/Memory/NvGpuBufferType.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Ryujinx.HLE.Gpu.Memory -{ - enum NvGpuBufferType - { - Index, - Vertex, - Texture, - Count - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/Gpu/Memory/NvGpuPBEntry.cs b/Ryujinx.HLE/Gpu/Memory/NvGpuPBEntry.cs deleted file mode 100644 index aba89e3c97..0000000000 --- a/Ryujinx.HLE/Gpu/Memory/NvGpuPBEntry.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Collections.ObjectModel; - -namespace Ryujinx.HLE.Gpu.Memory -{ - struct NvGpuPBEntry - { - public int Method { get; private set; } - - public int SubChannel { get; private set; } - - private int[] m_Arguments; - - public ReadOnlyCollection Arguments => Array.AsReadOnly(m_Arguments); - - public NvGpuPBEntry(int Method, int SubChannel, params int[] Arguments) - { - this.Method = Method; - this.SubChannel = SubChannel; - this.m_Arguments = Arguments; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/Gpu/Memory/NvGpuPushBuffer.cs b/Ryujinx.HLE/Gpu/Memory/NvGpuPushBuffer.cs deleted file mode 100644 index 6121b3e612..0000000000 --- a/Ryujinx.HLE/Gpu/Memory/NvGpuPushBuffer.cs +++ /dev/null @@ -1,101 +0,0 @@ -using System.Collections.Generic; -using System.IO; - -namespace Ryujinx.HLE.Gpu.Memory -{ - static class NvGpuPushBuffer - { - private enum SubmissionMode - { - Incrementing = 1, - NonIncrementing = 3, - Immediate = 4, - IncrementOnce = 5 - } - - public static NvGpuPBEntry[] Decode(byte[] Data) - { - using (MemoryStream MS = new MemoryStream(Data)) - { - BinaryReader Reader = new BinaryReader(MS); - - List PushBuffer = new List(); - - bool CanRead() => MS.Position + 4 <= MS.Length; - - while (CanRead()) - { - int Packed = Reader.ReadInt32(); - - int Meth = (Packed >> 0) & 0x1fff; - int SubC = (Packed >> 13) & 7; - int Args = (Packed >> 16) & 0x1fff; - int Mode = (Packed >> 29) & 7; - - switch ((SubmissionMode)Mode) - { - case SubmissionMode.Incrementing: - { - for (int Index = 0; Index < Args && CanRead(); Index++, Meth++) - { - PushBuffer.Add(new NvGpuPBEntry(Meth, SubC, Reader.ReadInt32())); - } - - break; - } - - case SubmissionMode.NonIncrementing: - { - int[] Arguments = new int[Args]; - - for (int Index = 0; Index < Arguments.Length; Index++) - { - if (!CanRead()) - { - break; - } - - Arguments[Index] = Reader.ReadInt32(); - } - - PushBuffer.Add(new NvGpuPBEntry(Meth, SubC, Arguments)); - - break; - } - - case SubmissionMode.Immediate: - { - PushBuffer.Add(new NvGpuPBEntry(Meth, SubC, Args)); - - break; - } - - case SubmissionMode.IncrementOnce: - { - if (CanRead()) - { - PushBuffer.Add(new NvGpuPBEntry(Meth, SubC, Reader.ReadInt32())); - } - - if (CanRead() && Args > 1) - { - int[] Arguments = new int[Args - 1]; - - for (int Index = 0; Index < Arguments.Length && CanRead(); Index++) - { - Arguments[Index] = Reader.ReadInt32(); - } - - PushBuffer.Add(new NvGpuPBEntry(Meth + 1, SubC, Arguments)); - } - - break; - } - } - } - - return PushBuffer.ToArray(); - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/Gpu/Memory/NvGpuVmm.cs b/Ryujinx.HLE/Gpu/Memory/NvGpuVmm.cs deleted file mode 100644 index 0c81dd1508..0000000000 --- a/Ryujinx.HLE/Gpu/Memory/NvGpuVmm.cs +++ /dev/null @@ -1,408 +0,0 @@ -using ChocolArm64.Memory; -using Ryujinx.Graphics.Gal; -using System.Collections.Concurrent; - -namespace Ryujinx.HLE.Gpu.Memory -{ - class NvGpuVmm : IAMemory, IGalMemory - { - public const long AddrSize = 1L << 40; - - private const int PTLvl0Bits = 14; - private const int PTLvl1Bits = 14; - private const int PTPageBits = 12; - - private const int PTLvl0Size = 1 << PTLvl0Bits; - private const int PTLvl1Size = 1 << PTLvl1Bits; - public const int PageSize = 1 << PTPageBits; - - private const int PTLvl0Mask = PTLvl0Size - 1; - private const int PTLvl1Mask = PTLvl1Size - 1; - public const int PageMask = PageSize - 1; - - private const int PTLvl0Bit = PTPageBits + PTLvl1Bits; - private const int PTLvl1Bit = PTPageBits; - - public AMemory Memory { get; private set; } - - private struct MappedMemory - { - public long Size; - - public MappedMemory(long Size) - { - this.Size = Size; - } - } - - private ConcurrentDictionary Maps; - - private NvGpuVmmCache Cache; - - private const long PteUnmapped = -1; - private const long PteReserved = -2; - - private long[][] PageTable; - - public NvGpuVmm(AMemory Memory) - { - this.Memory = Memory; - - Maps = new ConcurrentDictionary(); - - Cache = new NvGpuVmmCache(); - - PageTable = new long[PTLvl0Size][]; - } - - public long Map(long PA, long VA, long Size) - { - lock (PageTable) - { - for (long Offset = 0; Offset < Size; Offset += PageSize) - { - if (GetPte(VA + Offset) != PteReserved) - { - return Map(PA, Size); - } - } - - for (long Offset = 0; Offset < Size; Offset += PageSize) - { - SetPte(VA + Offset, PA + Offset); - } - } - - return VA; - } - - public long Map(long PA, long Size) - { - lock (PageTable) - { - long VA = GetFreePosition(Size); - - if (VA != -1) - { - MappedMemory Map = new MappedMemory(Size); - - Maps.AddOrUpdate(VA, Map, (Key, Old) => Map); - - for (long Offset = 0; Offset < Size; Offset += PageSize) - { - SetPte(VA + Offset, PA + Offset); - } - } - - return VA; - } - } - - public bool Unmap(long VA) - { - if (Maps.TryRemove(VA, out MappedMemory Map)) - { - Free(VA, Map.Size); - - return true; - } - - return false; - } - - public long Reserve(long VA, long Size, long Align) - { - lock (PageTable) - { - for (long Offset = 0; Offset < Size; Offset += PageSize) - { - if (IsPageInUse(VA + Offset)) - { - return Reserve(Size, Align); - } - } - - for (long Offset = 0; Offset < Size; Offset += PageSize) - { - SetPte(VA + Offset, PteReserved); - } - } - - return VA; - } - - public long Reserve(long Size, long Align) - { - lock (PageTable) - { - long Position = GetFreePosition(Size, Align); - - if (Position != -1) - { - for (long Offset = 0; Offset < Size; Offset += PageSize) - { - SetPte(Position + Offset, PteReserved); - } - } - - return Position; - } - } - - public void Free(long VA, long Size) - { - lock (PageTable) - { - for (long Offset = 0; Offset < Size; Offset += PageSize) - { - SetPte(VA + Offset, PteUnmapped); - } - } - } - - private long GetFreePosition(long Size, long Align = 1) - { - long Position = 0; - long FreeSize = 0; - - if (Align < 1) - { - Align = 1; - } - - Align = (Align + PageMask) & ~PageMask; - - while (Position + FreeSize < AddrSize) - { - if (!IsPageInUse(Position + FreeSize)) - { - FreeSize += PageSize; - - if (FreeSize >= Size) - { - return Position; - } - } - else - { - Position += FreeSize + PageSize; - FreeSize = 0; - - long Remainder = Position % Align; - - if (Remainder != 0) - { - Position = (Position - Remainder) + Align; - } - } - } - - return -1; - } - - public long GetPhysicalAddress(long VA) - { - long BasePos = GetPte(VA); - - if (BasePos < 0) - { - return -1; - } - - return BasePos + (VA & PageMask); - } - - public bool IsRegionFree(long VA, long Size) - { - for (long Offset = 0; Offset < Size; Offset += PageSize) - { - if (IsPageInUse(VA + Offset)) - { - return false; - } - } - - return true; - } - - private bool IsPageInUse(long VA) - { - if (VA >> PTLvl0Bits + PTLvl1Bits + PTPageBits != 0) - { - return false; - } - - long L0 = (VA >> PTLvl0Bit) & PTLvl0Mask; - long L1 = (VA >> PTLvl1Bit) & PTLvl1Mask; - - if (PageTable[L0] == null) - { - return false; - } - - return PageTable[L0][L1] != PteUnmapped; - } - - private long GetPte(long Position) - { - long L0 = (Position >> PTLvl0Bit) & PTLvl0Mask; - long L1 = (Position >> PTLvl1Bit) & PTLvl1Mask; - - if (PageTable[L0] == null) - { - return -1; - } - - return PageTable[L0][L1]; - } - - private void SetPte(long Position, long TgtAddr) - { - long L0 = (Position >> PTLvl0Bit) & PTLvl0Mask; - long L1 = (Position >> PTLvl1Bit) & PTLvl1Mask; - - if (PageTable[L0] == null) - { - PageTable[L0] = new long[PTLvl1Size]; - - for (int Index = 0; Index < PTLvl1Size; Index++) - { - PageTable[L0][Index] = PteUnmapped; - } - } - - PageTable[L0][L1] = TgtAddr; - } - - public bool IsRegionModified(long PA, long Size, NvGpuBufferType BufferType) - { - return Cache.IsRegionModified(Memory, BufferType, PA, Size); - } - - public byte ReadByte(long Position) - { - Position = GetPhysicalAddress(Position); - - return Memory.ReadByte(Position); - } - - public ushort ReadUInt16(long Position) - { - Position = GetPhysicalAddress(Position); - - return Memory.ReadUInt16(Position); - } - - public uint ReadUInt32(long Position) - { - Position = GetPhysicalAddress(Position); - - return Memory.ReadUInt32(Position); - } - - public ulong ReadUInt64(long Position) - { - Position = GetPhysicalAddress(Position); - - return Memory.ReadUInt64(Position); - } - - public sbyte ReadSByte(long Position) - { - Position = GetPhysicalAddress(Position); - - return Memory.ReadSByte(Position); - } - - public short ReadInt16(long Position) - { - Position = GetPhysicalAddress(Position); - - return Memory.ReadInt16(Position); - } - - public int ReadInt32(long Position) - { - Position = GetPhysicalAddress(Position); - - return Memory.ReadInt32(Position); - } - - public long ReadInt64(long Position) - { - Position = GetPhysicalAddress(Position); - - return Memory.ReadInt64(Position); - } - - public byte[] ReadBytes(long Position, long Size) - { - Position = GetPhysicalAddress(Position); - - return Memory.ReadBytes(Position, Size); - } - - public void WriteByte(long Position, byte Value) - { - Position = GetPhysicalAddress(Position); - - Memory.WriteByte(Position, Value); - } - - public void WriteUInt16(long Position, ushort Value) - { - Position = GetPhysicalAddress(Position); - - Memory.WriteUInt16(Position, Value); - } - - public void WriteUInt32(long Position, uint Value) - { - Position = GetPhysicalAddress(Position); - - Memory.WriteUInt32(Position, Value); - } - - public void WriteUInt64(long Position, ulong Value) - { - Position = GetPhysicalAddress(Position); - - Memory.WriteUInt64(Position, Value); - } - - public void WriteSByte(long Position, sbyte Value) - { - Position = GetPhysicalAddress(Position); - - Memory.WriteSByte(Position, Value); - } - - public void WriteInt16(long Position, short Value) - { - Position = GetPhysicalAddress(Position); - - Memory.WriteInt16(Position, Value); - } - - public void WriteInt32(long Position, int Value) - { - Position = GetPhysicalAddress(Position); - - Memory.WriteInt32(Position, Value); - } - - public void WriteInt64(long Position, long Value) - { - Position = GetPhysicalAddress(Position); - - Memory.WriteInt64(Position, Value); - } - - public void WriteBytes(long Position, byte[] Data) - { - Position = GetPhysicalAddress(Position); - - Memory.WriteBytes(Position, Data); - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/Gpu/Memory/NvGpuVmmCache.cs b/Ryujinx.HLE/Gpu/Memory/NvGpuVmmCache.cs deleted file mode 100644 index ac9bd850e0..0000000000 --- a/Ryujinx.HLE/Gpu/Memory/NvGpuVmmCache.cs +++ /dev/null @@ -1,221 +0,0 @@ -using ChocolArm64.Memory; -using System; -using System.Collections.Generic; - -namespace Ryujinx.HLE.Gpu.Memory -{ - class NvGpuVmmCache - { - private const int MaxCpCount = 10000; - private const int MaxCpTimeDelta = 60000; - - private class CachedPage - { - private struct Range - { - public long Start; - public long End; - - public Range(long Start, long End) - { - this.Start = Start; - this.End = End; - } - } - - private List[] Regions; - - public LinkedListNode Node { get; set; } - - public int Timestamp { get; private set; } - - public CachedPage() - { - Regions = new List[(int)NvGpuBufferType.Count]; - - for (int Index = 0; Index < Regions.Length; Index++) - { - Regions[Index] = new List(); - } - } - - public bool AddRange(long Start, long End, NvGpuBufferType BufferType) - { - List BtRegions = Regions[(int)BufferType]; - - for (int Index = 0; Index < BtRegions.Count; Index++) - { - Range Rg = BtRegions[Index]; - - if (Start >= Rg.Start && End <= Rg.End) - { - return false; - } - - if (Start <= Rg.End && Rg.Start <= End) - { - long MinStart = Math.Min(Rg.Start, Start); - long MaxEnd = Math.Max(Rg.End, End); - - BtRegions[Index] = new Range(MinStart, MaxEnd); - - Timestamp = Environment.TickCount; - - return true; - } - } - - BtRegions.Add(new Range(Start, End)); - - Timestamp = Environment.TickCount; - - return true; - } - - public int GetTotalCount() - { - int Count = 0; - - for (int Index = 0; Index < Regions.Length; Index++) - { - Count += Regions[Index].Count; - } - - return Count; - } - } - - private Dictionary Cache; - - private LinkedList SortedCache; - - private int CpCount; - - public NvGpuVmmCache() - { - Cache = new Dictionary(); - - SortedCache = new LinkedList(); - } - - public bool IsRegionModified(AMemory Memory, NvGpuBufferType BufferType, long PA, long Size) - { - bool[] Modified = Memory.IsRegionModified(PA, Size); - - if (Modified == null) - { - return true; - } - - ClearCachedPagesIfNeeded(); - - long PageSize = Memory.GetHostPageSize(); - - long Mask = PageSize - 1; - - long PAEnd = PA + Size; - - bool RegMod = false; - - int Index = 0; - - while (PA < PAEnd) - { - long Key = PA & ~Mask; - - long PAPgEnd = Math.Min((PA + PageSize) & ~Mask, PAEnd); - - bool IsCached = Cache.TryGetValue(Key, out CachedPage Cp); - - if (IsCached) - { - CpCount -= Cp.GetTotalCount(); - - SortedCache.Remove(Cp.Node); - } - else - { - Cp = new CachedPage(); - - Cache.Add(Key, Cp); - } - - if (Modified[Index++] && IsCached) - { - Cp = new CachedPage(); - - Cache[Key] = Cp; - } - - Cp.Node = SortedCache.AddLast(Key); - - RegMod |= Cp.AddRange(PA, PAPgEnd, BufferType); - - CpCount += Cp.GetTotalCount(); - - PA = PAPgEnd; - } - - return RegMod; - } - - private void ClearCachedPagesIfNeeded() - { - if (CpCount <= MaxCpCount) - { - return; - } - - int Timestamp = Environment.TickCount; - - int TimeDelta; - - do - { - if (!TryPopOldestCachedPageKey(Timestamp, out long Key)) - { - break; - } - - CachedPage Cp = Cache[Key]; - - Cache.Remove(Key); - - CpCount -= Cp.GetTotalCount(); - - TimeDelta = RingDelta(Cp.Timestamp, Timestamp); - } - while (CpCount > (MaxCpCount >> 1) || (uint)TimeDelta > (uint)MaxCpTimeDelta); - } - - private bool TryPopOldestCachedPageKey(int Timestamp, out long Key) - { - LinkedListNode Node = SortedCache.First; - - if (Node == null) - { - Key = 0; - - return false; - } - - SortedCache.Remove(Node); - - Key = Node.Value; - - return true; - } - - private int RingDelta(int Old, int New) - { - if ((uint)New < (uint)Old) - { - return New + (~Old + 1); - } - else - { - return New - Old; - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/Gpu/NvGpu.cs b/Ryujinx.HLE/Gpu/NvGpu.cs deleted file mode 100644 index 625cb727fe..0000000000 --- a/Ryujinx.HLE/Gpu/NvGpu.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Ryujinx.Graphics.Gal; -using Ryujinx.HLE.Gpu.Engines; - -namespace Ryujinx.HLE.Gpu -{ - class NvGpu - { - public IGalRenderer Renderer { get; private set; } - - public NvGpuFifo Fifo { get; private set; } - - public NvGpuEngine2d Engine2d { get; private set; } - public NvGpuEngine3d Engine3d { get; private set; } - public NvGpuEngineDma EngineDma { get; private set; } - - public NvGpu(IGalRenderer Renderer) - { - this.Renderer = Renderer; - - Fifo = new NvGpuFifo(this); - - Engine2d = new NvGpuEngine2d(this); - Engine3d = new NvGpuEngine3d(this); - EngineDma = new NvGpuEngineDma(this); - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/Gpu/Texture/BlockLinearSwizzle.cs b/Ryujinx.HLE/Gpu/Texture/BlockLinearSwizzle.cs deleted file mode 100644 index e66d76136a..0000000000 --- a/Ryujinx.HLE/Gpu/Texture/BlockLinearSwizzle.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; - -namespace Ryujinx.HLE.Gpu.Texture -{ - class BlockLinearSwizzle : ISwizzle - { - private int BhShift; - private int BppShift; - private int BhMask; - - private int XShift; - private int GobStride; - - public BlockLinearSwizzle(int Width, int Bpp, int BlockHeight = 16) - { - BhMask = (BlockHeight * 8) - 1; - - BhShift = CountLsbZeros(BlockHeight * 8); - BppShift = CountLsbZeros(Bpp); - - int WidthInGobs = (int)MathF.Ceiling(Width * Bpp / 64f); - - GobStride = 512 * BlockHeight * WidthInGobs; - - XShift = CountLsbZeros(512 * BlockHeight); - } - - private int CountLsbZeros(int Value) - { - int Count = 0; - - while (((Value >> Count) & 1) == 0) - { - Count++; - } - - return Count; - } - - public int GetSwizzleOffset(int X, int Y) - { - X <<= BppShift; - - int Position = (Y >> BhShift) * GobStride; - - Position += (X >> 6) << XShift; - - Position += ((Y & BhMask) >> 3) << 9; - - Position += ((X & 0x3f) >> 5) << 8; - Position += ((Y & 0x07) >> 1) << 6; - Position += ((X & 0x1f) >> 4) << 5; - Position += ((Y & 0x01) >> 0) << 4; - Position += ((X & 0x0f) >> 0) << 0; - - return Position; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/Gpu/Texture/ISwizzle.cs b/Ryujinx.HLE/Gpu/Texture/ISwizzle.cs deleted file mode 100644 index 222aab1630..0000000000 --- a/Ryujinx.HLE/Gpu/Texture/ISwizzle.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Ryujinx.HLE.Gpu.Texture -{ - interface ISwizzle - { - int GetSwizzleOffset(int X, int Y); - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/Gpu/Texture/LinearSwizzle.cs b/Ryujinx.HLE/Gpu/Texture/LinearSwizzle.cs deleted file mode 100644 index 720f78322a..0000000000 --- a/Ryujinx.HLE/Gpu/Texture/LinearSwizzle.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace Ryujinx.HLE.Gpu.Texture -{ - class LinearSwizzle : ISwizzle - { - private int Pitch; - private int Bpp; - - public LinearSwizzle(int Pitch, int Bpp) - { - this.Pitch = Pitch; - this.Bpp = Bpp; - } - - public int GetSwizzleOffset(int X, int Y) - { - return X * Bpp + Y * Pitch; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/Gpu/Texture/TextureFactory.cs b/Ryujinx.HLE/Gpu/Texture/TextureFactory.cs deleted file mode 100644 index 9df0b60002..0000000000 --- a/Ryujinx.HLE/Gpu/Texture/TextureFactory.cs +++ /dev/null @@ -1,117 +0,0 @@ -using Ryujinx.Graphics.Gal; -using Ryujinx.HLE.Gpu.Memory; -using System; - -namespace Ryujinx.HLE.Gpu.Texture -{ - static class TextureFactory - { - public static GalTexture MakeTexture(NvGpuVmm Vmm, long TicPosition) - { - int[] Tic = ReadWords(Vmm, TicPosition, 8); - - GalTextureFormat Format = (GalTextureFormat)(Tic[0] & 0x7f); - - GalTextureSource XSource = (GalTextureSource)((Tic[0] >> 19) & 7); - GalTextureSource YSource = (GalTextureSource)((Tic[0] >> 22) & 7); - GalTextureSource ZSource = (GalTextureSource)((Tic[0] >> 25) & 7); - GalTextureSource WSource = (GalTextureSource)((Tic[0] >> 28) & 7); - - int Width = (Tic[4] & 0xffff) + 1; - int Height = (Tic[5] & 0xffff) + 1; - - return new GalTexture( - Width, - Height, - Format, - XSource, - YSource, - ZSource, - WSource); - } - - public static byte[] GetTextureData(NvGpuVmm Vmm, long TicPosition) - { - int[] Tic = ReadWords(Vmm, TicPosition, 8); - - GalTextureFormat Format = (GalTextureFormat)(Tic[0] & 0x7f); - - long TextureAddress = (uint)Tic[1]; - - TextureAddress |= (long)((ushort)Tic[2]) << 32; - - TextureSwizzle Swizzle = (TextureSwizzle)((Tic[2] >> 21) & 7); - - if (Swizzle == TextureSwizzle.BlockLinear || - Swizzle == TextureSwizzle.BlockLinearColorKey) - { - TextureAddress &= ~0x1ffL; - } - else if (Swizzle == TextureSwizzle.Pitch || - Swizzle == TextureSwizzle.PitchColorKey) - { - TextureAddress &= ~0x1fL; - } - - int Pitch = (Tic[3] & 0xffff) << 5; - - int BlockHeightLog2 = (Tic[3] >> 3) & 7; - - int BlockHeight = 1 << BlockHeightLog2; - - int Width = (Tic[4] & 0xffff) + 1; - int Height = (Tic[5] & 0xffff) + 1; - - TextureInfo Texture = new TextureInfo( - TextureAddress, - Width, - Height, - Pitch, - BlockHeight, - Swizzle, - Format); - - return TextureReader.Read(Vmm, Texture); - } - - public static GalTextureSampler MakeSampler(NvGpu Gpu, NvGpuVmm Vmm, long TscPosition) - { - int[] Tsc = ReadWords(Vmm, TscPosition, 8); - - GalTextureWrap AddressU = (GalTextureWrap)((Tsc[0] >> 0) & 7); - GalTextureWrap AddressV = (GalTextureWrap)((Tsc[0] >> 3) & 7); - GalTextureWrap AddressP = (GalTextureWrap)((Tsc[0] >> 6) & 7); - - GalTextureFilter MagFilter = (GalTextureFilter) ((Tsc[1] >> 0) & 3); - GalTextureFilter MinFilter = (GalTextureFilter) ((Tsc[1] >> 4) & 3); - GalTextureMipFilter MipFilter = (GalTextureMipFilter)((Tsc[1] >> 6) & 3); - - GalColorF BorderColor = new GalColorF( - BitConverter.Int32BitsToSingle(Tsc[4]), - BitConverter.Int32BitsToSingle(Tsc[5]), - BitConverter.Int32BitsToSingle(Tsc[6]), - BitConverter.Int32BitsToSingle(Tsc[7])); - - return new GalTextureSampler( - AddressU, - AddressV, - AddressP, - MinFilter, - MagFilter, - MipFilter, - BorderColor); - } - - private static int[] ReadWords(NvGpuVmm Vmm, long Position, int Count) - { - int[] Words = new int[Count]; - - for (int Index = 0; Index < Count; Index++, Position += 4) - { - Words[Index] = Vmm.ReadInt32(Position); - } - - return Words; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/Gpu/Texture/TextureHelper.cs b/Ryujinx.HLE/Gpu/Texture/TextureHelper.cs deleted file mode 100644 index 3c633b6928..0000000000 --- a/Ryujinx.HLE/Gpu/Texture/TextureHelper.cs +++ /dev/null @@ -1,88 +0,0 @@ -using ChocolArm64.Memory; -using Ryujinx.Graphics.Gal; -using Ryujinx.HLE.Gpu.Memory; -using System; - -namespace Ryujinx.HLE.Gpu.Texture -{ - static class TextureHelper - { - public static ISwizzle GetSwizzle(TextureInfo Texture, int Width, int Bpp) - { - switch (Texture.Swizzle) - { - case TextureSwizzle._1dBuffer: - case TextureSwizzle.Pitch: - case TextureSwizzle.PitchColorKey: - return new LinearSwizzle(Texture.Pitch, Bpp); - - case TextureSwizzle.BlockLinear: - case TextureSwizzle.BlockLinearColorKey: - return new BlockLinearSwizzle(Width, Bpp, Texture.BlockHeight); - } - - throw new NotImplementedException(Texture.Swizzle.ToString()); - } - - public static int GetTextureSize(GalTexture Texture) - { - switch (Texture.Format) - { - case GalTextureFormat.R32G32B32A32: - return Texture.Width * Texture.Height * 16; - - case GalTextureFormat.R16G16B16A16: - return Texture.Width * Texture.Height * 8; - - case GalTextureFormat.A8B8G8R8: - case GalTextureFormat.R32: - case GalTextureFormat.ZF32: - return Texture.Width * Texture.Height * 4; - - case GalTextureFormat.A1B5G5R5: - case GalTextureFormat.B5G6R5: - case GalTextureFormat.G8R8: - case GalTextureFormat.R16: - return Texture.Width * Texture.Height * 2; - - case GalTextureFormat.R8: - return Texture.Width * Texture.Height; - - case GalTextureFormat.BC1: - case GalTextureFormat.BC4: - { - int W = (Texture.Width + 3) / 4; - int H = (Texture.Height + 3) / 4; - - return W * H * 8; - } - - case GalTextureFormat.BC7U: - case GalTextureFormat.BC2: - case GalTextureFormat.BC3: - case GalTextureFormat.BC5: - case GalTextureFormat.Astc2D4x4: - { - int W = (Texture.Width + 3) / 4; - int H = (Texture.Height + 3) / 4; - - return W * H * 16; - } - } - - throw new NotImplementedException(Texture.Format.ToString()); - } - - public static (AMemory Memory, long Position) GetMemoryAndPosition( - IAMemory Memory, - long Position) - { - if (Memory is NvGpuVmm Vmm) - { - return (Vmm.Memory, Vmm.GetPhysicalAddress(Position)); - } - - return ((AMemory)Memory, Position); - } - } -} diff --git a/Ryujinx.HLE/Gpu/Texture/TextureInfo.cs b/Ryujinx.HLE/Gpu/Texture/TextureInfo.cs deleted file mode 100644 index 31784bbc58..0000000000 --- a/Ryujinx.HLE/Gpu/Texture/TextureInfo.cs +++ /dev/null @@ -1,55 +0,0 @@ -using Ryujinx.Graphics.Gal; - -namespace Ryujinx.HLE.Gpu.Texture -{ - struct TextureInfo - { - public long Position { get; private set; } - - public int Width { get; private set; } - public int Height { get; private set; } - public int Pitch { get; private set; } - - public int BlockHeight { get; private set; } - - public TextureSwizzle Swizzle { get; private set; } - - public GalTextureFormat Format { get; private set; } - - public TextureInfo( - long Position, - int Width, - int Height) - { - this.Position = Position; - this.Width = Width; - this.Height = Height; - - Pitch = 0; - - BlockHeight = 16; - - Swizzle = TextureSwizzle.BlockLinear; - - Format = GalTextureFormat.A8B8G8R8; - } - - public TextureInfo( - long Position, - int Width, - int Height, - int Pitch, - int BlockHeight, - TextureSwizzle Swizzle, - GalTextureFormat Format) - { - this.Position = Position; - this.Width = Width; - this.Height = Height; - this.Pitch = Pitch; - this.BlockHeight = BlockHeight; - this.Swizzle = Swizzle; - this.Format = Format; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/Gpu/Texture/TextureReader.cs b/Ryujinx.HLE/Gpu/Texture/TextureReader.cs deleted file mode 100644 index 24bceffb12..0000000000 --- a/Ryujinx.HLE/Gpu/Texture/TextureReader.cs +++ /dev/null @@ -1,345 +0,0 @@ -using ChocolArm64.Memory; -using Ryujinx.Graphics.Gal; -using System; - -namespace Ryujinx.HLE.Gpu.Texture -{ - static class TextureReader - { - public static byte[] Read(IAMemory Memory, TextureInfo Texture) - { - switch (Texture.Format) - { - case GalTextureFormat.R32G32B32A32: return Read16Bpp (Memory, Texture); - case GalTextureFormat.R16G16B16A16: return Read8Bpp (Memory, Texture); - case GalTextureFormat.A8B8G8R8: return Read4Bpp (Memory, Texture); - case GalTextureFormat.R32: return Read4Bpp (Memory, Texture); - case GalTextureFormat.A1B5G5R5: return Read5551 (Memory, Texture); - case GalTextureFormat.B5G6R5: return Read565 (Memory, Texture); - case GalTextureFormat.G8R8: return Read2Bpp (Memory, Texture); - case GalTextureFormat.R16: return Read2Bpp (Memory, Texture); - case GalTextureFormat.R8: return Read1Bpp (Memory, Texture); - case GalTextureFormat.BC7U: return Read16Bpt4x4(Memory, Texture); - case GalTextureFormat.BC1: return Read8Bpt4x4 (Memory, Texture); - case GalTextureFormat.BC2: return Read16Bpt4x4(Memory, Texture); - case GalTextureFormat.BC3: return Read16Bpt4x4(Memory, Texture); - case GalTextureFormat.BC4: return Read8Bpt4x4 (Memory, Texture); - case GalTextureFormat.BC5: return Read16Bpt4x4(Memory, Texture); - case GalTextureFormat.ZF32: return Read4Bpp (Memory, Texture); - case GalTextureFormat.Astc2D4x4: return Read16Bpt4x4(Memory, Texture); - } - - throw new NotImplementedException(Texture.Format.ToString()); - } - - private unsafe static byte[] Read1Bpp(IAMemory Memory, TextureInfo Texture) - { - int Width = Texture.Width; - int Height = Texture.Height; - - byte[] Output = new byte[Width * Height]; - - ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, Width, 1); - - (AMemory CpuMem, long Position) = TextureHelper.GetMemoryAndPosition( - Memory, - Texture.Position); - - fixed (byte* BuffPtr = Output) - { - long OutOffs = 0; - - for (int Y = 0; Y < Height; Y++) - for (int X = 0; X < Width; X++) - { - long Offset = (uint)Swizzle.GetSwizzleOffset(X, Y); - - byte Pixel = CpuMem.ReadByteUnchecked(Position + Offset); - - *(BuffPtr + OutOffs) = Pixel; - - OutOffs++; - } - } - - return Output; - } - - private unsafe static byte[] Read5551(IAMemory Memory, TextureInfo Texture) - { - int Width = Texture.Width; - int Height = Texture.Height; - - byte[] Output = new byte[Width * Height * 2]; - - ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, Width, 2); - - (AMemory CpuMem, long Position) = TextureHelper.GetMemoryAndPosition( - Memory, - Texture.Position); - - fixed (byte* BuffPtr = Output) - { - long OutOffs = 0; - - for (int Y = 0; Y < Height; Y++) - for (int X = 0; X < Width; X++) - { - long Offset = (uint)Swizzle.GetSwizzleOffset(X, Y); - - uint Pixel = (uint)CpuMem.ReadInt16Unchecked(Position + Offset); - - Pixel = (Pixel & 0x001f) << 11 | - (Pixel & 0x03e0) << 1 | - (Pixel & 0x7c00) >> 9 | - (Pixel & 0x8000) >> 15; - - *(short*)(BuffPtr + OutOffs) = (short)Pixel; - - OutOffs += 2; - } - } - - return Output; - } - - private unsafe static byte[] Read565(IAMemory Memory, TextureInfo Texture) - { - int Width = Texture.Width; - int Height = Texture.Height; - - byte[] Output = new byte[Width * Height * 2]; - - ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, Width, 2); - - (AMemory CpuMem, long Position) = TextureHelper.GetMemoryAndPosition( - Memory, - Texture.Position); - - fixed (byte* BuffPtr = Output) - { - long OutOffs = 0; - - for (int Y = 0; Y < Height; Y++) - for (int X = 0; X < Width; X++) - { - long Offset = (uint)Swizzle.GetSwizzleOffset(X, Y); - - uint Pixel = (uint)CpuMem.ReadInt16Unchecked(Position + Offset); - - Pixel = (Pixel & 0x001f) << 11 | - (Pixel & 0x07e0) | - (Pixel & 0xf800) >> 11; - - *(short*)(BuffPtr + OutOffs) = (short)Pixel; - - OutOffs += 2; - } - } - - return Output; - } - - private unsafe static byte[] Read2Bpp(IAMemory Memory, TextureInfo Texture) - { - int Width = Texture.Width; - int Height = Texture.Height; - - byte[] Output = new byte[Width * Height * 2]; - - ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, Width, 2); - - (AMemory CpuMem, long Position) = TextureHelper.GetMemoryAndPosition( - Memory, - Texture.Position); - - fixed (byte* BuffPtr = Output) - { - long OutOffs = 0; - - for (int Y = 0; Y < Height; Y++) - for (int X = 0; X < Width; X++) - { - long Offset = (uint)Swizzle.GetSwizzleOffset(X, Y); - - short Pixel = CpuMem.ReadInt16Unchecked(Position + Offset); - - *(short*)(BuffPtr + OutOffs) = Pixel; - - OutOffs += 2; - } - } - - return Output; - } - - private unsafe static byte[] Read4Bpp(IAMemory Memory, TextureInfo Texture) - { - int Width = Texture.Width; - int Height = Texture.Height; - - byte[] Output = new byte[Width * Height * 4]; - - ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, Width, 4); - - (AMemory CpuMem, long Position) = TextureHelper.GetMemoryAndPosition( - Memory, - Texture.Position); - - fixed (byte* BuffPtr = Output) - { - long OutOffs = 0; - - for (int Y = 0; Y < Height; Y++) - for (int X = 0; X < Width; X++) - { - long Offset = (uint)Swizzle.GetSwizzleOffset(X, Y); - - int Pixel = CpuMem.ReadInt32Unchecked(Position + Offset); - - *(int*)(BuffPtr + OutOffs) = Pixel; - - OutOffs += 4; - } - } - - return Output; - } - - private unsafe static byte[] Read8Bpp(IAMemory Memory, TextureInfo Texture) - { - int Width = Texture.Width; - int Height = Texture.Height; - - byte[] Output = new byte[Width * Height * 8]; - - ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, Width, 8); - - (AMemory CpuMem, long Position) = TextureHelper.GetMemoryAndPosition( - Memory, - Texture.Position); - - fixed (byte* BuffPtr = Output) - { - long OutOffs = 0; - - for (int Y = 0; Y < Height; Y++) - for (int X = 0; X < Width; X++) - { - long Offset = (uint)Swizzle.GetSwizzleOffset(X, Y); - - long Pixel = CpuMem.ReadInt64Unchecked(Position + Offset); - - *(long*)(BuffPtr + OutOffs) = Pixel; - - OutOffs += 8; - } - } - - return Output; - } - - private unsafe static byte[] Read16Bpp(IAMemory Memory, TextureInfo Texture) - { - int Width = Texture.Width; - int Height = Texture.Height; - - byte[] Output = new byte[Width * Height * 16]; - - ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, Width, 16); - - (AMemory CpuMem, long Position) = TextureHelper.GetMemoryAndPosition( - Memory, - Texture.Position); - - fixed (byte* BuffPtr = Output) - { - long OutOffs = 0; - - for (int Y = 0; Y < Height; Y++) - for (int X = 0; X < Width; X++) - { - long Offset = (uint)Swizzle.GetSwizzleOffset(X, Y); - - long PxLow = CpuMem.ReadInt64Unchecked(Position + Offset + 0); - long PxHigh = CpuMem.ReadInt64Unchecked(Position + Offset + 8); - - *(long*)(BuffPtr + OutOffs + 0) = PxLow; - *(long*)(BuffPtr + OutOffs + 8) = PxHigh; - - OutOffs += 16; - } - } - - return Output; - } - - private unsafe static byte[] Read8Bpt4x4(IAMemory Memory, TextureInfo Texture) - { - int Width = (Texture.Width + 3) / 4; - int Height = (Texture.Height + 3) / 4; - - byte[] Output = new byte[Width * Height * 8]; - - ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, Width, 8); - - (AMemory CpuMem, long Position) = TextureHelper.GetMemoryAndPosition( - Memory, - Texture.Position); - - fixed (byte* BuffPtr = Output) - { - long OutOffs = 0; - - for (int Y = 0; Y < Height; Y++) - for (int X = 0; X < Width; X++) - { - long Offset = (uint)Swizzle.GetSwizzleOffset(X, Y); - - long Tile = CpuMem.ReadInt64Unchecked(Position + Offset); - - *(long*)(BuffPtr + OutOffs) = Tile; - - OutOffs += 8; - } - } - - return Output; - } - - private unsafe static byte[] Read16Bpt4x4(IAMemory Memory, TextureInfo Texture) - { - int Width = (Texture.Width + 3) / 4; - int Height = (Texture.Height + 3) / 4; - - byte[] Output = new byte[Width * Height * 16]; - - ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, Width, 16); - - (AMemory CpuMem, long Position) = TextureHelper.GetMemoryAndPosition( - Memory, - Texture.Position); - - fixed (byte* BuffPtr = Output) - { - long OutOffs = 0; - - for (int Y = 0; Y < Height; Y++) - for (int X = 0; X < Width; X++) - { - long Offset = (uint)Swizzle.GetSwizzleOffset(X, Y); - - long Tile0 = CpuMem.ReadInt64Unchecked(Position + Offset + 0); - long Tile1 = CpuMem.ReadInt64Unchecked(Position + Offset + 8); - - *(long*)(BuffPtr + OutOffs + 0) = Tile0; - *(long*)(BuffPtr + OutOffs + 8) = Tile1; - - OutOffs += 16; - } - } - - return Output; - } - } -} diff --git a/Ryujinx.HLE/Gpu/Texture/TextureSwizzle.cs b/Ryujinx.HLE/Gpu/Texture/TextureSwizzle.cs deleted file mode 100644 index 076df97ab5..0000000000 --- a/Ryujinx.HLE/Gpu/Texture/TextureSwizzle.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Ryujinx.HLE.Gpu.Texture -{ - enum TextureSwizzle - { - _1dBuffer = 0, - PitchColorKey = 1, - Pitch = 2, - BlockLinear = 3, - BlockLinearColorKey = 4 - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/Gpu/Texture/TextureWriter.cs b/Ryujinx.HLE/Gpu/Texture/TextureWriter.cs deleted file mode 100644 index b64302a5ab..0000000000 --- a/Ryujinx.HLE/Gpu/Texture/TextureWriter.cs +++ /dev/null @@ -1,55 +0,0 @@ -using ChocolArm64.Memory; -using Ryujinx.Graphics.Gal; -using System; - -namespace Ryujinx.HLE.Gpu.Texture -{ - static class TextureWriter - { - public static void Write( - IAMemory Memory, - TextureInfo Texture, - byte[] Data, - int Width, - int Height) - { - switch (Texture.Format) - { - case GalTextureFormat.A8B8G8R8: Write4Bpp(Memory, Texture, Data, Width, Height); break; - - default: throw new NotImplementedException(Texture.Format.ToString()); - } - } - - private unsafe static void Write4Bpp( - IAMemory Memory, - TextureInfo Texture, - byte[] Data, - int Width, - int Height) - { - ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, Width, 4); - - (AMemory CpuMem, long Position) = TextureHelper.GetMemoryAndPosition( - Memory, - Texture.Position); - - fixed (byte* BuffPtr = Data) - { - long InOffs = 0; - - for (int Y = 0; Y < Height; Y++) - for (int X = 0; X < Width; X++) - { - long Offset = (uint)Swizzle.GetSwizzleOffset(X, Y); - - int Pixel = *(int*)(BuffPtr + InOffs); - - CpuMem.WriteInt32Unchecked(Position + Offset, Pixel); - - InOffs += 4; - } - } - } - } -} diff --git a/Ryujinx.HLE/HOS/Applets/AppletManager.cs b/Ryujinx.HLE/HOS/Applets/AppletManager.cs new file mode 100644 index 0000000000..e731454048 --- /dev/null +++ b/Ryujinx.HLE/HOS/Applets/AppletManager.cs @@ -0,0 +1,30 @@ +using Ryujinx.HLE.HOS.Services.Am.AppletAE; +using System; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Applets +{ + static class AppletManager + { + private static Dictionary _appletMapping; + + static AppletManager() + { + _appletMapping = new Dictionary + { + { AppletId.PlayerSelect, typeof(PlayerSelectApplet) }, + { AppletId.SoftwareKeyboard, typeof(SoftwareKeyboardApplet) } + }; + } + + public static IApplet Create(AppletId applet, Horizon system) + { + if (_appletMapping.TryGetValue(applet, out Type appletClass)) + { + return (IApplet)Activator.CreateInstance(appletClass, system); + } + + throw new NotImplementedException($"{applet} applet is not implemented."); + } + } +} diff --git a/Ryujinx.HLE/HOS/Applets/IApplet.cs b/Ryujinx.HLE/HOS/Applets/IApplet.cs new file mode 100644 index 0000000000..c2d4aada1f --- /dev/null +++ b/Ryujinx.HLE/HOS/Applets/IApplet.cs @@ -0,0 +1,15 @@ +using Ryujinx.HLE.HOS.Services.Am.AppletAE; +using System; + +namespace Ryujinx.HLE.HOS.Applets +{ + interface IApplet + { + event EventHandler AppletStateChanged; + + ResultCode Start(AppletSession normalSession, + AppletSession interactiveSession); + + ResultCode GetResult(); + } +} diff --git a/Ryujinx.HLE/HOS/Applets/PlayerSelect/PlayerSelectApplet.cs b/Ryujinx.HLE/HOS/Applets/PlayerSelect/PlayerSelectApplet.cs new file mode 100644 index 0000000000..418f5c1004 --- /dev/null +++ b/Ryujinx.HLE/HOS/Applets/PlayerSelect/PlayerSelectApplet.cs @@ -0,0 +1,56 @@ +using Ryujinx.HLE.HOS.Services.Account.Acc; +using Ryujinx.HLE.HOS.Services.Am.AppletAE; +using System; +using System.IO; + +namespace Ryujinx.HLE.HOS.Applets +{ + internal class PlayerSelectApplet : IApplet + { + private Horizon _system; + + private AppletSession _normalSession; + private AppletSession _interactiveSession; + + public event EventHandler AppletStateChanged; + + public PlayerSelectApplet(Horizon system) + { + _system = system; + } + + public ResultCode Start(AppletSession normalSession, + AppletSession interactiveSession) + { + _normalSession = normalSession; + _interactiveSession = interactiveSession; + + // TODO(jduncanator): Parse PlayerSelectConfig from input data + _normalSession.Push(BuildResponse()); + + AppletStateChanged?.Invoke(this, null); + + return ResultCode.Success; + } + + public ResultCode GetResult() + { + return ResultCode.Success; + } + + private byte[] BuildResponse() + { + UserProfile currentUser = _system.State.Account.LastOpenedUser; + + using (MemoryStream stream = new MemoryStream()) + using (BinaryWriter writer = new BinaryWriter(stream)) + { + writer.Write((ulong)PlayerSelectResult.Success); + + currentUser.UserId.Write(writer); + + return stream.ToArray(); + } + } + } +} diff --git a/Ryujinx.HLE/HOS/Applets/PlayerSelect/PlayerSelectResult.cs b/Ryujinx.HLE/HOS/Applets/PlayerSelect/PlayerSelectResult.cs new file mode 100644 index 0000000000..682e094ed6 --- /dev/null +++ b/Ryujinx.HLE/HOS/Applets/PlayerSelect/PlayerSelectResult.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Applets +{ + enum PlayerSelectResult : ulong + { + Success = 0, + Failure = 2 + } +} diff --git a/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InitialCursorPosition.cs b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InitialCursorPosition.cs new file mode 100644 index 0000000000..727b6d27b1 --- /dev/null +++ b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InitialCursorPosition.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard +{ + /// + /// Identifies the initial position of the cursor displayed in the area. + /// + enum InitialCursorPosition : uint + { + /// + /// Position the cursor at the beginning of the text + /// + Start, + + /// + /// Position the cursor at the end of the text + /// + End + } +} diff --git a/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InputFormMode.cs b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InputFormMode.cs new file mode 100644 index 0000000000..c3ce2c1251 --- /dev/null +++ b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InputFormMode.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard +{ + /// + /// Identifies the text entry mode. + /// + enum InputFormMode : uint + { + /// + /// Displays the text entry area as a single-line field. + /// + SingleLine, + + /// + /// Displays the text entry area as a multi-line field. + /// + MultiLine + } +} diff --git a/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InvalidCharFlags.cs b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InvalidCharFlags.cs new file mode 100644 index 0000000000..f3fd8ac85d --- /dev/null +++ b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InvalidCharFlags.cs @@ -0,0 +1,56 @@ +using System; + +namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard +{ + /// + /// Identifies prohibited character sets. + /// + [Flags] + enum InvalidCharFlags : uint + { + /// + /// No characters are prohibited. + /// + None = 0 << 1, + + /// + /// Prohibits spaces. + /// + Space = 1 << 1, + + /// + /// Prohibits the at (@) symbol. + /// + AtSymbol = 1 << 2, + + /// + /// Prohibits the percent (%) symbol. + /// + Percent = 1 << 3, + + /// + /// Prohibits the forward slash (/) symbol. + /// + ForwardSlash = 1 << 4, + + /// + /// Prohibits the backward slash (\) symbol. + /// + BackSlash = 1 << 5, + + /// + /// Prohibits numbers. + /// + Numbers = 1 << 6, + + /// + /// Prohibits characters outside of those allowed in download codes. + /// + DownloadCode = 1 << 7, + + /// + /// Prohibits characters outside of those allowed in Mii Nicknames. + /// + Username = 1 << 8 + } +} diff --git a/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardMode.cs b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardMode.cs new file mode 100644 index 0000000000..e5418a6f34 --- /dev/null +++ b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardMode.cs @@ -0,0 +1,28 @@ +namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard +{ + /// + /// Identifies the variant of keyboard displayed on screen. + /// + enum KeyboardMode : uint + { + /// + /// A full alpha-numeric keyboard. + /// + Default, + + /// + /// Number pad. + /// + NumbersOnly, + + /// + /// QWERTY (and variants) keyboard only. + /// + LettersOnly, + + /// + /// Unknown keyboard variant. + /// + Unknown + } +} diff --git a/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/PasswordMode.cs b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/PasswordMode.cs new file mode 100644 index 0000000000..fc9e1ff8e2 --- /dev/null +++ b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/PasswordMode.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard +{ + /// + /// Identifies the display mode of text in a password field. + /// + enum PasswordMode : uint + { + /// + /// Display input characters. + /// + Disabled, + + /// + /// Hide input characters. + /// + Enabled + } +} diff --git a/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardApplet.cs b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardApplet.cs new file mode 100644 index 0000000000..2780446a8b --- /dev/null +++ b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardApplet.cs @@ -0,0 +1,195 @@ +using Ryujinx.HLE.HOS.Applets.SoftwareKeyboard; +using Ryujinx.HLE.HOS.Services.Am.AppletAE; +using System; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; + +namespace Ryujinx.HLE.HOS.Applets +{ + internal class SoftwareKeyboardApplet : IApplet + { + private const string DefaultNumb = "1"; + private const string DefaultText = "Ryujinx"; + + private const int StandardBufferSize = 0x7D8; + private const int InteractiveBufferSize = 0x7D4; + + private SoftwareKeyboardState _state = SoftwareKeyboardState.Uninitialized; + + private AppletSession _normalSession; + private AppletSession _interactiveSession; + + private SoftwareKeyboardConfig _keyboardConfig; + + private string _textValue = DefaultText; + private Encoding _encoding = Encoding.Unicode; + + public event EventHandler AppletStateChanged; + + public SoftwareKeyboardApplet(Horizon system) { } + + public ResultCode Start(AppletSession normalSession, + AppletSession interactiveSession) + { + _normalSession = normalSession; + _interactiveSession = interactiveSession; + + _interactiveSession.DataAvailable += OnInteractiveData; + + var launchParams = _normalSession.Pop(); + var keyboardConfig = _normalSession.Pop(); + var transferMemory = _normalSession.Pop(); + + _keyboardConfig = ReadStruct(keyboardConfig); + + if (_keyboardConfig.UseUtf8) + { + _encoding = Encoding.UTF8; + } + + _state = SoftwareKeyboardState.Ready; + + Execute(); + + return ResultCode.Success; + } + + public ResultCode GetResult() + { + return ResultCode.Success; + } + + private void Execute() + { + // If the keyboard type is numbers only, we swap to a default + // text that only contains numbers. + if (_keyboardConfig.Mode == KeyboardMode.NumbersOnly) + { + _textValue = DefaultNumb; + } + + // If the max string length is 0, we set it to a large default + // length. + if (_keyboardConfig.StringLengthMax == 0) + { + _keyboardConfig.StringLengthMax = 100; + } + + // If the game requests a string with a minimum length less + // than our default text, repeat our default text until we meet + // the minimum length requirement. + // This should always be done before the text truncation step. + while (_textValue.Length < _keyboardConfig.StringLengthMin) + { + _textValue = String.Join(" ", _textValue, _textValue); + } + + // If our default text is longer than the allowed length, + // we truncate it. + if (_textValue.Length > _keyboardConfig.StringLengthMax) + { + _textValue = _textValue.Substring(0, (int)_keyboardConfig.StringLengthMax); + } + + // Does the application want to validate the text itself? + if (_keyboardConfig.CheckText) + { + // The application needs to validate the response, so we + // submit it to the interactive output buffer, and poll it + // for validation. Once validated, the application will submit + // back a validation status, which is handled in OnInteractiveDataPushIn. + _state = SoftwareKeyboardState.ValidationPending; + + _interactiveSession.Push(BuildResponse(_textValue, true)); + } + else + { + // If the application doesn't need to validate the response, + // we push the data to the non-interactive output buffer + // and poll it for completion. + _state = SoftwareKeyboardState.Complete; + + _normalSession.Push(BuildResponse(_textValue, false)); + + AppletStateChanged?.Invoke(this, null); + } + } + + private void OnInteractiveData(object sender, EventArgs e) + { + // Obtain the validation status response, + var data = _interactiveSession.Pop(); + + if (_state == SoftwareKeyboardState.ValidationPending) + { + // TODO(jduncantor): + // If application rejects our "attempt", submit another attempt, + // and put the applet back in PendingValidation state. + + // For now we assume success, so we push the final result + // to the standard output buffer and carry on our merry way. + _normalSession.Push(BuildResponse(_textValue, false)); + + AppletStateChanged?.Invoke(this, null); + + _state = SoftwareKeyboardState.Complete; + } + else if(_state == SoftwareKeyboardState.Complete) + { + // If we have already completed, we push the result text + // back on the output buffer and poll the application. + _normalSession.Push(BuildResponse(_textValue, false)); + + AppletStateChanged?.Invoke(this, null); + } + else + { + // We shouldn't be able to get here through standard swkbd execution. + throw new InvalidOperationException("Software Keyboard is in an invalid state."); + } + } + + private byte[] BuildResponse(string text, bool interactive) + { + int bufferSize = interactive ? InteractiveBufferSize : StandardBufferSize; + + using (MemoryStream stream = new MemoryStream(new byte[bufferSize])) + using (BinaryWriter writer = new BinaryWriter(stream)) + { + byte[] output = _encoding.GetBytes(text); + + if (!interactive) + { + // Result Code + writer.Write((uint)0); + } + else + { + // In interactive mode, we write the length of the text + // as a long, rather than a result code. + writer.Write((long)output.Length); + } + + writer.Write(output); + + return stream.ToArray(); + } + } + + private static T ReadStruct(byte[] data) + where T : struct + { + GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned); + + try + { + return Marshal.PtrToStructure(handle.AddrOfPinnedObject()); + } + finally + { + handle.Free(); + } + } + } +} diff --git a/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardConfig.cs b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardConfig.cs new file mode 100644 index 0000000000..fd462382bc --- /dev/null +++ b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardConfig.cs @@ -0,0 +1,138 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard +{ + /// + /// A structure that defines the configuration options of the software keyboard. + /// + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + struct SoftwareKeyboardConfig + { + private const int SubmitTextLength = 8; + private const int HeaderTextLength = 64; + private const int SubtitleTextLength = 128; + private const int GuideTextLength = 256; + + /// + /// Type of keyboard. + /// + public KeyboardMode Mode; + + /// + /// The string displayed in the Submit button. + /// + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = SubmitTextLength + 1)] + public string SubmitText; + + /// + /// The character displayed in the left button of the numeric keyboard. + /// This is ignored when Mode is not set to NumbersOnly. + /// + public char LeftOptionalSymbolKey; + + /// + /// The character displayed in the right button of the numeric keyboard. + /// This is ignored when Mode is not set to NumbersOnly. + /// + public char RightOptionalSymbolKey; + + /// + /// When set, predictive typing is enabled making use of the system dictionary, + /// and any custom user dictionary. + /// + [MarshalAs(UnmanagedType.I1)] + public bool PredictionEnabled; + + /// + /// Specifies prohibited characters that cannot be input into the text entry area. + /// + public InvalidCharFlags InvalidCharFlag; + + /// + /// The initial position of the text cursor displayed in the text entry area. + /// + public InitialCursorPosition InitialCursorPosition; + + /// + /// The string displayed in the header area of the keyboard. + /// + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = HeaderTextLength + 1)] + public string HeaderText; + + /// + /// The string displayed in the subtitle area of the keyboard. + /// + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = SubtitleTextLength + 1)] + public string SubtitleText; + + /// + /// The placeholder string displayed in the text entry area when no text is entered. + /// + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = GuideTextLength + 1)] + public string GuideText; + + /// + /// When non-zero, specifies the maximum allowed length of the string entered into the text entry area. + /// + public int StringLengthMax; + + /// + /// When non-zero, specifies the minimum allowed length of the string entered into the text entry area. + /// + public int StringLengthMin; + + /// + /// When enabled, hides input characters as dots in the text entry area. + /// + public PasswordMode PasswordMode; + + /// + /// Specifies whether the text entry area is displayed as a single-line entry, or a multi-line entry field. + /// + public InputFormMode InputFormMode; + + /// + /// When set, enables or disables the return key. This value is ignored when single-line entry is specified as the InputFormMode. + /// + [MarshalAs(UnmanagedType.I1)] + public bool UseNewLine; + + /// + /// When set, the software keyboard will return a UTF-8 encoded string, rather than UTF-16. + /// + [MarshalAs(UnmanagedType.I1)] + public bool UseUtf8; + + /// + /// When set, the software keyboard will blur the game application rendered behind the keyboard. + /// + [MarshalAs(UnmanagedType.I1)] + public bool UseBlurBackground; + + /// + /// Offset into the work buffer of the initial text when the keyboard is first displayed. + /// + public int InitialStringOffset; + + /// + /// Length of the initial text. + /// + public int InitialStringLength; + + /// + /// Offset into the work buffer of the custom user dictionary. + /// + public int CustomDictionaryOffset; + + /// + /// Number of entries in the custom user dictionary. + /// + public int CustomDictionaryCount; + + /// + /// When set, the text entered will be validated on the application side after the keyboard has been submitted. + /// + [MarshalAs(UnmanagedType.I1)] + public bool CheckText; + } +} diff --git a/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardState.cs b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardState.cs new file mode 100644 index 0000000000..0f66fc9bac --- /dev/null +++ b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardState.cs @@ -0,0 +1,28 @@ +namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard +{ + /// + /// Identifies the software keyboard state. + /// + enum SoftwareKeyboardState + { + /// + /// swkbd is uninitialized. + /// + Uninitialized, + + /// + /// swkbd is ready to process data. + /// + Ready, + + /// + /// swkbd is awaiting an interactive reply with a validation status. + /// + ValidationPending, + + /// + /// swkbd has completed. + /// + Complete + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ArraySubscriptingExpression.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ArraySubscriptingExpression.cs new file mode 100644 index 0000000000..5145ff7b0c --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ArraySubscriptingExpression.cs @@ -0,0 +1,25 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class ArraySubscriptingExpression : BaseNode + { + private BaseNode _leftNode; + private BaseNode _subscript; + + public ArraySubscriptingExpression(BaseNode leftNode, BaseNode subscript) : base(NodeType.ArraySubscriptingExpression) + { + _leftNode = leftNode; + _subscript = subscript; + } + + public override void PrintLeft(TextWriter writer) + { + writer.Write("("); + _leftNode.Print(writer); + writer.Write(")["); + _subscript.Print(writer); + writer.Write("]"); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ArrayType.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ArrayType.cs new file mode 100644 index 0000000000..4b1041ab79 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ArrayType.cs @@ -0,0 +1,59 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class ArrayType : BaseNode + { + private BaseNode _base; + private BaseNode _dimensionExpression; + private string _dimensionString; + + public ArrayType(BaseNode Base, BaseNode dimensionExpression = null) : base(NodeType.ArrayType) + { + _base = Base; + _dimensionExpression = dimensionExpression; + } + + public ArrayType(BaseNode Base, string dimensionString) : base(NodeType.ArrayType) + { + _base = Base; + _dimensionString = dimensionString; + } + + public override bool HasRightPart() + { + return true; + } + + public override bool IsArray() + { + return true; + } + + public override void PrintLeft(TextWriter writer) + { + _base.PrintLeft(writer); + } + + public override void PrintRight(TextWriter writer) + { + // FIXME: detect if previous char was a ]. + writer.Write(" "); + + writer.Write("["); + + if (_dimensionString != null) + { + writer.Write(_dimensionString); + } + else if (_dimensionExpression != null) + { + _dimensionExpression.Print(writer); + } + + writer.Write("]"); + + _base.PrintRight(writer); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BaseNode.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BaseNode.cs new file mode 100644 index 0000000000..ca4b98f88f --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BaseNode.cs @@ -0,0 +1,113 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public enum NodeType + { + CvQualifierType, + SimpleReferenceType, + NameType, + EncodedFunction, + NestedName, + SpecialName, + LiteralOperator, + NodeArray, + ElaboratedType, + PostfixQualifiedType, + SpecialSubstitution, + ExpandedSpecialSubstitution, + CtorDtorNameType, + EnclosedExpression, + ForwardTemplateReference, + NameTypeWithTemplateArguments, + PackedTemplateArgument, + TemplateArguments, + BooleanExpression, + CastExpression, + CallExpression, + IntegerCastExpression, + PackedTemplateParameter, + PackedTemplateParameterExpansion, + IntegerLiteral, + DeleteExpression, + MemberExpression, + ArraySubscriptingExpression, + InitListExpression, + PostfixExpression, + ConditionalExpression, + ThrowExpression, + FunctionParameter, + ConversionExpression, + BinaryExpression, + PrefixExpression, + BracedExpression, + BracedRangeExpression, + NewExpression, + QualifiedName, + StdQualifiedName, + DtOrName, + GlobalQualifiedName, + NoexceptSpec, + DynamicExceptionSpec, + FunctionType, + PointerType, + ReferenceType, + ConversionOperatorType, + LocalName, + CtorVtableSpecialName, + ArrayType + } + + public abstract class BaseNode + { + public NodeType Type { get; protected set; } + + public BaseNode(NodeType type) + { + Type = type; + } + + public virtual void Print(TextWriter writer) + { + PrintLeft(writer); + + if (HasRightPart()) + { + PrintRight(writer); + } + } + + public abstract void PrintLeft(TextWriter writer); + + public virtual bool HasRightPart() + { + return false; + } + + public virtual bool IsArray() + { + return false; + } + + public virtual bool HasFunctions() + { + return false; + } + + public virtual string GetName() + { + return null; + } + + public virtual void PrintRight(TextWriter writer) {} + + public override string ToString() + { + StringWriter writer = new StringWriter(); + + Print(writer); + + return writer.ToString(); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BinaryExpression.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BinaryExpression.cs new file mode 100644 index 0000000000..0c492df394 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BinaryExpression.cs @@ -0,0 +1,41 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class BinaryExpression : BaseNode + { + private BaseNode _leftPart; + private string _name; + private BaseNode _rightPart; + + public BinaryExpression(BaseNode leftPart, string name, BaseNode rightPart) : base(NodeType.BinaryExpression) + { + _leftPart = leftPart; + _name = name; + _rightPart = rightPart; + } + + public override void PrintLeft(TextWriter writer) + { + if (_name.Equals(">")) + { + writer.Write("("); + } + + writer.Write("("); + _leftPart.Print(writer); + writer.Write(") "); + + writer.Write(_name); + + writer.Write(" ("); + _rightPart.Print(writer); + writer.Write(")"); + + if (_name.Equals(">")) + { + writer.Write(")"); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BracedExpression.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BracedExpression.cs new file mode 100644 index 0000000000..6b9782f5c2 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BracedExpression.cs @@ -0,0 +1,40 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class BracedExpression : BaseNode + { + private BaseNode _element; + private BaseNode _expression; + private bool _isArrayExpression; + + public BracedExpression(BaseNode element, BaseNode expression, bool isArrayExpression) : base(NodeType.BracedExpression) + { + _element = element; + _expression = expression; + _isArrayExpression = isArrayExpression; + } + + public override void PrintLeft(TextWriter writer) + { + if (_isArrayExpression) + { + writer.Write("["); + _element.Print(writer); + writer.Write("]"); + } + else + { + writer.Write("."); + _element.Print(writer); + } + + if (!_expression.GetType().Equals(NodeType.BracedExpression) || !_expression.GetType().Equals(NodeType.BracedRangeExpression)) + { + writer.Write(" = "); + } + + _expression.Print(writer); + } + } +} diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BracedRangeExpression.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BracedRangeExpression.cs new file mode 100644 index 0000000000..802422d9a1 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BracedRangeExpression.cs @@ -0,0 +1,34 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class BracedRangeExpression : BaseNode + { + private BaseNode _firstNode; + private BaseNode _lastNode; + private BaseNode _expression; + + public BracedRangeExpression(BaseNode firstNode, BaseNode lastNode, BaseNode expression) : base(NodeType.BracedRangeExpression) + { + _firstNode = firstNode; + _lastNode = lastNode; + _expression = expression; + } + + public override void PrintLeft(TextWriter writer) + { + writer.Write("["); + _firstNode.Print(writer); + writer.Write(" ... "); + _lastNode.Print(writer); + writer.Write("]"); + + if (!_expression.GetType().Equals(NodeType.BracedExpression) || !_expression.GetType().Equals(NodeType.BracedRangeExpression)) + { + writer.Write(" = "); + } + + _expression.Print(writer); + } + } +} diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CallExpression.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CallExpression.cs new file mode 100644 index 0000000000..8e3fc3e699 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CallExpression.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class CallExpression : NodeArray + { + private BaseNode _callee; + + public CallExpression(BaseNode callee, List nodes) : base(nodes, NodeType.CallExpression) + { + _callee = callee; + } + + public override void PrintLeft(TextWriter writer) + { + _callee.Print(writer); + + writer.Write("("); + writer.Write(string.Join(", ", Nodes.ToArray())); + writer.Write(")"); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CastExpression.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CastExpression.cs new file mode 100644 index 0000000000..1149a788c1 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CastExpression.cs @@ -0,0 +1,28 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class CastExpression : BaseNode + { + private string _kind; + private BaseNode _to; + private BaseNode _from; + + public CastExpression(string kind, BaseNode to, BaseNode from) : base(NodeType.CastExpression) + { + _kind = kind; + _to = to; + _from = from; + } + + public override void PrintLeft(TextWriter writer) + { + writer.Write(_kind); + writer.Write("<"); + _to.PrintLeft(writer); + writer.Write(">("); + _from.PrintLeft(writer); + writer.Write(")"); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ConditionalExpression.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ConditionalExpression.cs new file mode 100644 index 0000000000..c0dd671793 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ConditionalExpression.cs @@ -0,0 +1,29 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class ConditionalExpression : BaseNode + { + private BaseNode _thenNode; + private BaseNode _elseNode; + private BaseNode _conditionNode; + + public ConditionalExpression(BaseNode conditionNode, BaseNode thenNode, BaseNode elseNode) : base(NodeType.ConditionalExpression) + { + _thenNode = thenNode; + _conditionNode = conditionNode; + _elseNode = elseNode; + } + + public override void PrintLeft(TextWriter writer) + { + writer.Write("("); + _conditionNode.Print(writer); + writer.Write(") ? ("); + _thenNode.Print(writer); + writer.Write(") : ("); + _elseNode.Print(writer); + writer.Write(")"); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ConversionExpression.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ConversionExpression.cs new file mode 100644 index 0000000000..dd1f7a0080 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ConversionExpression.cs @@ -0,0 +1,24 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class ConversionExpression : BaseNode + { + private BaseNode _typeNode; + private BaseNode _expressions; + + public ConversionExpression(BaseNode typeNode, BaseNode expressions) : base(NodeType.ConversionExpression) + { + _typeNode = typeNode; + _expressions = expressions; + } + + public override void PrintLeft(TextWriter writer) + { + writer.Write("("); + _typeNode.Print(writer); + writer.Write(")("); + _expressions.Print(writer); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ConversionOperatorType.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ConversionOperatorType.cs new file mode 100644 index 0000000000..8a5cde860b --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ConversionOperatorType.cs @@ -0,0 +1,15 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class ConversionOperatorType : ParentNode + { + public ConversionOperatorType(BaseNode child) : base(NodeType.ConversionOperatorType, child) { } + + public override void PrintLeft(TextWriter writer) + { + writer.Write("operator "); + Child.Print(writer); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CtorDtorNameType.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CtorDtorNameType.cs new file mode 100644 index 0000000000..5f45812358 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CtorDtorNameType.cs @@ -0,0 +1,24 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class CtorDtorNameType : ParentNode + { + private bool _isDestructor; + + public CtorDtorNameType(BaseNode name, bool isDestructor) : base(NodeType.CtorDtorNameType, name) + { + _isDestructor = isDestructor; + } + + public override void PrintLeft(TextWriter writer) + { + if (_isDestructor) + { + writer.Write("~"); + } + + writer.Write(Child.GetName()); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CtorVtableSpecialName.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CtorVtableSpecialName.cs new file mode 100644 index 0000000000..3bb5b16312 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CtorVtableSpecialName.cs @@ -0,0 +1,24 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class CtorVtableSpecialName : BaseNode + { + private BaseNode _firstType; + private BaseNode _secondType; + + public CtorVtableSpecialName(BaseNode firstType, BaseNode secondType) : base(NodeType.CtorVtableSpecialName) + { + _firstType = firstType; + _secondType = secondType; + } + + public override void PrintLeft(TextWriter writer) + { + writer.Write("construction vtable for "); + _firstType.Print(writer); + writer.Write("-in-"); + _secondType.Print(writer); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/DeleteExpression.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/DeleteExpression.cs new file mode 100644 index 0000000000..14715d25e5 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/DeleteExpression.cs @@ -0,0 +1,33 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class DeleteExpression : ParentNode + { + private bool _isGlobal; + private bool _isArrayExpression; + + public DeleteExpression(BaseNode child, bool isGlobal, bool isArrayExpression) : base(NodeType.DeleteExpression, child) + { + _isGlobal = isGlobal; + _isArrayExpression = isArrayExpression; + } + + public override void PrintLeft(TextWriter writer) + { + if (_isGlobal) + { + writer.Write("::"); + } + + writer.Write("delete"); + + if (_isArrayExpression) + { + writer.Write("[] "); + } + + Child.Print(writer); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/DtorName.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/DtorName.cs new file mode 100644 index 0000000000..5cc4e6cfb2 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/DtorName.cs @@ -0,0 +1,15 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class DtorName : ParentNode + { + public DtorName(BaseNode name) : base(NodeType.DtOrName, name) { } + + public override void PrintLeft(TextWriter writer) + { + writer.Write("~"); + Child.PrintLeft(writer); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/DynamicExceptionSpec.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/DynamicExceptionSpec.cs new file mode 100644 index 0000000000..faa91443a1 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/DynamicExceptionSpec.cs @@ -0,0 +1,16 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class DynamicExceptionSpec : ParentNode + { + public DynamicExceptionSpec(BaseNode child) : base(NodeType.DynamicExceptionSpec, child) { } + + public override void PrintLeft(TextWriter writer) + { + writer.Write("throw("); + Child.Print(writer); + writer.Write(")"); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ElaboratedType.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ElaboratedType.cs new file mode 100644 index 0000000000..086cd3dc72 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ElaboratedType.cs @@ -0,0 +1,21 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class ElaboratedType : ParentNode + { + private string _elaborated; + + public ElaboratedType(string elaborated, BaseNode type) : base(NodeType.ElaboratedType, type) + { + _elaborated = elaborated; + } + + public override void PrintLeft(TextWriter writer) + { + writer.Write(_elaborated); + writer.Write(" "); + Child.Print(writer); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/EnclosedExpression.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/EnclosedExpression.cs new file mode 100644 index 0000000000..b45481ddce --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/EnclosedExpression.cs @@ -0,0 +1,25 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class EnclosedExpression : BaseNode + { + private string _prefix; + private BaseNode _expression; + private string _postfix; + + public EnclosedExpression(string prefix, BaseNode expression, string postfix) : base(NodeType.EnclosedExpression) + { + _prefix = prefix; + _expression = expression; + _postfix = postfix; + } + + public override void PrintLeft(TextWriter writer) + { + writer.Write(_prefix); + _expression.Print(writer); + writer.Write(_postfix); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/EncodedFunction.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/EncodedFunction.cs new file mode 100644 index 0000000000..c7b6dab1a8 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/EncodedFunction.cs @@ -0,0 +1,77 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class EncodedFunction : BaseNode + { + private BaseNode _name; + private BaseNode _params; + private BaseNode _cv; + private BaseNode _ref; + private BaseNode _attrs; + private BaseNode _ret; + + public EncodedFunction(BaseNode name, BaseNode Params, BaseNode cv, BaseNode Ref, BaseNode attrs, BaseNode ret) : base(NodeType.NameType) + { + _name = name; + _params = Params; + _cv = cv; + _ref = Ref; + _attrs = attrs; + _ret = ret; + } + + public override void PrintLeft(TextWriter writer) + { + if (_ret != null) + { + _ret.PrintLeft(writer); + + if (!_ret.HasRightPart()) + { + writer.Write(" "); + } + } + + _name.Print(writer); + + } + + public override bool HasRightPart() + { + return true; + } + + public override void PrintRight(TextWriter writer) + { + writer.Write("("); + + if (_params != null) + { + _params.Print(writer); + } + + writer.Write(")"); + + if (_ret != null) + { + _ret.PrintRight(writer); + } + + if (_cv != null) + { + _cv.Print(writer); + } + + if (_ref != null) + { + _ref.Print(writer); + } + + if (_attrs != null) + { + _attrs.Print(writer); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/FoldExpression.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/FoldExpression.cs new file mode 100644 index 0000000000..04f7053e39 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/FoldExpression.cs @@ -0,0 +1,48 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class FoldExpression : BaseNode + { + private bool _isLeftFold; + private string _operatorName; + private BaseNode _expression; + private BaseNode _initializer; + + public FoldExpression(bool isLeftFold, string operatorName, BaseNode expression, BaseNode initializer) : base(NodeType.FunctionParameter) + { + _isLeftFold = isLeftFold; + _operatorName = operatorName; + _expression = expression; + _initializer = initializer; + } + + public override void PrintLeft(TextWriter writer) + { + writer.Write("("); + + if (_isLeftFold && _initializer != null) + { + _initializer.Print(writer); + writer.Write(" "); + writer.Write(_operatorName); + writer.Write(" "); + } + + writer.Write(_isLeftFold ? "... " : " "); + writer.Write(_operatorName); + writer.Write(!_isLeftFold ? " ..." : " "); + _expression.Print(writer); + + if (!_isLeftFold && _initializer != null) + { + _initializer.Print(writer); + writer.Write(" "); + writer.Write(_operatorName); + writer.Write(" "); + } + + writer.Write(")"); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ForwardTemplateReference.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ForwardTemplateReference.cs new file mode 100644 index 0000000000..1bbf6ef9a8 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ForwardTemplateReference.cs @@ -0,0 +1,36 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class ForwardTemplateReference : BaseNode + { + // TODO: Compute inside the Demangler + public BaseNode Reference; + private int _index; + + public ForwardTemplateReference(int index) : base(NodeType.ForwardTemplateReference) + { + _index = index; + } + + public override string GetName() + { + return Reference.GetName(); + } + + public override void PrintLeft(TextWriter writer) + { + Reference.PrintLeft(writer); + } + + public override void PrintRight(TextWriter writer) + { + Reference.PrintRight(writer); + } + + public override bool HasRightPart() + { + return Reference.HasRightPart(); + } + } +} diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/FunctionParameter.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/FunctionParameter.cs new file mode 100644 index 0000000000..5654a048f4 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/FunctionParameter.cs @@ -0,0 +1,24 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class FunctionParameter : BaseNode + { + private string _number; + + public FunctionParameter(string number) : base(NodeType.FunctionParameter) + { + _number = number; + } + + public override void PrintLeft(TextWriter writer) + { + writer.Write("fp "); + + if (_number != null) + { + writer.Write(_number); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/FunctionType.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/FunctionType.cs new file mode 100644 index 0000000000..4ad0c9f5fc --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/FunctionType.cs @@ -0,0 +1,61 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class FunctionType : BaseNode + { + private BaseNode _returnType; + private BaseNode _params; + private BaseNode _cvQualifier; + private SimpleReferenceType _referenceQualifier; + private BaseNode _exceptionSpec; + + public FunctionType(BaseNode returnType, BaseNode Params, BaseNode cvQualifier, SimpleReferenceType referenceQualifier, BaseNode exceptionSpec) : base(NodeType.FunctionType) + { + _returnType = returnType; + _params = Params; + _cvQualifier = cvQualifier; + _referenceQualifier = referenceQualifier; + _exceptionSpec = exceptionSpec; + } + + public override void PrintLeft(TextWriter writer) + { + _returnType.PrintLeft(writer); + writer.Write(" "); + } + + public override void PrintRight(TextWriter writer) + { + writer.Write("("); + _params.Print(writer); + writer.Write(")"); + + _returnType.PrintRight(writer); + + _cvQualifier.Print(writer); + + if (_referenceQualifier.Qualifier != Reference.None) + { + writer.Write(" "); + _referenceQualifier.PrintQualifier(writer); + } + + if (_exceptionSpec != null) + { + writer.Write(" "); + _exceptionSpec.Print(writer); + } + } + + public override bool HasRightPart() + { + return true; + } + + public override bool HasFunctions() + { + return true; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/GlobalQualifiedName.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/GlobalQualifiedName.cs new file mode 100644 index 0000000000..d3b6a558fe --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/GlobalQualifiedName.cs @@ -0,0 +1,15 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class GlobalQualifiedName : ParentNode + { + public GlobalQualifiedName(BaseNode child) : base(NodeType.GlobalQualifiedName, child) { } + + public override void PrintLeft(TextWriter writer) + { + writer.Write("::"); + Child.Print(writer); + } + } +} diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/InitListExpression.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/InitListExpression.cs new file mode 100644 index 0000000000..7155dd601e --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/InitListExpression.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class InitListExpression : BaseNode + { + private BaseNode _typeNode; + private List _nodes; + + public InitListExpression(BaseNode typeNode, List nodes) : base(NodeType.InitListExpression) + { + _typeNode = typeNode; + _nodes = nodes; + } + + public override void PrintLeft(TextWriter writer) + { + if (_typeNode != null) + { + _typeNode.Print(writer); + } + + writer.Write("{"); + writer.Write(string.Join(", ", _nodes.ToArray())); + writer.Write("}"); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/IntegerCastExpression.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/IntegerCastExpression.cs new file mode 100644 index 0000000000..ef07414dec --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/IntegerCastExpression.cs @@ -0,0 +1,22 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class IntegerCastExpression : ParentNode + { + private string _number; + + public IntegerCastExpression(BaseNode type, string number) : base(NodeType.IntegerCastExpression, type) + { + _number = number; + } + + public override void PrintLeft(TextWriter writer) + { + writer.Write("("); + Child.Print(writer); + writer.Write(")"); + writer.Write(_number); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/IntegerLiteral.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/IntegerLiteral.cs new file mode 100644 index 0000000000..951faa5549 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/IntegerLiteral.cs @@ -0,0 +1,41 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class IntegerLiteral : BaseNode + { + private string _literalName; + private string _literalValue; + + public IntegerLiteral(string literalName, string literalValue) : base(NodeType.IntegerLiteral) + { + _literalValue = literalValue; + _literalName = literalName; + } + + public override void PrintLeft(TextWriter writer) + { + if (_literalName.Length > 3) + { + writer.Write("("); + writer.Write(_literalName); + writer.Write(")"); + } + + if (_literalValue[0] == 'n') + { + writer.Write("-"); + writer.Write(_literalValue.Substring(1)); + } + else + { + writer.Write(_literalValue); + } + + if (_literalName.Length <= 3) + { + writer.Write(_literalName); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/LiteralOperator.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/LiteralOperator.cs new file mode 100644 index 0000000000..f7e86c9e2a --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/LiteralOperator.cs @@ -0,0 +1,16 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class LiteralOperator : ParentNode + { + public LiteralOperator(BaseNode child) : base(NodeType.LiteralOperator, child) { } + + public override void PrintLeft(TextWriter writer) + { + writer.Write("operator \""); + Child.PrintLeft(writer); + writer.Write("\""); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/LocalName.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/LocalName.cs new file mode 100644 index 0000000000..15d46b387d --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/LocalName.cs @@ -0,0 +1,23 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class LocalName : BaseNode + { + private BaseNode _encoding; + private BaseNode _entity; + + public LocalName(BaseNode encoding, BaseNode entity) : base(NodeType.LocalName) + { + _encoding = encoding; + _entity = entity; + } + + public override void PrintLeft(TextWriter writer) + { + _encoding.Print(writer); + writer.Write("::"); + _entity.Print(writer); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/MemberExpression.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/MemberExpression.cs new file mode 100644 index 0000000000..9b91f6f5d7 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/MemberExpression.cs @@ -0,0 +1,25 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class MemberExpression : BaseNode + { + private BaseNode _leftNode; + private string _kind; + private BaseNode _rightNode; + + public MemberExpression(BaseNode leftNode, string kind, BaseNode rightNode) : base(NodeType.MemberExpression) + { + _leftNode = leftNode; + _kind = kind; + _rightNode = rightNode; + } + + public override void PrintLeft(TextWriter writer) + { + _leftNode.Print(writer); + writer.Write(_kind); + _rightNode.Print(writer); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NameType.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NameType.cs new file mode 100644 index 0000000000..f9f4cb20ee --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NameType.cs @@ -0,0 +1,29 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class NameType : BaseNode + { + private string _nameValue; + + public NameType(string nameValue, NodeType type) : base(type) + { + _nameValue = nameValue; + } + + public NameType(string nameValue) : base(NodeType.NameType) + { + _nameValue = nameValue; + } + + public override string GetName() + { + return _nameValue; + } + + public override void PrintLeft(TextWriter writer) + { + writer.Write(_nameValue); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NameTypeWithTemplateArguments.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NameTypeWithTemplateArguments.cs new file mode 100644 index 0000000000..ee725f36b6 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NameTypeWithTemplateArguments.cs @@ -0,0 +1,27 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class NameTypeWithTemplateArguments : BaseNode + { + private BaseNode _prev; + private BaseNode _templateArgument; + + public NameTypeWithTemplateArguments(BaseNode prev, BaseNode templateArgument) : base(NodeType.NameTypeWithTemplateArguments) + { + _prev = prev; + _templateArgument = templateArgument; + } + + public override string GetName() + { + return _prev.GetName(); + } + + public override void PrintLeft(TextWriter writer) + { + _prev.Print(writer); + _templateArgument.Print(writer); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NestedName.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NestedName.cs new file mode 100644 index 0000000000..640c200cbb --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NestedName.cs @@ -0,0 +1,26 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class NestedName : ParentNode + { + private BaseNode _name; + + public NestedName(BaseNode name, BaseNode type) : base(NodeType.NestedName, type) + { + _name = name; + } + + public override string GetName() + { + return _name.GetName(); + } + + public override void PrintLeft(TextWriter writer) + { + Child.Print(writer); + writer.Write("::"); + _name.Print(writer); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NewExpression.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NewExpression.cs new file mode 100644 index 0000000000..ba4690af4d --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NewExpression.cs @@ -0,0 +1,55 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class NewExpression : BaseNode + { + private NodeArray _expressions; + private BaseNode _typeNode; + private NodeArray _initializers; + + private bool _isGlobal; + private bool _isArrayExpression; + + public NewExpression(NodeArray expressions, BaseNode typeNode, NodeArray initializers, bool isGlobal, bool isArrayExpression) : base(NodeType.NewExpression) + { + _expressions = expressions; + _typeNode = typeNode; + _initializers = initializers; + + _isGlobal = isGlobal; + _isArrayExpression = isArrayExpression; + } + + public override void PrintLeft(TextWriter writer) + { + if (_isGlobal) + { + writer.Write("::operator "); + } + + writer.Write("new "); + + if (_isArrayExpression) + { + writer.Write("[] "); + } + + if (_expressions.Nodes.Count != 0) + { + writer.Write("("); + _expressions.Print(writer); + writer.Write(")"); + } + + _typeNode.Print(writer); + + if (_initializers.Nodes.Count != 0) + { + writer.Write("("); + _initializers.Print(writer); + writer.Write(")"); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NodeArray.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NodeArray.cs new file mode 100644 index 0000000000..1482dfc377 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NodeArray.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class NodeArray : BaseNode + { + public List Nodes { get; protected set; } + + public NodeArray(List nodes) : base(NodeType.NodeArray) + { + Nodes = nodes; + } + + public NodeArray(List nodes, NodeType type) : base(type) + { + Nodes = nodes; + } + + public override bool IsArray() + { + return true; + } + + public override void PrintLeft(TextWriter writer) + { + writer.Write(string.Join(", ", Nodes.ToArray())); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NoexceptSpec.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NoexceptSpec.cs new file mode 100644 index 0000000000..49044493ed --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NoexceptSpec.cs @@ -0,0 +1,16 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class NoexceptSpec : ParentNode + { + public NoexceptSpec(BaseNode child) : base(NodeType.NoexceptSpec, child) { } + + public override void PrintLeft(TextWriter writer) + { + writer.Write("noexcept("); + Child.Print(writer); + writer.Write(")"); + } + } +} diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PackedTemplateParameter.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PackedTemplateParameter.cs new file mode 100644 index 0000000000..4c82009588 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PackedTemplateParameter.cs @@ -0,0 +1,39 @@ +using System.Collections.Generic; +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class PackedTemplateParameter : NodeArray + { + public PackedTemplateParameter(List nodes) : base(nodes, NodeType.PackedTemplateParameter) { } + + public override void PrintLeft(TextWriter writer) + { + foreach (BaseNode node in Nodes) + { + node.PrintLeft(writer); + } + } + + public override void PrintRight(TextWriter writer) + { + foreach (BaseNode node in Nodes) + { + node.PrintLeft(writer); + } + } + + public override bool HasRightPart() + { + foreach (BaseNode node in Nodes) + { + if (node.HasRightPart()) + { + return true; + } + } + + return false; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PackedTemplateParameterExpansion.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PackedTemplateParameterExpansion.cs new file mode 100644 index 0000000000..c3645044ab --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PackedTemplateParameterExpansion.cs @@ -0,0 +1,24 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class PackedTemplateParameterExpansion : ParentNode + { + public PackedTemplateParameterExpansion(BaseNode child) : base(NodeType.PackedTemplateParameterExpansion, child) {} + + public override void PrintLeft(TextWriter writer) + { + if (Child is PackedTemplateParameter) + { + if (((PackedTemplateParameter)Child).Nodes.Count != 0) + { + Child.Print(writer); + } + } + else + { + writer.Write("..."); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ParentNode.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ParentNode.cs new file mode 100644 index 0000000000..786abced8d --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ParentNode.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public abstract class ParentNode : BaseNode + { + public BaseNode Child { get; private set; } + + public ParentNode(NodeType type, BaseNode child) : base(type) + { + Child = child; + } + + public override string GetName() + { + return Child.GetName(); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PointerType.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PointerType.cs new file mode 100644 index 0000000000..b1a3ec422e --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PointerType.cs @@ -0,0 +1,45 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class PointerType : BaseNode + { + private BaseNode _child; + + public PointerType(BaseNode child) : base(NodeType.PointerType) + { + _child = child; + } + + public override bool HasRightPart() + { + return _child.HasRightPart(); + } + + public override void PrintLeft(TextWriter writer) + { + _child.PrintLeft(writer); + if (_child.IsArray()) + { + writer.Write(" "); + } + + if (_child.IsArray() || _child.HasFunctions()) + { + writer.Write("("); + } + + writer.Write("*"); + } + + public override void PrintRight(TextWriter writer) + { + if (_child.IsArray() || _child.HasFunctions()) + { + writer.Write(")"); + } + + _child.PrintRight(writer); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PostfixExpression.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PostfixExpression.cs new file mode 100644 index 0000000000..ccaea3ba7f --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PostfixExpression.cs @@ -0,0 +1,22 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class PostfixExpression : ParentNode + { + private string _operator; + + public PostfixExpression(BaseNode type, string Operator) : base(NodeType.PostfixExpression, type) + { + _operator = Operator; + } + + public override void PrintLeft(TextWriter writer) + { + writer.Write("("); + Child.Print(writer); + writer.Write(")"); + writer.Write(_operator); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PostfixQualifiedType.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PostfixQualifiedType.cs new file mode 100644 index 0000000000..5024a8f99c --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PostfixQualifiedType.cs @@ -0,0 +1,20 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class PostfixQualifiedType : ParentNode + { + private string _postfixQualifier; + + public PostfixQualifiedType(string postfixQualifier, BaseNode type) : base(NodeType.PostfixQualifiedType, type) + { + _postfixQualifier = postfixQualifier; + } + + public override void PrintLeft(TextWriter writer) + { + Child.Print(writer); + writer.Write(_postfixQualifier); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PrefixExpression.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PrefixExpression.cs new file mode 100644 index 0000000000..9c3d4552af --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PrefixExpression.cs @@ -0,0 +1,22 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class PrefixExpression : ParentNode + { + private string _prefix; + + public PrefixExpression(string prefix, BaseNode child) : base(NodeType.PrefixExpression, child) + { + _prefix = prefix; + } + + public override void PrintLeft(TextWriter writer) + { + writer.Write(_prefix); + writer.Write("("); + Child.Print(writer); + writer.Write(")"); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/QualifiedName.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/QualifiedName.cs new file mode 100644 index 0000000000..2e18f564e2 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/QualifiedName.cs @@ -0,0 +1,23 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class QualifiedName : BaseNode + { + private BaseNode _qualifier; + private BaseNode _name; + + public QualifiedName(BaseNode qualifier, BaseNode name) : base(NodeType.QualifiedName) + { + _qualifier = qualifier; + _name = name; + } + + public override void PrintLeft(TextWriter writer) + { + _qualifier.Print(writer); + writer.Write("::"); + _name.Print(writer); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/Qualifier.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/Qualifier.cs new file mode 100644 index 0000000000..cb6dd6bf66 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/Qualifier.cs @@ -0,0 +1,120 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public enum Cv + { + None, + Const, + Volatile, + Restricted = 4 + } + + public enum Reference + { + None, + RValue, + LValue + } + + public class CvType : ParentNode + { + public Cv Qualifier; + + public CvType(Cv qualifier, BaseNode child) : base(NodeType.CvQualifierType, child) + { + Qualifier = qualifier; + } + + public void PrintQualifier(TextWriter writer) + { + if ((Qualifier & Cv.Const) != 0) + { + writer.Write(" const"); + } + + if ((Qualifier & Cv.Volatile) != 0) + { + writer.Write(" volatile"); + } + + if ((Qualifier & Cv.Restricted) != 0) + { + writer.Write(" restrict"); + } + } + + public override void PrintLeft(TextWriter writer) + { + if (Child != null) + { + Child.PrintLeft(writer); + } + + PrintQualifier(writer); + } + + public override bool HasRightPart() + { + return Child != null && Child.HasRightPart(); + } + + public override void PrintRight(TextWriter writer) + { + if (Child != null) + { + Child.PrintRight(writer); + } + } + } + + public class SimpleReferenceType : ParentNode + { + public Reference Qualifier; + + public SimpleReferenceType(Reference qualifier, BaseNode child) : base(NodeType.SimpleReferenceType, child) + { + Qualifier = qualifier; + } + + public void PrintQualifier(TextWriter writer) + { + if ((Qualifier & Reference.LValue) != 0) + { + writer.Write("&"); + } + + if ((Qualifier & Reference.RValue) != 0) + { + writer.Write("&&"); + } + } + + public override void PrintLeft(TextWriter writer) + { + if (Child != null) + { + Child.PrintLeft(writer); + } + else if (Qualifier != Reference.None) + { + writer.Write(" "); + } + + PrintQualifier(writer); + } + + public override bool HasRightPart() + { + return Child != null && Child.HasRightPart(); + } + + public override void PrintRight(TextWriter writer) + { + if (Child != null) + { + Child.PrintRight(writer); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ReferenceType.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ReferenceType.cs new file mode 100644 index 0000000000..a3214171f5 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ReferenceType.cs @@ -0,0 +1,47 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class ReferenceType : BaseNode + { + private string _reference; + private BaseNode _child; + + public ReferenceType(string reference, BaseNode child) : base(NodeType.ReferenceType) + { + _reference = reference; + _child = child; + } + + public override bool HasRightPart() + { + return _child.HasRightPart(); + } + + public override void PrintLeft(TextWriter writer) + { + _child.PrintLeft(writer); + + if (_child.IsArray()) + { + writer.Write(" "); + } + + if (_child.IsArray() || _child.HasFunctions()) + { + writer.Write("("); + } + + writer.Write(_reference); + } + public override void PrintRight(TextWriter writer) + { + if (_child.IsArray() || _child.HasFunctions()) + { + writer.Write(")"); + } + + _child.PrintRight(writer); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/SpecialName.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/SpecialName.cs new file mode 100644 index 0000000000..1447458b3d --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/SpecialName.cs @@ -0,0 +1,20 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class SpecialName : ParentNode + { + private string _specialValue; + + public SpecialName(string specialValue, BaseNode type) : base(NodeType.SpecialName, type) + { + _specialValue = specialValue; + } + + public override void PrintLeft(TextWriter writer) + { + writer.Write(_specialValue); + Child.Print(writer); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/SpecialSubstitution.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/SpecialSubstitution.cs new file mode 100644 index 0000000000..8d45e180b7 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/SpecialSubstitution.cs @@ -0,0 +1,89 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class SpecialSubstitution : BaseNode + { + public enum SpecialType + { + Allocator, + BasicString, + String, + IStream, + OStream, + IOStream + } + + private SpecialType _specialSubstitutionKey; + + public SpecialSubstitution(SpecialType specialSubstitutionKey) : base(NodeType.SpecialSubstitution) + { + _specialSubstitutionKey = specialSubstitutionKey; + } + + public void SetExtended() + { + Type = NodeType.ExpandedSpecialSubstitution; + } + + public override string GetName() + { + switch (_specialSubstitutionKey) + { + case SpecialType.Allocator: + return "allocator"; + case SpecialType.BasicString: + return "basic_string"; + case SpecialType.String: + if (Type == NodeType.ExpandedSpecialSubstitution) + { + return "basic_string"; + } + + return "string"; + case SpecialType.IStream: + return "istream"; + case SpecialType.OStream: + return "ostream"; + case SpecialType.IOStream: + return "iostream"; + } + + return null; + } + + private string GetExtendedName() + { + switch (_specialSubstitutionKey) + { + case SpecialType.Allocator: + return "std::allocator"; + case SpecialType.BasicString: + return "std::basic_string"; + case SpecialType.String: + return "std::basic_string, std::allocator >"; + case SpecialType.IStream: + return "std::basic_istream >"; + case SpecialType.OStream: + return "std::basic_ostream >"; + case SpecialType.IOStream: + return "std::basic_iostream >"; + } + + return null; + } + + public override void PrintLeft(TextWriter writer) + { + if (Type == NodeType.ExpandedSpecialSubstitution) + { + writer.Write(GetExtendedName()); + } + else + { + writer.Write("std::"); + writer.Write(GetName()); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/StdQualifiedName.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/StdQualifiedName.cs new file mode 100644 index 0000000000..c3a97d60a3 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/StdQualifiedName.cs @@ -0,0 +1,15 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class StdQualifiedName : ParentNode + { + public StdQualifiedName(BaseNode child) : base(NodeType.StdQualifiedName, child) { } + + public override void PrintLeft(TextWriter writer) + { + writer.Write("std::"); + Child.Print(writer); + } + } +} diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/TemplateArguments.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/TemplateArguments.cs new file mode 100644 index 0000000000..aefd668de7 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/TemplateArguments.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class TemplateArguments : NodeArray + { + public TemplateArguments(List nodes) : base(nodes, NodeType.TemplateArguments) { } + + public override void PrintLeft(TextWriter writer) + { + string Params = string.Join(", ", Nodes.ToArray()); + + writer.Write("<"); + + writer.Write(Params); + + if (Params.EndsWith(">")) + { + writer.Write(" "); + } + + writer.Write(">"); + } + } +} diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ThrowExpression.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ThrowExpression.cs new file mode 100644 index 0000000000..2972a31c26 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ThrowExpression.cs @@ -0,0 +1,20 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class ThrowExpression : BaseNode + { + private BaseNode _expression; + + public ThrowExpression(BaseNode expression) : base(NodeType.ThrowExpression) + { + _expression = expression; + } + + public override void PrintLeft(TextWriter writer) + { + writer.Write("throw "); + _expression.Print(writer); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Demangler.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Demangler.cs new file mode 100644 index 0000000000..1e62112114 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Demangler.cs @@ -0,0 +1,3367 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler +{ + class Demangler + { + private static readonly string Base36 = "0123456789abcdefghijklmnopqrstuvwxyz"; + private List _substitutionList = new List(); + private List _templateParamList = new List(); + + private List _forwardTemplateReferenceList = new List(); + + public string Mangled { get; private set; } + + private int _position; + private int _length; + + private bool _canForwardTemplateReference; + private bool _canParseTemplateArgs; + + public Demangler(string mangled) + { + Mangled = mangled; + _position = 0; + _length = mangled.Length; + _canParseTemplateArgs = true; + } + + private bool ConsumeIf(string toConsume) + { + string mangledPart = Mangled.Substring(_position); + + if (mangledPart.StartsWith(toConsume)) + { + _position += toConsume.Length; + + return true; + } + + return false; + } + + private string PeekString(int offset = 0, int length = 1) + { + if (_position + offset >= length) + { + return null; + } + + return Mangled.Substring(_position + offset, length); + } + + private char Peek(int offset = 0) + { + if (_position + offset >= _length) + { + return '\0'; + } + + return Mangled[_position + offset]; + } + + private char Consume() + { + if (_position < _length) + { + return Mangled[_position++]; + } + + return '\0'; + } + + private int Count() + { + return _length - _position; + } + + private static int FromBase36(string encoded) + { + char[] reversedEncoded = encoded.ToLower().ToCharArray().Reverse().ToArray(); + + int result = 0; + + for (int i = 0; i < reversedEncoded.Length; i++) + { + int value = Base36.IndexOf(reversedEncoded[i]); + if (value == -1) + { + return -1; + } + + result += value * (int)Math.Pow(36, i); + } + + return result; + } + + private int ParseSeqId() + { + string part = Mangled.Substring(_position); + int seqIdLen = 0; + + for (; seqIdLen < part.Length; seqIdLen++) + { + if (!char.IsLetterOrDigit(part[seqIdLen])) + { + break; + } + } + + _position += seqIdLen; + + return FromBase36(part.Substring(0, seqIdLen)); + } + + // ::= S _ + // ::= S_ + // ::= St # std:: + // ::= Sa # std::allocator + // ::= Sb # std::basic_string + // ::= Ss # std::basic_string, std::allocator > + // ::= Si # std::basic_istream > + // ::= So # std::basic_ostream > + // ::= Sd # std::basic_iostream > + private BaseNode ParseSubstitution() + { + if (!ConsumeIf("S")) + { + return null; + } + + char substitutionSecondChar = Peek(); + if (char.IsLower(substitutionSecondChar)) + { + switch (substitutionSecondChar) + { + case 'a': + _position++; + return new SpecialSubstitution(SpecialSubstitution.SpecialType.Allocator); + case 'b': + _position++; + return new SpecialSubstitution(SpecialSubstitution.SpecialType.BasicString); + case 's': + _position++; + return new SpecialSubstitution(SpecialSubstitution.SpecialType.String); + case 'i': + _position++; + return new SpecialSubstitution(SpecialSubstitution.SpecialType.IStream); + case 'o': + _position++; + return new SpecialSubstitution(SpecialSubstitution.SpecialType.OStream); + case 'd': + _position++; + return new SpecialSubstitution(SpecialSubstitution.SpecialType.IOStream); + default: + return null; + } + } + + // ::= S_ + if (ConsumeIf("_")) + { + if (_substitutionList.Count != 0) + { + return _substitutionList[0]; + } + + return null; + } + + // ::= S _ + int seqId = ParseSeqId(); + if (seqId < 0) + { + return null; + } + + seqId++; + + if (!ConsumeIf("_") || seqId >= _substitutionList.Count) + { + return null; + } + + return _substitutionList[seqId]; + } + + // NOTE: thoses data aren't used in the output + // ::= h _ + // ::= v _ + // ::= + // # non-virtual base override + // ::= _ + // # virtual base override, with vcall offset + private bool ParseCallOffset() + { + if (ConsumeIf("h")) + { + return ParseNumber(true).Length == 0 || !ConsumeIf("_"); + } + else if (ConsumeIf("v")) + { + return ParseNumber(true).Length == 0 || !ConsumeIf("_") || ParseNumber(true).Length == 0 || !ConsumeIf("_"); + } + + return true; + } + + + // ::= # non-dependent type name, dependent type name, or dependent typename-specifier + // ::= Ts # dependent elaborated type specifier using 'struct' or 'class' + // ::= Tu # dependent elaborated type specifier using 'union' + // ::= Te # dependent elaborated type specifier using 'enum' + private BaseNode ParseClassEnumType() + { + string elaboratedType = null; + + if (ConsumeIf("Ts")) + { + elaboratedType = "struct"; + } + else if (ConsumeIf("Tu")) + { + elaboratedType = "union"; + } + else if (ConsumeIf("Te")) + { + elaboratedType = "enum"; + } + + BaseNode name = ParseName(); + if (name == null) + { + return null; + } + + if (elaboratedType == null) + { + return name; + } + + return new ElaboratedType(elaboratedType, name); + } + + // ::= [] [] [Dx] F [Y] [] E + // ::= + + // # types are possible return type, then parameter types + // ::= Do # non-throwing exception-specification (e.g., noexcept, throw()) + // ::= DO E # computed (instantiation-dependent) noexcept + // ::= Dw + E # dynamic exception specification with instantiation-dependent types + private BaseNode ParseFunctionType() + { + Cv cvQualifiers = ParseCvQualifiers(); + + BaseNode exceptionSpec = null; + + if (ConsumeIf("Do")) + { + exceptionSpec = new NameType("noexcept"); + } + else if (ConsumeIf("DO")) + { + BaseNode expression = ParseExpression(); + if (expression == null || !ConsumeIf("E")) + { + return null; + } + + exceptionSpec = new NoexceptSpec(expression); + } + else if (ConsumeIf("Dw")) + { + List types = new List(); + + while (!ConsumeIf("E")) + { + BaseNode type = ParseType(); + if (type == null) + { + return null; + } + + types.Add(type); + } + + exceptionSpec = new DynamicExceptionSpec(new NodeArray(types)); + } + + // We don't need the transaction + ConsumeIf("Dx"); + + if (!ConsumeIf("F")) + { + return null; + } + + // extern "C" + ConsumeIf("Y"); + + BaseNode returnType = ParseType(); + if (returnType == null) + { + return null; + } + + Reference referenceQualifier = Reference.None; + List Params = new List(); + + while (true) + { + if (ConsumeIf("E")) + { + break; + } + + if (ConsumeIf("v")) + { + continue; + } + + if (ConsumeIf("RE")) + { + referenceQualifier = Reference.LValue; + break; + } + else if (ConsumeIf("OE")) + { + referenceQualifier = Reference.RValue; + break; + } + + BaseNode type = ParseType(); + if (type == null) + { + return null; + } + + Params.Add(type); + } + + return new FunctionType(returnType, new NodeArray(Params), new CvType(cvQualifiers, null), new SimpleReferenceType(referenceQualifier, null), exceptionSpec); + } + + // ::= A _ + // ::= A [] _ + private BaseNode ParseArrayType() + { + if (!ConsumeIf("A")) + { + return null; + } + + BaseNode elementType; + if (char.IsDigit(Peek())) + { + string dimension = ParseNumber(); + if (dimension.Length == 0 || !ConsumeIf("_")) + { + return null; + } + + elementType = ParseType(); + if (elementType == null) + { + return null; + } + + return new ArrayType(elementType, dimension); + } + + if (!ConsumeIf("_")) + { + BaseNode dimensionExpression = ParseExpression(); + if (dimensionExpression == null || !ConsumeIf("_")) + { + return null; + } + + elementType = ParseType(); + if (elementType == null) + { + return null; + } + + return new ArrayType(elementType, dimensionExpression); + } + + elementType = ParseType(); + if (elementType == null) + { + return null; + } + + return new ArrayType(elementType); + } + + // ::= + // ::= (PARTIAL) + // ::= + // ::= + // ::= (TODO) + // ::= (TODO) + // ::= + // ::= + // ::= + // ::= P # pointer + // ::= R # l-value reference + // ::= O # r-value reference (C++11) + // ::= C # complex pair (C99) + // ::= G # imaginary (C99) + // ::= # See Compression below + private BaseNode ParseType(NameParserContext context = null) + { + // Temporary context + if (context == null) + { + context = new NameParserContext(); + } + + BaseNode result = null; + switch (Peek()) + { + case 'r': + case 'V': + case 'K': + int typePos = 0; + + if (Peek(typePos) == 'r') + { + typePos++; + } + + if (Peek(typePos) == 'V') + { + typePos++; + } + + if (Peek(typePos) == 'K') + { + typePos++; + } + + if (Peek(typePos) == 'F' || (Peek(typePos) == 'D' && (Peek(typePos + 1) == 'o' || Peek(typePos + 1) == 'O' || Peek(typePos + 1) == 'w' || Peek(typePos + 1) == 'x'))) + { + result = ParseFunctionType(); + break; + } + + Cv cv = ParseCvQualifiers(); + + result = ParseType(context); + + if (result == null) + { + return null; + } + + result = new CvType(cv, result); + break; + case 'U': + // TODO: + return null; + case 'v': + _position++; + return new NameType("void"); + case 'w': + _position++; + return new NameType("wchar_t"); + case 'b': + _position++; + return new NameType("bool"); + case 'c': + _position++; + return new NameType("char"); + case 'a': + _position++; + return new NameType("signed char"); + case 'h': + _position++; + return new NameType("unsigned char"); + case 's': + _position++; + return new NameType("short"); + case 't': + _position++; + return new NameType("unsigned short"); + case 'i': + _position++; + return new NameType("int"); + case 'j': + _position++; + return new NameType("unsigned int"); + case 'l': + _position++; + return new NameType("long"); + case 'm': + _position++; + return new NameType("unsigned long"); + case 'x': + _position++; + return new NameType("long long"); + case 'y': + _position++; + return new NameType("unsigned long long"); + case 'n': + _position++; + return new NameType("__int128"); + case 'o': + _position++; + return new NameType("unsigned __int128"); + case 'f': + _position++; + return new NameType("float"); + case 'd': + _position++; + return new NameType("double"); + case 'e': + _position++; + return new NameType("long double"); + case 'g': + _position++; + return new NameType("__float128"); + case 'z': + _position++; + return new NameType("..."); + case 'u': + _position++; + return ParseSourceName(); + case 'D': + switch (Peek(1)) + { + case 'd': + _position += 2; + return new NameType("decimal64"); + case 'e': + _position += 2; + return new NameType("decimal128"); + case 'f': + _position += 2; + return new NameType("decimal32"); + case 'h': + _position += 2; + // FIXME: GNU c++flit returns this but that is not what is supposed to be returned. + return new NameType("half"); + // return new NameType("decimal16"); + case 'i': + _position += 2; + return new NameType("char32_t"); + case 's': + _position += 2; + return new NameType("char16_t"); + case 'a': + _position += 2; + return new NameType("decltype(auto)"); + case 'n': + _position += 2; + // FIXME: GNU c++flit returns this but that is not what is supposed to be returned. + return new NameType("decltype(nullptr)"); + // return new NameType("std::nullptr_t"); + case 't': + case 'T': + _position += 2; + result = ParseDecltype(); + break; + case 'o': + case 'O': + case 'w': + case 'x': + result = ParseFunctionType(); + break; + default: + return null; + } + break; + case 'F': + result = ParseFunctionType(); + break; + case 'A': + return ParseArrayType(); + case 'M': + // TODO: + _position++; + return null; + case 'T': + // might just be a class enum type + if (Peek(1) == 's' || Peek(1) == 'u' || Peek(1) == 'e') + { + result = ParseClassEnumType(); + break; + } + + result = ParseTemplateParam(); + if (result == null) + { + return null; + } + + if (_canParseTemplateArgs && Peek() == 'I') + { + BaseNode templateArguments = ParseTemplateArguments(); + if (templateArguments == null) + { + return null; + } + + result = new NameTypeWithTemplateArguments(result, templateArguments); + } + break; + case 'P': + _position++; + result = ParseType(context); + + if (result == null) + { + return null; + } + + result = new PointerType(result); + break; + case 'R': + _position++; + result = ParseType(context); + + if (result == null) + { + return null; + } + + result = new ReferenceType("&", result); + break; + case 'O': + _position++; + result = ParseType(context); + + if (result == null) + { + return null; + } + + result = new ReferenceType("&&", result); + break; + case 'C': + _position++; + result = ParseType(context); + + if (result == null) + { + return null; + } + + result = new PostfixQualifiedType(" complex", result); + break; + case 'G': + _position++; + result = ParseType(context); + + if (result == null) + { + return null; + } + + result = new PostfixQualifiedType(" imaginary", result); + break; + case 'S': + if (Peek(1) != 't') + { + BaseNode substitution = ParseSubstitution(); + if (substitution == null) + { + return null; + } + + if (_canParseTemplateArgs && Peek() == 'I') + { + BaseNode templateArgument = ParseTemplateArgument(); + if (templateArgument == null) + { + return null; + } + + result = new NameTypeWithTemplateArguments(substitution, templateArgument); + break; + } + return substitution; + } + else + { + result = ParseClassEnumType(); + break; + } + default: + result = ParseClassEnumType(); + break; + } + if (result != null) + { + _substitutionList.Add(result); + } + + return result; + } + + // ::= TV # virtual table + // ::= TT # VTT structure (construction vtable index) + // ::= TI # typeinfo structure + // ::= TS # typeinfo name (null-terminated byte string) + // ::= Tc + // ::= TW # Thread-local wrapper + // ::= TH # Thread-local initialization + // ::= T + // # base is the nominal target function of thunk + // ::= GV # Guard variable for one-time initialization + private BaseNode ParseSpecialName(NameParserContext context = null) + { + if (Peek() != 'T') + { + if (ConsumeIf("GV")) + { + BaseNode name = ParseName(); + if (name == null) + { + return null; + } + + return new SpecialName("guard variable for ", name); + } + return null; + } + + BaseNode node; + switch (Peek(1)) + { + // ::= TV # virtual table + case 'V': + _position += 2; + node = ParseType(context); + if (node == null) + { + return null; + } + + return new SpecialName("vtable for ", node); + // ::= TT # VTT structure (construction vtable index) + case 'T': + _position += 2; + node = ParseType(context); + if (node == null) + { + return null; + } + + return new SpecialName("VTT for ", node); + // ::= TI # typeinfo structure + case 'I': + _position += 2; + node = ParseType(context); + if (node == null) + { + return null; + } + + return new SpecialName("typeinfo for ", node); + // ::= TS # typeinfo name (null-terminated byte string) + case 'S': + _position += 2; + node = ParseType(context); + if (node == null) + { + return null; + } + + return new SpecialName("typeinfo name for ", node); + // ::= Tc + case 'c': + _position += 2; + if (ParseCallOffset() || ParseCallOffset()) + { + return null; + } + + node = ParseEncoding(); + if (node == null) + { + return null; + } + + return new SpecialName("covariant return thunk to ", node); + // extension ::= TC _ + case 'C': + _position += 2; + BaseNode firstType = ParseType(); + if (firstType == null || ParseNumber(true).Length == 0 || !ConsumeIf("_")) + { + return null; + } + + BaseNode secondType = ParseType(); + + return new CtorVtableSpecialName(secondType, firstType); + // ::= TH # Thread-local initialization + case 'H': + _position += 2; + node = ParseName(); + if (node == null) + { + return null; + } + + return new SpecialName("thread-local initialization routine for ", node); + // ::= TW # Thread-local wrapper + case 'W': + _position += 2; + node = ParseName(); + if (node == null) + { + return null; + } + + return new SpecialName("thread-local wrapper routine for ", node); + default: + _position++; + bool isVirtual = Peek() == 'v'; + if (ParseCallOffset()) + { + return null; + } + + node = ParseEncoding(); + if (node == null) + { + return null; + } + + if (isVirtual) + { + return new SpecialName("virtual thunk to ", node); + } + + return new SpecialName("non-virtual thunk to ", node); + } + } + + // ::= [r] [V] [K] # restrict (C99), volatile, const + private Cv ParseCvQualifiers() + { + Cv qualifiers = Cv.None; + + if (ConsumeIf("r")) + { + qualifiers |= Cv.Restricted; + } + if (ConsumeIf("V")) + { + qualifiers |= Cv.Volatile; + } + if (ConsumeIf("K")) + { + qualifiers |= Cv.Const; + } + + return qualifiers; + } + + + // ::= R # & ref-qualifier + // ::= O # && ref-qualifier + private SimpleReferenceType ParseRefQualifiers() + { + Reference result = Reference.None; + if (ConsumeIf("O")) + { + result = Reference.RValue; + } + else if (ConsumeIf("R")) + { + result = Reference.LValue; + } + return new SimpleReferenceType(result, null); + } + + private BaseNode CreateNameNode(BaseNode prev, BaseNode name, NameParserContext context) + { + BaseNode result = name; + if (prev != null) + { + result = new NestedName(name, prev); + } + + if (context != null) + { + context.FinishWithTemplateArguments = false; + } + + return result; + } + + private int ParsePositiveNumber() + { + string part = Mangled.Substring(_position); + int numberLength = 0; + + for (; numberLength < part.Length; numberLength++) + { + if (!char.IsDigit(part[numberLength])) + { + break; + } + } + + _position += numberLength; + + if (numberLength == 0) + { + return -1; + } + + return int.Parse(part.Substring(0, numberLength)); + } + + private string ParseNumber(bool isSigned = false) + { + if (isSigned) + { + ConsumeIf("n"); + } + + if (Count() == 0 || !char.IsDigit(Mangled[_position])) + { + return null; + } + + string part = Mangled.Substring(_position); + int numberLength = 0; + + for (; numberLength < part.Length; numberLength++) + { + if (!char.IsDigit(part[numberLength])) + { + break; + } + } + + _position += numberLength; + + return part.Substring(0, numberLength); + } + + // ::= + private BaseNode ParseSourceName() + { + int length = ParsePositiveNumber(); + if (Count() < length || length <= 0) + { + return null; + } + + string name = Mangled.Substring(_position, length); + _position += length; + if (name.StartsWith("_GLOBAL__N")) + { + return new NameType("(anonymous namespace)"); + } + + return new NameType(name); + } + + // ::= nw # new + // ::= na # new[] + // ::= dl # delete + // ::= da # delete[] + // ::= ps # + (unary) + // ::= ng # - (unary) + // ::= ad # & (unary) + // ::= de # * (unary) + // ::= co # ~ + // ::= pl # + + // ::= mi # - + // ::= ml # * + // ::= dv # / + // ::= rm # % + // ::= an # & + // ::= or # | + // ::= eo # ^ + // ::= aS # = + // ::= pL # += + // ::= mI # -= + // ::= mL # *= + // ::= dV # /= + // ::= rM # %= + // ::= aN # &= + // ::= oR # |= + // ::= eO # ^= + // ::= ls # << + // ::= rs # >> + // ::= lS # <<= + // ::= rS # >>= + // ::= eq # == + // ::= ne # != + // ::= lt # < + // ::= gt # > + // ::= le # <= + // ::= ge # >= + // ::= ss # <=> + // ::= nt # ! + // ::= aa # && + // ::= oo # || + // ::= pp # ++ (postfix in context) + // ::= mm # -- (postfix in context) + // ::= cm # , + // ::= pm # ->* + // ::= pt # -> + // ::= cl # () + // ::= ix # [] + // ::= qu # ? + // ::= cv # (cast) (TODO) + // ::= li # operator "" + // ::= v # vendor extended operator (TODO) + private BaseNode ParseOperatorName(NameParserContext context) + { + switch (Peek()) + { + case 'a': + switch (Peek(1)) + { + case 'a': + _position += 2; + return new NameType("operator&&"); + case 'd': + case 'n': + _position += 2; + return new NameType("operator&"); + case 'N': + _position += 2; + return new NameType("operator&="); + case 'S': + _position += 2; + return new NameType("operator="); + default: + return null; + } + case 'c': + switch (Peek(1)) + { + case 'l': + _position += 2; + return new NameType("operator()"); + case 'm': + _position += 2; + return new NameType("operator,"); + case 'o': + _position += 2; + return new NameType("operator~"); + case 'v': + _position += 2; + + bool canParseTemplateArgsBackup = _canParseTemplateArgs; + bool canForwardTemplateReferenceBackup = _canForwardTemplateReference; + + _canParseTemplateArgs = false; + _canForwardTemplateReference = canForwardTemplateReferenceBackup || context != null; + + BaseNode type = ParseType(); + + _canParseTemplateArgs = canParseTemplateArgsBackup; + _canForwardTemplateReference = canForwardTemplateReferenceBackup; + + if (type == null) + { + return null; + } + + if (context != null) + { + context.CtorDtorConversion = true; + } + + return new ConversionOperatorType(type); + default: + return null; + } + case 'd': + switch (Peek(1)) + { + case 'a': + _position += 2; + return new NameType("operator delete[]"); + case 'e': + _position += 2; + return new NameType("operator*"); + case 'l': + _position += 2; + return new NameType("operator delete"); + case 'v': + _position += 2; + return new NameType("operator/"); + case 'V': + _position += 2; + return new NameType("operator/="); + default: + return null; + } + case 'e': + switch (Peek(1)) + { + case 'o': + _position += 2; + return new NameType("operator^"); + case 'O': + _position += 2; + return new NameType("operator^="); + case 'q': + _position += 2; + return new NameType("operator=="); + default: + return null; + } + case 'g': + switch (Peek(1)) + { + case 'e': + _position += 2; + return new NameType("operator>="); + case 't': + _position += 2; + return new NameType("operator>"); + default: + return null; + } + case 'i': + if (Peek(1) == 'x') + { + _position += 2; + return new NameType("operator[]"); + } + return null; + case 'l': + switch (Peek(1)) + { + case 'e': + _position += 2; + return new NameType("operator<="); + case 'i': + _position += 2; + BaseNode sourceName = ParseSourceName(); + if (sourceName == null) + { + return null; + } + + return new LiteralOperator(sourceName); + case 's': + _position += 2; + return new NameType("operator<<"); + case 'S': + _position += 2; + return new NameType("operator<<="); + case 't': + _position += 2; + return new NameType("operator<"); + default: + return null; + } + case 'm': + switch (Peek(1)) + { + case 'i': + _position += 2; + return new NameType("operator-"); + case 'I': + _position += 2; + return new NameType("operator-="); + case 'l': + _position += 2; + return new NameType("operator*"); + case 'L': + _position += 2; + return new NameType("operator*="); + case 'm': + _position += 2; + return new NameType("operator--"); + default: + return null; + } + case 'n': + switch (Peek(1)) + { + case 'a': + _position += 2; + return new NameType("operator new[]"); + case 'e': + _position += 2; + return new NameType("operator!="); + case 'g': + _position += 2; + return new NameType("operator-"); + case 't': + _position += 2; + return new NameType("operator!"); + case 'w': + _position += 2; + return new NameType("operator new"); + default: + return null; + } + case 'o': + switch (Peek(1)) + { + case 'o': + _position += 2; + return new NameType("operator||"); + case 'r': + _position += 2; + return new NameType("operator|"); + case 'R': + _position += 2; + return new NameType("operator|="); + default: + return null; + } + case 'p': + switch (Peek(1)) + { + case 'm': + _position += 2; + return new NameType("operator->*"); + case 's': + case 'l': + _position += 2; + return new NameType("operator+"); + case 'L': + _position += 2; + return new NameType("operator+="); + case 'p': + _position += 2; + return new NameType("operator++"); + case 't': + _position += 2; + return new NameType("operator->"); + default: + return null; + } + case 'q': + if (Peek(1) == 'u') + { + _position += 2; + return new NameType("operator?"); + } + return null; + case 'r': + switch (Peek(1)) + { + case 'm': + _position += 2; + return new NameType("operator%"); + case 'M': + _position += 2; + return new NameType("operator%="); + case 's': + _position += 2; + return new NameType("operator>>"); + case 'S': + _position += 2; + return new NameType("operator>>="); + default: + return null; + } + case 's': + if (Peek(1) == 's') + { + _position += 2; + return new NameType("operator<=>"); + } + return null; + case 'v': + // TODO: ::= v # vendor extended operator + return null; + default: + return null; + } + } + + // ::= [ (TODO)] + // ::= (TODO) + // ::= + // ::= (TODO) + // ::= DC + E # structured binding declaration (TODO) + private BaseNode ParseUnqualifiedName(NameParserContext context) + { + BaseNode result = null; + char c = Peek(); + if (c == 'U') + { + // TODO: Unnamed Type Name + // throw new Exception("Unnamed Type Name not implemented"); + } + else if (char.IsDigit(c)) + { + result = ParseSourceName(); + } + else if (ConsumeIf("DC")) + { + // TODO: Structured Binding Declaration + // throw new Exception("Structured Binding Declaration not implemented"); + } + else + { + result = ParseOperatorName(context); + } + + if (result != null) + { + // TODO: ABI Tags + // throw new Exception("ABI Tags not implemented"); + } + return result; + } + + // ::= C1 # complete object constructor + // ::= C2 # base object constructor + // ::= C3 # complete object allocating constructor + // ::= D0 # deleting destructor + // ::= D1 # complete object destructor + // ::= D2 # base object destructor + private BaseNode ParseCtorDtorName(NameParserContext context, BaseNode prev) + { + if (prev.Type == NodeType.SpecialSubstitution && prev is SpecialSubstitution) + { + ((SpecialSubstitution)prev).SetExtended(); + } + + if (ConsumeIf("C")) + { + bool isInherited = ConsumeIf("I"); + + char ctorDtorType = Peek(); + if (ctorDtorType != '1' && ctorDtorType != '2' && ctorDtorType != '3') + { + return null; + } + + _position++; + + if (context != null) + { + context.CtorDtorConversion = true; + } + + if (isInherited && ParseName(context) == null) + { + return null; + } + + return new CtorDtorNameType(prev, false); + } + + if (ConsumeIf("D")) + { + char c = Peek(); + if (c != '0' && c != '1' && c != '2') + { + return null; + } + + _position++; + + if (context != null) + { + context.CtorDtorConversion = true; + } + + return new CtorDtorNameType(prev, true); + } + + return null; + } + + // ::= fp _ # L == 0, first parameter + // ::= fp _ # L == 0, second and later parameters + // ::= fL p _ # L > 0, first parameter + // ::= fL p _ # L > 0, second and later parameters + private BaseNode ParseFunctionParameter() + { + if (ConsumeIf("fp")) + { + // ignored + ParseCvQualifiers(); + + if (!ConsumeIf("_")) + { + return null; + } + + return new FunctionParameter(ParseNumber()); + } + else if (ConsumeIf("fL")) + { + string l1Number = ParseNumber(); + if (l1Number == null || l1Number.Length == 0) + { + return null; + } + + if (!ConsumeIf("p")) + { + return null; + } + + // ignored + ParseCvQualifiers(); + + if (!ConsumeIf("_")) + { + return null; + } + + return new FunctionParameter(ParseNumber()); + } + + return null; + } + + // ::= fL + // ::= fR + // ::= fl + // ::= fr + private BaseNode ParseFoldExpression() + { + if (!ConsumeIf("f")) + { + return null; + } + + char foldKind = Peek(); + bool hasInitializer = foldKind == 'L' || foldKind == 'R'; + bool isLeftFold = foldKind == 'l' || foldKind == 'L'; + + if (!isLeftFold && !(foldKind == 'r' || foldKind == 'R')) + { + return null; + } + + _position++; + + string operatorName = null; + + switch (PeekString(0, 2)) + { + case "aa": + operatorName = "&&"; + break; + case "an": + operatorName = "&"; + break; + case "aN": + operatorName = "&="; + break; + case "aS": + operatorName = "="; + break; + case "cm": + operatorName = ","; + break; + case "ds": + operatorName = ".*"; + break; + case "dv": + operatorName = "/"; + break; + case "dV": + operatorName = "/="; + break; + case "eo": + operatorName = "^"; + break; + case "eO": + operatorName = "^="; + break; + case "eq": + operatorName = "=="; + break; + case "ge": + operatorName = ">="; + break; + case "gt": + operatorName = ">"; + break; + case "le": + operatorName = "<="; + break; + case "ls": + operatorName = "<<"; + break; + case "lS": + operatorName = "<<="; + break; + case "lt": + operatorName = "<"; + break; + case "mi": + operatorName = "-"; + break; + case "mI": + operatorName = "-="; + break; + case "ml": + operatorName = "*"; + break; + case "mL": + operatorName = "*="; + break; + case "ne": + operatorName = "!="; + break; + case "oo": + operatorName = "||"; + break; + case "or": + operatorName = "|"; + break; + case "oR": + operatorName = "|="; + break; + case "pl": + operatorName = "+"; + break; + case "pL": + operatorName = "+="; + break; + case "rm": + operatorName = "%"; + break; + case "rM": + operatorName = "%="; + break; + case "rs": + operatorName = ">>"; + break; + case "rS": + operatorName = ">>="; + break; + default: + return null; + } + + _position += 2; + + BaseNode expression = ParseExpression(); + if (expression == null) + { + return null; + } + + BaseNode initializer = null; + + if (hasInitializer) + { + initializer = ParseExpression(); + if (initializer == null) + { + return null; + } + } + + if (isLeftFold && initializer != null) + { + BaseNode temp = expression; + expression = initializer; + initializer = temp; + } + + return new FoldExpression(isLeftFold, operatorName, new PackedTemplateParameterExpansion(expression), initializer); + } + + + // ::= cv # type (expression), conversion with one argument + // ::= cv _ * E # type (expr-list), conversion with other than one argument + private BaseNode ParseConversionExpression() + { + if (!ConsumeIf("cv")) + { + return null; + } + + bool canParseTemplateArgsBackup = _canParseTemplateArgs; + _canParseTemplateArgs = false; + BaseNode type = ParseType(); + _canParseTemplateArgs = canParseTemplateArgsBackup; + + if (type == null) + { + return null; + } + + List expressions = new List(); + if (ConsumeIf("_")) + { + while (!ConsumeIf("E")) + { + BaseNode expression = ParseExpression(); + if (expression == null) + { + return null; + } + + expressions.Add(expression); + } + } + else + { + BaseNode expression = ParseExpression(); + if (expression == null) + { + return null; + } + + expressions.Add(expression); + } + + return new ConversionExpression(type, new NodeArray(expressions)); + } + + private BaseNode ParseBinaryExpression(string name) + { + BaseNode leftPart = ParseExpression(); + if (leftPart == null) + { + return null; + } + + BaseNode rightPart = ParseExpression(); + if (rightPart == null) + { + return null; + } + + return new BinaryExpression(leftPart, name, rightPart); + } + + private BaseNode ParsePrefixExpression(string name) + { + BaseNode expression = ParseExpression(); + if (expression == null) + { + return null; + } + + return new PrefixExpression(name, expression); + } + + + // ::= + // ::= di # .name = expr + // ::= dx # [expr] = expr + // ::= dX + // # [expr ... expr] = expr + private BaseNode ParseBracedExpression() + { + if (Peek() == 'd') + { + BaseNode bracedExpressionNode; + switch (Peek(1)) + { + case 'i': + _position += 2; + BaseNode field = ParseSourceName(); + if (field == null) + { + return null; + } + + bracedExpressionNode = ParseBracedExpression(); + if (bracedExpressionNode == null) + { + return null; + } + + return new BracedExpression(field, bracedExpressionNode, false); + case 'x': + _position += 2; + BaseNode index = ParseExpression(); + if (index == null) + { + return null; + } + + bracedExpressionNode = ParseBracedExpression(); + if (bracedExpressionNode == null) + { + return null; + } + + return new BracedExpression(index, bracedExpressionNode, true); + case 'X': + _position += 2; + BaseNode rangeBeginExpression = ParseExpression(); + if (rangeBeginExpression == null) + { + return null; + } + + BaseNode rangeEndExpression = ParseExpression(); + if (rangeEndExpression == null) + { + return null; + } + + bracedExpressionNode = ParseBracedExpression(); + if (bracedExpressionNode == null) + { + return null; + } + + return new BracedRangeExpression(rangeBeginExpression, rangeEndExpression, bracedExpressionNode); + } + } + + return ParseExpression(); + } + + // ::= [gs] nw * _ E # new (expr-list) type + // ::= [gs] nw * _ # new (expr-list) type (init) + // ::= [gs] na * _ E # new[] (expr-list) type + // ::= [gs] na * _ # new[] (expr-list) type (init) + // + // ::= pi * E # parenthesized initialization + private BaseNode ParseNewExpression() + { + bool isGlobal = ConsumeIf("gs"); + bool isArray = Peek(1) == 'a'; + + if (!ConsumeIf("nw") || !ConsumeIf("na")) + { + return null; + } + + List expressions = new List(); + List initializers = new List(); + + while (!ConsumeIf("_")) + { + BaseNode expression = ParseExpression(); + if (expression == null) + { + return null; + } + + expressions.Add(expression); + } + + BaseNode typeNode = ParseType(); + if (typeNode == null) + { + return null; + } + + if (ConsumeIf("pi")) + { + while (!ConsumeIf("E")) + { + BaseNode initializer = ParseExpression(); + if (initializer == null) + { + return null; + } + + initializers.Add(initializer); + } + } + else if (!ConsumeIf("E")) + { + return null; + } + + return new NewExpression(new NodeArray(expressions), typeNode, new NodeArray(initializers), isGlobal, isArray); + } + + + // ::= + // ::= + // ::= + // ::= pp_ # prefix ++ + // ::= mm_ # prefix -- + // ::= cl + E # expression (expr-list), call + // ::= cv # type (expression), conversion with one argument + // ::= cv _ * E # type (expr-list), conversion with other than one argument + // ::= tl * E # type {expr-list}, conversion with braced-init-list argument + // ::= il * E # {expr-list}, braced-init-list in any other context + // ::= [gs] nw * _ E # new (expr-list) type + // ::= [gs] nw * _ # new (expr-list) type (init) + // ::= [gs] na * _ E # new[] (expr-list) type + // ::= [gs] na * _ # new[] (expr-list) type (init) + // ::= [gs] dl # delete expression + // ::= [gs] da # delete[] expression + // ::= dc # dynamic_cast (expression) + // ::= sc # static_cast (expression) + // ::= cc # const_cast (expression) + // ::= rc # reinterpret_cast (expression) + // ::= ti # typeid (type) + // ::= te # typeid (expression) + // ::= st # sizeof (type) + // ::= sz # sizeof (expression) + // ::= at # alignof (type) + // ::= az # alignof (expression) + // ::= nx # noexcept (expression) + // ::= + // ::= + // ::= dt # expr.name + // ::= pt # expr->name + // ::= ds # expr.*expr + // ::= sZ # sizeof...(T), size of a template parameter pack + // ::= sZ # sizeof...(parameter), size of a function parameter pack + // ::= sP * E # sizeof...(T), size of a captured template parameter pack from an alias template + // ::= sp # expression..., pack expansion + // ::= tw # throw expression + // ::= tr # throw with no operand (rethrow) + // ::= # f(p), N::f(p), ::f(p), + // # freestanding dependent name (e.g., T::x), + // # objectless nonstatic member reference + // ::= + private BaseNode ParseExpression() + { + bool isGlobal = ConsumeIf("gs"); + BaseNode expression = null; + if (Count() < 2) + { + return null; + } + + switch (Peek()) + { + case 'L': + return ParseExpressionPrimary(); + case 'T': + return ParseTemplateParam(); + case 'f': + char c = Peek(1); + if (c == 'p' || (c == 'L' && char.IsDigit(Peek(2)))) + { + return ParseFunctionParameter(); + } + + return ParseFoldExpression(); + case 'a': + switch (Peek(1)) + { + case 'a': + _position += 2; + return ParseBinaryExpression("&&"); + case 'd': + case 'n': + _position += 2; + return ParseBinaryExpression("&"); + case 'N': + _position += 2; + return ParseBinaryExpression("&="); + case 'S': + _position += 2; + return ParseBinaryExpression("="); + case 't': + _position += 2; + BaseNode type = ParseType(); + if (type == null) + { + return null; + } + + return new EnclosedExpression("alignof (", type, ")"); + case 'z': + _position += 2; + expression = ParseExpression(); + if (expression == null) + { + return null; + } + + return new EnclosedExpression("alignof (", expression, ")"); + } + return null; + case 'c': + switch (Peek(1)) + { + case 'c': + _position += 2; + BaseNode to = ParseType(); + if (to == null) + { + return null; + } + + BaseNode from = ParseExpression(); + if (from == null) + { + return null; + } + + return new CastExpression("const_cast", to, from); + case 'l': + _position += 2; + BaseNode callee = ParseExpression(); + if (callee == null) + { + return null; + } + + List names = new List(); + while (!ConsumeIf("E")) + { + expression = ParseExpression(); + if (expression == null) + { + return null; + } + + names.Add(expression); + } + return new CallExpression(callee, names); + case 'm': + _position += 2; + return ParseBinaryExpression(","); + case 'o': + _position += 2; + return ParsePrefixExpression("~"); + case 'v': + return ParseConversionExpression(); + } + return null; + case 'd': + BaseNode leftNode = null; + BaseNode rightNode = null; + switch (Peek(1)) + { + case 'a': + _position += 2; + expression = ParseExpression(); + if (expression == null) + { + return expression; + } + + return new DeleteExpression(expression, isGlobal, true); + case 'c': + _position += 2; + BaseNode type = ParseType(); + if (type == null) + { + return null; + } + + expression = ParseExpression(); + if (expression == null) + { + return expression; + } + + return new CastExpression("dynamic_cast", type, expression); + case 'e': + _position += 2; + return ParsePrefixExpression("*"); + case 'l': + _position += 2; + expression = ParseExpression(); + if (expression == null) + { + return null; + } + + return new DeleteExpression(expression, isGlobal, false); + case 'n': + return ParseUnresolvedName(); + case 's': + _position += 2; + leftNode = ParseExpression(); + if (leftNode == null) + { + return null; + } + + rightNode = ParseExpression(); + if (rightNode == null) + { + return null; + } + + return new MemberExpression(leftNode, ".*", rightNode); + case 't': + _position += 2; + leftNode = ParseExpression(); + if (leftNode == null) + { + return null; + } + + rightNode = ParseExpression(); + if (rightNode == null) + { + return null; + } + + return new MemberExpression(leftNode, ".", rightNode); + case 'v': + _position += 2; + return ParseBinaryExpression("/"); + case 'V': + _position += 2; + return ParseBinaryExpression("/="); + } + return null; + case 'e': + switch (Peek(1)) + { + case 'o': + _position += 2; + return ParseBinaryExpression("^"); + case 'O': + _position += 2; + return ParseBinaryExpression("^="); + case 'q': + _position += 2; + return ParseBinaryExpression("=="); + } + return null; + case 'g': + switch (Peek(1)) + { + case 'e': + _position += 2; + return ParseBinaryExpression(">="); + case 't': + _position += 2; + return ParseBinaryExpression(">"); + } + return null; + case 'i': + switch (Peek(1)) + { + case 'x': + _position += 2; + BaseNode Base = ParseExpression(); + if (Base == null) + { + return null; + } + + BaseNode subscript = ParseExpression(); + if (Base == null) + { + return null; + } + + return new ArraySubscriptingExpression(Base, subscript); + case 'l': + _position += 2; + + List bracedExpressions = new List(); + while (!ConsumeIf("E")) + { + expression = ParseBracedExpression(); + if (expression == null) + { + return null; + } + + bracedExpressions.Add(expression); + } + return new InitListExpression(null, bracedExpressions); + } + return null; + case 'l': + switch (Peek(1)) + { + case 'e': + _position += 2; + return ParseBinaryExpression("<="); + case 's': + _position += 2; + return ParseBinaryExpression("<<"); + case 'S': + _position += 2; + return ParseBinaryExpression("<<="); + case 't': + _position += 2; + return ParseBinaryExpression("<"); + } + return null; + case 'm': + switch (Peek(1)) + { + case 'i': + _position += 2; + return ParseBinaryExpression("-"); + case 'I': + _position += 2; + return ParseBinaryExpression("-="); + case 'l': + _position += 2; + return ParseBinaryExpression("*"); + case 'L': + _position += 2; + return ParseBinaryExpression("*="); + case 'm': + _position += 2; + if (ConsumeIf("_")) + { + return ParsePrefixExpression("--"); + } + + expression = ParseExpression(); + if (expression == null) + { + return null; + } + + return new PostfixExpression(expression, "--"); + } + return null; + case 'n': + switch (Peek(1)) + { + case 'a': + case 'w': + _position += 2; + return ParseNewExpression(); + case 'e': + _position += 2; + return ParseBinaryExpression("!="); + case 'g': + _position += 2; + return ParsePrefixExpression("-"); + case 't': + _position += 2; + return ParsePrefixExpression("!"); + case 'x': + _position += 2; + expression = ParseExpression(); + if (expression == null) + { + return null; + } + + return new EnclosedExpression("noexcept (", expression, ")"); + } + return null; + case 'o': + switch (Peek(1)) + { + case 'n': + return ParseUnresolvedName(); + case 'o': + _position += 2; + return ParseBinaryExpression("||"); + case 'r': + _position += 2; + return ParseBinaryExpression("|"); + case 'R': + _position += 2; + return ParseBinaryExpression("|="); + } + return null; + case 'p': + switch (Peek(1)) + { + case 'm': + _position += 2; + return ParseBinaryExpression("->*"); + case 'l': + case 's': + _position += 2; + return ParseBinaryExpression("+"); + case 'L': + _position += 2; + return ParseBinaryExpression("+="); + case 'p': + _position += 2; + if (ConsumeIf("_")) + { + return ParsePrefixExpression("++"); + } + + expression = ParseExpression(); + if (expression == null) + { + return null; + } + + return new PostfixExpression(expression, "++"); + case 't': + _position += 2; + leftNode = ParseExpression(); + if (leftNode == null) + { + return null; + } + + rightNode = ParseExpression(); + if (rightNode == null) + { + return null; + } + + return new MemberExpression(leftNode, "->", rightNode); + } + return null; + case 'q': + if (Peek(1) == 'u') + { + _position += 2; + BaseNode condition = ParseExpression(); + if (condition == null) + { + return null; + } + + leftNode = ParseExpression(); + if (leftNode == null) + { + return null; + } + + rightNode = ParseExpression(); + if (rightNode == null) + { + return null; + } + + return new ConditionalExpression(condition, leftNode, rightNode); + } + return null; + case 'r': + switch (Peek(1)) + { + case 'c': + _position += 2; + BaseNode to = ParseType(); + if (to == null) + { + return null; + } + + BaseNode from = ParseExpression(); + if (from == null) + { + return null; + } + + return new CastExpression("reinterpret_cast", to, from); + case 'm': + _position += 2; + return ParseBinaryExpression("%"); + case 'M': + _position += 2; + return ParseBinaryExpression("%"); + case 's': + _position += 2; + return ParseBinaryExpression(">>"); + case 'S': + _position += 2; + return ParseBinaryExpression(">>="); + } + return null; + case 's': + switch (Peek(1)) + { + case 'c': + _position += 2; + BaseNode to = ParseType(); + if (to == null) + { + return null; + } + + BaseNode from = ParseExpression(); + if (from == null) + { + return null; + } + + return new CastExpression("static_cast", to, from); + case 'p': + _position += 2; + expression = ParseExpression(); + if (expression == null) + { + return null; + } + + return new PackedTemplateParameterExpansion(expression); + case 'r': + return ParseUnresolvedName(); + case 't': + _position += 2; + BaseNode enclosedType = ParseType(); + if (enclosedType == null) + { + return null; + } + + return new EnclosedExpression("sizeof (", enclosedType, ")"); + case 'z': + _position += 2; + expression = ParseExpression(); + if (expression == null) + { + return null; + } + + return new EnclosedExpression("sizeof (", expression, ")"); + case 'Z': + _position += 2; + BaseNode sizeofParamNode = null; + switch (Peek()) + { + case 'T': + // FIXME: ??? Not entire sure if it's right + sizeofParamNode = ParseFunctionParameter(); + if (sizeofParamNode == null) + { + return null; + } + + return new EnclosedExpression("sizeof...(", new PackedTemplateParameterExpansion(sizeofParamNode), ")"); + case 'f': + sizeofParamNode = ParseFunctionParameter(); + if (sizeofParamNode == null) + { + return null; + } + + return new EnclosedExpression("sizeof...(", sizeofParamNode, ")"); + } + return null; + case 'P': + _position += 2; + List arguments = new List(); + while (!ConsumeIf("E")) + { + BaseNode argument = ParseTemplateArgument(); + if (argument == null) + { + return null; + } + + arguments.Add(argument); + } + return new EnclosedExpression("sizeof...(", new NodeArray(arguments), ")"); + } + return null; + case 't': + switch (Peek(1)) + { + case 'e': + expression = ParseExpression(); + if (expression == null) + { + return null; + } + + return new EnclosedExpression("typeid (", expression, ")"); + case 't': + BaseNode enclosedType = ParseExpression(); + if (enclosedType == null) + { + return null; + } + + return new EnclosedExpression("typeid (", enclosedType, ")"); + case 'l': + _position += 2; + BaseNode typeNode = ParseType(); + if (typeNode == null) + { + return null; + } + + List bracedExpressions = new List(); + while (!ConsumeIf("E")) + { + expression = ParseBracedExpression(); + if (expression == null) + { + return null; + } + + bracedExpressions.Add(expression); + } + return new InitListExpression(typeNode, bracedExpressions); + case 'r': + _position += 2; + return new NameType("throw"); + case 'w': + _position += 2; + expression = ParseExpression(); + if (expression == null) + { + return null; + } + + return new ThrowExpression(expression); + } + return null; + } + + if (char.IsDigit(Peek())) + { + return ParseUnresolvedName(); + } + + return null; + } + + private BaseNode ParseIntegerLiteral(string literalName) + { + string number = ParseNumber(true); + if (number == null || number.Length == 0 || !ConsumeIf("E")) + { + return null; + } + + return new IntegerLiteral(literalName, number); + } + + // ::= L E # integer literal + // ::= L E # floating literal (TODO) + // ::= L E # string literal + // ::= L E # nullptr literal (i.e., "LDnE") + // ::= L 0 E # null pointer template argument + // ::= L _ E # complex floating point literal (C 2000) + // ::= L _Z E # external name + private BaseNode ParseExpressionPrimary() + { + if (!ConsumeIf("L")) + { + return null; + } + + switch (Peek()) + { + case 'w': + _position++; + return ParseIntegerLiteral("wchar_t"); + case 'b': + if (ConsumeIf("b0E")) + { + return new NameType("false", NodeType.BooleanExpression); + } + + if (ConsumeIf("b1E")) + { + return new NameType("true", NodeType.BooleanExpression); + } + + return null; + case 'c': + _position++; + return ParseIntegerLiteral("char"); + case 'a': + _position++; + return ParseIntegerLiteral("signed char"); + case 'h': + _position++; + return ParseIntegerLiteral("unsigned char"); + case 's': + _position++; + return ParseIntegerLiteral("short"); + case 't': + _position++; + return ParseIntegerLiteral("unsigned short"); + case 'i': + _position++; + return ParseIntegerLiteral(""); + case 'j': + _position++; + return ParseIntegerLiteral("u"); + case 'l': + _position++; + return ParseIntegerLiteral("l"); + case 'm': + _position++; + return ParseIntegerLiteral("ul"); + case 'x': + _position++; + return ParseIntegerLiteral("ll"); + case 'y': + _position++; + return ParseIntegerLiteral("ull"); + case 'n': + _position++; + return ParseIntegerLiteral("__int128"); + case 'o': + _position++; + return ParseIntegerLiteral("unsigned __int128"); + case 'd': + case 'e': + case 'f': + // TODO: floating literal + return null; + case '_': + if (ConsumeIf("_Z")) + { + BaseNode encoding = ParseEncoding(); + if (encoding != null && ConsumeIf("E")) + { + return encoding; + } + } + return null; + case 'T': + return null; + default: + BaseNode type = ParseType(); + if (type == null) + { + return null; + } + + string number = ParseNumber(); + if (number == null || number.Length == 0 || !ConsumeIf("E")) + { + return null; + } + + return new IntegerCastExpression(type, number); + } + } + + // ::= Dt E # decltype of an id-expression or class member access (C++0x) + // ::= DT E # decltype of an expression (C++0x) + private BaseNode ParseDecltype() + { + if (!ConsumeIf("D") || (!ConsumeIf("t") && !ConsumeIf("T"))) + { + return null; + } + + BaseNode expression = ParseExpression(); + if (expression == null) + { + return null; + } + + if (!ConsumeIf("E")) + { + return null; + } + + return new EnclosedExpression("decltype(", expression, ")"); + } + + // ::= T_ # first template parameter + // ::= T _ + // ::= + // ::= + private BaseNode ParseTemplateParam() + { + if (!ConsumeIf("T")) + { + return null; + } + + int index = 0; + if (!ConsumeIf("_")) + { + index = ParsePositiveNumber(); + if (index < 0) + { + return null; + } + + index++; + if (!ConsumeIf("_")) + { + return null; + } + } + + // 5.1.8: TODO: lambda? + // if (IsParsingLambdaParameters) + // return new NameType("auto"); + + if (_canForwardTemplateReference) + { + ForwardTemplateReference forwardTemplateReference = new ForwardTemplateReference(index); + _forwardTemplateReferenceList.Add(forwardTemplateReference); + return forwardTemplateReference; + } + if (index >= _templateParamList.Count) + { + return null; + } + + return _templateParamList[index]; + } + + // ::= I + E + private BaseNode ParseTemplateArguments(bool hasContext = false) + { + if (!ConsumeIf("I")) + { + return null; + } + + if (hasContext) + { + _templateParamList.Clear(); + } + + List args = new List(); + while (!ConsumeIf("E")) + { + if (hasContext) + { + List templateParamListTemp = new List(_templateParamList); + BaseNode templateArgument = ParseTemplateArgument(); + _templateParamList = templateParamListTemp; + if (templateArgument == null) + { + return null; + } + + args.Add(templateArgument); + if (templateArgument.GetType().Equals(NodeType.PackedTemplateArgument)) + { + templateArgument = new PackedTemplateParameter(((NodeArray)templateArgument).Nodes); + } + _templateParamList.Add(templateArgument); + } + else + { + BaseNode templateArgument = ParseTemplateArgument(); + if (templateArgument == null) + { + return null; + } + + args.Add(templateArgument); + } + } + return new TemplateArguments(args); + } + + + // ::= # type or template + // ::= X E # expression + // ::= # simple expressions + // ::= J * E # argument pack + private BaseNode ParseTemplateArgument() + { + switch (Peek()) + { + // X E + case 'X': + _position++; + BaseNode expression = ParseExpression(); + if (expression == null || !ConsumeIf("E")) + { + return null; + } + + return expression; + // + case 'L': + return ParseExpressionPrimary(); + // J * E + case 'J': + _position++; + List templateArguments = new List(); + while (!ConsumeIf("E")) + { + BaseNode templateArgument = ParseTemplateArgument(); + if (templateArgument == null) + { + return null; + } + + templateArguments.Add(templateArgument); + } + return new NodeArray(templateArguments, NodeType.PackedTemplateArgument); + // + default: + return ParseType(); + } + } + + class NameParserContext + { + public CvType Cv; + public SimpleReferenceType Ref; + public bool FinishWithTemplateArguments; + public bool CtorDtorConversion; + } + + + // ::= [ ] # T:: or T:: + // ::= # decltype(p):: + // ::= + private BaseNode ParseUnresolvedType() + { + if (Peek() == 'T') + { + BaseNode templateParam = ParseTemplateParam(); + if (templateParam == null) + { + return null; + } + + _substitutionList.Add(templateParam); + return templateParam; + } + else if (Peek() == 'D') + { + BaseNode declType = ParseDecltype(); + if (declType == null) + { + return null; + } + + _substitutionList.Add(declType); + return declType; + } + return ParseSubstitution(); + } + + // ::= [ ] + private BaseNode ParseSimpleId() + { + BaseNode sourceName = ParseSourceName(); + if (sourceName == null) + { + return null; + } + + if (Peek() == 'I') + { + BaseNode templateArguments = ParseTemplateArguments(); + if (templateArguments == null) + { + return null; + } + + return new NameTypeWithTemplateArguments(sourceName, templateArguments); + } + return sourceName; + } + + // ::= # e.g., ~T or ~decltype(f()) + // ::= # e.g., ~A<2*N> + private BaseNode ParseDestructorName() + { + BaseNode node; + if (char.IsDigit(Peek())) + { + node = ParseSimpleId(); + } + else + { + node = ParseUnresolvedType(); + } + if (node == null) + { + return null; + } + + return new DtorName(node); + } + + // ::= # unresolved name + // extension ::= # unresolved operator-function-id + // extension ::= # unresolved operator template-id + // ::= on # unresolved operator-function-id + // ::= on # unresolved operator template-id + // ::= dn # destructor or pseudo-destructor; + // # e.g. ~X or ~X + private BaseNode ParseBaseUnresolvedName() + { + if (char.IsDigit(Peek())) + { + return ParseSimpleId(); + } + else if (ConsumeIf("dn")) + { + return ParseDestructorName(); + } + + ConsumeIf("on"); + BaseNode operatorName = ParseOperatorName(null); + if (operatorName == null) + { + return null; + } + + if (Peek() == 'I') + { + BaseNode templateArguments = ParseTemplateArguments(); + if (templateArguments == null) + { + return null; + } + + return new NameTypeWithTemplateArguments(operatorName, templateArguments); + } + return operatorName; + } + + // ::= [gs] # x or (with "gs") ::x + // ::= sr # T::x / decltype(p)::x + // ::= srN + E + // # T::N::x /decltype(p)::N::x + // ::= [gs] sr + E + // # A::x, N::y, A::z; "gs" means leading "::" + private BaseNode ParseUnresolvedName(NameParserContext context = null) + { + BaseNode result = null; + if (ConsumeIf("srN")) + { + result = ParseUnresolvedType(); + if (result == null) + { + return null; + } + + if (Peek() == 'I') + { + BaseNode templateArguments = ParseTemplateArguments(); + if (templateArguments == null) + { + return null; + } + + result = new NameTypeWithTemplateArguments(result, templateArguments); + if (result == null) + { + return null; + } + } + + while (!ConsumeIf("E")) + { + BaseNode simpleId = ParseSimpleId(); + if (simpleId == null) + { + return null; + } + + result = new QualifiedName(result, simpleId); + if (result == null) + { + return null; + } + } + + BaseNode baseName = ParseBaseUnresolvedName(); + if (baseName == null) + { + return null; + } + + return new QualifiedName(result, baseName); + } + + bool isGlobal = ConsumeIf("gs"); + + // ::= [gs] # x or (with "gs") ::x + if (!ConsumeIf("sr")) + { + result = ParseBaseUnresolvedName(); + if (result == null) + { + return null; + } + + if (isGlobal) + { + result = new GlobalQualifiedName(result); + } + + return result; + } + + // ::= [gs] sr + E + if (char.IsDigit(Peek())) + { + do + { + BaseNode qualifier = ParseSimpleId(); + if (qualifier == null) + { + return null; + } + + if (result != null) + { + result = new QualifiedName(result, qualifier); + } + else if (isGlobal) + { + result = new GlobalQualifiedName(qualifier); + } + else + { + result = qualifier; + } + + if (result == null) + { + return null; + } + } while (!ConsumeIf("E")); + } + // ::= sr [template-args] # T::x / decltype(p)::x + else + { + result = ParseUnresolvedType(); + if (result == null) + { + return null; + } + + if (Peek() == 'I') + { + BaseNode templateArguments = ParseTemplateArguments(); + if (templateArguments == null) + { + return null; + } + + result = new NameTypeWithTemplateArguments(result, templateArguments); + if (result == null) + { + return null; + } + } + } + + if (result == null) + { + return null; + } + + BaseNode baseUnresolvedName = ParseBaseUnresolvedName(); + if (baseUnresolvedName == null) + { + return null; + } + + return new QualifiedName(result, baseUnresolvedName); + } + + // ::= + // ::= St # ::std:: + private BaseNode ParseUnscopedName(NameParserContext context) + { + if (ConsumeIf("St")) + { + BaseNode unresolvedName = ParseUnresolvedName(context); + if (unresolvedName == null) + { + return null; + } + + return new StdQualifiedName(unresolvedName); + } + return ParseUnresolvedName(context); + } + + // ::= N [] [] E + // ::= N [] [] E + private BaseNode ParseNestedName(NameParserContext context) + { + // Impossible in theory + if (Consume() != 'N') + { + return null; + } + + BaseNode result = null; + CvType cv = new CvType(ParseCvQualifiers(), null); + if (context != null) + { + context.Cv = cv; + } + + SimpleReferenceType Ref = ParseRefQualifiers(); + if (context != null) + { + context.Ref = Ref; + } + + if (ConsumeIf("St")) + { + result = new NameType("std"); + } + + while (!ConsumeIf("E")) + { + // end + if (ConsumeIf("M")) + { + if (result == null) + { + return null; + } + + continue; + } + char c = Peek(); + + // TODO: template args + if (c == 'T') + { + BaseNode templateParam = ParseTemplateParam(); + if (templateParam == null) + { + return null; + } + + result = CreateNameNode(result, templateParam, context); + _substitutionList.Add(result); + continue; + } + + // + if (c == 'I') + { + BaseNode templateArgument = ParseTemplateArguments(context != null); + if (templateArgument == null || result == null) + { + return null; + } + + result = new NameTypeWithTemplateArguments(result, templateArgument); + if (context != null) + { + context.FinishWithTemplateArguments = true; + } + + _substitutionList.Add(result); + continue; + } + + // + if (c == 'D' && (Peek(1) == 't' || Peek(1) == 'T')) + { + BaseNode decltype = ParseDecltype(); + if (decltype == null) + { + return null; + } + + result = CreateNameNode(result, decltype, context); + _substitutionList.Add(result); + continue; + } + + // + if (c == 'S' && Peek(1) != 't') + { + BaseNode substitution = ParseSubstitution(); + if (substitution == null) + { + return null; + } + + result = CreateNameNode(result, substitution, context); + if (result != substitution) + { + _substitutionList.Add(substitution); + } + + continue; + } + + // of ParseUnqualifiedName + if (c == 'C' || (c == 'D' && Peek(1) != 'C')) + { + // We cannot have nothing before this + if (result == null) + { + return null; + } + + BaseNode ctOrDtorName = ParseCtorDtorName(context, result); + + if (ctOrDtorName == null) + { + return null; + } + + result = CreateNameNode(result, ctOrDtorName, context); + + // TODO: ABI Tags (before) + if (result == null) + { + return null; + } + + _substitutionList.Add(result); + continue; + } + + BaseNode unqualifiedName = ParseUnqualifiedName(context); + if (unqualifiedName == null) + { + return null; + } + result = CreateNameNode(result, unqualifiedName, context); + + _substitutionList.Add(result); + } + if (result == null || _substitutionList.Count == 0) + { + return null; + } + + _substitutionList.RemoveAt(_substitutionList.Count - 1); + return result; + } + + // ::= _ # when number < 10 + // ::= __ _ # when number >= 10 + private void ParseDiscriminator() + { + if (Count() == 0) + { + return; + } + // We ignore the discriminator, we don't need it. + if (ConsumeIf("_")) + { + ConsumeIf("_"); + while (char.IsDigit(Peek()) && Count() != 0) + { + Consume(); + } + ConsumeIf("_"); + } + } + + // ::= Z E [] + // ::= Z E s [] + // ::= Z Ed [ ] _ + private BaseNode ParseLocalName(NameParserContext context) + { + if (!ConsumeIf("Z")) + { + return null; + } + + BaseNode encoding = ParseEncoding(); + if (encoding == null || !ConsumeIf("E")) + { + return null; + } + + BaseNode entityName; + if (ConsumeIf("s")) + { + ParseDiscriminator(); + return new LocalName(encoding, new NameType("string literal")); + } + else if (ConsumeIf("d")) + { + ParseNumber(true); + if (!ConsumeIf("_")) + { + return null; + } + + entityName = ParseName(context); + if (entityName == null) + { + return null; + } + + return new LocalName(encoding, entityName); + } + + entityName = ParseName(context); + if (entityName == null) + { + return null; + } + + ParseDiscriminator(); + return new LocalName(encoding, entityName); + } + + // ::= + // ::= + // ::= + // ::= # See Scope Encoding below (TODO) + private BaseNode ParseName(NameParserContext context = null) + { + ConsumeIf("L"); + + if (Peek() == 'N') + { + return ParseNestedName(context); + } + + if (Peek() == 'Z') + { + return ParseLocalName(context); + } + + if (Peek() == 'S' && Peek(1) != 't') + { + BaseNode substitution = ParseSubstitution(); + if (substitution == null) + { + return null; + } + + if (Peek() != 'I') + { + return null; + } + + BaseNode templateArguments = ParseTemplateArguments(context != null); + if (templateArguments == null) + { + return null; + } + + if (context != null) + { + context.FinishWithTemplateArguments = true; + } + + return new NameTypeWithTemplateArguments(substitution, templateArguments); + } + + BaseNode result = ParseUnscopedName(context); + if (result == null) + { + return null; + } + + if (Peek() == 'I') + { + _substitutionList.Add(result); + BaseNode templateArguments = ParseTemplateArguments(context != null); + if (templateArguments == null) + { + return null; + } + + if (context != null) + { + context.FinishWithTemplateArguments = true; + } + + return new NameTypeWithTemplateArguments(result, templateArguments); + } + + return result; + } + + private bool IsEncodingEnd() + { + char c = Peek(); + return Count() == 0 || c == 'E' || c == '.' || c == '_'; + } + + // ::= + // ::= + // ::= + private BaseNode ParseEncoding() + { + NameParserContext context = new NameParserContext(); + if (Peek() == 'T' || (Peek() == 'G' && Peek(1) == 'V')) + { + return ParseSpecialName(context); + } + + BaseNode name = ParseName(context); + if (name == null) + { + return null; + } + + // TODO: compute template refs here + + if (IsEncodingEnd()) + { + return name; + } + + // TODO: Ua9enable_ifI + + BaseNode returnType = null; + if (!context.CtorDtorConversion && context.FinishWithTemplateArguments) + { + returnType = ParseType(); + if (returnType == null) + { + return null; + } + } + + if (ConsumeIf("v")) + { + return new EncodedFunction(name, null, context.Cv, context.Ref, null, returnType); + } + + List Params = new List(); + + // backup because that can be destroyed by parseType + CvType cv = context.Cv; + SimpleReferenceType Ref = context.Ref; + + while (!IsEncodingEnd()) + { + BaseNode param = ParseType(); + if (param == null) + { + return null; + } + + Params.Add(param); + } + + return new EncodedFunction(name, new NodeArray(Params), cv, Ref, null, returnType); + } + + // ::= _Z + // ::= + private BaseNode Parse() + { + if (ConsumeIf("_Z")) + { + BaseNode encoding = ParseEncoding(); + if (encoding != null && Count() == 0) + { + return encoding; + } + return null; + } + else + { + BaseNode type = ParseType(); + if (type != null && Count() == 0) + { + return type; + } + return null; + } + } + + public static string Parse(string originalMangled) + { + Demangler instance = new Demangler(originalMangled); + BaseNode resNode = instance.Parse(); + + if (resNode != null) + { + StringWriter writer = new StringWriter(); + resNode.Print(writer); + return writer.ToString(); + } + + return originalMangled; + } + } +} diff --git a/Ryujinx.HLE/HOS/Font/SharedFontManager.cs b/Ryujinx.HLE/HOS/Font/SharedFontManager.cs new file mode 100644 index 0000000000..e126cd5799 --- /dev/null +++ b/Ryujinx.HLE/HOS/Font/SharedFontManager.cs @@ -0,0 +1,176 @@ +using LibHac.Fs; +using LibHac.FsSystem; +using LibHac.FsSystem.NcaUtils; +using Ryujinx.Common; +using Ryujinx.HLE.Exceptions; +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.FileSystem.Content; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.IO; + +using static Ryujinx.HLE.Utilities.FontUtils; + +namespace Ryujinx.HLE.HOS.Font +{ + class SharedFontManager + { + private Switch _device; + + private long _physicalAddress; + + private string _fontsPath; + + private struct FontInfo + { + public int Offset; + public int Size; + + public FontInfo(int offset, int size) + { + Offset = offset; + Size = size; + } + } + + private Dictionary _fontData; + + public SharedFontManager(Switch device, long physicalAddress) + { + _physicalAddress = physicalAddress; + + _device = device; + + _fontsPath = Path.Combine(device.FileSystem.GetSystemPath(), "fonts"); + } + + public void Initialize(ContentManager contentManager, bool ignoreMissingFonts) + { + _fontData?.Clear(); + _fontData = null; + + EnsureInitialized(contentManager, ignoreMissingFonts); + } + + public void EnsureInitialized(ContentManager contentManager, bool ignoreMissingFonts) + { + if (_fontData == null) + { + _device.Memory.FillWithZeros(_physicalAddress, Horizon.FontSize); + + uint fontOffset = 0; + + FontInfo CreateFont(string name) + { + if (contentManager.TryGetFontTitle(name, out long fontTitle) && + contentManager.TryGetFontFilename(name, out string fontFilename)) + { + string contentPath = contentManager.GetInstalledContentPath(fontTitle, StorageId.NandSystem, NcaContentType.Data); + string fontPath = _device.FileSystem.SwitchPathToSystemPath(contentPath); + + if (!string.IsNullOrWhiteSpace(fontPath)) + { + byte[] data; + + using (IStorage ncaFileStream = new LocalStorage(fontPath, FileAccess.Read, FileMode.Open)) + { + Nca nca = new Nca(_device.System.KeySet, ncaFileStream); + IFileSystem romfs = nca.OpenFileSystem(NcaSectionType.Data, _device.System.FsIntegrityCheckLevel); + + romfs.OpenFile(out IFile fontFile, "/" + fontFilename, OpenMode.Read).ThrowIfFailure(); + + data = DecryptFont(fontFile.AsStream()); + } + + FontInfo info = new FontInfo((int)fontOffset, data.Length); + + WriteMagicAndSize(_physicalAddress + fontOffset, data.Length); + + fontOffset += 8; + + uint start = fontOffset; + + for (; fontOffset - start < data.Length; fontOffset++) + { + _device.Memory.WriteByte(_physicalAddress + fontOffset, data[fontOffset - start]); + } + + return info; + } + } + + string fontFilePath = Path.Combine(_fontsPath, name + ".ttf"); + + if (File.Exists(fontFilePath)) + { + byte[] data = File.ReadAllBytes(fontFilePath); + + FontInfo info = new FontInfo((int)fontOffset, data.Length); + + WriteMagicAndSize(_physicalAddress + fontOffset, data.Length); + + fontOffset += 8; + + uint start = fontOffset; + + for (; fontOffset - start < data.Length; fontOffset++) + { + _device.Memory.WriteByte(_physicalAddress + fontOffset, data[fontOffset - start]); + } + + return info; + } + else if (!ignoreMissingFonts) + { + throw new InvalidSystemResourceException($"Font \"{name}.ttf\" not found. Please provide it in \"{_fontsPath}\"."); + } + + return new FontInfo(); + } + + _fontData = new Dictionary + { + { SharedFontType.JapanUsEurope, CreateFont("FontStandard") }, + { SharedFontType.SimplifiedChinese, CreateFont("FontChineseSimplified") }, + { SharedFontType.SimplifiedChineseEx, CreateFont("FontExtendedChineseSimplified") }, + { SharedFontType.TraditionalChinese, CreateFont("FontChineseTraditional") }, + { SharedFontType.Korean, CreateFont("FontKorean") }, + { SharedFontType.NintendoEx, CreateFont("FontNintendoExtended") } + }; + + if (fontOffset > Horizon.FontSize && !ignoreMissingFonts) + { + throw new InvalidSystemResourceException( + $"The sum of all fonts size exceed the shared memory size. " + + $"Please make sure that the fonts don't exceed {Horizon.FontSize} bytes in total. " + + $"(actual size: {fontOffset} bytes)."); + } + } + } + + private void WriteMagicAndSize(long position, int size) + { + const int decMagic = 0x18029a7f; + const int key = 0x49621806; + + int encryptedSize = BinaryPrimitives.ReverseEndianness(size ^ key); + + _device.Memory.WriteInt32(position + 0, decMagic); + _device.Memory.WriteInt32(position + 4, encryptedSize); + } + + public int GetFontSize(SharedFontType fontType) + { + EnsureInitialized(_device.System.ContentManager, false); + + return _fontData[fontType].Size; + } + + public int GetSharedMemoryAddressOffset(SharedFontType fontType) + { + EnsureInitialized(_device.System.ContentManager, false); + + return _fontData[fontType].Offset + 8; + } + } +} diff --git a/Ryujinx.HLE/OsHle/Services/Pl/SharedFontType.cs b/Ryujinx.HLE/HOS/Font/SharedFontType.cs similarity index 62% rename from Ryujinx.HLE/OsHle/Services/Pl/SharedFontType.cs rename to Ryujinx.HLE/HOS/Font/SharedFontType.cs index 97fd95dc9d..53dca62664 100644 --- a/Ryujinx.HLE/OsHle/Services/Pl/SharedFontType.cs +++ b/Ryujinx.HLE/HOS/Font/SharedFontType.cs @@ -1,12 +1,13 @@ -namespace Ryujinx.HLE.OsHle.Services.Pl +namespace Ryujinx.HLE.HOS.Font { - enum SharedFontType + public enum SharedFontType { JapanUsEurope = 0, SimplifiedChinese = 1, SimplifiedChineseEx = 2, TraditionalChinese = 3, Korean = 4, - NintendoEx = 5 + NintendoEx = 5, + Count } } \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Homebrew.cs b/Ryujinx.HLE/HOS/Homebrew.cs new file mode 100644 index 0000000000..dca02d9181 --- /dev/null +++ b/Ryujinx.HLE/HOS/Homebrew.cs @@ -0,0 +1,77 @@ +using ARMeilleure.Memory; +using System.Text; + +namespace Ryujinx.HLE.HOS +{ + static class Homebrew + { + public const string TemporaryNroSuffix = ".ryu_tmp.nro"; + + // http://switchbrew.org/index.php?title=Homebrew_ABI + public static void WriteHbAbiData(MemoryManager memory, long position, int mainThreadHandle, string switchPath) + { + // MainThreadHandle. + WriteConfigEntry(memory, ref position, 1, 0, mainThreadHandle); + + // NextLoadPath. + WriteConfigEntry(memory, ref position, 2, 0, position + 0x200, position + 0x400); + + // Argv. + long argvPosition = position + 0xC00; + + memory.WriteBytes(argvPosition, Encoding.ASCII.GetBytes(switchPath + "\0")); + + WriteConfigEntry(memory, ref position, 5, 0, 0, argvPosition); + + // AppletType. + WriteConfigEntry(memory, ref position, 7); + + // EndOfList. + WriteConfigEntry(memory, ref position, 0); + } + + private static void WriteConfigEntry( + MemoryManager memory, + ref long position, + int key, + int flags = 0, + long value0 = 0, + long value1 = 0) + { + memory.WriteInt32(position + 0x00, key); + memory.WriteInt32(position + 0x04, flags); + memory.WriteInt64(position + 0x08, value0); + memory.WriteInt64(position + 0x10, value1); + + position += 0x18; + } + + public static string ReadHbAbiNextLoadPath(MemoryManager memory, long position) + { + string fileName = null; + + while (true) + { + long key = memory.ReadInt64(position); + + if (key == 2) + { + long value0 = memory.ReadInt64(position + 0x08); + long value1 = memory.ReadInt64(position + 0x10); + + fileName = MemoryHelper.ReadAsciiString(memory, value0, value1 - value0); + + break; + } + else if (key == 0) + { + break; + } + + position += 0x18; + } + + return fileName; + } + } +} diff --git a/Ryujinx.HLE/HOS/HomebrewRomFsStream.cs b/Ryujinx.HLE/HOS/HomebrewRomFsStream.cs new file mode 100644 index 0000000000..59bc881f0e --- /dev/null +++ b/Ryujinx.HLE/HOS/HomebrewRomFsStream.cs @@ -0,0 +1,77 @@ +using System; +using System.IO; + +namespace Ryujinx.HLE.HOS +{ + class HomebrewRomFsStream : Stream + { + private Stream _baseStream; + private long _positionOffset; + + public HomebrewRomFsStream(Stream baseStream, long positionOffset) + { + _baseStream = baseStream; + _positionOffset = positionOffset; + + _baseStream.Position = _positionOffset; + } + + public override bool CanRead => _baseStream.CanRead; + + public override bool CanSeek => _baseStream.CanSeek; + + public override bool CanWrite => false; + + public override long Length => _baseStream.Length - _positionOffset; + + public override long Position + { + get + { + return _baseStream.Position - _positionOffset; + } + set + { + _baseStream.Position = value + _positionOffset; + } + } + + public override void Flush() + { + _baseStream.Flush(); + } + + public override int Read(byte[] buffer, int offset, int count) + { + return _baseStream.Read(buffer, offset, count); + } + + public override long Seek(long offset, SeekOrigin origin) + { + if (origin == SeekOrigin.Begin) + { + offset += _positionOffset; + } + + return _baseStream.Seek(offset, origin); + } + + public override void SetLength(long value) + { + throw new NotImplementedException(); + } + + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotImplementedException(); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _baseStream.Dispose(); + } + } + } +} diff --git a/Ryujinx.HLE/HOS/Horizon.cs b/Ryujinx.HLE/HOS/Horizon.cs new file mode 100644 index 0000000000..428e19227d --- /dev/null +++ b/Ryujinx.HLE/HOS/Horizon.cs @@ -0,0 +1,866 @@ +using LibHac; +using LibHac.Account; +using LibHac.Common; +using LibHac.Fs; +using LibHac.FsService; +using LibHac.FsSystem; +using LibHac.FsSystem.NcaUtils; +using LibHac.Ncm; +using LibHac.Ns; +using LibHac.Spl; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.FileSystem.Content; +using Ryujinx.HLE.HOS.Font; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Memory; +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Pcv.Bpc; +using Ryujinx.HLE.HOS.Services.Settings; +using Ryujinx.HLE.HOS.Services.Sm; +using Ryujinx.HLE.HOS.Services.Time.Clock; +using Ryujinx.HLE.HOS.SystemState; +using Ryujinx.HLE.Loaders.Executables; +using Ryujinx.HLE.Loaders.Npdm; +using Ryujinx.HLE.Utilities; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Threading; + +using TimeServiceManager = Ryujinx.HLE.HOS.Services.Time.TimeManager; +using NxStaticObject = Ryujinx.HLE.Loaders.Executables.NxStaticObject; + +using static LibHac.Fs.ApplicationSaveDataManagement; + +namespace Ryujinx.HLE.HOS +{ + public class Horizon : IDisposable + { + internal const int InitialKipId = 1; + internal const int InitialProcessId = 0x51; + + internal const int HidSize = 0x40000; + internal const int FontSize = 0x1100000; + internal const int IirsSize = 0x8000; + internal const int TimeSize = 0x1000; + + private const int MemoryBlockAllocatorSize = 0x2710; + + private const ulong UserSlabHeapBase = DramMemoryMap.SlabHeapBase; + private const ulong UserSlabHeapItemSize = KMemoryManager.PageSize; + private const ulong UserSlabHeapSize = 0x3de000; + + internal long PrivilegedProcessLowestId { get; set; } = 1; + internal long PrivilegedProcessHighestId { get; set; } = 8; + + internal Switch Device { get; private set; } + + public SystemStateMgr State { get; private set; } + + internal bool KernelInitialized { get; private set; } + + internal KResourceLimit ResourceLimit { get; private set; } + + internal KMemoryRegionManager[] MemoryRegions { get; private set; } + + internal KMemoryBlockAllocator LargeMemoryBlockAllocator { get; private set; } + internal KMemoryBlockAllocator SmallMemoryBlockAllocator { get; private set; } + + internal KSlabHeap UserSlabHeapPages { get; private set; } + + internal KCriticalSection CriticalSection { get; private set; } + + internal KScheduler Scheduler { get; private set; } + + internal KTimeManager TimeManager { get; private set; } + + internal KSynchronization Synchronization { get; private set; } + + internal KContextIdManager ContextIdManager { get; private set; } + + private long _kipId; + private long _processId; + private long _threadUid; + + internal CountdownEvent ThreadCounter; + + internal SortedDictionary Processes; + + internal ConcurrentDictionary AutoObjectNames; + + internal bool EnableVersionChecks { get; private set; } + + internal AppletStateMgr AppletState { get; private set; } + + internal KSharedMemory HidSharedMem { get; private set; } + internal KSharedMemory FontSharedMem { get; private set; } + internal KSharedMemory IirsSharedMem { get; private set; } + internal SharedFontManager Font { get; private set; } + + internal ContentManager ContentManager { get; private set; } + + internal KEvent VsyncEvent { get; private set; } + + public Keyset KeySet { get; private set; } + + private bool _hasStarted; + + public BlitStruct ControlData { get; set; } + + public string TitleName { get; private set; } + + public ulong TitleId { get; private set; } + public string TitleIdText => TitleId.ToString("x16"); + + public IntegrityCheckLevel FsIntegrityCheckLevel { get; set; } + + public int GlobalAccessLogMode { get; set; } + + internal long HidBaseAddress { get; private set; } + + internal FileSystemServer FsServer { get; private set; } + public FileSystemClient FsClient { get; private set; } + + internal EmulatedGameCard GameCard { get; private set; } + + public Horizon(Switch device) + { + ControlData = new BlitStruct(1); + + Device = device; + + State = new SystemStateMgr(); + + ResourceLimit = new KResourceLimit(this); + + KernelInit.InitializeResourceLimit(ResourceLimit); + + MemoryRegions = KernelInit.GetMemoryRegions(); + + LargeMemoryBlockAllocator = new KMemoryBlockAllocator(MemoryBlockAllocatorSize * 2); + SmallMemoryBlockAllocator = new KMemoryBlockAllocator(MemoryBlockAllocatorSize); + + UserSlabHeapPages = new KSlabHeap( + UserSlabHeapBase, + UserSlabHeapItemSize, + UserSlabHeapSize); + + CriticalSection = new KCriticalSection(this); + + Scheduler = new KScheduler(this); + + TimeManager = new KTimeManager(); + + Synchronization = new KSynchronization(this); + + ContextIdManager = new KContextIdManager(); + + _kipId = InitialKipId; + _processId = InitialProcessId; + + Scheduler.StartAutoPreemptionThread(); + + KernelInitialized = true; + + ThreadCounter = new CountdownEvent(1); + + Processes = new SortedDictionary(); + + AutoObjectNames = new ConcurrentDictionary(); + + // Note: This is not really correct, but with HLE of services, the only memory + // region used that is used is Application, so we can use the other ones for anything. + KMemoryRegionManager region = MemoryRegions[(int)MemoryRegion.NvServices]; + + ulong hidPa = region.Address; + ulong fontPa = region.Address + HidSize; + ulong iirsPa = region.Address + HidSize + FontSize; + ulong timePa = region.Address + HidSize + FontSize + IirsSize; + + HidBaseAddress = (long)(hidPa - DramMemoryMap.DramBase); + + KPageList hidPageList = new KPageList(); + KPageList fontPageList = new KPageList(); + KPageList iirsPageList = new KPageList(); + KPageList timePageList = new KPageList(); + + hidPageList .AddRange(hidPa, HidSize / KMemoryManager.PageSize); + fontPageList.AddRange(fontPa, FontSize / KMemoryManager.PageSize); + iirsPageList.AddRange(iirsPa, IirsSize / KMemoryManager.PageSize); + timePageList.AddRange(timePa, TimeSize / KMemoryManager.PageSize); + + HidSharedMem = new KSharedMemory(this, hidPageList, 0, 0, MemoryPermission.Read); + FontSharedMem = new KSharedMemory(this, fontPageList, 0, 0, MemoryPermission.Read); + IirsSharedMem = new KSharedMemory(this, iirsPageList, 0, 0, MemoryPermission.Read); + + KSharedMemory timeSharedMemory = new KSharedMemory(this, timePageList, 0, 0, MemoryPermission.Read); + + TimeServiceManager.Instance.Initialize(device, this, timeSharedMemory, (long)(timePa - DramMemoryMap.DramBase), TimeSize); + + AppletState = new AppletStateMgr(this); + + AppletState.SetFocus(true); + + Font = new SharedFontManager(device, (long)(fontPa - DramMemoryMap.DramBase)); + + IUserInterface.InitializePort(this); + + VsyncEvent = new KEvent(this); + + LoadKeySet(); + + ContentManager = new ContentManager(device); + + // TODO: use set:sys (and get external clock source id from settings) + // TODO: use "time!standard_steady_clock_rtc_update_interval_minutes" and implement a worker thread to be accurate. + UInt128 clockSourceId = new UInt128(Guid.NewGuid().ToByteArray()); + IRtcManager.GetExternalRtcValue(out ulong rtcValue); + + // We assume the rtc is system time. + TimeSpanType systemTime = TimeSpanType.FromSeconds((long)rtcValue); + + // First init the standard steady clock + TimeServiceManager.Instance.SetupStandardSteadyClock(null, clockSourceId, systemTime, TimeSpanType.Zero, TimeSpanType.Zero, false); + TimeServiceManager.Instance.SetupStandardLocalSystemClock(null, new SystemClockContext(), systemTime.ToSeconds()); + + if (NxSettings.Settings.TryGetValue("time!standard_network_clock_sufficient_accuracy_minutes", out object standardNetworkClockSufficientAccuracyMinutes)) + { + TimeSpanType standardNetworkClockSufficientAccuracy = new TimeSpanType((int)standardNetworkClockSufficientAccuracyMinutes * 60000000000); + + TimeServiceManager.Instance.SetupStandardNetworkSystemClock(new SystemClockContext(), standardNetworkClockSufficientAccuracy); + } + + TimeServiceManager.Instance.SetupStandardUserSystemClock(null, false, SteadyClockTimePoint.GetRandom()); + + // FIXME: TimeZone shoud be init here but it's actually done in ContentManager + + TimeServiceManager.Instance.SetupEphemeralNetworkSystemClock(); + + LocalFileSystem serverBaseFs = new LocalFileSystem(device.FileSystem.GetBasePath()); + + DefaultFsServerObjects fsServerObjects = DefaultFsServerObjects.GetDefaultEmulatedCreators(serverBaseFs, KeySet); + + GameCard = fsServerObjects.GameCard; + + FileSystemServerConfig fsServerConfig = new FileSystemServerConfig + { + FsCreators = fsServerObjects.FsCreators, + DeviceOperator = fsServerObjects.DeviceOperator, + ExternalKeySet = KeySet.ExternalKeySet + }; + + FsServer = new FileSystemServer(fsServerConfig); + FsClient = FsServer.CreateFileSystemClient(); + } + + public void LoadCart(string exeFsDir, string romFsFile = null) + { + if (romFsFile != null) + { + Device.FileSystem.LoadRomFs(romFsFile); + } + + LocalFileSystem codeFs = new LocalFileSystem(exeFsDir); + + LoadExeFs(codeFs, out _); + } + + public void LoadXci(string xciFile) + { + FileStream file = new FileStream(xciFile, FileMode.Open, FileAccess.Read); + + Xci xci = new Xci(KeySet, file.AsStorage()); + + (Nca mainNca, Nca patchNca, Nca controlNca) = GetXciGameData(xci); + + if (mainNca == null) + { + Logger.PrintError(LogClass.Loader, "Unable to load XCI"); + + return; + } + + ContentManager.LoadEntries(); + + LoadNca(mainNca, patchNca, controlNca); + } + + public void LoadKip(string kipFile) + { + using (FileStream fs = new FileStream(kipFile, FileMode.Open)) + { + ProgramLoader.LoadKernelInitalProcess(this, new KernelInitialProcess(fs)); + } + } + + private (Nca Main, Nca patch, Nca Control) GetXciGameData(Xci xci) + { + if (!xci.HasPartition(XciPartitionType.Secure)) + { + throw new InvalidDataException("Could not find XCI secure partition"); + } + + Nca mainNca = null; + Nca patchNca = null; + Nca controlNca = null; + + XciPartition securePartition = xci.OpenPartition(XciPartitionType.Secure); + + foreach (DirectoryEntryEx ticketEntry in securePartition.EnumerateEntries("/", "*.tik")) + { + Result result = securePartition.OpenFile(out IFile ticketFile, ticketEntry.FullPath, OpenMode.Read); + + if (result.IsSuccess()) + { + Ticket ticket = new Ticket(ticketFile.AsStream()); + + KeySet.ExternalKeySet.Add(new RightsId(ticket.RightsId), new AccessKey(ticket.GetTitleKey(KeySet))); + } + } + + foreach (DirectoryEntryEx fileEntry in securePartition.EnumerateEntries("/", "*.nca")) + { + Result result = securePartition.OpenFile(out IFile ncaFile, fileEntry.FullPath, OpenMode.Read); + if (result.IsFailure()) + { + continue; + } + + Nca nca = new Nca(KeySet, ncaFile.AsStorage()); + + if (nca.Header.ContentType == NcaContentType.Program) + { + int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program); + + if (nca.Header.GetFsHeader(dataIndex).IsPatchSection()) + { + patchNca = nca; + } + else + { + mainNca = nca; + } + } + else if (nca.Header.ContentType == NcaContentType.Control) + { + controlNca = nca; + } + } + + if (mainNca == null) + { + Logger.PrintError(LogClass.Loader, "Could not find an Application NCA in the provided XCI file"); + } + + if (controlNca != null) + { + ReadControlData(controlNca); + } + else + { + ControlData.ByteSpan.Clear(); + } + + return (mainNca, patchNca, controlNca); + } + + public void ReadControlData(Nca controlNca) + { + IFileSystem controlFs = controlNca.OpenFileSystem(NcaSectionType.Data, FsIntegrityCheckLevel); + + Result result = controlFs.OpenFile(out IFile controlFile, "/control.nacp", OpenMode.Read); + + if (result.IsSuccess()) + { + result = controlFile.Read(out long bytesRead, 0, ControlData.ByteSpan, ReadOption.None); + + if (result.IsSuccess() && bytesRead == ControlData.ByteSpan.Length) + { + TitleName = ControlData.Value + .Titles[(int) State.DesiredTitleLanguage].Name.ToString(); + + if (string.IsNullOrWhiteSpace(TitleName)) + { + TitleName = ControlData.Value.Titles.ToArray() + .FirstOrDefault(x => x.Name[0] != 0).Name.ToString(); + } + } + } + else + { + ControlData.ByteSpan.Clear(); + } + } + + public void LoadNca(string ncaFile) + { + FileStream file = new FileStream(ncaFile, FileMode.Open, FileAccess.Read); + + Nca nca = new Nca(KeySet, file.AsStorage(false)); + + LoadNca(nca, null, null); + } + + public void LoadNsp(string nspFile) + { + FileStream file = new FileStream(nspFile, FileMode.Open, FileAccess.Read); + + PartitionFileSystem nsp = new PartitionFileSystem(file.AsStorage()); + + foreach (DirectoryEntryEx ticketEntry in nsp.EnumerateEntries("/", "*.tik")) + { + Result result = nsp.OpenFile(out IFile ticketFile, ticketEntry.FullPath, OpenMode.Read); + + if (result.IsSuccess()) + { + Ticket ticket = new Ticket(ticketFile.AsStream()); + + KeySet.ExternalKeySet.Add(new RightsId(ticket.RightsId), new AccessKey(ticket.GetTitleKey(KeySet))); + } + } + + Nca mainNca = null; + Nca patchNca = null; + Nca controlNca = null; + + foreach (DirectoryEntryEx fileEntry in nsp.EnumerateEntries("/", "*.nca")) + { + nsp.OpenFile(out IFile ncaFile, fileEntry.FullPath, OpenMode.Read).ThrowIfFailure(); + + Nca nca = new Nca(KeySet, ncaFile.AsStorage()); + + if (nca.Header.ContentType == NcaContentType.Program) + { + int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program); + + if (nca.Header.GetFsHeader(dataIndex).IsPatchSection()) + { + patchNca = nca; + } + else + { + mainNca = nca; + } + } + else if (nca.Header.ContentType == NcaContentType.Control) + { + controlNca = nca; + } + } + + if (mainNca != null) + { + LoadNca(mainNca, patchNca, controlNca); + + return; + } + + // This is not a normal NSP, it's actually a ExeFS as a NSP + LoadExeFs(nsp, out _); + } + + public void LoadNca(Nca mainNca, Nca patchNca, Nca controlNca) + { + if (mainNca.Header.ContentType != NcaContentType.Program) + { + Logger.PrintError(LogClass.Loader, "Selected NCA is not a \"Program\" NCA"); + + return; + } + + IStorage dataStorage = null; + IFileSystem codeFs = null; + + if (patchNca == null) + { + if (mainNca.CanOpenSection(NcaSectionType.Data)) + { + dataStorage = mainNca.OpenStorage(NcaSectionType.Data, FsIntegrityCheckLevel); + } + + if (mainNca.CanOpenSection(NcaSectionType.Code)) + { + codeFs = mainNca.OpenFileSystem(NcaSectionType.Code, FsIntegrityCheckLevel); + } + } + else + { + if (patchNca.CanOpenSection(NcaSectionType.Data)) + { + dataStorage = mainNca.OpenStorageWithPatch(patchNca, NcaSectionType.Data, FsIntegrityCheckLevel); + } + + if (patchNca.CanOpenSection(NcaSectionType.Code)) + { + codeFs = mainNca.OpenFileSystemWithPatch(patchNca, NcaSectionType.Code, FsIntegrityCheckLevel); + } + } + + if (codeFs == null) + { + Logger.PrintError(LogClass.Loader, "No ExeFS found in NCA"); + + return; + } + + if (dataStorage == null) + { + Logger.PrintWarning(LogClass.Loader, "No RomFS found in NCA"); + } + else + { + Device.FileSystem.SetRomFs(dataStorage.AsStream(FileAccess.Read)); + } + + LoadExeFs(codeFs, out Npdm metaData); + + TitleId = metaData.Aci0.TitleId; + + if (controlNca != null) + { + ReadControlData(controlNca); + } + else + { + ControlData.ByteSpan.Clear(); + } + + if (TitleId != 0) + { + EnsureSaveData(new TitleId(TitleId)); + } + } + + private void LoadExeFs(IFileSystem codeFs, out Npdm metaData) + { + Result result = codeFs.OpenFile(out IFile npdmFile, "/main.npdm", OpenMode.Read); + + if (result == ResultFs.PathNotFound) + { + Logger.PrintWarning(LogClass.Loader, "NPDM file not found, using default values!"); + + metaData = GetDefaultNpdm(); + } + else + { + metaData = new Npdm(npdmFile.AsStream()); + } + + List staticObjects = new List(); + + void LoadNso(string filename) + { + foreach (DirectoryEntryEx file in codeFs.EnumerateEntries("/", $"{filename}*")) + { + if (Path.GetExtension(file.Name) != string.Empty) + { + continue; + } + + Logger.PrintInfo(LogClass.Loader, $"Loading {file.Name}..."); + + codeFs.OpenFile(out IFile nsoFile, file.FullPath, OpenMode.Read).ThrowIfFailure(); + + NxStaticObject staticObject = new NxStaticObject(nsoFile.AsStream()); + + staticObjects.Add(staticObject); + } + } + + TitleId = metaData.Aci0.TitleId; + + LoadNso("rtld"); + LoadNso("main"); + LoadNso("subsdk"); + LoadNso("sdk"); + + ContentManager.LoadEntries(); + + ProgramLoader.LoadStaticObjects(this, metaData, staticObjects.ToArray()); + } + + public void LoadProgram(string filePath) + { + Npdm metaData = GetDefaultNpdm(); + + bool isNro = Path.GetExtension(filePath).ToLower() == ".nro"; + + FileStream input = new FileStream(filePath, FileMode.Open); + + IExecutable staticObject; + + if (isNro) + { + NxRelocatableObject obj = new NxRelocatableObject(input); + staticObject = obj; + + // homebrew NRO can actually have some data after the actual NRO + if (input.Length > obj.FileSize) + { + input.Position = obj.FileSize; + + BinaryReader reader = new BinaryReader(input); + + uint asetMagic = reader.ReadUInt32(); + + if (asetMagic == 0x54455341) + { + uint asetVersion = reader.ReadUInt32(); + if (asetVersion == 0) + { + ulong iconOffset = reader.ReadUInt64(); + ulong iconSize = reader.ReadUInt64(); + + ulong nacpOffset = reader.ReadUInt64(); + ulong nacpSize = reader.ReadUInt64(); + + ulong romfsOffset = reader.ReadUInt64(); + ulong romfsSize = reader.ReadUInt64(); + + if (romfsSize != 0) + { + Device.FileSystem.SetRomFs(new HomebrewRomFsStream(input, obj.FileSize + (long)romfsOffset)); + } + + if (nacpSize != 0) + { + input.Seek(obj.FileSize + (long)nacpOffset, SeekOrigin.Begin); + + reader.Read(ControlData.ByteSpan); + + ref ApplicationControlProperty nacp = ref ControlData.Value; + + metaData.TitleName = nacp.Titles[(int)State.DesiredTitleLanguage].Name.ToString(); + + if (string.IsNullOrWhiteSpace(metaData.TitleName)) + { + metaData.TitleName = nacp.Titles.ToArray().FirstOrDefault(x => x.Name[0] != 0).Name.ToString(); + } + + metaData.Aci0.TitleId = nacp.PresenceGroupId; + + if (metaData.Aci0.TitleId == 0) + { + metaData.Aci0.TitleId = nacp.SaveDataOwnerId.Value; + } + + if (metaData.Aci0.TitleId == 0) + { + metaData.Aci0.TitleId = nacp.AddOnContentBaseId - 0x1000; + } + + if (metaData.Aci0.TitleId.ToString("x16") == "fffffffffffff000") + { + metaData.Aci0.TitleId = 0000000000000000; + } + } + } + else + { + Logger.PrintWarning(LogClass.Loader, $"Unsupported ASET header version found \"{asetVersion}\""); + } + } + } + } + else + { + staticObject = new NxStaticObject(input); + } + + ContentManager.LoadEntries(); + + TitleName = metaData.TitleName; + TitleId = metaData.Aci0.TitleId; + + ProgramLoader.LoadStaticObjects(this, metaData, new IExecutable[] { staticObject }); + } + + private Npdm GetDefaultNpdm() + { + Assembly asm = Assembly.GetCallingAssembly(); + + using (Stream npdmStream = asm.GetManifestResourceStream("Ryujinx.HLE.Homebrew.npdm")) + { + return new Npdm(npdmStream); + } + } + + private Result EnsureSaveData(TitleId titleId) + { + Logger.PrintInfo(LogClass.Application, "Ensuring required savedata exists."); + + UInt128 lastOpenedUser = State.Account.LastOpenedUser.UserId; + Uid user = new Uid((ulong)lastOpenedUser.Low, (ulong)lastOpenedUser.High); + + ref ApplicationControlProperty control = ref ControlData.Value; + + if (LibHac.Util.IsEmpty(ControlData.ByteSpan)) + { + // If the current application doesn't have a loaded control property, create a dummy one + // and set the savedata sizes so a user savedata will be created. + control = ref new BlitStruct(1).Value; + + // The set sizes don't actually matter as long as they're non-zero because we use directory savedata. + control.UserAccountSaveDataSize = 0x4000; + control.UserAccountSaveDataJournalSize = 0x4000; + + Logger.PrintWarning(LogClass.Application, + "No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games."); + } + + Result rc = EnsureApplicationSaveData(FsClient, out _, titleId, ref ControlData.Value, ref user); + + if (rc.IsFailure()) + { + Logger.PrintError(LogClass.Application, $"Error calling EnsureApplicationSaveData. Result code {rc.ToStringWithName()}"); + } + + return rc; + } + + public void LoadKeySet() + { + string keyFile = null; + string titleKeyFile = null; + string consoleKeyFile = null; + + string home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + + LoadSetAtPath(Path.Combine(home, ".switch")); + LoadSetAtPath(Device.FileSystem.GetSystemPath()); + + KeySet = ExternalKeyReader.ReadKeyFile(keyFile, titleKeyFile, consoleKeyFile); + + void LoadSetAtPath(string basePath) + { + string localKeyFile = Path.Combine(basePath, "prod.keys"); + string localTitleKeyFile = Path.Combine(basePath, "title.keys"); + string localConsoleKeyFile = Path.Combine(basePath, "console.keys"); + + if (File.Exists(localKeyFile)) + { + keyFile = localKeyFile; + } + + if (File.Exists(localTitleKeyFile)) + { + titleKeyFile = localTitleKeyFile; + } + + if (File.Exists(localConsoleKeyFile)) + { + consoleKeyFile = localConsoleKeyFile; + } + } + } + + public SystemVersion VerifyFirmwarePackage(string firmwarePackage) + { + return ContentManager.VerifyFirmwarePackage(firmwarePackage); + } + + public SystemVersion GetCurrentFirmwareVersion() + { + return ContentManager.GetCurrentFirmwareVersion(); + } + + public void InstallFirmware(string firmwarePackage) + { + ContentManager.InstallFirmware(firmwarePackage); + } + + public void SignalVsync() + { + VsyncEvent.ReadableEvent.Signal(); + } + + internal long GetThreadUid() + { + return Interlocked.Increment(ref _threadUid) - 1; + } + + internal long GetKipId() + { + return Interlocked.Increment(ref _kipId) - 1; + } + + internal long GetProcessId() + { + return Interlocked.Increment(ref _processId) - 1; + } + + public void EnableMultiCoreScheduling() + { + if (!_hasStarted) + { + Scheduler.MultiCoreScheduling = true; + } + } + + public void DisableMultiCoreScheduling() + { + if (!_hasStarted) + { + Scheduler.MultiCoreScheduling = false; + } + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + KProcess terminationProcess = new KProcess(this); + + KThread terminationThread = new KThread(this); + + terminationThread.Initialize(0, 0, 0, 3, 0, terminationProcess, ThreadType.Kernel, () => + { + // Force all threads to exit. + lock (Processes) + { + foreach (KProcess process in Processes.Values) + { + process.Terminate(); + } + } + + // Exit ourself now! + Scheduler.ExitThread(terminationThread); + Scheduler.GetCurrentThread().Exit(); + Scheduler.RemoveThread(terminationThread); + }); + + terminationThread.Start(); + + // Signal the vsync event to avoid issues of KThread waiting on it. + if (Device.EnableDeviceVsync) + { + Device.VsyncEvent.Set(); + } + + // This is needed as the IPC Dummy KThread is also counted in the ThreadCounter. + ThreadCounter.Signal(); + + // It's only safe to release resources once all threads + // have exited. + ThreadCounter.Signal(); + ThreadCounter.Wait(); + + Scheduler.Dispose(); + + TimeManager.Dispose(); + + Device.Unload(); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/IdDictionary.cs b/Ryujinx.HLE/HOS/IdDictionary.cs new file mode 100644 index 0000000000..c6356725ab --- /dev/null +++ b/Ryujinx.HLE/HOS/IdDictionary.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS +{ + class IdDictionary + { + private ConcurrentDictionary _objs; + + public IdDictionary() + { + _objs = new ConcurrentDictionary(); + } + + public bool Add(int id, object data) + { + return _objs.TryAdd(id, data); + } + + public int Add(object data) + { + for (int id = 1; id < int.MaxValue; id++) + { + if (_objs.TryAdd(id, data)) + { + return id; + } + } + + throw new InvalidOperationException(); + } + + public object GetData(int id) + { + if (_objs.TryGetValue(id, out object data)) + { + return data; + } + + return null; + } + + public T GetData(int id) + { + if (_objs.TryGetValue(id, out object data) && data is T) + { + return (T)data; + } + + return default(T); + } + + public object Delete(int id) + { + if (_objs.TryRemove(id, out object obj)) + { + return obj; + } + + return null; + } + + public ICollection Clear() + { + ICollection values = _objs.Values; + + _objs.Clear(); + + return values; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Ipc/IpcBuffDesc.cs b/Ryujinx.HLE/HOS/Ipc/IpcBuffDesc.cs new file mode 100644 index 0000000000..dddd2671a1 --- /dev/null +++ b/Ryujinx.HLE/HOS/Ipc/IpcBuffDesc.cs @@ -0,0 +1,27 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Ipc +{ + struct IpcBuffDesc + { + public long Position { get; private set; } + public long Size { get; private set; } + public int Flags { get; private set; } + + public IpcBuffDesc(BinaryReader reader) + { + long word0 = reader.ReadUInt32(); + long word1 = reader.ReadUInt32(); + long word2 = reader.ReadUInt32(); + + Position = word1; + Position |= (word2 << 4) & 0x0f00000000; + Position |= (word2 << 34) & 0x7000000000; + + Size = word0; + Size |= (word2 << 8) & 0xf00000000; + + Flags = (int)word2 & 3; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Ipc/IpcHandleDesc.cs b/Ryujinx.HLE/HOS/Ipc/IpcHandleDesc.cs new file mode 100644 index 0000000000..5b1d5788f6 --- /dev/null +++ b/Ryujinx.HLE/HOS/Ipc/IpcHandleDesc.cs @@ -0,0 +1,92 @@ +using System; +using System.IO; + +namespace Ryujinx.HLE.HOS.Ipc +{ + class IpcHandleDesc + { + public bool HasPId { get; private set; } + + public long PId { get; private set; } + + public int[] ToCopy { get; private set; } + public int[] ToMove { get; private set; } + + public IpcHandleDesc(BinaryReader reader) + { + int word = reader.ReadInt32(); + + HasPId = (word & 1) != 0; + + ToCopy = new int[(word >> 1) & 0xf]; + ToMove = new int[(word >> 5) & 0xf]; + + PId = HasPId ? reader.ReadInt64() : 0; + + for (int index = 0; index < ToCopy.Length; index++) + { + ToCopy[index] = reader.ReadInt32(); + } + + for (int index = 0; index < ToMove.Length; index++) + { + ToMove[index] = reader.ReadInt32(); + } + } + + public IpcHandleDesc(int[] copy, int[] move) + { + ToCopy = copy ?? throw new ArgumentNullException(nameof(copy)); + ToMove = move ?? throw new ArgumentNullException(nameof(move)); + } + + public IpcHandleDesc(int[] copy, int[] move, long pId) : this(copy, move) + { + PId = pId; + + HasPId = true; + } + + public static IpcHandleDesc MakeCopy(params int[] handles) + { + return new IpcHandleDesc(handles, new int[0]); + } + + public static IpcHandleDesc MakeMove(params int[] handles) + { + return new IpcHandleDesc(new int[0], handles); + } + + public byte[] GetBytes() + { + using (MemoryStream ms = new MemoryStream()) + { + BinaryWriter writer = new BinaryWriter(ms); + + int word = HasPId ? 1 : 0; + + word |= (ToCopy.Length & 0xf) << 1; + word |= (ToMove.Length & 0xf) << 5; + + writer.Write(word); + + if (HasPId) + { + writer.Write(PId); + } + + foreach (int handle in ToCopy) + { + writer.Write(handle); + } + + foreach (int handle in ToMove) + { + writer.Write(handle); + } + + return ms.ToArray(); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Ipc/IpcHandler.cs b/Ryujinx.HLE/HOS/Ipc/IpcHandler.cs new file mode 100644 index 0000000000..ee3ace68d2 --- /dev/null +++ b/Ryujinx.HLE/HOS/Ipc/IpcHandler.cs @@ -0,0 +1,149 @@ +using ARMeilleure.Memory; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Ipc; +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.HLE.HOS.Kernel.Threading; +using System; +using System.IO; + +namespace Ryujinx.HLE.HOS.Ipc +{ + static class IpcHandler + { + public static KernelResult IpcCall( + Switch device, + KProcess process, + MemoryManager memory, + KThread thread, + KClientSession session, + IpcMessage request, + long cmdPtr) + { + IpcMessage response = new IpcMessage(); + + using (MemoryStream raw = new MemoryStream(request.RawData)) + { + BinaryReader reqReader = new BinaryReader(raw); + + if (request.Type == IpcMessageType.Request || + request.Type == IpcMessageType.RequestWithContext) + { + response.Type = IpcMessageType.Response; + + using (MemoryStream resMs = new MemoryStream()) + { + BinaryWriter resWriter = new BinaryWriter(resMs); + + ServiceCtx context = new ServiceCtx( + device, + process, + memory, + thread, + session, + request, + response, + reqReader, + resWriter); + + session.Service.CallMethod(context); + + response.RawData = resMs.ToArray(); + } + } + else if (request.Type == IpcMessageType.Control || + request.Type == IpcMessageType.ControlWithContext) + { + long magic = reqReader.ReadInt64(); + long cmdId = reqReader.ReadInt64(); + + switch (cmdId) + { + case 0: + { + request = FillResponse(response, 0, session.Service.ConvertToDomain()); + + break; + } + + case 3: + { + request = FillResponse(response, 0, 0x1000); + + break; + } + + // TODO: Whats the difference between IpcDuplicateSession/Ex? + case 2: + case 4: + { + int unknown = reqReader.ReadInt32(); + + if (process.HandleTable.GenerateHandle(session, out int handle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + response.HandleDesc = IpcHandleDesc.MakeMove(handle); + + request = FillResponse(response, 0); + + break; + } + + default: throw new NotImplementedException(cmdId.ToString()); + } + } + else if (request.Type == IpcMessageType.CloseSession) + { + // TODO + return KernelResult.PortRemoteClosed; + } + else + { + throw new NotImplementedException(request.Type.ToString()); + } + + memory.WriteBytes(cmdPtr, response.GetBytes(cmdPtr)); + } + + return KernelResult.Success; + } + + private static IpcMessage FillResponse(IpcMessage response, long result, params int[] values) + { + using (MemoryStream ms = new MemoryStream()) + { + BinaryWriter writer = new BinaryWriter(ms); + + foreach (int value in values) + { + writer.Write(value); + } + + return FillResponse(response, result, ms.ToArray()); + } + } + + private static IpcMessage FillResponse(IpcMessage response, long result, byte[] data = null) + { + response.Type = IpcMessageType.Response; + + using (MemoryStream ms = new MemoryStream()) + { + BinaryWriter writer = new BinaryWriter(ms); + + writer.Write(IpcMagic.Sfco); + writer.Write(result); + + if (data != null) + { + writer.Write(data); + } + + response.RawData = ms.ToArray(); + } + + return response; + } + } +} diff --git a/Ryujinx.HLE/OsHle/Ipc/IpcMagic.cs b/Ryujinx.HLE/HOS/Ipc/IpcMagic.cs similarity index 86% rename from Ryujinx.HLE/OsHle/Ipc/IpcMagic.cs rename to Ryujinx.HLE/HOS/Ipc/IpcMagic.cs index c3f9655fb8..72770b90ed 100644 --- a/Ryujinx.HLE/OsHle/Ipc/IpcMagic.cs +++ b/Ryujinx.HLE/HOS/Ipc/IpcMagic.cs @@ -1,4 +1,4 @@ -namespace Ryujinx.HLE.OsHle.Ipc +namespace Ryujinx.HLE.HOS.Ipc { abstract class IpcMagic { diff --git a/Ryujinx.HLE/HOS/Ipc/IpcMessage.cs b/Ryujinx.HLE/HOS/Ipc/IpcMessage.cs new file mode 100644 index 0000000000..012c3167a3 --- /dev/null +++ b/Ryujinx.HLE/HOS/Ipc/IpcMessage.cs @@ -0,0 +1,217 @@ +using System.Collections.Generic; +using System.IO; + +namespace Ryujinx.HLE.HOS.Ipc +{ + class IpcMessage + { + public IpcMessageType Type { get; set; } + + public IpcHandleDesc HandleDesc { get; set; } + + public List PtrBuff { get; private set; } + public List SendBuff { get; private set; } + public List ReceiveBuff { get; private set; } + public List ExchangeBuff { get; private set; } + public List RecvListBuff { get; private set; } + + public List ObjectIds { get; private set; } + + public byte[] RawData { get; set; } + + public IpcMessage() + { + PtrBuff = new List(); + SendBuff = new List(); + ReceiveBuff = new List(); + ExchangeBuff = new List(); + RecvListBuff = new List(); + + ObjectIds = new List(); + } + + public IpcMessage(byte[] data, long cmdPtr) : this() + { + using (MemoryStream ms = new MemoryStream(data)) + { + BinaryReader reader = new BinaryReader(ms); + + Initialize(reader, cmdPtr); + } + } + + private void Initialize(BinaryReader reader, long cmdPtr) + { + int word0 = reader.ReadInt32(); + int word1 = reader.ReadInt32(); + + Type = (IpcMessageType)(word0 & 0xffff); + + int ptrBuffCount = (word0 >> 16) & 0xf; + int sendBuffCount = (word0 >> 20) & 0xf; + int recvBuffCount = (word0 >> 24) & 0xf; + int xchgBuffCount = (word0 >> 28) & 0xf; + + int rawDataSize = (word1 >> 0) & 0x3ff; + int recvListFlags = (word1 >> 10) & 0xf; + bool hndDescEnable = ((word1 >> 31) & 0x1) != 0; + + if (hndDescEnable) + { + HandleDesc = new IpcHandleDesc(reader); + } + + for (int index = 0; index < ptrBuffCount; index++) + { + PtrBuff.Add(new IpcPtrBuffDesc(reader)); + } + + void ReadBuff(List buff, int count) + { + for (int index = 0; index < count; index++) + { + buff.Add(new IpcBuffDesc(reader)); + } + } + + ReadBuff(SendBuff, sendBuffCount); + ReadBuff(ReceiveBuff, recvBuffCount); + ReadBuff(ExchangeBuff, xchgBuffCount); + + rawDataSize *= 4; + + long recvListPos = reader.BaseStream.Position + rawDataSize; + + long pad0 = GetPadSize16(reader.BaseStream.Position + cmdPtr); + + reader.BaseStream.Seek(pad0, SeekOrigin.Current); + + int recvListCount = recvListFlags - 2; + + if (recvListCount == 0) + { + recvListCount = 1; + } + else if (recvListCount < 0) + { + recvListCount = 0; + } + + RawData = reader.ReadBytes(rawDataSize); + + reader.BaseStream.Seek(recvListPos, SeekOrigin.Begin); + + for (int index = 0; index < recvListCount; index++) + { + RecvListBuff.Add(new IpcRecvListBuffDesc(reader)); + } + } + + public byte[] GetBytes(long cmdPtr) + { + using (MemoryStream ms = new MemoryStream()) + { + BinaryWriter writer = new BinaryWriter(ms); + + int word0; + int word1; + + word0 = (int)Type; + word0 |= (PtrBuff.Count & 0xf) << 16; + word0 |= (SendBuff.Count & 0xf) << 20; + word0 |= (ReceiveBuff.Count & 0xf) << 24; + word0 |= (ExchangeBuff.Count & 0xf) << 28; + + byte[] handleData = new byte[0]; + + if (HandleDesc != null) + { + handleData = HandleDesc.GetBytes(); + } + + int dataLength = RawData?.Length ?? 0; + + int pad0 = (int)GetPadSize16(cmdPtr + 8 + handleData.Length); + + // Apparently, padding after Raw Data is 16 bytes, however when there is + // padding before Raw Data too, we need to subtract the size of this padding. + // This is the weirdest padding I've seen so far... + int pad1 = 0x10 - pad0; + + dataLength = (dataLength + pad0 + pad1) / 4; + + word1 = dataLength & 0x3ff; + + if (HandleDesc != null) + { + word1 |= 1 << 31; + } + + writer.Write(word0); + writer.Write(word1); + writer.Write(handleData); + + ms.Seek(pad0, SeekOrigin.Current); + + if (RawData != null) + { + writer.Write(RawData); + } + + writer.Write(new byte[pad1]); + + return ms.ToArray(); + } + } + + private long GetPadSize16(long position) + { + if ((position & 0xf) != 0) + { + return 0x10 - (position & 0xf); + } + + return 0; + } + + // ReSharper disable once InconsistentNaming + public (long Position, long Size) GetBufferType0x21(int index = 0) + { + if (PtrBuff.Count > index && + PtrBuff[index].Position != 0 && + PtrBuff[index].Size != 0) + { + return (PtrBuff[index].Position, PtrBuff[index].Size); + } + + if (SendBuff.Count > index && + SendBuff[index].Position != 0 && + SendBuff[index].Size != 0) + { + return (SendBuff[index].Position, SendBuff[index].Size); + } + + return (0, 0); + } + + // ReSharper disable once InconsistentNaming + public (long Position, long Size) GetBufferType0x22(int index = 0) + { + if (RecvListBuff.Count > index && + RecvListBuff[index].Position != 0 && + RecvListBuff[index].Size != 0) + { + return (RecvListBuff[index].Position, RecvListBuff[index].Size); + } + + if (ReceiveBuff.Count > index && + ReceiveBuff[index].Position != 0 && + ReceiveBuff[index].Size != 0) + { + return (ReceiveBuff[index].Position, ReceiveBuff[index].Size); + } + + return (0, 0); + } + } +} diff --git a/Ryujinx.HLE/HOS/Ipc/IpcMessageType.cs b/Ryujinx.HLE/HOS/Ipc/IpcMessageType.cs new file mode 100644 index 0000000000..e258accc9b --- /dev/null +++ b/Ryujinx.HLE/HOS/Ipc/IpcMessageType.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.HLE.HOS.Ipc +{ + enum IpcMessageType + { + Response = 0, + CloseSession = 2, + Request = 4, + Control = 5, + RequestWithContext = 6, + ControlWithContext = 7 + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Ipc/IpcPtrBuffDesc.cs b/Ryujinx.HLE/HOS/Ipc/IpcPtrBuffDesc.cs new file mode 100644 index 0000000000..cdc43f17f0 --- /dev/null +++ b/Ryujinx.HLE/HOS/Ipc/IpcPtrBuffDesc.cs @@ -0,0 +1,26 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Ipc +{ + struct IpcPtrBuffDesc + { + public long Position { get; private set; } + public int Index { get; private set; } + public long Size { get; private set; } + + public IpcPtrBuffDesc(BinaryReader reader) + { + long word0 = reader.ReadUInt32(); + long word1 = reader.ReadUInt32(); + + Position = word1; + Position |= (word0 << 20) & 0x0f00000000; + Position |= (word0 << 30) & 0x7000000000; + + Index = ((int)word0 >> 0) & 0x03f; + Index |= ((int)word0 >> 3) & 0x1c0; + + Size = (ushort)(word0 >> 16); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Ipc/IpcRecvListBuffDesc.cs b/Ryujinx.HLE/HOS/Ipc/IpcRecvListBuffDesc.cs new file mode 100644 index 0000000000..3fd92f02a0 --- /dev/null +++ b/Ryujinx.HLE/HOS/Ipc/IpcRecvListBuffDesc.cs @@ -0,0 +1,19 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Ipc +{ + struct IpcRecvListBuffDesc + { + public long Position { get; private set; } + public long Size { get; private set; } + + public IpcRecvListBuffDesc(BinaryReader reader) + { + long value = reader.ReadInt64(); + + Position = value & 0xffffffffffff; + + Size = (ushort)(value >> 48); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Ipc/ServiceProcessRequest.cs b/Ryujinx.HLE/HOS/Ipc/ServiceProcessRequest.cs new file mode 100644 index 0000000000..b3aaa2191b --- /dev/null +++ b/Ryujinx.HLE/HOS/Ipc/ServiceProcessRequest.cs @@ -0,0 +1,4 @@ +namespace Ryujinx.HLE.HOS.Ipc +{ + delegate long ServiceProcessRequest(ServiceCtx context); +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Common/IKFutureSchedulerObject.cs b/Ryujinx.HLE/HOS/Kernel/Common/IKFutureSchedulerObject.cs new file mode 100644 index 0000000000..473683ff72 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Common/IKFutureSchedulerObject.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.HLE.HOS.Kernel.Common +{ + interface IKFutureSchedulerObject + { + void TimeUp(); + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Common/KAutoObject.cs b/Ryujinx.HLE/HOS/Kernel/Common/KAutoObject.cs new file mode 100644 index 0000000000..3b30dd808e --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Common/KAutoObject.cs @@ -0,0 +1,63 @@ +using System.Threading; + +namespace Ryujinx.HLE.HOS.Kernel.Common +{ + class KAutoObject + { + protected Horizon System; + + private int _referenceCount; + + public KAutoObject(Horizon system) + { + System = system; + + _referenceCount = 1; + } + + public virtual KernelResult SetName(string name) + { + if (!System.AutoObjectNames.TryAdd(name, this)) + { + return KernelResult.InvalidState; + } + + return KernelResult.Success; + } + + public static KernelResult RemoveName(Horizon system, string name) + { + if (!system.AutoObjectNames.TryRemove(name, out _)) + { + return KernelResult.NotFound; + } + + return KernelResult.Success; + } + + public static KAutoObject FindNamedObject(Horizon system, string name) + { + if (system.AutoObjectNames.TryGetValue(name, out KAutoObject obj)) + { + return obj; + } + + return null; + } + + public void IncrementReferenceCount() + { + Interlocked.Increment(ref _referenceCount); + } + + public void DecrementReferenceCount() + { + if (Interlocked.Decrement(ref _referenceCount) == 0) + { + Destroy(); + } + } + + protected virtual void Destroy() { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Common/KResourceLimit.cs b/Ryujinx.HLE/HOS/Kernel/Common/KResourceLimit.cs new file mode 100644 index 0000000000..a7955d7ad6 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Common/KResourceLimit.cs @@ -0,0 +1,143 @@ +using Ryujinx.Common; +using Ryujinx.HLE.HOS.Kernel.Threading; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Kernel.Common +{ + class KResourceLimit : KAutoObject + { + private const int Time10SecondsMs = 10000; + + private long[] _current; + private long[] _limit; + private long[] _available; + + private object _lockObj; + + private LinkedList _waitingThreads; + + private int _waitingThreadsCount; + + public KResourceLimit(Horizon system) : base(system) + { + _current = new long[(int)LimitableResource.Count]; + _limit = new long[(int)LimitableResource.Count]; + _available = new long[(int)LimitableResource.Count]; + + _lockObj = new object(); + + _waitingThreads = new LinkedList(); + } + + public bool Reserve(LimitableResource resource, ulong amount) + { + return Reserve(resource, (long)amount); + } + + public bool Reserve(LimitableResource resource, long amount) + { + return Reserve(resource, amount, KTimeManager.ConvertMillisecondsToNanoseconds(Time10SecondsMs)); + } + + public bool Reserve(LimitableResource resource, long amount, long timeout) + { + long endTimePoint = KTimeManager.ConvertNanosecondsToMilliseconds(timeout); + + endTimePoint += PerformanceCounter.ElapsedMilliseconds; + + bool success = false; + + int index = GetIndex(resource); + + lock (_lockObj) + { + long newCurrent = _current[index] + amount; + + while (newCurrent > _limit[index] && _available[index] + amount <= _limit[index]) + { + _waitingThreadsCount++; + + KConditionVariable.Wait(System, _waitingThreads, _lockObj, timeout); + + _waitingThreadsCount--; + + newCurrent = _current[index] + amount; + + if (timeout >= 0 && PerformanceCounter.ElapsedMilliseconds > endTimePoint) + { + break; + } + } + + if (newCurrent <= _limit[index]) + { + _current[index] = newCurrent; + + success = true; + } + } + + return success; + } + + public void Release(LimitableResource resource, ulong amount) + { + Release(resource, (long)amount); + } + + public void Release(LimitableResource resource, long amount) + { + Release(resource, amount, amount); + } + + public void Release(LimitableResource resource, long usedAmount, long availableAmount) + { + int index = GetIndex(resource); + + lock (_lockObj) + { + _current [index] -= usedAmount; + _available[index] -= availableAmount; + + if (_waitingThreadsCount > 0) + { + KConditionVariable.NotifyAll(System, _waitingThreads); + } + } + } + + public long GetRemainingValue(LimitableResource resource) + { + int index = GetIndex(resource); + + lock (_lockObj) + { + return _limit[index] - _current[index]; + } + } + + public KernelResult SetLimitValue(LimitableResource resource, long limit) + { + int index = GetIndex(resource); + + lock (_lockObj) + { + if (_current[index] <= limit) + { + _limit[index] = limit; + + return KernelResult.Success; + } + else + { + return KernelResult.InvalidState; + } + } + } + + private static int GetIndex(LimitableResource resource) + { + return (int)resource; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Common/KSynchronizationObject.cs b/Ryujinx.HLE/HOS/Kernel/Common/KSynchronizationObject.cs new file mode 100644 index 0000000000..77d8fbff8f --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Common/KSynchronizationObject.cs @@ -0,0 +1,35 @@ +using Ryujinx.HLE.HOS.Kernel.Threading; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Kernel.Common +{ + class KSynchronizationObject : KAutoObject + { + public LinkedList WaitingThreads { get; } + + public KSynchronizationObject(Horizon system) : base(system) + { + WaitingThreads = new LinkedList(); + } + + public LinkedListNode AddWaitingThread(KThread thread) + { + return WaitingThreads.AddLast(thread); + } + + public void RemoveWaitingThread(LinkedListNode node) + { + WaitingThreads.Remove(node); + } + + public virtual void Signal() + { + System.Synchronization.SignalObject(this); + } + + public virtual bool IsSignaled() + { + return false; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Common/KTimeManager.cs b/Ryujinx.HLE/HOS/Kernel/Common/KTimeManager.cs new file mode 100644 index 0000000000..8273520fd1 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Common/KTimeManager.cs @@ -0,0 +1,146 @@ +using Ryujinx.Common; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Kernel.Common +{ + class KTimeManager : IDisposable + { + private class WaitingObject + { + public IKFutureSchedulerObject Object { get; private set; } + + public long TimePoint { get; private set; } + + public WaitingObject(IKFutureSchedulerObject schedulerObj, long timePoint) + { + Object = schedulerObj; + TimePoint = timePoint; + } + } + + private List _waitingObjects; + + private AutoResetEvent _waitEvent; + + private bool _keepRunning; + + public KTimeManager() + { + _waitingObjects = new List(); + + _keepRunning = true; + + Thread work = new Thread(WaitAndCheckScheduledObjects) + { + Name = "HLE.TimeManager" + }; + + work.Start(); + } + + public void ScheduleFutureInvocation(IKFutureSchedulerObject schedulerObj, long timeout) + { + long timePoint = PerformanceCounter.ElapsedMilliseconds + ConvertNanosecondsToMilliseconds(timeout); + + lock (_waitingObjects) + { + _waitingObjects.Add(new WaitingObject(schedulerObj, timePoint)); + } + + _waitEvent.Set(); + } + + public static long ConvertNanosecondsToMilliseconds(long time) + { + time /= 1000000; + + if ((ulong)time > int.MaxValue) + { + return int.MaxValue; + } + + return time; + } + + public static long ConvertMillisecondsToNanoseconds(long time) + { + return time * 1000000; + } + + public static long ConvertMillisecondsToTicks(long time) + { + return time * 19200; + } + + public void UnscheduleFutureInvocation(IKFutureSchedulerObject Object) + { + lock (_waitingObjects) + { + _waitingObjects.RemoveAll(x => x.Object == Object); + } + } + + private void WaitAndCheckScheduledObjects() + { + using (_waitEvent = new AutoResetEvent(false)) + { + while (_keepRunning) + { + WaitingObject next; + + lock (_waitingObjects) + { + next = _waitingObjects.OrderBy(x => x.TimePoint).FirstOrDefault(); + } + + if (next != null) + { + long timePoint = PerformanceCounter.ElapsedMilliseconds; + + if (next.TimePoint > timePoint) + { + _waitEvent.WaitOne((int)(next.TimePoint - timePoint)); + } + + bool timeUp = PerformanceCounter.ElapsedMilliseconds >= next.TimePoint; + + if (timeUp) + { + lock (_waitingObjects) + { + timeUp = _waitingObjects.Remove(next); + } + } + + if (timeUp) + { + next.Object.TimeUp(); + } + } + else + { + _waitEvent.WaitOne(); + } + } + } + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _keepRunning = false; + + _waitEvent?.Set(); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Common/KernelInit.cs b/Ryujinx.HLE/HOS/Kernel/Common/KernelInit.cs new file mode 100644 index 0000000000..bbb75f182d --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Common/KernelInit.cs @@ -0,0 +1,137 @@ +using Ryujinx.HLE.HOS.Kernel.Memory; +using System; + +namespace Ryujinx.HLE.HOS.Kernel.Common +{ + static class KernelInit + { + public static void InitializeResourceLimit(KResourceLimit resourceLimit) + { + void EnsureSuccess(KernelResult result) + { + if (result != KernelResult.Success) + { + throw new InvalidOperationException($"Unexpected result \"{result}\"."); + } + } + + int kernelMemoryCfg = 0; + + long ramSize = GetRamSize(kernelMemoryCfg); + + EnsureSuccess(resourceLimit.SetLimitValue(LimitableResource.Memory, ramSize)); + EnsureSuccess(resourceLimit.SetLimitValue(LimitableResource.Thread, 800)); + EnsureSuccess(resourceLimit.SetLimitValue(LimitableResource.Event, 700)); + EnsureSuccess(resourceLimit.SetLimitValue(LimitableResource.TransferMemory, 200)); + EnsureSuccess(resourceLimit.SetLimitValue(LimitableResource.Session, 900)); + + if (!resourceLimit.Reserve(LimitableResource.Memory, 0) || + !resourceLimit.Reserve(LimitableResource.Memory, 0x60000)) + { + throw new InvalidOperationException("Unexpected failure reserving memory on resource limit."); + } + } + + public static KMemoryRegionManager[] GetMemoryRegions() + { + KMemoryArrange arrange = GetMemoryArrange(); + + return new KMemoryRegionManager[] + { + GetMemoryRegion(arrange.Application), + GetMemoryRegion(arrange.Applet), + GetMemoryRegion(arrange.Service), + GetMemoryRegion(arrange.NvServices) + }; + } + + private static KMemoryRegionManager GetMemoryRegion(KMemoryArrangeRegion region) + { + return new KMemoryRegionManager(region.Address, region.Size, region.EndAddr); + } + + private static KMemoryArrange GetMemoryArrange() + { + int mcEmemCfg = 0x1000; + + ulong ememApertureSize = (ulong)(mcEmemCfg & 0x3fff) << 20; + + int kernelMemoryCfg = 0; + + ulong ramSize = (ulong)GetRamSize(kernelMemoryCfg); + + ulong ramPart0; + ulong ramPart1; + + if (ramSize * 2 > ememApertureSize) + { + ramPart0 = ememApertureSize / 2; + ramPart1 = ememApertureSize / 2; + } + else + { + ramPart0 = ememApertureSize; + ramPart1 = 0; + } + + int memoryArrange = 1; + + ulong applicationRgSize; + + switch (memoryArrange) + { + case 2: applicationRgSize = 0x80000000; break; + case 0x11: + case 0x21: applicationRgSize = 0x133400000; break; + default: applicationRgSize = 0xcd500000; break; + } + + ulong appletRgSize; + + switch (memoryArrange) + { + case 2: appletRgSize = 0x61200000; break; + case 3: appletRgSize = 0x1c000000; break; + case 0x11: appletRgSize = 0x23200000; break; + case 0x12: + case 0x21: appletRgSize = 0x89100000; break; + default: appletRgSize = 0x1fb00000; break; + } + + KMemoryArrangeRegion serviceRg; + KMemoryArrangeRegion nvServicesRg; + KMemoryArrangeRegion appletRg; + KMemoryArrangeRegion applicationRg; + + const ulong nvServicesRgSize = 0x29ba000; + + ulong applicationRgEnd = DramMemoryMap.DramEnd; //- RamPart0; + + applicationRg = new KMemoryArrangeRegion(applicationRgEnd - applicationRgSize, applicationRgSize); + + ulong nvServicesRgEnd = applicationRg.Address - appletRgSize; + + nvServicesRg = new KMemoryArrangeRegion(nvServicesRgEnd - nvServicesRgSize, nvServicesRgSize); + appletRg = new KMemoryArrangeRegion(nvServicesRgEnd, appletRgSize); + + // Note: There is an extra region used by the kernel, however + // since we are doing HLE we are not going to use that memory, so give all + // the remaining memory space to services. + ulong serviceRgSize = nvServicesRg.Address - DramMemoryMap.SlabHeapEnd; + + serviceRg = new KMemoryArrangeRegion(DramMemoryMap.SlabHeapEnd, serviceRgSize); + + return new KMemoryArrange(serviceRg, nvServicesRg, appletRg, applicationRg); + } + + private static long GetRamSize(int kernelMemoryCfg) + { + switch ((kernelMemoryCfg >> 16) & 3) + { + case 1: return 0x180000000; + case 2: return 0x200000000; + default: return 0x100000000; + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Common/KernelResult.cs b/Ryujinx.HLE/HOS/Kernel/Common/KernelResult.cs new file mode 100644 index 0000000000..357b01ea5d --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Common/KernelResult.cs @@ -0,0 +1,37 @@ +namespace Ryujinx.HLE.HOS.Kernel.Common +{ + enum KernelResult + { + Success = 0, + SessionCountExceeded = 0xe01, + InvalidCapability = 0x1c01, + ThreadNotStarted = 0x7201, + ThreadTerminating = 0x7601, + InvalidSize = 0xca01, + InvalidAddress = 0xcc01, + OutOfResource = 0xce01, + OutOfMemory = 0xd001, + HandleTableFull = 0xd201, + InvalidMemState = 0xd401, + InvalidPermission = 0xd801, + InvalidMemRange = 0xdc01, + InvalidPriority = 0xe001, + InvalidCpuCore = 0xe201, + InvalidHandle = 0xe401, + UserCopyFailed = 0xe601, + InvalidCombination = 0xe801, + TimedOut = 0xea01, + Cancelled = 0xec01, + MaximumExceeded = 0xee01, + InvalidEnumValue = 0xf001, + NotFound = 0xf201, + InvalidThread = 0xf401, + PortRemoteClosed = 0xf601, + InvalidState = 0xfa01, + ReservedValue = 0xfc01, + PortClosed = 0x10601, + ResLimitExceeded = 0x10801, + OutOfVaSpace = 0x20601, + CmdBufferTooSmall = 0x20801 + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Common/KernelTransfer.cs b/Ryujinx.HLE/HOS/Kernel/Common/KernelTransfer.cs new file mode 100644 index 0000000000..62330d6bad --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Common/KernelTransfer.cs @@ -0,0 +1,92 @@ +using Ryujinx.HLE.HOS.Kernel.Process; +using ARMeilleure.Memory; + +namespace Ryujinx.HLE.HOS.Kernel.Common +{ + static class KernelTransfer + { + public static bool UserToKernelInt32(Horizon system, ulong address, out int value) + { + KProcess currentProcess = system.Scheduler.GetCurrentProcess(); + + if (currentProcess.CpuMemory.IsMapped((long)address) && + currentProcess.CpuMemory.IsMapped((long)address + 3)) + { + value = currentProcess.CpuMemory.ReadInt32((long)address); + + return true; + } + + value = 0; + + return false; + } + + public static bool UserToKernelInt32Array(Horizon system, ulong address, int[] values) + { + KProcess currentProcess = system.Scheduler.GetCurrentProcess(); + + for (int index = 0; index < values.Length; index++, address += 4) + { + if (currentProcess.CpuMemory.IsMapped((long)address) && + currentProcess.CpuMemory.IsMapped((long)address + 3)) + { + values[index]= currentProcess.CpuMemory.ReadInt32((long)address); + } + else + { + return false; + } + } + + return true; + } + + public static bool UserToKernelString(Horizon system, ulong address, int size, out string value) + { + KProcess currentProcess = system.Scheduler.GetCurrentProcess(); + + if (currentProcess.CpuMemory.IsMapped((long)address) && + currentProcess.CpuMemory.IsMapped((long)address + size - 1)) + { + value = MemoryHelper.ReadAsciiString(currentProcess.CpuMemory, (long)address, size); + + return true; + } + + value = null; + + return false; + } + + public static bool KernelToUserInt32(Horizon system, ulong address, int value) + { + KProcess currentProcess = system.Scheduler.GetCurrentProcess(); + + if (currentProcess.CpuMemory.IsMapped((long)address) && + currentProcess.CpuMemory.IsMapped((long)address + 3)) + { + currentProcess.CpuMemory.WriteInt32((long)address, value); + + return true; + } + + return false; + } + + public static bool KernelToUserInt64(Horizon system, ulong address, long value) + { + KProcess currentProcess = system.Scheduler.GetCurrentProcess(); + + if (currentProcess.CpuMemory.IsMapped((long)address) && + currentProcess.CpuMemory.IsMapped((long)address + 7)) + { + currentProcess.CpuMemory.WriteInt64((long)address, value); + + return true; + } + + return false; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Common/LimitableResource.cs b/Ryujinx.HLE/HOS/Kernel/Common/LimitableResource.cs new file mode 100644 index 0000000000..2e6a3e4552 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Common/LimitableResource.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.HLE.HOS.Kernel.Common +{ + enum LimitableResource : byte + { + Memory = 0, + Thread = 1, + Event = 2, + TransferMemory = 3, + Session = 4, + + Count = 5 + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Common/MersenneTwister.cs b/Ryujinx.HLE/HOS/Kernel/Common/MersenneTwister.cs new file mode 100644 index 0000000000..7f767c1c41 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Common/MersenneTwister.cs @@ -0,0 +1,128 @@ +using Ryujinx.Common; + +namespace Ryujinx.HLE.HOS.Kernel.Common +{ + class MersenneTwister + { + private int _index; + private uint[] _mt; + + public MersenneTwister(uint seed) + { + _mt = new uint[624]; + + _mt[0] = seed; + + for (int mtIdx = 1; mtIdx < _mt.Length; mtIdx++) + { + uint prev = _mt[mtIdx - 1]; + + _mt[mtIdx] = (uint)(0x6c078965 * (prev ^ (prev >> 30)) + mtIdx); + } + + _index = _mt.Length; + } + + public long GenRandomNumber(long min, long max) + { + long range = max - min; + + if (min == max) + { + return min; + } + + if (range == -1) + { + // Increment would cause a overflow, special case. + return GenRandomNumber(2, 2, 32, 0xffffffffu, 0xffffffffu); + } + + range++; + + // This is log2(Range) plus one. + int nextRangeLog2 = 64 - BitUtils.CountLeadingZeros64(range); + + // If Range is already power of 2, subtract one to use log2(Range) directly. + int rangeLog2 = nextRangeLog2 - (BitUtils.IsPowerOfTwo64(range) ? 1 : 0); + + int parts = rangeLog2 > 32 ? 2 : 1; + int bitsPerPart = rangeLog2 / parts; + + int fullParts = parts - (rangeLog2 - parts * bitsPerPart); + + uint mask = 0xffffffffu >> (32 - bitsPerPart); + uint maskPlus1 = 0xffffffffu >> (31 - bitsPerPart); + + long randomNumber; + + do + { + randomNumber = GenRandomNumber(parts, fullParts, bitsPerPart, mask, maskPlus1); + } + while ((ulong)randomNumber >= (ulong)range); + + return min + randomNumber; + } + + private long GenRandomNumber( + int parts, + int fullParts, + int bitsPerPart, + uint mask, + uint maskPlus1) + { + long randomNumber = 0; + + int part = 0; + + for (; part < fullParts; part++) + { + randomNumber <<= bitsPerPart; + randomNumber |= GenRandomNumber() & mask; + } + + for (; part < parts; part++) + { + randomNumber <<= bitsPerPart + 1; + randomNumber |= GenRandomNumber() & maskPlus1; + } + + return randomNumber; + } + + private uint GenRandomNumber() + { + if (_index >= _mt.Length) + { + Twist(); + } + + uint value = _mt[_index++]; + + value ^= value >> 11; + value ^= (value << 7) & 0x9d2c5680; + value ^= (value << 15) & 0xefc60000; + value ^= value >> 18; + + return value; + } + + private void Twist() + { + for (int mtIdx = 0; mtIdx < _mt.Length; mtIdx++) + { + uint value = (_mt[mtIdx] & 0x80000000) + (_mt[(mtIdx + 1) % _mt.Length] & 0x7fffffff); + + _mt[mtIdx] = _mt[(mtIdx + 397) % _mt.Length] ^ (value >> 1); + + if ((value & 1) != 0) + { + _mt[mtIdx] ^= 0x9908b0df; + } + } + + _index = 0; + } + } +} diff --git a/Ryujinx.HLE/HOS/Kernel/Ipc/ChannelState.cs b/Ryujinx.HLE/HOS/Kernel/Ipc/ChannelState.cs new file mode 100644 index 0000000000..4827384eba --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Ipc/ChannelState.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Kernel.Ipc +{ + enum ChannelState + { + NotInitialized, + Open, + ClientDisconnected, + ServerDisconnected + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Ipc/KBufferDescriptor.cs b/Ryujinx.HLE/HOS/Kernel/Ipc/KBufferDescriptor.cs new file mode 100644 index 0000000000..e28244d4ac --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Ipc/KBufferDescriptor.cs @@ -0,0 +1,20 @@ +using Ryujinx.HLE.HOS.Kernel.Memory; + +namespace Ryujinx.HLE.HOS.Kernel.Ipc +{ + class KBufferDescriptor + { + public ulong ClientAddress { get; } + public ulong ServerAddress { get; } + public ulong Size { get; } + public MemoryState State { get; } + + public KBufferDescriptor(ulong src, ulong dst, ulong size, MemoryState state) + { + ClientAddress = src; + ServerAddress = dst; + Size = size; + State = state; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Ipc/KBufferDescriptorTable.cs b/Ryujinx.HLE/HOS/Kernel/Ipc/KBufferDescriptorTable.cs new file mode 100644 index 0000000000..6aa211dd69 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Ipc/KBufferDescriptorTable.cs @@ -0,0 +1,216 @@ +using Ryujinx.Common; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Memory; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Kernel.Ipc +{ + class KBufferDescriptorTable + { + private const int MaxInternalBuffersCount = 8; + + private List _sendBufferDescriptors; + private List _receiveBufferDescriptors; + private List _exchangeBufferDescriptors; + + public KBufferDescriptorTable() + { + _sendBufferDescriptors = new List(MaxInternalBuffersCount); + _receiveBufferDescriptors = new List(MaxInternalBuffersCount); + _exchangeBufferDescriptors = new List(MaxInternalBuffersCount); + } + + public KernelResult AddSendBuffer(ulong src, ulong dst, ulong size, MemoryState state) + { + return Add(_sendBufferDescriptors, src, dst, size, state); + } + + public KernelResult AddReceiveBuffer(ulong src, ulong dst, ulong size, MemoryState state) + { + return Add(_receiveBufferDescriptors, src, dst, size, state); + } + + public KernelResult AddExchangeBuffer(ulong src, ulong dst, ulong size, MemoryState state) + { + return Add(_exchangeBufferDescriptors, src, dst, size, state); + } + + private KernelResult Add(List list, ulong src, ulong dst, ulong size, MemoryState state) + { + if (list.Count < MaxInternalBuffersCount) + { + list.Add(new KBufferDescriptor(src, dst, size, state)); + + return KernelResult.Success; + } + + return KernelResult.OutOfMemory; + } + + public KernelResult CopyBuffersToClient(KMemoryManager memoryManager) + { + KernelResult result = CopyToClient(memoryManager, _receiveBufferDescriptors); + + if (result != KernelResult.Success) + { + return result; + } + + return CopyToClient(memoryManager, _exchangeBufferDescriptors); + } + + private KernelResult CopyToClient(KMemoryManager memoryManager, List list) + { + foreach (KBufferDescriptor desc in list) + { + MemoryState stateMask; + + switch (desc.State) + { + case MemoryState.IpcBuffer0: stateMask = MemoryState.IpcSendAllowedType0; break; + case MemoryState.IpcBuffer1: stateMask = MemoryState.IpcSendAllowedType1; break; + case MemoryState.IpcBuffer3: stateMask = MemoryState.IpcSendAllowedType3; break; + + default: return KernelResult.InvalidCombination; + } + + MemoryAttribute attributeMask = MemoryAttribute.Borrowed | MemoryAttribute.Uncached; + + if (desc.State == MemoryState.IpcBuffer0) + { + attributeMask |= MemoryAttribute.DeviceMapped; + } + + ulong clientAddrTruncated = BitUtils.AlignDown(desc.ClientAddress, KMemoryManager.PageSize); + ulong clientAddrRounded = BitUtils.AlignUp (desc.ClientAddress, KMemoryManager.PageSize); + + // Check if address is not aligned, in this case we need to perform 2 copies. + if (clientAddrTruncated != clientAddrRounded) + { + ulong copySize = clientAddrRounded - desc.ClientAddress; + + if (copySize > desc.Size) + { + copySize = desc.Size; + } + + KernelResult result = memoryManager.CopyDataFromCurrentProcess( + desc.ClientAddress, + copySize, + stateMask, + stateMask, + MemoryPermission.ReadAndWrite, + attributeMask, + MemoryAttribute.None, + desc.ServerAddress); + + if (result != KernelResult.Success) + { + return result; + } + } + + ulong clientEndAddr = desc.ClientAddress + desc.Size; + ulong serverEndAddr = desc.ServerAddress + desc.Size; + + ulong clientEndAddrTruncated = BitUtils.AlignDown(clientEndAddr, KMemoryManager.PageSize); + ulong clientEndAddrRounded = BitUtils.AlignUp (clientEndAddr, KMemoryManager.PageSize); + ulong serverEndAddrTruncated = BitUtils.AlignDown(clientEndAddr, KMemoryManager.PageSize); + + if (clientEndAddrTruncated < clientAddrRounded) + { + KernelResult result = memoryManager.CopyDataToCurrentProcess( + clientEndAddrTruncated, + clientEndAddr - clientEndAddrTruncated, + serverEndAddrTruncated, + stateMask, + stateMask, + MemoryPermission.ReadAndWrite, + attributeMask, + MemoryAttribute.None); + + if (result != KernelResult.Success) + { + return result; + } + } + } + + return KernelResult.Success; + } + + public KernelResult UnmapServerBuffers(KMemoryManager memoryManager) + { + KernelResult result = UnmapServer(memoryManager, _sendBufferDescriptors); + + if (result != KernelResult.Success) + { + return result; + } + + result = UnmapServer(memoryManager, _receiveBufferDescriptors); + + if (result != KernelResult.Success) + { + return result; + } + + return UnmapServer(memoryManager, _exchangeBufferDescriptors); + } + + private KernelResult UnmapServer(KMemoryManager memoryManager, List list) + { + foreach (KBufferDescriptor descriptor in list) + { + KernelResult result = memoryManager.UnmapNoAttributeIfStateEquals( + descriptor.ServerAddress, + descriptor.Size, + descriptor.State); + + if (result != KernelResult.Success) + { + return result; + } + } + + return KernelResult.Success; + } + + public KernelResult RestoreClientBuffers(KMemoryManager memoryManager) + { + KernelResult result = RestoreClient(memoryManager, _sendBufferDescriptors); + + if (result != KernelResult.Success) + { + return result; + } + + result = RestoreClient(memoryManager, _receiveBufferDescriptors); + + if (result != KernelResult.Success) + { + return result; + } + + return RestoreClient(memoryManager, _exchangeBufferDescriptors); + } + + private KernelResult RestoreClient(KMemoryManager memoryManager, List list) + { + foreach (KBufferDescriptor descriptor in list) + { + KernelResult result = memoryManager.UnmapIpcRestorePermission( + descriptor.ClientAddress, + descriptor.Size, + descriptor.State); + + if (result != KernelResult.Success) + { + return result; + } + } + + return KernelResult.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Ipc/KClientPort.cs b/Ryujinx.HLE/HOS/Kernel/Ipc/KClientPort.cs new file mode 100644 index 0000000000..901b022239 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Ipc/KClientPort.cs @@ -0,0 +1,139 @@ +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.HLE.HOS.Services; + +namespace Ryujinx.HLE.HOS.Kernel.Ipc +{ + class KClientPort : KSynchronizationObject + { + private int _sessionsCount; + private int _currentCapacity; + private int _maxSessions; + + private KPort _parent; + + public bool IsLight => _parent.IsLight; + + private object _countIncLock; + + // TODO: Remove that, we need it for now to allow HLE + // SM implementation to work with the new IPC system. + public IpcService Service { get; set; } + + public KClientPort(Horizon system, KPort parent, int maxSessions) : base(system) + { + _maxSessions = maxSessions; + _parent = parent; + + _countIncLock = new object(); + } + + public KernelResult Connect(out KClientSession clientSession) + { + clientSession = null; + + KProcess currentProcess = System.Scheduler.GetCurrentProcess(); + + if (currentProcess.ResourceLimit != null && + !currentProcess.ResourceLimit.Reserve(LimitableResource.Session, 1)) + { + return KernelResult.ResLimitExceeded; + } + + lock (_countIncLock) + { + if (_sessionsCount < _maxSessions) + { + _sessionsCount++; + } + else + { + currentProcess.ResourceLimit?.Release(LimitableResource.Session, 1); + + return KernelResult.SessionCountExceeded; + } + + if (_currentCapacity < _sessionsCount) + { + _currentCapacity = _sessionsCount; + } + } + + KSession session = new KSession(System); + + if (Service != null) + { + session.ClientSession.Service = Service; + } + + KernelResult result = _parent.EnqueueIncomingSession(session.ServerSession); + + if (result != KernelResult.Success) + { + session.ClientSession.DecrementReferenceCount(); + session.ServerSession.DecrementReferenceCount(); + + return result; + } + + clientSession = session.ClientSession; + + return result; + } + + public KernelResult ConnectLight(out KLightClientSession clientSession) + { + clientSession = null; + + KProcess currentProcess = System.Scheduler.GetCurrentProcess(); + + if (currentProcess.ResourceLimit != null && + !currentProcess.ResourceLimit.Reserve(LimitableResource.Session, 1)) + { + return KernelResult.ResLimitExceeded; + } + + lock (_countIncLock) + { + if (_sessionsCount < _maxSessions) + { + _sessionsCount++; + } + else + { + currentProcess.ResourceLimit?.Release(LimitableResource.Session, 1); + + return KernelResult.SessionCountExceeded; + } + } + + KLightSession session = new KLightSession(System); + + KernelResult result = _parent.EnqueueIncomingLightSession(session.ServerSession); + + if (result != KernelResult.Success) + { + session.ClientSession.DecrementReferenceCount(); + session.ServerSession.DecrementReferenceCount(); + + return result; + } + + clientSession = session.ClientSession; + + return result; + } + + public new static KernelResult RemoveName(Horizon system, string name) + { + KAutoObject foundObj = FindNamedObject(system, name); + + if (!(foundObj is KClientPort)) + { + return KernelResult.NotFound; + } + + return KAutoObject.RemoveName(system, name); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Ipc/KClientSession.cs b/Ryujinx.HLE/HOS/Kernel/Ipc/KClientSession.cs new file mode 100644 index 0000000000..a5109e96ad --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Ipc/KClientSession.cs @@ -0,0 +1,60 @@ +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services; + +namespace Ryujinx.HLE.HOS.Kernel.Ipc +{ + class KClientSession : KSynchronizationObject + { + public KProcess CreatorProcess { get; } + + private KSession _parent; + + public ChannelState State { get; set; } + + // TODO: Remove that, we need it for now to allow HLE + // services implementation to work with the new IPC system. + public IpcService Service { get; set; } + + public KClientSession(Horizon system, KSession parent) : base(system) + { + _parent = parent; + + State = ChannelState.Open; + + CreatorProcess = system.Scheduler.GetCurrentProcess(); + + CreatorProcess.IncrementReferenceCount(); + } + + public KernelResult SendSyncRequest(ulong customCmdBuffAddr = 0, ulong customCmdBuffSize = 0) + { + KThread currentThread = System.Scheduler.GetCurrentThread(); + + KSessionRequest request = new KSessionRequest(currentThread, customCmdBuffAddr, customCmdBuffSize); + + System.CriticalSection.Enter(); + + currentThread.SignaledObj = null; + currentThread.ObjSyncResult = KernelResult.Success; + + KernelResult result = _parent.ServerSession.EnqueueRequest(request); + + System.CriticalSection.Leave(); + + if (result == KernelResult.Success) + { + result = currentThread.ObjSyncResult; + } + + return result; + } + + protected override void Destroy() + { + _parent.DisconnectClient(); + _parent.DecrementReferenceCount(); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Ipc/KLightClientSession.cs b/Ryujinx.HLE/HOS/Kernel/Ipc/KLightClientSession.cs new file mode 100644 index 0000000000..62c352bfe8 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Ipc/KLightClientSession.cs @@ -0,0 +1,14 @@ +using Ryujinx.HLE.HOS.Kernel.Common; + +namespace Ryujinx.HLE.HOS.Kernel.Ipc +{ + class KLightClientSession : KAutoObject + { + private KLightSession _parent; + + public KLightClientSession(Horizon system, KLightSession parent) : base(system) + { + _parent = parent; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Ipc/KLightServerSession.cs b/Ryujinx.HLE/HOS/Kernel/Ipc/KLightServerSession.cs new file mode 100644 index 0000000000..1ea2205d0b --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Ipc/KLightServerSession.cs @@ -0,0 +1,14 @@ +using Ryujinx.HLE.HOS.Kernel.Common; + +namespace Ryujinx.HLE.HOS.Kernel.Ipc +{ + class KLightServerSession : KAutoObject + { + private KLightSession _parent; + + public KLightServerSession(Horizon system, KLightSession parent) : base(system) + { + _parent = parent; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Ipc/KLightSession.cs b/Ryujinx.HLE/HOS/Kernel/Ipc/KLightSession.cs new file mode 100644 index 0000000000..a12a1986fb --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Ipc/KLightSession.cs @@ -0,0 +1,20 @@ +using Ryujinx.HLE.HOS.Kernel.Common; + +namespace Ryujinx.HLE.HOS.Kernel.Ipc +{ + class KLightSession : KAutoObject + { + public KLightServerSession ServerSession { get; } + public KLightClientSession ClientSession { get; } + + private bool _hasBeenInitialized; + + public KLightSession(Horizon system) : base(system) + { + ServerSession = new KLightServerSession(system, this); + ClientSession = new KLightClientSession(system, this); + + _hasBeenInitialized = true; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Ipc/KPort.cs b/Ryujinx.HLE/HOS/Kernel/Ipc/KPort.cs new file mode 100644 index 0000000000..9d93cf7b98 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Ipc/KPort.cs @@ -0,0 +1,71 @@ +using Ryujinx.HLE.HOS.Kernel.Common; + +namespace Ryujinx.HLE.HOS.Kernel.Ipc +{ + class KPort : KAutoObject + { + public KServerPort ServerPort { get; } + public KClientPort ClientPort { get; } + + private long _nameAddress; + + private ChannelState _state; + + public bool IsLight { get; private set; } + + public KPort(Horizon system, int maxSessions, bool isLight, long nameAddress) : base(system) + { + ServerPort = new KServerPort(system, this); + ClientPort = new KClientPort(system, this, maxSessions); + + IsLight = isLight; + _nameAddress = nameAddress; + + _state = ChannelState.Open; + } + + public KernelResult EnqueueIncomingSession(KServerSession session) + { + KernelResult result; + + System.CriticalSection.Enter(); + + if (_state == ChannelState.Open) + { + ServerPort.EnqueueIncomingSession(session); + + result = KernelResult.Success; + } + else + { + result = KernelResult.PortClosed; + } + + System.CriticalSection.Leave(); + + return result; + } + + public KernelResult EnqueueIncomingLightSession(KLightServerSession session) + { + KernelResult result; + + System.CriticalSection.Enter(); + + if (_state == ChannelState.Open) + { + ServerPort.EnqueueIncomingLightSession(session); + + result = KernelResult.Success; + } + else + { + result = KernelResult.PortClosed; + } + + System.CriticalSection.Leave(); + + return result; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Ipc/KServerPort.cs b/Ryujinx.HLE/HOS/Kernel/Ipc/KServerPort.cs new file mode 100644 index 0000000000..919df357bd --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Ipc/KServerPort.cs @@ -0,0 +1,87 @@ +using Ryujinx.HLE.HOS.Kernel.Common; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Kernel.Ipc +{ + class KServerPort : KSynchronizationObject + { + private LinkedList _incomingConnections; + private LinkedList _lightIncomingConnections; + + private KPort _parent; + + public bool IsLight => _parent.IsLight; + + public KServerPort(Horizon system, KPort parent) : base(system) + { + _parent = parent; + + _incomingConnections = new LinkedList(); + _lightIncomingConnections = new LinkedList(); + } + + public void EnqueueIncomingSession(KServerSession session) + { + AcceptIncomingConnection(_incomingConnections, session); + } + + public void EnqueueIncomingLightSession(KLightServerSession session) + { + AcceptIncomingConnection(_lightIncomingConnections, session); + } + + private void AcceptIncomingConnection(LinkedList list, T session) + { + System.CriticalSection.Enter(); + + list.AddLast(session); + + if (list.Count == 1) + { + Signal(); + } + + System.CriticalSection.Leave(); + } + + public KServerSession AcceptIncomingConnection() + { + return AcceptIncomingConnection(_incomingConnections); + } + + public KLightServerSession AcceptIncomingLightConnection() + { + return AcceptIncomingConnection(_lightIncomingConnections); + } + + private T AcceptIncomingConnection(LinkedList list) + { + T session = default(T); + + System.CriticalSection.Enter(); + + if (list.Count != 0) + { + session = list.First.Value; + + list.RemoveFirst(); + } + + System.CriticalSection.Leave(); + + return session; + } + + public override bool IsSignaled() + { + if (_parent.IsLight) + { + return _lightIncomingConnections.Count != 0; + } + else + { + return _incomingConnections.Count != 0; + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Ipc/KServerSession.cs b/Ryujinx.HLE/HOS/Kernel/Ipc/KServerSession.cs new file mode 100644 index 0000000000..7fba645f55 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Ipc/KServerSession.cs @@ -0,0 +1,1262 @@ +using Ryujinx.Common; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Memory; +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.HLE.HOS.Kernel.Threading; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Kernel.Ipc +{ + class KServerSession : KSynchronizationObject + { + private static readonly MemoryState[] IpcMemoryStates = new MemoryState[] + { + MemoryState.IpcBuffer3, + MemoryState.IpcBuffer0, + MemoryState.IpcBuffer1, + (MemoryState)0xfffce5d4 //This is invalid, shouldn't be accessed. + }; + + private struct Message + { + public ulong Address { get; } + public ulong DramAddress { get; } + public ulong Size { get; } + public bool IsCustom { get; } + + public Message(KThread thread, ulong customCmdBuffAddress, ulong customCmdBuffSize) + { + IsCustom = customCmdBuffAddress != 0; + + if (IsCustom) + { + Address = customCmdBuffAddress; + Size = customCmdBuffSize; + + KProcess process = thread.Owner; + + DramAddress = process.MemoryManager.GetDramAddressFromVa(Address); + } + else + { + Address = thread.TlsAddress; + DramAddress = thread.TlsDramAddress; + Size = 0x100; + } + } + + public Message(KSessionRequest request) : this( + request.ClientThread, + request.CustomCmdBuffAddr, + request.CustomCmdBuffSize) { } + } + + private struct MessageHeader + { + public uint Word0 { get; } + public uint Word1 { get; } + public uint Word2 { get; } + + public uint PointerBuffersCount { get; } + public uint SendBuffersCount { get; } + public uint ReceiveBuffersCount { get; } + public uint ExchangeBuffersCount { get; } + + public uint RawDataSizeInWords { get; } + + public uint ReceiveListType { get; } + + public uint MessageSizeInWords { get; } + public uint ReceiveListOffsetInWords { get; } + public uint ReceiveListOffset { get; } + + public bool HasHandles { get; } + + public bool HasPid { get; } + + public uint CopyHandlesCount { get; } + public uint MoveHandlesCount { get; } + + public MessageHeader(uint word0, uint word1, uint word2) + { + Word0 = word0; + Word1 = word1; + Word2 = word2; + + HasHandles = word1 >> 31 != 0; + + uint handleDescSizeInWords = 0; + + if (HasHandles) + { + uint pidSize = (word2 & 1) * 8; + + HasPid = pidSize != 0; + + CopyHandlesCount = (word2 >> 1) & 0xf; + MoveHandlesCount = (word2 >> 5) & 0xf; + + handleDescSizeInWords = (pidSize + CopyHandlesCount * 4 + MoveHandlesCount * 4) / 4; + } + else + { + HasPid = false; + + CopyHandlesCount = 0; + MoveHandlesCount = 0; + } + + PointerBuffersCount = (word0 >> 16) & 0xf; + SendBuffersCount = (word0 >> 20) & 0xf; + ReceiveBuffersCount = (word0 >> 24) & 0xf; + ExchangeBuffersCount = word0 >> 28; + + uint pointerDescSizeInWords = PointerBuffersCount * 2; + uint sendDescSizeInWords = SendBuffersCount * 3; + uint receiveDescSizeInWords = ReceiveBuffersCount * 3; + uint exchangeDescSizeInWords = ExchangeBuffersCount * 3; + + RawDataSizeInWords = word1 & 0x3ff; + + ReceiveListType = (word1 >> 10) & 0xf; + + ReceiveListOffsetInWords = (word1 >> 20) & 0x7ff; + + uint paddingSizeInWords = HasHandles ? 3u : 2u; + + MessageSizeInWords = pointerDescSizeInWords + + sendDescSizeInWords + + receiveDescSizeInWords + + exchangeDescSizeInWords + + RawDataSizeInWords + + paddingSizeInWords + + handleDescSizeInWords; + + if (ReceiveListOffsetInWords == 0) + { + ReceiveListOffsetInWords = MessageSizeInWords; + } + + ReceiveListOffset = ReceiveListOffsetInWords * 4; + } + } + + private struct PointerBufferDesc + { + public uint ReceiveIndex { get; } + + public uint BufferSize { get; } + public ulong BufferAddress { get; set; } + + public PointerBufferDesc(ulong dword) + { + ReceiveIndex = (uint)dword & 0xf; + BufferSize = (uint)dword >> 16; + + BufferAddress = (dword >> 2) & 0x70; + BufferAddress |= (dword >> 12) & 0xf; + + BufferAddress = (BufferAddress << 32) | (dword >> 32); + } + + public ulong Pack() + { + ulong dword = (ReceiveIndex & 0xf) | ((BufferSize & 0xffff) << 16); + + dword |= BufferAddress << 32; + dword |= (BufferAddress >> 20) & 0xf000; + dword |= (BufferAddress >> 30) & 0xffc0; + + return dword; + } + } + + private KSession _parent; + + private LinkedList _requests; + + private KSessionRequest _activeRequest; + + public KServerSession(Horizon system, KSession parent) : base(system) + { + _parent = parent; + + _requests = new LinkedList(); + } + + public KernelResult EnqueueRequest(KSessionRequest request) + { + if (_parent.ClientSession.State != ChannelState.Open) + { + return KernelResult.PortRemoteClosed; + } + + if (request.AsyncEvent == null) + { + if (request.ClientThread.ShallBeTerminated || + request.ClientThread.SchedFlags == ThreadSchedState.TerminationPending) + { + return KernelResult.ThreadTerminating; + } + + request.ClientThread.Reschedule(ThreadSchedState.Paused); + } + + _requests.AddLast(request); + + if (_requests.Count == 1) + { + Signal(); + } + + return KernelResult.Success; + } + + public KernelResult Receive(ulong customCmdBuffAddr = 0, ulong customCmdBuffSize = 0) + { + KThread serverThread = System.Scheduler.GetCurrentThread(); + KProcess serverProcess = serverThread.Owner; + + System.CriticalSection.Enter(); + + if (_parent.ClientSession.State != ChannelState.Open) + { + System.CriticalSection.Leave(); + + return KernelResult.PortRemoteClosed; + } + + if (_activeRequest != null || !DequeueRequest(out KSessionRequest request)) + { + System.CriticalSection.Leave(); + + return KernelResult.NotFound; + } + + if (request.ClientThread == null) + { + System.CriticalSection.Leave(); + + return KernelResult.PortRemoteClosed; + } + + KThread clientThread = request.ClientThread; + KProcess clientProcess = clientThread.Owner; + + System.CriticalSection.Leave(); + + _activeRequest = request; + + request.ServerProcess = serverProcess; + + Message clientMsg = new Message(request); + Message serverMsg = new Message(serverThread, customCmdBuffAddr, customCmdBuffSize); + + MessageHeader clientHeader = GetClientMessageHeader(clientMsg); + MessageHeader serverHeader = GetServerMessageHeader(serverMsg); + + KernelResult serverResult = KernelResult.NotFound; + KernelResult clientResult = KernelResult.Success; + + void CleanUpForError() + { + if (request.BufferDescriptorTable.UnmapServerBuffers(serverProcess.MemoryManager) == KernelResult.Success) + { + request.BufferDescriptorTable.RestoreClientBuffers(clientProcess.MemoryManager); + } + + CloseAllHandles(serverMsg, clientHeader, serverProcess); + + System.CriticalSection.Enter(); + + _activeRequest = null; + + if (_requests.Count != 0) + { + Signal(); + } + + System.CriticalSection.Leave(); + + WakeClientThread(request, clientResult); + } + + if (clientHeader.ReceiveListType < 2 && + clientHeader.ReceiveListOffset > clientMsg.Size) + { + CleanUpForError(); + + return KernelResult.InvalidCombination; + } + else if (clientHeader.ReceiveListType == 2 && + clientHeader.ReceiveListOffset + 8 > clientMsg.Size) + { + CleanUpForError(); + + return KernelResult.InvalidCombination; + } + else if (clientHeader.ReceiveListType > 2 && + clientHeader.ReceiveListType * 8 - 0x10 + clientHeader.ReceiveListOffset > clientMsg.Size) + { + CleanUpForError(); + + return KernelResult.InvalidCombination; + } + + if (clientHeader.ReceiveListOffsetInWords < clientHeader.MessageSizeInWords) + { + CleanUpForError(); + + return KernelResult.InvalidCombination; + } + + if (clientHeader.MessageSizeInWords * 4 > clientMsg.Size) + { + CleanUpForError(); + + return KernelResult.CmdBufferTooSmall; + } + + ulong[] receiveList = GetReceiveList( + serverMsg, + serverHeader.ReceiveListType, + serverHeader.ReceiveListOffset); + + serverProcess.CpuMemory.WriteUInt32((long)serverMsg.Address + 0, clientHeader.Word0); + serverProcess.CpuMemory.WriteUInt32((long)serverMsg.Address + 4, clientHeader.Word1); + + uint offset; + + // Copy handles. + if (clientHeader.HasHandles) + { + if (clientHeader.MoveHandlesCount != 0) + { + CleanUpForError(); + + return KernelResult.InvalidCombination; + } + + serverProcess.CpuMemory.WriteUInt32((long)serverMsg.Address + 8, clientHeader.Word2); + + offset = 3; + + if (clientHeader.HasPid) + { + serverProcess.CpuMemory.WriteInt64((long)serverMsg.Address + offset * 4, clientProcess.Pid); + + offset += 2; + } + + for (int index = 0; index < clientHeader.CopyHandlesCount; index++) + { + int newHandle = 0; + + int handle = System.Device.Memory.ReadInt32((long)clientMsg.DramAddress + offset * 4); + + if (clientResult == KernelResult.Success && handle != 0) + { + clientResult = GetCopyObjectHandle(clientThread, serverProcess, handle, out newHandle); + } + + serverProcess.CpuMemory.WriteInt32((long)serverMsg.Address + offset * 4, newHandle); + + offset++; + } + + for (int index = 0; index < clientHeader.MoveHandlesCount; index++) + { + int newHandle = 0; + + int handle = System.Device.Memory.ReadInt32((long)clientMsg.DramAddress + offset * 4); + + if (handle != 0) + { + if (clientResult == KernelResult.Success) + { + clientResult = GetMoveObjectHandle(clientProcess, serverProcess, handle, out newHandle); + } + else + { + clientProcess.HandleTable.CloseHandle(handle); + } + } + + serverProcess.CpuMemory.WriteInt32((long)serverMsg.Address + offset * 4, newHandle); + + offset++; + } + + if (clientResult != KernelResult.Success) + { + CleanUpForError(); + + return serverResult; + } + } + else + { + offset = 2; + } + + // Copy pointer/receive list buffers. + uint recvListDstOffset = 0; + + for (int index = 0; index < clientHeader.PointerBuffersCount; index++) + { + ulong pointerDesc = System.Device.Memory.ReadUInt64((long)clientMsg.DramAddress + offset * 4); + + PointerBufferDesc descriptor = new PointerBufferDesc(pointerDesc); + + if (descriptor.BufferSize != 0) + { + clientResult = GetReceiveListAddress( + descriptor, + serverMsg, + serverHeader.ReceiveListType, + clientHeader.MessageSizeInWords, + receiveList, + ref recvListDstOffset, + out ulong recvListBufferAddress); + + if (clientResult != KernelResult.Success) + { + CleanUpForError(); + + return serverResult; + } + + clientResult = clientProcess.MemoryManager.CopyDataToCurrentProcess( + recvListBufferAddress, + descriptor.BufferSize, + descriptor.BufferAddress, + MemoryState.IsPoolAllocated, + MemoryState.IsPoolAllocated, + MemoryPermission.Read, + MemoryAttribute.Uncached, + MemoryAttribute.None); + + if (clientResult != KernelResult.Success) + { + CleanUpForError(); + + return serverResult; + } + + descriptor.BufferAddress = recvListBufferAddress; + } + else + { + descriptor.BufferAddress = 0; + } + + serverProcess.CpuMemory.WriteUInt64((long)serverMsg.Address + offset * 4, descriptor.Pack()); + + offset += 2; + } + + // Copy send, receive and exchange buffers. + uint totalBuffersCount = + clientHeader.SendBuffersCount + + clientHeader.ReceiveBuffersCount + + clientHeader.ExchangeBuffersCount; + + for (int index = 0; index < totalBuffersCount; index++) + { + long clientDescAddress = (long)clientMsg.DramAddress + offset * 4; + + uint descWord0 = System.Device.Memory.ReadUInt32(clientDescAddress + 0); + uint descWord1 = System.Device.Memory.ReadUInt32(clientDescAddress + 4); + uint descWord2 = System.Device.Memory.ReadUInt32(clientDescAddress + 8); + + bool isSendDesc = index < clientHeader.SendBuffersCount; + bool isExchangeDesc = index >= clientHeader.SendBuffersCount + clientHeader.ReceiveBuffersCount; + + bool notReceiveDesc = isSendDesc || isExchangeDesc; + bool isReceiveDesc = !notReceiveDesc; + + MemoryPermission permission = index >= clientHeader.SendBuffersCount + ? MemoryPermission.ReadAndWrite + : MemoryPermission.Read; + + uint sizeHigh4 = (descWord2 >> 24) & 0xf; + + ulong bufferSize = descWord0 | (ulong)sizeHigh4 << 32; + + ulong dstAddress = 0; + + if (bufferSize != 0) + { + ulong bufferAddress; + + bufferAddress = descWord2 >> 28; + bufferAddress |= ((descWord2 >> 2) & 7) << 4; + + bufferAddress = (bufferAddress << 32) | descWord1; + + MemoryState state = IpcMemoryStates[(descWord2 + 1) & 3]; + + clientResult = serverProcess.MemoryManager.MapBufferFromClientProcess( + bufferSize, + bufferAddress, + clientProcess.MemoryManager, + permission, + state, + notReceiveDesc, + out dstAddress); + + if (clientResult != KernelResult.Success) + { + CleanUpForError(); + + return serverResult; + } + + if (isSendDesc) + { + clientResult = request.BufferDescriptorTable.AddSendBuffer(bufferAddress, dstAddress, bufferSize, state); + } + else if (isReceiveDesc) + { + clientResult = request.BufferDescriptorTable.AddReceiveBuffer(bufferAddress, dstAddress, bufferSize, state); + } + else /* if (isExchangeDesc) */ + { + clientResult = request.BufferDescriptorTable.AddExchangeBuffer(bufferAddress, dstAddress, bufferSize, state); + } + + if (clientResult != KernelResult.Success) + { + CleanUpForError(); + + return serverResult; + } + } + + descWord1 = (uint)dstAddress; + + descWord2 &= 3; + + descWord2 |= sizeHigh4 << 24; + + descWord2 |= (uint)(dstAddress >> 34) & 0x3ffffffc; + descWord2 |= (uint)(dstAddress >> 4) & 0xf0000000; + + long serverDescAddress = (long)serverMsg.Address + offset * 4; + + serverProcess.CpuMemory.WriteUInt32(serverDescAddress + 0, descWord0); + serverProcess.CpuMemory.WriteUInt32(serverDescAddress + 4, descWord1); + serverProcess.CpuMemory.WriteUInt32(serverDescAddress + 8, descWord2); + + offset += 3; + } + + // Copy raw data. + if (clientHeader.RawDataSizeInWords != 0) + { + ulong copySrc = clientMsg.Address + offset * 4; + ulong copyDst = serverMsg.Address + offset * 4; + + ulong copySize = clientHeader.RawDataSizeInWords * 4; + + if (serverMsg.IsCustom || clientMsg.IsCustom) + { + MemoryPermission permission = clientMsg.IsCustom + ? MemoryPermission.None + : MemoryPermission.Read; + + clientResult = clientProcess.MemoryManager.CopyDataToCurrentProcess( + copyDst, + copySize, + copySrc, + MemoryState.IsPoolAllocated, + MemoryState.IsPoolAllocated, + permission, + MemoryAttribute.Uncached, + MemoryAttribute.None); + } + else + { + copySrc = clientProcess.MemoryManager.GetDramAddressFromVa(copySrc); + copyDst = serverProcess.MemoryManager.GetDramAddressFromVa(copyDst); + + System.Device.Memory.Copy(copyDst, copySrc, copySize); + } + + if (clientResult != KernelResult.Success) + { + CleanUpForError(); + + return serverResult; + } + } + + return KernelResult.Success; + } + + public KernelResult Reply(ulong customCmdBuffAddr = 0, ulong customCmdBuffSize = 0) + { + KThread serverThread = System.Scheduler.GetCurrentThread(); + KProcess serverProcess = serverThread.Owner; + + System.CriticalSection.Enter(); + + if (_activeRequest == null) + { + System.CriticalSection.Leave(); + + return KernelResult.InvalidState; + } + + KSessionRequest request = _activeRequest; + + _activeRequest = null; + + if (_requests.Count != 0) + { + Signal(); + } + + System.CriticalSection.Leave(); + + KThread clientThread = request.ClientThread; + KProcess clientProcess = clientThread.Owner; + + Message clientMsg = new Message(request); + Message serverMsg = new Message(serverThread, customCmdBuffAddr, customCmdBuffSize); + + MessageHeader clientHeader = GetClientMessageHeader(clientMsg); + MessageHeader serverHeader = GetServerMessageHeader(serverMsg); + + KernelResult clientResult = KernelResult.Success; + KernelResult serverResult = KernelResult.Success; + + void CleanUpForError() + { + CloseAllHandles(clientMsg, serverHeader, clientProcess); + + CancelRequest(request, clientResult); + } + + if (clientHeader.ReceiveListType < 2 && + clientHeader.ReceiveListOffset > clientMsg.Size) + { + CleanUpForError(); + + return KernelResult.InvalidCombination; + } + else if (clientHeader.ReceiveListType == 2 && + clientHeader.ReceiveListOffset + 8 > clientMsg.Size) + { + CleanUpForError(); + + return KernelResult.InvalidCombination; + } + else if (clientHeader.ReceiveListType > 2 && + clientHeader.ReceiveListType * 8 - 0x10 + clientHeader.ReceiveListOffset > clientMsg.Size) + { + CleanUpForError(); + + return KernelResult.InvalidCombination; + } + + if (clientHeader.ReceiveListOffsetInWords < clientHeader.MessageSizeInWords) + { + CleanUpForError(); + + return KernelResult.InvalidCombination; + } + + if (serverHeader.MessageSizeInWords * 4 > clientMsg.Size) + { + CleanUpForError(); + + return KernelResult.CmdBufferTooSmall; + } + + if (serverHeader.SendBuffersCount != 0 || + serverHeader.ReceiveBuffersCount != 0 || + serverHeader.ExchangeBuffersCount != 0) + { + CleanUpForError(); + + return KernelResult.InvalidCombination; + } + + // Read receive list. + ulong[] receiveList = GetReceiveList( + clientMsg, + clientHeader.ReceiveListType, + clientHeader.ReceiveListOffset); + + // Copy receive and exchange buffers. + clientResult = request.BufferDescriptorTable.CopyBuffersToClient(clientProcess.MemoryManager); + + if (clientResult != KernelResult.Success) + { + CleanUpForError(); + + return serverResult; + } + + // Copy header. + System.Device.Memory.WriteUInt32((long)clientMsg.DramAddress + 0, serverHeader.Word0); + System.Device.Memory.WriteUInt32((long)clientMsg.DramAddress + 4, serverHeader.Word1); + + // Copy handles. + uint offset; + + if (serverHeader.HasHandles) + { + offset = 3; + + System.Device.Memory.WriteUInt32((long)clientMsg.DramAddress + 8, serverHeader.Word2); + + if (serverHeader.HasPid) + { + System.Device.Memory.WriteInt64((long)clientMsg.DramAddress + offset * 4, serverProcess.Pid); + + offset += 2; + } + + for (int index = 0; index < serverHeader.CopyHandlesCount; index++) + { + int newHandle = 0; + + int handle = serverProcess.CpuMemory.ReadInt32((long)serverMsg.Address + offset * 4); + + if (handle != 0) + { + GetCopyObjectHandle(serverThread, clientProcess, handle, out newHandle); + } + + System.Device.Memory.WriteInt32((long)clientMsg.DramAddress + offset * 4, newHandle); + + offset++; + } + + for (int index = 0; index < serverHeader.MoveHandlesCount; index++) + { + int newHandle = 0; + + int handle = serverProcess.CpuMemory.ReadInt32((long)serverMsg.Address + offset * 4); + + if (handle != 0) + { + if (clientResult == KernelResult.Success) + { + clientResult = GetMoveObjectHandle(serverProcess, clientProcess, handle, out newHandle); + } + else + { + serverProcess.HandleTable.CloseHandle(handle); + } + } + + System.Device.Memory.WriteInt32((long)clientMsg.DramAddress + offset * 4, newHandle); + + offset++; + } + } + else + { + offset = 2; + } + + // Copy pointer/receive list buffers. + uint recvListDstOffset = 0; + + for (int index = 0; index < serverHeader.PointerBuffersCount; index++) + { + ulong pointerDesc = serverProcess.CpuMemory.ReadUInt64((long)serverMsg.Address + offset * 4); + + PointerBufferDesc descriptor = new PointerBufferDesc(pointerDesc); + + if (descriptor.BufferSize != 0) + { + clientResult = GetReceiveListAddress( + descriptor, + clientMsg, + clientHeader.ReceiveListType, + serverHeader.MessageSizeInWords, + receiveList, + ref recvListDstOffset, + out ulong recvListBufferAddress); + + if (clientResult != KernelResult.Success) + { + CleanUpForError(); + + return serverResult; + } + + clientResult = clientProcess.MemoryManager.CopyDataFromCurrentProcess( + recvListBufferAddress, + descriptor.BufferSize, + MemoryState.IsPoolAllocated, + MemoryState.IsPoolAllocated, + MemoryPermission.Read, + MemoryAttribute.Uncached, + MemoryAttribute.None, + descriptor.BufferAddress); + + if (clientResult != KernelResult.Success) + { + CleanUpForError(); + + return serverResult; + } + } + + offset += 2; + } + + // Set send, receive and exchange buffer descriptors to zero. + uint totalBuffersCount = + serverHeader.SendBuffersCount + + serverHeader.ReceiveBuffersCount + + serverHeader.ExchangeBuffersCount; + + for (int index = 0; index < totalBuffersCount; index++) + { + long dstDescAddress = (long)clientMsg.DramAddress + offset * 4; + + System.Device.Memory.WriteUInt32(dstDescAddress + 0, 0); + System.Device.Memory.WriteUInt32(dstDescAddress + 4, 0); + System.Device.Memory.WriteUInt32(dstDescAddress + 8, 0); + + offset += 3; + } + + // Copy raw data. + if (serverHeader.RawDataSizeInWords != 0) + { + ulong copyDst = clientMsg.Address + offset * 4; + ulong copySrc = serverMsg.Address + offset * 4; + + ulong copySize = serverHeader.RawDataSizeInWords * 4; + + if (serverMsg.IsCustom || clientMsg.IsCustom) + { + MemoryPermission permission = clientMsg.IsCustom + ? MemoryPermission.None + : MemoryPermission.Read; + + clientResult = clientProcess.MemoryManager.CopyDataFromCurrentProcess( + copyDst, + copySize, + MemoryState.IsPoolAllocated, + MemoryState.IsPoolAllocated, + permission, + MemoryAttribute.Uncached, + MemoryAttribute.None, + copySrc); + } + else + { + copyDst = clientProcess.MemoryManager.GetDramAddressFromVa(copyDst); + copySrc = serverProcess.MemoryManager.GetDramAddressFromVa(copySrc); + + System.Device.Memory.Copy(copyDst, copySrc, copySize); + } + } + + // Unmap buffers from server. + clientResult = request.BufferDescriptorTable.UnmapServerBuffers(serverProcess.MemoryManager); + + if (clientResult != KernelResult.Success) + { + CleanUpForError(); + + return serverResult; + } + + WakeClientThread(request, clientResult); + + return serverResult; + } + + private MessageHeader GetClientMessageHeader(Message clientMsg) + { + uint word0 = System.Device.Memory.ReadUInt32((long)clientMsg.DramAddress + 0); + uint word1 = System.Device.Memory.ReadUInt32((long)clientMsg.DramAddress + 4); + uint word2 = System.Device.Memory.ReadUInt32((long)clientMsg.DramAddress + 8); + + return new MessageHeader(word0, word1, word2); + } + + private MessageHeader GetServerMessageHeader(Message serverMsg) + { + KProcess currentProcess = System.Scheduler.GetCurrentProcess(); + + uint word0 = currentProcess.CpuMemory.ReadUInt32((long)serverMsg.Address + 0); + uint word1 = currentProcess.CpuMemory.ReadUInt32((long)serverMsg.Address + 4); + uint word2 = currentProcess.CpuMemory.ReadUInt32((long)serverMsg.Address + 8); + + return new MessageHeader(word0, word1, word2); + } + + private KernelResult GetCopyObjectHandle( + KThread srcThread, + KProcess dstProcess, + int srcHandle, + out int dstHandle) + { + dstHandle = 0; + + KProcess srcProcess = srcThread.Owner; + + KAutoObject obj; + + if (srcHandle == KHandleTable.SelfProcessHandle) + { + obj = srcProcess; + } + else if (srcHandle == KHandleTable.SelfThreadHandle) + { + obj = srcThread; + } + else + { + obj = srcProcess.HandleTable.GetObject(srcHandle); + } + + if (obj != null) + { + return dstProcess.HandleTable.GenerateHandle(obj, out dstHandle); + } + else + { + return KernelResult.InvalidHandle; + } + } + + private KernelResult GetMoveObjectHandle( + KProcess srcProcess, + KProcess dstProcess, + int srcHandle, + out int dstHandle) + { + dstHandle = 0; + + KAutoObject obj = srcProcess.HandleTable.GetObject(srcHandle); + + if (obj != null) + { + KernelResult result = dstProcess.HandleTable.GenerateHandle(obj, out dstHandle); + + srcProcess.HandleTable.CloseHandle(srcHandle); + + return result; + } + else + { + return KernelResult.InvalidHandle; + } + } + + private ulong[] GetReceiveList(Message message, uint recvListType, uint recvListOffset) + { + int recvListSize = 0; + + if (recvListType >= 3) + { + recvListSize = (int)recvListType - 2; + } + else if (recvListType == 2) + { + recvListSize = 1; + } + + ulong[] receiveList = new ulong[recvListSize]; + + long recvListAddress = (long)message.DramAddress + recvListOffset; + + for (int index = 0; index < recvListSize; index++) + { + receiveList[index] = System.Device.Memory.ReadUInt64(recvListAddress + index * 8); + } + + return receiveList; + } + + private KernelResult GetReceiveListAddress( + PointerBufferDesc descriptor, + Message message, + uint recvListType, + uint messageSizeInWords, + ulong[] receiveList, + ref uint dstOffset, + out ulong address) + { + ulong recvListBufferAddress = address = 0; + + if (recvListType == 0) + { + return KernelResult.OutOfResource; + } + else if (recvListType == 1 || recvListType == 2) + { + ulong recvListBaseAddr; + ulong recvListEndAddr; + + if (recvListType == 1) + { + recvListBaseAddr = message.Address + messageSizeInWords * 4; + recvListEndAddr = message.Address + message.Size; + } + else /* if (recvListType == 2) */ + { + ulong packed = receiveList[0]; + + recvListBaseAddr = packed & 0x7fffffffff; + + uint size = (uint)(packed >> 48); + + if (size == 0) + { + return KernelResult.OutOfResource; + } + + recvListEndAddr = recvListBaseAddr + size; + } + + recvListBufferAddress = BitUtils.AlignUp(recvListBaseAddr + dstOffset, 0x10); + + ulong endAddress = recvListBufferAddress + descriptor.BufferSize; + + dstOffset = (uint)endAddress - (uint)recvListBaseAddr; + + if (recvListBufferAddress + descriptor.BufferSize <= recvListBufferAddress || + recvListBufferAddress + descriptor.BufferSize > recvListEndAddr) + { + return KernelResult.OutOfResource; + } + } + else /* if (recvListType > 2) */ + { + if (descriptor.ReceiveIndex >= receiveList.Length) + { + return KernelResult.OutOfResource; + } + + ulong packed = receiveList[descriptor.ReceiveIndex]; + + recvListBufferAddress = packed & 0x7fffffffff; + + uint size = (uint)(packed >> 48); + + if (recvListBufferAddress == 0 || size == 0 || size < descriptor.BufferSize) + { + return KernelResult.OutOfResource; + } + } + + address = recvListBufferAddress; + + return KernelResult.Success; + } + + private void CloseAllHandles(Message message, MessageHeader header, KProcess process) + { + if (header.HasHandles) + { + uint totalHandeslCount = header.CopyHandlesCount + header.MoveHandlesCount; + + uint offset = 3; + + if (header.HasPid) + { + process.CpuMemory.WriteInt64((long)message.Address + offset * 4, 0); + + offset += 2; + } + + for (int index = 0; index < totalHandeslCount; index++) + { + int handle = process.CpuMemory.ReadInt32((long)message.Address + offset * 4); + + if (handle != 0) + { + process.HandleTable.CloseHandle(handle); + + process.CpuMemory.WriteInt32((long)message.Address + offset * 4, 0); + } + + offset++; + } + } + } + + public override bool IsSignaled() + { + if (_parent.ClientSession.State != ChannelState.Open) + { + return true; + } + + return _requests.Count != 0 && _activeRequest == null; + } + + protected override void Destroy() + { + _parent.DisconnectServer(); + + CancelAllRequestsServerDisconnected(); + + _parent.DecrementReferenceCount(); + } + + private void CancelAllRequestsServerDisconnected() + { + foreach (KSessionRequest request in IterateWithRemovalOfAllRequests()) + { + CancelRequest(request, KernelResult.PortRemoteClosed); + } + } + + public void CancelAllRequestsClientDisconnected() + { + foreach (KSessionRequest request in IterateWithRemovalOfAllRequests()) + { + if (request.ClientThread.ShallBeTerminated || + request.ClientThread.SchedFlags == ThreadSchedState.TerminationPending) + { + continue; + } + + // Client sessions can only be disconnected on async requests (because + // the client would be otherwise blocked waiting for the response), so + // we only need to handle the async case here. + if (request.AsyncEvent != null) + { + SendResultToAsyncRequestClient(request, KernelResult.PortRemoteClosed); + } + } + + WakeServerThreads(KernelResult.PortRemoteClosed); + } + + private IEnumerable IterateWithRemovalOfAllRequests() + { + System.CriticalSection.Enter(); + + if (_activeRequest != null) + { + KSessionRequest request = _activeRequest; + + _activeRequest = null; + + System.CriticalSection.Leave(); + + yield return request; + } + else + { + System.CriticalSection.Leave(); + } + + while (DequeueRequest(out KSessionRequest request)) + { + yield return request; + } + } + + private bool DequeueRequest(out KSessionRequest request) + { + request = null; + + System.CriticalSection.Enter(); + + bool hasRequest = _requests.First != null; + + if (hasRequest) + { + request = _requests.First.Value; + + _requests.RemoveFirst(); + } + + System.CriticalSection.Leave(); + + return hasRequest; + } + + private void CancelRequest(KSessionRequest request, KernelResult result) + { + KProcess clientProcess = request.ClientThread.Owner; + KProcess serverProcess = request.ServerProcess; + + KernelResult unmapResult = KernelResult.Success; + + if (serverProcess != null) + { + unmapResult = request.BufferDescriptorTable.UnmapServerBuffers(serverProcess.MemoryManager); + } + + if (unmapResult == KernelResult.Success) + { + request.BufferDescriptorTable.RestoreClientBuffers(clientProcess.MemoryManager); + } + + WakeClientThread(request, result); + } + + private void WakeClientThread(KSessionRequest request, KernelResult result) + { + // Wait client thread waiting for a response for the given request. + if (request.AsyncEvent != null) + { + SendResultToAsyncRequestClient(request, result); + } + else + { + System.CriticalSection.Enter(); + + WakeAndSetResult(request.ClientThread, result); + + System.CriticalSection.Leave(); + } + } + + private void SendResultToAsyncRequestClient(KSessionRequest request, KernelResult result) + { + KProcess clientProcess = request.ClientThread.Owner; + + ulong address = clientProcess.MemoryManager.GetDramAddressFromVa(request.CustomCmdBuffAddr); + + System.Device.Memory.WriteInt64((long)address + 0, 0); + System.Device.Memory.WriteInt32((long)address + 8, (int)result); + + clientProcess.MemoryManager.UnborrowIpcBuffer( + request.CustomCmdBuffAddr, + request.CustomCmdBuffSize); + + request.AsyncEvent.Signal(); + } + + private void WakeServerThreads(KernelResult result) + { + // Wake all server threads waiting for requests. + System.CriticalSection.Enter(); + + foreach (KThread thread in WaitingThreads) + { + WakeAndSetResult(thread, result); + } + + System.CriticalSection.Leave(); + } + + private void WakeAndSetResult(KThread thread, KernelResult result) + { + if ((thread.SchedFlags & ThreadSchedState.LowMask) == ThreadSchedState.Paused) + { + thread.SignaledObj = null; + thread.ObjSyncResult = result; + + thread.Reschedule(ThreadSchedState.Running); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Ipc/KSession.cs b/Ryujinx.HLE/HOS/Kernel/Ipc/KSession.cs new file mode 100644 index 0000000000..cbf689a57c --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Ipc/KSession.cs @@ -0,0 +1,65 @@ +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Process; +using System; + +namespace Ryujinx.HLE.HOS.Kernel.Ipc +{ + class KSession : KAutoObject, IDisposable + { + public KServerSession ServerSession { get; } + public KClientSession ClientSession { get; } + + private bool _hasBeenInitialized; + + public KSession(Horizon system) : base(system) + { + ServerSession = new KServerSession(system, this); + ClientSession = new KClientSession(system, this); + + _hasBeenInitialized = true; + } + + public void DisconnectClient() + { + if (ClientSession.State == ChannelState.Open) + { + ClientSession.State = ChannelState.ClientDisconnected; + + ServerSession.CancelAllRequestsClientDisconnected(); + } + } + + public void DisconnectServer() + { + if (ClientSession.State == ChannelState.Open) + { + ClientSession.State = ChannelState.ServerDisconnected; + } + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing && ClientSession.Service is IDisposable disposableService) + { + disposableService.Dispose(); + } + } + + protected override void Destroy() + { + if (_hasBeenInitialized) + { + KProcess creatorProcess = ClientSession.CreatorProcess; + + creatorProcess.ResourceLimit?.Release(LimitableResource.Session, 1); + + creatorProcess.DecrementReferenceCount(); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Ipc/KSessionRequest.cs b/Ryujinx.HLE/HOS/Kernel/Ipc/KSessionRequest.cs new file mode 100644 index 0000000000..f3467f3954 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Ipc/KSessionRequest.cs @@ -0,0 +1,31 @@ +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.HLE.HOS.Kernel.Threading; + +namespace Ryujinx.HLE.HOS.Kernel.Ipc +{ + class KSessionRequest + { + public KBufferDescriptorTable BufferDescriptorTable { get; } + + public KThread ClientThread { get; } + + public KProcess ServerProcess { get; set; } + + public KWritableEvent AsyncEvent { get; } + + public ulong CustomCmdBuffAddr { get; } + public ulong CustomCmdBuffSize { get; } + + public KSessionRequest( + KThread clientThread, + ulong customCmdBuffAddr, + ulong customCmdBuffSize) + { + ClientThread = clientThread; + CustomCmdBuffAddr = customCmdBuffAddr; + CustomCmdBuffSize = customCmdBuffSize; + + BufferDescriptorTable = new KBufferDescriptorTable(); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Memory/AddressSpaceType.cs b/Ryujinx.HLE/HOS/Kernel/Memory/AddressSpaceType.cs new file mode 100644 index 0000000000..8395c5777a --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Memory/AddressSpaceType.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + enum AddressSpaceType + { + Addr32Bits = 0, + Addr36Bits = 1, + Addr32BitsNoMap = 2, + Addr39Bits = 3 + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Memory/DramMemoryMap.cs b/Ryujinx.HLE/HOS/Kernel/Memory/DramMemoryMap.cs new file mode 100644 index 0000000000..261c2972b3 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Memory/DramMemoryMap.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + static class DramMemoryMap + { + public const ulong DramBase = 0x80000000; + public const ulong DramSize = 0x100000000; + public const ulong DramEnd = DramBase + DramSize; + + public const ulong KernelReserveBase = DramBase + 0x60000; + + public const ulong SlabHeapBase = KernelReserveBase + 0x85000; + public const ulong SlapHeapSize = 0xa21000; + public const ulong SlabHeapEnd = SlabHeapBase + SlapHeapSize; + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryArrange.cs b/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryArrange.cs new file mode 100644 index 0000000000..7dfc2b7780 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryArrange.cs @@ -0,0 +1,22 @@ +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + class KMemoryArrange + { + public KMemoryArrangeRegion Service { get; private set; } + public KMemoryArrangeRegion NvServices { get; private set; } + public KMemoryArrangeRegion Applet { get; private set; } + public KMemoryArrangeRegion Application { get; private set; } + + public KMemoryArrange( + KMemoryArrangeRegion service, + KMemoryArrangeRegion nvServices, + KMemoryArrangeRegion applet, + KMemoryArrangeRegion application) + { + Service = service; + NvServices = nvServices; + Applet = applet; + Application = application; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryArrangeRegion.cs b/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryArrangeRegion.cs new file mode 100644 index 0000000000..eaf0fe5fb8 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryArrangeRegion.cs @@ -0,0 +1,16 @@ +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + struct KMemoryArrangeRegion + { + public ulong Address { get; private set; } + public ulong Size { get; private set; } + + public ulong EndAddr => Address + Size; + + public KMemoryArrangeRegion(ulong address, ulong size) + { + Address = address; + Size = size; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryBlock.cs b/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryBlock.cs new file mode 100644 index 0000000000..b7c2b309d3 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryBlock.cs @@ -0,0 +1,123 @@ +using System; + +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + class KMemoryBlock + { + public ulong BaseAddress { get; private set; } + public ulong PagesCount { get; private set; } + + public MemoryState State { get; private set; } + public MemoryPermission Permission { get; private set; } + public MemoryAttribute Attribute { get; private set; } + public MemoryPermission SourcePermission { get; private set; } + + public int IpcRefCount { get; private set; } + public int DeviceRefCount { get; private set; } + + public KMemoryBlock( + ulong baseAddress, + ulong pagesCount, + MemoryState state, + MemoryPermission permission, + MemoryAttribute attribute, + int ipcRefCount = 0, + int deviceRefCount = 0) + { + BaseAddress = baseAddress; + PagesCount = pagesCount; + State = state; + Attribute = attribute; + Permission = permission; + IpcRefCount = ipcRefCount; + DeviceRefCount = deviceRefCount; + } + + public void SetState(MemoryPermission permission, MemoryState state, MemoryAttribute attribute) + { + Permission = permission; + State = state; + Attribute &= MemoryAttribute.IpcAndDeviceMapped; + Attribute |= attribute; + } + + public void SetIpcMappingPermission(MemoryPermission permission) + { + int oldIpcRefCount = IpcRefCount++; + + if ((ushort)IpcRefCount == 0) + { + throw new InvalidOperationException("IPC reference count increment overflowed."); + } + + if (oldIpcRefCount == 0) + { + SourcePermission = permission; + + Permission &= ~MemoryPermission.ReadAndWrite; + Permission |= MemoryPermission.ReadAndWrite & permission; + } + + Attribute |= MemoryAttribute.IpcMapped; + } + + public void RestoreIpcMappingPermission() + { + int oldIpcRefCount = IpcRefCount--; + + if (oldIpcRefCount == 0) + { + throw new InvalidOperationException("IPC reference count decrement underflowed."); + } + + if (oldIpcRefCount == 1) + { + Permission = SourcePermission; + + SourcePermission = MemoryPermission.None; + + Attribute &= ~MemoryAttribute.IpcMapped; + } + } + + public KMemoryBlock SplitRightAtAddress(ulong address) + { + ulong leftAddress = BaseAddress; + + ulong leftPagesCount = (address - leftAddress) / KMemoryManager.PageSize; + + BaseAddress = address; + + PagesCount -= leftPagesCount; + + return new KMemoryBlock( + leftAddress, + leftPagesCount, + State, + Permission, + Attribute, + IpcRefCount, + DeviceRefCount); + } + + public void AddPages(ulong pagesCount) + { + PagesCount += pagesCount; + } + + public KMemoryInfo GetInfo() + { + ulong size = PagesCount * KMemoryManager.PageSize; + + return new KMemoryInfo( + BaseAddress, + size, + State, + Permission, + Attribute, + SourcePermission, + IpcRefCount, + DeviceRefCount); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryBlockAllocator.cs b/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryBlockAllocator.cs new file mode 100644 index 0000000000..ae68bf3912 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryBlockAllocator.cs @@ -0,0 +1,19 @@ +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + class KMemoryBlockAllocator + { + private ulong _capacityElements; + + public int Count { get; set; } + + public KMemoryBlockAllocator(ulong capacityElements) + { + _capacityElements = capacityElements; + } + + public bool CanAllocate(int count) + { + return (ulong)(Count + count) <= _capacityElements; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryInfo.cs b/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryInfo.cs new file mode 100644 index 0000000000..21e9e49407 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryInfo.cs @@ -0,0 +1,36 @@ +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + class KMemoryInfo + { + public ulong Address { get; } + public ulong Size { get; } + + public MemoryState State { get; } + public MemoryPermission Permission { get; } + public MemoryAttribute Attribute { get; } + public MemoryPermission SourcePermission { get; } + + public int IpcRefCount { get; } + public int DeviceRefCount { get; } + + public KMemoryInfo( + ulong address, + ulong size, + MemoryState state, + MemoryPermission permission, + MemoryAttribute attribute, + MemoryPermission sourcePermission, + int ipcRefCount, + int deviceRefCount) + { + Address = address; + Size = size; + State = state; + Permission = permission; + Attribute = attribute; + SourcePermission = sourcePermission; + IpcRefCount = ipcRefCount; + DeviceRefCount = deviceRefCount; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryManager.cs b/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryManager.cs new file mode 100644 index 0000000000..fd2078ee16 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryManager.cs @@ -0,0 +1,3186 @@ +using ARMeilleure.Memory; +using Ryujinx.Common; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Process; +using System; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + class KMemoryManager + { + private static readonly int[] MappingUnitSizes = new int[] + { + 0x1000, + 0x10000, + 0x200000, + 0x400000, + 0x2000000, + 0x40000000 + }; + + public const int PageSize = 0x1000; + + private const int KMemoryBlockSize = 0x40; + + // We need 2 blocks for the case where a big block + // needs to be split in 2, plus one block that will be the new one inserted. + private const int MaxBlocksNeededForInsertion = 2; + + private LinkedList _blocks; + + private MemoryManager _cpuMemory; + + private Horizon _system; + + public ulong AddrSpaceStart { get; private set; } + public ulong AddrSpaceEnd { get; private set; } + + public ulong CodeRegionStart { get; private set; } + public ulong CodeRegionEnd { get; private set; } + + public ulong HeapRegionStart { get; private set; } + public ulong HeapRegionEnd { get; private set; } + + private ulong _currentHeapAddr; + + public ulong AliasRegionStart { get; private set; } + public ulong AliasRegionEnd { get; private set; } + + public ulong StackRegionStart { get; private set; } + public ulong StackRegionEnd { get; private set; } + + public ulong TlsIoRegionStart { get; private set; } + public ulong TlsIoRegionEnd { get; private set; } + + private ulong _heapCapacity; + + public ulong PhysicalMemoryUsage { get; private set; } + + private MemoryRegion _memRegion; + + private bool _aslrDisabled; + + public int AddrSpaceWidth { get; private set; } + + private bool _isKernel; + private bool _aslrEnabled; + + private KMemoryBlockAllocator _blockAllocator; + + private int _contextId; + + private MersenneTwister _randomNumberGenerator; + + public KMemoryManager(Horizon system, MemoryManager cpuMemory) + { + _system = system; + _cpuMemory = cpuMemory; + + _blocks = new LinkedList(); + } + + private static readonly int[] AddrSpaceSizes = new int[] { 32, 36, 32, 39 }; + + public KernelResult InitializeForProcess( + AddressSpaceType addrSpaceType, + bool aslrEnabled, + bool aslrDisabled, + MemoryRegion memRegion, + ulong address, + ulong size, + KMemoryBlockAllocator blockAllocator) + { + if ((uint)addrSpaceType > (uint)AddressSpaceType.Addr39Bits) + { + throw new ArgumentException(nameof(addrSpaceType)); + } + + _contextId = _system.ContextIdManager.GetId(); + + ulong addrSpaceBase = 0; + ulong addrSpaceSize = 1UL << AddrSpaceSizes[(int)addrSpaceType]; + + KernelResult result = CreateUserAddressSpace( + addrSpaceType, + aslrEnabled, + aslrDisabled, + addrSpaceBase, + addrSpaceSize, + memRegion, + address, + size, + blockAllocator); + + if (result != KernelResult.Success) + { + _system.ContextIdManager.PutId(_contextId); + } + + return result; + } + + private class Region + { + public ulong Start; + public ulong End; + public ulong Size; + public ulong AslrOffset; + } + + private KernelResult CreateUserAddressSpace( + AddressSpaceType addrSpaceType, + bool aslrEnabled, + bool aslrDisabled, + ulong addrSpaceStart, + ulong addrSpaceEnd, + MemoryRegion memRegion, + ulong address, + ulong size, + KMemoryBlockAllocator blockAllocator) + { + ulong endAddr = address + size; + + Region aliasRegion = new Region(); + Region heapRegion = new Region(); + Region stackRegion = new Region(); + Region tlsIoRegion = new Region(); + + ulong codeRegionSize; + ulong stackAndTlsIoStart; + ulong stackAndTlsIoEnd; + ulong baseAddress; + + switch (addrSpaceType) + { + case AddressSpaceType.Addr32Bits: + aliasRegion.Size = 0x40000000; + heapRegion.Size = 0x40000000; + stackRegion.Size = 0; + tlsIoRegion.Size = 0; + CodeRegionStart = 0x200000; + codeRegionSize = 0x3fe00000; + stackAndTlsIoStart = 0x200000; + stackAndTlsIoEnd = 0x40000000; + baseAddress = 0x200000; + AddrSpaceWidth = 32; + break; + + case AddressSpaceType.Addr36Bits: + aliasRegion.Size = 0x180000000; + heapRegion.Size = 0x180000000; + stackRegion.Size = 0; + tlsIoRegion.Size = 0; + CodeRegionStart = 0x8000000; + codeRegionSize = 0x78000000; + stackAndTlsIoStart = 0x8000000; + stackAndTlsIoEnd = 0x80000000; + baseAddress = 0x8000000; + AddrSpaceWidth = 36; + break; + + case AddressSpaceType.Addr32BitsNoMap: + aliasRegion.Size = 0; + heapRegion.Size = 0x80000000; + stackRegion.Size = 0; + tlsIoRegion.Size = 0; + CodeRegionStart = 0x200000; + codeRegionSize = 0x3fe00000; + stackAndTlsIoStart = 0x200000; + stackAndTlsIoEnd = 0x40000000; + baseAddress = 0x200000; + AddrSpaceWidth = 32; + break; + + case AddressSpaceType.Addr39Bits: + aliasRegion.Size = 0x1000000000; + heapRegion.Size = 0x180000000; + stackRegion.Size = 0x80000000; + tlsIoRegion.Size = 0x1000000000; + CodeRegionStart = BitUtils.AlignDown(address, 0x200000); + codeRegionSize = BitUtils.AlignUp (endAddr, 0x200000) - CodeRegionStart; + stackAndTlsIoStart = 0; + stackAndTlsIoEnd = 0; + baseAddress = 0x8000000; + AddrSpaceWidth = 39; + break; + + default: throw new ArgumentException(nameof(addrSpaceType)); + } + + CodeRegionEnd = CodeRegionStart + codeRegionSize; + + ulong mapBaseAddress; + ulong mapAvailableSize; + + if (CodeRegionStart - baseAddress >= addrSpaceEnd - CodeRegionEnd) + { + // Has more space before the start of the code region. + mapBaseAddress = baseAddress; + mapAvailableSize = CodeRegionStart - baseAddress; + } + else + { + // Has more space after the end of the code region. + mapBaseAddress = CodeRegionEnd; + mapAvailableSize = addrSpaceEnd - CodeRegionEnd; + } + + ulong mapTotalSize = aliasRegion.Size + heapRegion.Size + stackRegion.Size + tlsIoRegion.Size; + + ulong aslrMaxOffset = mapAvailableSize - mapTotalSize; + + _aslrEnabled = aslrEnabled; + + AddrSpaceStart = addrSpaceStart; + AddrSpaceEnd = addrSpaceEnd; + + _blockAllocator = blockAllocator; + + if (mapAvailableSize < mapTotalSize) + { + return KernelResult.OutOfMemory; + } + + if (aslrEnabled) + { + aliasRegion.AslrOffset = GetRandomValue(0, aslrMaxOffset >> 21) << 21; + heapRegion.AslrOffset = GetRandomValue(0, aslrMaxOffset >> 21) << 21; + stackRegion.AslrOffset = GetRandomValue(0, aslrMaxOffset >> 21) << 21; + tlsIoRegion.AslrOffset = GetRandomValue(0, aslrMaxOffset >> 21) << 21; + } + + // Regions are sorted based on ASLR offset. + // When ASLR is disabled, the order is Map, Heap, NewMap and TlsIo. + aliasRegion.Start = mapBaseAddress + aliasRegion.AslrOffset; + aliasRegion.End = aliasRegion.Start + aliasRegion.Size; + heapRegion.Start = mapBaseAddress + heapRegion.AslrOffset; + heapRegion.End = heapRegion.Start + heapRegion.Size; + stackRegion.Start = mapBaseAddress + stackRegion.AslrOffset; + stackRegion.End = stackRegion.Start + stackRegion.Size; + tlsIoRegion.Start = mapBaseAddress + tlsIoRegion.AslrOffset; + tlsIoRegion.End = tlsIoRegion.Start + tlsIoRegion.Size; + + SortRegion(heapRegion, aliasRegion); + + if (stackRegion.Size != 0) + { + SortRegion(stackRegion, aliasRegion); + SortRegion(stackRegion, heapRegion); + } + else + { + stackRegion.Start = stackAndTlsIoStart; + stackRegion.End = stackAndTlsIoEnd; + } + + if (tlsIoRegion.Size != 0) + { + SortRegion(tlsIoRegion, aliasRegion); + SortRegion(tlsIoRegion, heapRegion); + SortRegion(tlsIoRegion, stackRegion); + } + else + { + tlsIoRegion.Start = stackAndTlsIoStart; + tlsIoRegion.End = stackAndTlsIoEnd; + } + + AliasRegionStart = aliasRegion.Start; + AliasRegionEnd = aliasRegion.End; + HeapRegionStart = heapRegion.Start; + HeapRegionEnd = heapRegion.End; + StackRegionStart = stackRegion.Start; + StackRegionEnd = stackRegion.End; + TlsIoRegionStart = tlsIoRegion.Start; + TlsIoRegionEnd = tlsIoRegion.End; + + _currentHeapAddr = HeapRegionStart; + _heapCapacity = 0; + PhysicalMemoryUsage = 0; + + _memRegion = memRegion; + _aslrDisabled = aslrDisabled; + + return InitializeBlocks(addrSpaceStart, addrSpaceEnd); + } + + private ulong GetRandomValue(ulong min, ulong max) + { + return (ulong)GetRandomValue((long)min, (long)max); + } + + private long GetRandomValue(long min, long max) + { + if (_randomNumberGenerator == null) + { + _randomNumberGenerator = new MersenneTwister(0); + } + + return _randomNumberGenerator.GenRandomNumber(min, max); + } + + private static void SortRegion(Region lhs, Region rhs) + { + if (lhs.AslrOffset < rhs.AslrOffset) + { + rhs.Start += lhs.Size; + rhs.End += lhs.Size; + } + else + { + lhs.Start += rhs.Size; + lhs.End += rhs.Size; + } + } + + private KernelResult InitializeBlocks(ulong addrSpaceStart, ulong addrSpaceEnd) + { + // First insertion will always need only a single block, + // because there's nothing else to split. + if (!_blockAllocator.CanAllocate(1)) + { + return KernelResult.OutOfResource; + } + + ulong addrSpacePagesCount = (addrSpaceEnd - addrSpaceStart) / PageSize; + + _blocks.AddFirst(new KMemoryBlock( + addrSpaceStart, + addrSpacePagesCount, + MemoryState.Unmapped, + MemoryPermission.None, + MemoryAttribute.None)); + + return KernelResult.Success; + } + + public KernelResult MapPages( + ulong address, + KPageList pageList, + MemoryState state, + MemoryPermission permission) + { + ulong pagesCount = pageList.GetPagesCount(); + + ulong size = pagesCount * PageSize; + + if (!ValidateRegionForState(address, size, state)) + { + return KernelResult.InvalidMemState; + } + + lock (_blocks) + { + if (!IsUnmapped(address, pagesCount * PageSize)) + { + return KernelResult.InvalidMemState; + } + + if (!_blockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } + + KernelResult result = MapPages(address, pageList, permission); + + if (result == KernelResult.Success) + { + InsertBlock(address, pagesCount, state, permission); + } + + return result; + } + } + + public KernelResult UnmapPages(ulong address, KPageList pageList, MemoryState stateExpected) + { + ulong pagesCount = pageList.GetPagesCount(); + + ulong size = pagesCount * PageSize; + + ulong endAddr = address + size; + + ulong addrSpacePagesCount = (AddrSpaceEnd - AddrSpaceStart) / PageSize; + + if (AddrSpaceStart > address) + { + return KernelResult.InvalidMemState; + } + + if (addrSpacePagesCount < pagesCount) + { + return KernelResult.InvalidMemState; + } + + if (endAddr - 1 > AddrSpaceEnd - 1) + { + return KernelResult.InvalidMemState; + } + + lock (_blocks) + { + KPageList currentPageList = new KPageList(); + + AddVaRangeToPageList(currentPageList, address, pagesCount); + + if (!currentPageList.IsEqual(pageList)) + { + return KernelResult.InvalidMemRange; + } + + if (CheckRange( + address, + size, + MemoryState.Mask, + stateExpected, + MemoryPermission.None, + MemoryPermission.None, + MemoryAttribute.Mask, + MemoryAttribute.None, + MemoryAttribute.IpcAndDeviceMapped, + out MemoryState state, + out _, + out _)) + { + if (!_blockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } + + KernelResult result = MmuUnmap(address, pagesCount); + + if (result == KernelResult.Success) + { + InsertBlock(address, pagesCount, MemoryState.Unmapped); + } + + return result; + } + else + { + return KernelResult.InvalidMemState; + } + } + } + + public KernelResult MapNormalMemory(long address, long size, MemoryPermission permission) + { + // TODO. + return KernelResult.Success; + } + + public KernelResult MapIoMemory(long address, long size, MemoryPermission permission) + { + // TODO. + return KernelResult.Success; + } + + public KernelResult AllocateOrMapPa( + ulong neededPagesCount, + int alignment, + ulong srcPa, + bool map, + ulong regionStart, + ulong regionPagesCount, + MemoryState state, + MemoryPermission permission, + out ulong address) + { + address = 0; + + ulong regionSize = regionPagesCount * PageSize; + + ulong regionEndAddr = regionStart + regionSize; + + if (!ValidateRegionForState(regionStart, regionSize, state)) + { + return KernelResult.InvalidMemState; + } + + if (regionPagesCount <= neededPagesCount) + { + return KernelResult.OutOfMemory; + } + + lock (_blocks) + { + address = AllocateVa(regionStart, regionPagesCount, neededPagesCount, alignment); + + if (address == 0) + { + return KernelResult.OutOfMemory; + } + + if (!_blockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } + + MemoryOperation operation = map + ? MemoryOperation.MapPa + : MemoryOperation.Allocate; + + KernelResult result = DoMmuOperation( + address, + neededPagesCount, + srcPa, + map, + permission, + operation); + + if (result != KernelResult.Success) + { + return result; + } + + InsertBlock(address, neededPagesCount, state, permission); + } + + return KernelResult.Success; + } + + public KernelResult MapNewProcessCode( + ulong address, + ulong pagesCount, + MemoryState state, + MemoryPermission permission) + { + ulong size = pagesCount * PageSize; + + if (!ValidateRegionForState(address, size, state)) + { + return KernelResult.InvalidMemState; + } + + lock (_blocks) + { + if (!IsUnmapped(address, size)) + { + return KernelResult.InvalidMemState; + } + + if (!_blockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } + + KernelResult result = DoMmuOperation( + address, + pagesCount, + 0, + false, + permission, + MemoryOperation.Allocate); + + if (result == KernelResult.Success) + { + InsertBlock(address, pagesCount, state, permission); + } + + return result; + } + } + + public KernelResult MapProcessCodeMemory(ulong dst, ulong src, ulong size) + { + ulong pagesCount = size / PageSize; + + lock (_blocks) + { + bool success = CheckRange( + src, + size, + MemoryState.Mask, + MemoryState.Heap, + MemoryPermission.Mask, + MemoryPermission.ReadAndWrite, + MemoryAttribute.Mask, + MemoryAttribute.None, + MemoryAttribute.IpcAndDeviceMapped, + out MemoryState state, + out MemoryPermission permission, + out _); + + success &= IsUnmapped(dst, size); + + if (success) + { + if (!_blockAllocator.CanAllocate(MaxBlocksNeededForInsertion * 2)) + { + return KernelResult.OutOfResource; + } + + KPageList pageList = new KPageList(); + + AddVaRangeToPageList(pageList, src, pagesCount); + + KernelResult result = MmuChangePermission(src, pagesCount, MemoryPermission.None); + + if (result != KernelResult.Success) + { + return result; + } + + result = MapPages(dst, pageList, MemoryPermission.None); + + if (result != KernelResult.Success) + { + MmuChangePermission(src, pagesCount, permission); + + return result; + } + + InsertBlock(src, pagesCount, state, MemoryPermission.None, MemoryAttribute.Borrowed); + InsertBlock(dst, pagesCount, MemoryState.ModCodeStatic); + + return KernelResult.Success; + } + else + { + return KernelResult.InvalidMemState; + } + } + } + + public KernelResult UnmapProcessCodeMemory(ulong dst, ulong src, ulong size) + { + ulong pagesCount = size / PageSize; + + lock (_blocks) + { + bool success = CheckRange( + src, + size, + MemoryState.Mask, + MemoryState.Heap, + MemoryPermission.None, + MemoryPermission.None, + MemoryAttribute.Mask, + MemoryAttribute.Borrowed, + MemoryAttribute.IpcAndDeviceMapped, + out _, + out _, + out _); + + success &= CheckRange( + dst, + PageSize, + MemoryState.UnmapProcessCodeMemoryAllowed, + MemoryState.UnmapProcessCodeMemoryAllowed, + MemoryPermission.None, + MemoryPermission.None, + MemoryAttribute.Mask, + MemoryAttribute.None, + MemoryAttribute.IpcAndDeviceMapped, + out MemoryState state, + out _, + out _); + + success &= CheckRange( + dst, + size, + MemoryState.Mask, + state, + MemoryPermission.None, + MemoryPermission.None, + MemoryAttribute.Mask, + MemoryAttribute.None); + + if (success) + { + KernelResult result = MmuUnmap(dst, pagesCount); + + if (result != KernelResult.Success) + { + return result; + } + + // TODO: Missing some checks here. + + if (!_blockAllocator.CanAllocate(MaxBlocksNeededForInsertion * 2)) + { + return KernelResult.OutOfResource; + } + + InsertBlock(dst, pagesCount, MemoryState.Unmapped); + InsertBlock(src, pagesCount, MemoryState.Heap, MemoryPermission.ReadAndWrite); + + return KernelResult.Success; + } + else + { + return KernelResult.InvalidMemState; + } + } + } + + public KernelResult SetHeapSize(ulong size, out ulong address) + { + address = 0; + + if (size > HeapRegionEnd - HeapRegionStart) + { + return KernelResult.OutOfMemory; + } + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + ulong currentHeapSize = GetHeapSize(); + + if (currentHeapSize <= size) + { + // Expand. + ulong diffSize = size - currentHeapSize; + + lock (_blocks) + { + if (currentProcess.ResourceLimit != null && diffSize != 0 && + !currentProcess.ResourceLimit.Reserve(LimitableResource.Memory, diffSize)) + { + return KernelResult.ResLimitExceeded; + } + + ulong pagesCount = diffSize / PageSize; + + KMemoryRegionManager region = GetMemoryRegionManager(); + + KernelResult result = region.AllocatePages(pagesCount, _aslrDisabled, out KPageList pageList); + + void CleanUpForError() + { + if (pageList != null) + { + region.FreePages(pageList); + } + + if (currentProcess.ResourceLimit != null && diffSize != 0) + { + currentProcess.ResourceLimit.Release(LimitableResource.Memory, diffSize); + } + } + + if (result != KernelResult.Success) + { + CleanUpForError(); + + return result; + } + + if (!_blockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) + { + CleanUpForError(); + + return KernelResult.OutOfResource; + } + + if (!IsUnmapped(_currentHeapAddr, diffSize)) + { + CleanUpForError(); + + return KernelResult.InvalidMemState; + } + + result = DoMmuOperation( + _currentHeapAddr, + pagesCount, + pageList, + MemoryPermission.ReadAndWrite, + MemoryOperation.MapVa); + + if (result != KernelResult.Success) + { + CleanUpForError(); + + return result; + } + + InsertBlock(_currentHeapAddr, pagesCount, MemoryState.Heap, MemoryPermission.ReadAndWrite); + } + } + else + { + // Shrink. + ulong freeAddr = HeapRegionStart + size; + ulong diffSize = currentHeapSize - size; + + lock (_blocks) + { + if (!_blockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } + + if (!CheckRange( + freeAddr, + diffSize, + MemoryState.Mask, + MemoryState.Heap, + MemoryPermission.Mask, + MemoryPermission.ReadAndWrite, + MemoryAttribute.Mask, + MemoryAttribute.None, + MemoryAttribute.IpcAndDeviceMapped, + out _, + out _, + out _)) + { + return KernelResult.InvalidMemState; + } + + ulong pagesCount = diffSize / PageSize; + + KernelResult result = MmuUnmap(freeAddr, pagesCount); + + if (result != KernelResult.Success) + { + return result; + } + + currentProcess.ResourceLimit?.Release(LimitableResource.Memory, BitUtils.AlignDown(diffSize, PageSize)); + + InsertBlock(freeAddr, pagesCount, MemoryState.Unmapped); + } + } + + _currentHeapAddr = HeapRegionStart + size; + + address = HeapRegionStart; + + return KernelResult.Success; + } + + public ulong GetTotalHeapSize() + { + lock (_blocks) + { + return GetHeapSize() + PhysicalMemoryUsage; + } + } + + private ulong GetHeapSize() + { + return _currentHeapAddr - HeapRegionStart; + } + + public KernelResult SetHeapCapacity(ulong capacity) + { + lock (_blocks) + { + _heapCapacity = capacity; + } + + return KernelResult.Success; + } + + public KernelResult SetMemoryAttribute( + ulong address, + ulong size, + MemoryAttribute attributeMask, + MemoryAttribute attributeValue) + { + lock (_blocks) + { + if (CheckRange( + address, + size, + MemoryState.AttributeChangeAllowed, + MemoryState.AttributeChangeAllowed, + MemoryPermission.None, + MemoryPermission.None, + MemoryAttribute.BorrowedAndIpcMapped, + MemoryAttribute.None, + MemoryAttribute.DeviceMappedAndUncached, + out MemoryState state, + out MemoryPermission permission, + out MemoryAttribute attribute)) + { + if (!_blockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } + + ulong pagesCount = size / PageSize; + + attribute &= ~attributeMask; + attribute |= attributeMask & attributeValue; + + InsertBlock(address, pagesCount, state, permission, attribute); + + return KernelResult.Success; + } + else + { + return KernelResult.InvalidMemState; + } + } + } + + public KMemoryInfo QueryMemory(ulong address) + { + if (address >= AddrSpaceStart && + address < AddrSpaceEnd) + { + lock (_blocks) + { + return FindBlock(address).GetInfo(); + } + } + else + { + return new KMemoryInfo( + AddrSpaceEnd, + ~AddrSpaceEnd + 1, + MemoryState.Reserved, + MemoryPermission.None, + MemoryAttribute.None, + MemoryPermission.None, + 0, + 0); + } + } + + public KernelResult Map(ulong dst, ulong src, ulong size) + { + bool success; + + lock (_blocks) + { + success = CheckRange( + src, + size, + MemoryState.MapAllowed, + MemoryState.MapAllowed, + MemoryPermission.Mask, + MemoryPermission.ReadAndWrite, + MemoryAttribute.Mask, + MemoryAttribute.None, + MemoryAttribute.IpcAndDeviceMapped, + out MemoryState srcState, + out _, + out _); + + success &= IsUnmapped(dst, size); + + if (success) + { + if (!_blockAllocator.CanAllocate(MaxBlocksNeededForInsertion * 2)) + { + return KernelResult.OutOfResource; + } + + ulong pagesCount = size / PageSize; + + KPageList pageList = new KPageList(); + + AddVaRangeToPageList(pageList, src, pagesCount); + + KernelResult result = MmuChangePermission(src, pagesCount, MemoryPermission.None); + + if (result != KernelResult.Success) + { + return result; + } + + result = MapPages(dst, pageList, MemoryPermission.ReadAndWrite); + + if (result != KernelResult.Success) + { + if (MmuChangePermission(src, pagesCount, MemoryPermission.ReadAndWrite) != KernelResult.Success) + { + throw new InvalidOperationException("Unexpected failure reverting memory permission."); + } + + return result; + } + + InsertBlock(src, pagesCount, srcState, MemoryPermission.None, MemoryAttribute.Borrowed); + InsertBlock(dst, pagesCount, MemoryState.Stack, MemoryPermission.ReadAndWrite); + + return KernelResult.Success; + } + else + { + return KernelResult.InvalidMemState; + } + } + } + + public KernelResult UnmapForKernel(ulong address, ulong pagesCount, MemoryState stateExpected) + { + ulong size = pagesCount * PageSize; + + lock (_blocks) + { + if (CheckRange( + address, + size, + MemoryState.Mask, + stateExpected, + MemoryPermission.None, + MemoryPermission.None, + MemoryAttribute.Mask, + MemoryAttribute.None, + MemoryAttribute.IpcAndDeviceMapped, + out _, + out _, + out _)) + { + if (!_blockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } + + KernelResult result = MmuUnmap(address, pagesCount); + + if (result == KernelResult.Success) + { + InsertBlock(address, pagesCount, MemoryState.Unmapped); + } + + return KernelResult.Success; + } + else + { + return KernelResult.InvalidMemState; + } + } + } + + public KernelResult Unmap(ulong dst, ulong src, ulong size) + { + bool success; + + lock (_blocks) + { + success = CheckRange( + src, + size, + MemoryState.MapAllowed, + MemoryState.MapAllowed, + MemoryPermission.Mask, + MemoryPermission.None, + MemoryAttribute.Mask, + MemoryAttribute.Borrowed, + MemoryAttribute.IpcAndDeviceMapped, + out MemoryState srcState, + out _, + out _); + + success &= CheckRange( + dst, + size, + MemoryState.Mask, + MemoryState.Stack, + MemoryPermission.None, + MemoryPermission.None, + MemoryAttribute.Mask, + MemoryAttribute.None, + MemoryAttribute.IpcAndDeviceMapped, + out _, + out MemoryPermission dstPermission, + out _); + + if (success) + { + if (!_blockAllocator.CanAllocate(MaxBlocksNeededForInsertion * 2)) + { + return KernelResult.OutOfResource; + } + + ulong pagesCount = size / PageSize; + + KPageList srcPageList = new KPageList(); + KPageList dstPageList = new KPageList(); + + AddVaRangeToPageList(srcPageList, src, pagesCount); + AddVaRangeToPageList(dstPageList, dst, pagesCount); + + if (!dstPageList.IsEqual(srcPageList)) + { + return KernelResult.InvalidMemRange; + } + + KernelResult result = MmuUnmap(dst, pagesCount); + + if (result != KernelResult.Success) + { + return result; + } + + result = MmuChangePermission(src, pagesCount, MemoryPermission.ReadAndWrite); + + if (result != KernelResult.Success) + { + MapPages(dst, dstPageList, dstPermission); + + return result; + } + + InsertBlock(src, pagesCount, srcState, MemoryPermission.ReadAndWrite); + InsertBlock(dst, pagesCount, MemoryState.Unmapped); + + return KernelResult.Success; + } + else + { + return KernelResult.InvalidMemState; + } + } + } + + public KernelResult ReserveTransferMemory(ulong address, ulong size, MemoryPermission permission) + { + lock (_blocks) + { + if (CheckRange( + address, + size, + MemoryState.TransferMemoryAllowed | MemoryState.IsPoolAllocated, + MemoryState.TransferMemoryAllowed | MemoryState.IsPoolAllocated, + MemoryPermission.Mask, + MemoryPermission.ReadAndWrite, + MemoryAttribute.Mask, + MemoryAttribute.None, + MemoryAttribute.IpcAndDeviceMapped, + out MemoryState state, + out _, + out MemoryAttribute attribute)) + { + // TODO: Missing checks. + + if (!_blockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } + + ulong pagesCount = size / PageSize; + + attribute |= MemoryAttribute.Borrowed; + + InsertBlock(address, pagesCount, state, permission, attribute); + + return KernelResult.Success; + } + else + { + return KernelResult.InvalidMemState; + } + } + } + + public KernelResult ResetTransferMemory(ulong address, ulong size) + { + lock (_blocks) + { + if (CheckRange( + address, + size, + MemoryState.TransferMemoryAllowed | MemoryState.IsPoolAllocated, + MemoryState.TransferMemoryAllowed | MemoryState.IsPoolAllocated, + MemoryPermission.None, + MemoryPermission.None, + MemoryAttribute.Mask, + MemoryAttribute.Borrowed, + MemoryAttribute.IpcAndDeviceMapped, + out MemoryState state, + out _, + out _)) + { + if (!_blockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } + + ulong pagesCount = size / PageSize; + + InsertBlock(address, pagesCount, state, MemoryPermission.ReadAndWrite); + + return KernelResult.Success; + } + else + { + return KernelResult.InvalidMemState; + } + } + } + + public KernelResult SetProcessMemoryPermission(ulong address, ulong size, MemoryPermission permission) + { + lock (_blocks) + { + if (CheckRange( + address, + size, + MemoryState.ProcessPermissionChangeAllowed, + MemoryState.ProcessPermissionChangeAllowed, + MemoryPermission.None, + MemoryPermission.None, + MemoryAttribute.Mask, + MemoryAttribute.None, + MemoryAttribute.IpcAndDeviceMapped, + out MemoryState oldState, + out MemoryPermission oldPermission, + out _)) + { + MemoryState newState = oldState; + + // If writing into the code region is allowed, then we need + // to change it to mutable. + if ((permission & MemoryPermission.Write) != 0) + { + if (oldState == MemoryState.CodeStatic) + { + newState = MemoryState.CodeMutable; + } + else if (oldState == MemoryState.ModCodeStatic) + { + newState = MemoryState.ModCodeMutable; + } + else + { + throw new InvalidOperationException($"Memory state \"{oldState}\" not valid for this operation."); + } + } + + if (newState != oldState || permission != oldPermission) + { + if (!_blockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } + + ulong pagesCount = size / PageSize; + + MemoryOperation operation = (permission & MemoryPermission.Execute) != 0 + ? MemoryOperation.ChangePermsAndAttributes + : MemoryOperation.ChangePermRw; + + KernelResult result = DoMmuOperation(address, pagesCount, 0, false, permission, operation); + + if (result != KernelResult.Success) + { + return result; + } + + InsertBlock(address, pagesCount, newState, permission); + } + + return KernelResult.Success; + } + else + { + return KernelResult.InvalidMemState; + } + } + } + + public KernelResult MapPhysicalMemory(ulong address, ulong size) + { + ulong endAddr = address + size; + + lock (_blocks) + { + ulong mappedSize = 0; + + foreach (KMemoryInfo info in IterateOverRange(address, endAddr)) + { + if (info.State != MemoryState.Unmapped) + { + mappedSize += GetSizeInRange(info, address, endAddr); + } + } + + if (mappedSize == size) + { + return KernelResult.Success; + } + + ulong remainingSize = size - mappedSize; + + ulong remainingPages = remainingSize / PageSize; + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + if (currentProcess.ResourceLimit != null && + !currentProcess.ResourceLimit.Reserve(LimitableResource.Memory, remainingSize)) + { + return KernelResult.ResLimitExceeded; + } + + KMemoryRegionManager region = GetMemoryRegionManager(); + + KernelResult result = region.AllocatePages(remainingPages, _aslrDisabled, out KPageList pageList); + + void CleanUpForError() + { + if (pageList != null) + { + region.FreePages(pageList); + } + + currentProcess.ResourceLimit?.Release(LimitableResource.Memory, remainingSize); + } + + if (result != KernelResult.Success) + { + CleanUpForError(); + + return result; + } + + if (!_blockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) + { + CleanUpForError(); + + return KernelResult.OutOfResource; + } + + MapPhysicalMemory(pageList, address, endAddr); + + PhysicalMemoryUsage += remainingSize; + + ulong pagesCount = size / PageSize; + + InsertBlock( + address, + pagesCount, + MemoryState.Unmapped, + MemoryPermission.None, + MemoryAttribute.None, + MemoryState.Heap, + MemoryPermission.ReadAndWrite, + MemoryAttribute.None); + } + + return KernelResult.Success; + } + + public KernelResult UnmapPhysicalMemory(ulong address, ulong size) + { + ulong endAddr = address + size; + + lock (_blocks) + { + // Scan, ensure that the region can be unmapped (all blocks are heap or + // already unmapped), fill pages list for freeing memory. + ulong heapMappedSize = 0; + + KPageList pageList = new KPageList(); + + foreach (KMemoryInfo info in IterateOverRange(address, endAddr)) + { + if (info.State == MemoryState.Heap) + { + if (info.Attribute != MemoryAttribute.None) + { + return KernelResult.InvalidMemState; + } + + ulong blockSize = GetSizeInRange(info, address, endAddr); + ulong blockAddress = GetAddrInRange(info, address); + + AddVaRangeToPageList(pageList, blockAddress, blockSize / PageSize); + + heapMappedSize += blockSize; + } + else if (info.State != MemoryState.Unmapped) + { + return KernelResult.InvalidMemState; + } + } + + if (heapMappedSize == 0) + { + return KernelResult.Success; + } + + if (!_blockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } + + // Try to unmap all the heap mapped memory inside range. + KernelResult result = KernelResult.Success; + + foreach (KMemoryInfo info in IterateOverRange(address, endAddr)) + { + if (info.State == MemoryState.Heap) + { + ulong blockSize = GetSizeInRange(info, address, endAddr); + ulong blockAddress = GetAddrInRange(info, address); + + ulong blockPagesCount = blockSize / PageSize; + + result = MmuUnmap(blockAddress, blockPagesCount); + + if (result != KernelResult.Success) + { + // If we failed to unmap, we need to remap everything back again. + MapPhysicalMemory(pageList, address, blockAddress + blockSize); + + break; + } + } + } + + if (result == KernelResult.Success) + { + GetMemoryRegionManager().FreePages(pageList); + + PhysicalMemoryUsage -= heapMappedSize; + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + currentProcess.ResourceLimit?.Release(LimitableResource.Memory, heapMappedSize); + + ulong pagesCount = size / PageSize; + + InsertBlock(address, pagesCount, MemoryState.Unmapped); + } + + return result; + } + } + + private void MapPhysicalMemory(KPageList pageList, ulong address, ulong endAddr) + { + LinkedListNode pageListNode = pageList.Nodes.First; + + KPageNode pageNode = pageListNode.Value; + + ulong srcPa = pageNode.Address; + ulong srcPaPages = pageNode.PagesCount; + + foreach (KMemoryInfo info in IterateOverRange(address, endAddr)) + { + if (info.State == MemoryState.Unmapped) + { + ulong blockSize = GetSizeInRange(info, address, endAddr); + + ulong dstVaPages = blockSize / PageSize; + + ulong dstVa = GetAddrInRange(info, address); + + while (dstVaPages > 0) + { + if (srcPaPages == 0) + { + pageListNode = pageListNode.Next; + + pageNode = pageListNode.Value; + + srcPa = pageNode.Address; + srcPaPages = pageNode.PagesCount; + } + + ulong pagesCount = srcPaPages; + + if (pagesCount > dstVaPages) + { + pagesCount = dstVaPages; + } + + DoMmuOperation( + dstVa, + pagesCount, + srcPa, + true, + MemoryPermission.ReadAndWrite, + MemoryOperation.MapPa); + + dstVa += pagesCount * PageSize; + srcPa += pagesCount * PageSize; + srcPaPages -= pagesCount; + dstVaPages -= pagesCount; + } + } + } + } + + public KernelResult CopyDataToCurrentProcess( + ulong dst, + ulong size, + ulong src, + MemoryState stateMask, + MemoryState stateExpected, + MemoryPermission permission, + MemoryAttribute attributeMask, + MemoryAttribute attributeExpected) + { + // Client -> server. + return CopyDataFromOrToCurrentProcess( + size, + src, + dst, + stateMask, + stateExpected, + permission, + attributeMask, + attributeExpected, + toServer: true); + } + + public KernelResult CopyDataFromCurrentProcess( + ulong dst, + ulong size, + MemoryState stateMask, + MemoryState stateExpected, + MemoryPermission permission, + MemoryAttribute attributeMask, + MemoryAttribute attributeExpected, + ulong src) + { + // Server -> client. + return CopyDataFromOrToCurrentProcess( + size, + dst, + src, + stateMask, + stateExpected, + permission, + attributeMask, + attributeExpected, + toServer: false); + } + + private KernelResult CopyDataFromOrToCurrentProcess( + ulong size, + ulong clientAddress, + ulong serverAddress, + MemoryState stateMask, + MemoryState stateExpected, + MemoryPermission permission, + MemoryAttribute attributeMask, + MemoryAttribute attributeExpected, + bool toServer) + { + if (AddrSpaceStart > clientAddress) + { + return KernelResult.InvalidMemState; + } + + ulong srcEndAddr = clientAddress + size; + + if (srcEndAddr <= clientAddress || srcEndAddr - 1 > AddrSpaceEnd - 1) + { + return KernelResult.InvalidMemState; + } + + lock (_blocks) + { + if (CheckRange( + clientAddress, + size, + stateMask, + stateExpected, + permission, + permission, + attributeMask | MemoryAttribute.Uncached, + attributeExpected)) + { + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + serverAddress = currentProcess.MemoryManager.GetDramAddressFromVa(serverAddress); + + if (toServer) + { + _system.Device.Memory.Copy(serverAddress, GetDramAddressFromVa(clientAddress), size); + } + else + { + _system.Device.Memory.Copy(GetDramAddressFromVa(clientAddress), serverAddress, size); + } + + return KernelResult.Success; + } + else + { + return KernelResult.InvalidMemState; + } + } + } + + public KernelResult MapBufferFromClientProcess( + ulong size, + ulong src, + KMemoryManager sourceMemMgr, + MemoryPermission permission, + MemoryState state, + bool copyData, + out ulong dst) + { + dst = 0; + + KernelResult result = sourceMemMgr.GetPagesForMappingIntoAnotherProcess( + src, + size, + permission, + state, + copyData, + _aslrDisabled, + _memRegion, + out KPageList pageList); + + if (result != KernelResult.Success) + { + return result; + } + + result = MapPagesFromAnotherProcess(size, src, permission, state, pageList, out ulong va); + + if (result != KernelResult.Success) + { + sourceMemMgr.UnmapIpcRestorePermission(src, size, state); + } + else + { + dst = va; + } + + return result; + } + + private KernelResult GetPagesForMappingIntoAnotherProcess( + ulong address, + ulong size, + MemoryPermission permission, + MemoryState state, + bool copyData, + bool aslrDisabled, + MemoryRegion region, + out KPageList pageList) + { + pageList = null; + + if (AddrSpaceStart > address) + { + return KernelResult.InvalidMemState; + } + + ulong endAddr = address + size; + + if (endAddr <= address || endAddr - 1 > AddrSpaceEnd - 1) + { + return KernelResult.InvalidMemState; + } + + MemoryState stateMask; + + switch (state) + { + case MemoryState.IpcBuffer0: stateMask = MemoryState.IpcSendAllowedType0; break; + case MemoryState.IpcBuffer1: stateMask = MemoryState.IpcSendAllowedType1; break; + case MemoryState.IpcBuffer3: stateMask = MemoryState.IpcSendAllowedType3; break; + + default: return KernelResult.InvalidCombination; + } + + MemoryPermission permissionMask = permission == MemoryPermission.ReadAndWrite + ? MemoryPermission.None + : MemoryPermission.Read; + + MemoryAttribute attributeMask = MemoryAttribute.Borrowed | MemoryAttribute.Uncached; + + if (state == MemoryState.IpcBuffer0) + { + attributeMask |= MemoryAttribute.DeviceMapped; + } + + ulong addressRounded = BitUtils.AlignUp (address, PageSize); + ulong endAddrRounded = BitUtils.AlignUp (endAddr, PageSize); + ulong endAddrTruncated = BitUtils.AlignDown(endAddr, PageSize); + + if (!_blockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } + + ulong visitedSize = 0; + + void CleanUpForError() + { + ulong endAddrVisited = address + visitedSize; + + foreach (KMemoryInfo info in IterateOverRange(address, endAddrVisited)) + { + if ((info.Permission & MemoryPermission.ReadAndWrite) != permissionMask && info.IpcRefCount == 0) + { + ulong blockAddress = GetAddrInRange(info, addressRounded); + ulong blockSize = GetSizeInRange(info, addressRounded, endAddrVisited); + + ulong blockPagesCount = blockSize / PageSize; + + if (DoMmuOperation( + blockAddress, + blockPagesCount, + 0, + false, + info.Permission, + MemoryOperation.ChangePermRw) != KernelResult.Success) + { + throw new InvalidOperationException("Unexpected failure trying to restore permission."); + } + } + } + } + + lock (_blocks) + { + KernelResult result; + + foreach (KMemoryInfo info in IterateOverRange(address, endAddrRounded)) + { + // Check if the block state matches what we expect. + if ((info.State & stateMask) != stateMask || + (info.Permission & permission) != permission || + (info.Attribute & attributeMask) != MemoryAttribute.None) + { + CleanUpForError(); + + return KernelResult.InvalidMemState; + } + + ulong blockAddress = GetAddrInRange(info, addressRounded); + ulong blockSize = GetSizeInRange(info, addressRounded, endAddrTruncated); + + ulong blockPagesCount = blockSize / PageSize; + + if ((info.Permission & MemoryPermission.ReadAndWrite) != permissionMask && info.IpcRefCount == 0) + { + result = DoMmuOperation( + blockAddress, + blockPagesCount, + 0, + false, + permissionMask, + MemoryOperation.ChangePermRw); + + if (result != KernelResult.Success) + { + CleanUpForError(); + + return result; + } + } + + visitedSize += blockSize; + } + + result = GetPagesForIpcTransfer(address, size, copyData, aslrDisabled, region, out pageList); + + if (result != KernelResult.Success) + { + CleanUpForError(); + + return result; + } + + if (visitedSize != 0) + { + InsertBlock(address, visitedSize / PageSize, SetIpcMappingPermissions, permissionMask); + } + } + + return KernelResult.Success; + } + + private KernelResult GetPagesForIpcTransfer( + ulong address, + ulong size, + bool copyData, + bool aslrDisabled, + MemoryRegion region, + out KPageList pageList) + { + pageList = null; + + ulong addressTruncated = BitUtils.AlignDown(address, PageSize); + ulong addressRounded = BitUtils.AlignUp (address, PageSize); + + ulong endAddr = address + size; + + ulong dstFirstPagePa = AllocateSinglePage(region, aslrDisabled); + + if (dstFirstPagePa == 0) + { + return KernelResult.OutOfMemory; + } + + ulong dstLastPagePa = 0; + + void CleanUpForError() + { + FreeSinglePage(region, dstFirstPagePa); + + if (dstLastPagePa != 0) + { + FreeSinglePage(region, dstLastPagePa); + } + } + + ulong firstPageFillAddress = dstFirstPagePa; + + if (!ConvertVaToPa(addressTruncated, out ulong srcFirstPagePa)) + { + CleanUpForError(); + + return KernelResult.InvalidMemState; + } + + ulong unusedSizeAfter; + + // When the start address is unaligned, we can't safely map the + // first page as it would expose other undesirable information on the + // target process. So, instead we allocate new pages, copy the data + // inside the range, and then clear the remaining space. + // The same also holds for the last page, if the end address + // (address + size) is also not aligned. + if (copyData) + { + ulong unusedSizeBefore = address - addressTruncated; + + _system.Device.Memory.Set(dstFirstPagePa, 0, unusedSizeBefore); + + ulong copySize = addressRounded <= endAddr ? addressRounded - address : size; + + _system.Device.Memory.Copy( + GetDramAddressFromPa(dstFirstPagePa + unusedSizeBefore), + GetDramAddressFromPa(srcFirstPagePa + unusedSizeBefore), copySize); + + firstPageFillAddress += unusedSizeBefore + copySize; + + unusedSizeAfter = addressRounded > endAddr ? addressRounded - endAddr : 0; + } + else + { + unusedSizeAfter = PageSize; + } + + if (unusedSizeAfter != 0) + { + _system.Device.Memory.Set(firstPageFillAddress, 0, unusedSizeAfter); + } + + KPageList pages = new KPageList(); + + if (pages.AddRange(dstFirstPagePa, 1) != KernelResult.Success) + { + CleanUpForError(); + + return KernelResult.OutOfResource; + } + + ulong endAddrTruncated = BitUtils.AlignDown(endAddr, PageSize); + ulong endAddrRounded = BitUtils.AlignUp (endAddr, PageSize); + + if (endAddrTruncated > addressRounded) + { + ulong alignedPagesCount = (endAddrTruncated - addressRounded) / PageSize; + + AddVaRangeToPageList(pages, addressRounded, alignedPagesCount); + } + + if (endAddrTruncated != endAddrRounded) + { + // End is also not aligned... + dstLastPagePa = AllocateSinglePage(region, aslrDisabled); + + if (dstLastPagePa == 0) + { + CleanUpForError(); + + return KernelResult.OutOfMemory; + } + + ulong lastPageFillAddr = dstLastPagePa; + + if (!ConvertVaToPa(endAddrTruncated, out ulong srcLastPagePa)) + { + CleanUpForError(); + + return KernelResult.InvalidMemState; + } + + if (copyData) + { + ulong copySize = endAddr - endAddrTruncated; + + _system.Device.Memory.Copy( + GetDramAddressFromPa(dstLastPagePa), + GetDramAddressFromPa(srcLastPagePa), copySize); + + lastPageFillAddr += copySize; + + unusedSizeAfter = PageSize - copySize; + } + else + { + unusedSizeAfter = PageSize; + } + + _system.Device.Memory.Set(lastPageFillAddr, 0, unusedSizeAfter); + + if (pages.AddRange(dstFirstPagePa, 1) != KernelResult.Success) + { + CleanUpForError(); + + return KernelResult.OutOfResource; + } + } + + pageList = pages; + + return KernelResult.Success; + } + + private ulong AllocateSinglePage(MemoryRegion region, bool aslrDisabled) + { + KMemoryRegionManager regionMgr = _system.MemoryRegions[(int)region]; + + return regionMgr.AllocatePagesContiguous(1, aslrDisabled); + } + + private void FreeSinglePage(MemoryRegion region, ulong address) + { + KMemoryRegionManager regionMgr = _system.MemoryRegions[(int)region]; + + regionMgr.FreePage(address); + } + + private KernelResult MapPagesFromAnotherProcess( + ulong size, + ulong address, + MemoryPermission permission, + MemoryState state, + KPageList pageList, + out ulong mappedVa) + { + mappedVa = 0; + + lock (_blocks) + { + if (!_blockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } + + ulong endAddr = address + size; + + ulong addressTruncated = BitUtils.AlignDown(address, PageSize); + ulong endAddrRounded = BitUtils.AlignUp (endAddr, PageSize); + + ulong neededSize = endAddrRounded - addressTruncated; + + ulong neededPagesCount = neededSize / PageSize; + + ulong regionPagesCount = (AliasRegionEnd - AliasRegionStart) / PageSize; + + ulong va = 0; + + for (int unit = MappingUnitSizes.Length - 1; unit >= 0 && va == 0; unit--) + { + int alignment = MappingUnitSizes[unit]; + + va = AllocateVa(AliasRegionStart, regionPagesCount, neededPagesCount, alignment); + } + + if (va == 0) + { + return KernelResult.OutOfVaSpace; + } + + if (pageList.Nodes.Count != 0) + { + KernelResult result = MapPages(va, pageList, permission); + + if (result != KernelResult.Success) + { + return result; + } + } + + InsertBlock(va, neededPagesCount, state, permission); + + mappedVa = va; + } + + return KernelResult.Success; + } + + public KernelResult UnmapNoAttributeIfStateEquals(ulong address, ulong size, MemoryState state) + { + if (AddrSpaceStart > address) + { + return KernelResult.InvalidMemState; + } + + ulong endAddr = address + size; + + if (endAddr <= address || endAddr - 1 > AddrSpaceEnd - 1) + { + return KernelResult.InvalidMemState; + } + + lock (_blocks) + { + if (CheckRange( + address, + size, + MemoryState.Mask, + state, + MemoryPermission.Read, + MemoryPermission.Read, + MemoryAttribute.Mask, + MemoryAttribute.None, + MemoryAttribute.IpcAndDeviceMapped, + out _, + out _, + out _)) + { + if (!_blockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } + + ulong addressTruncated = BitUtils.AlignDown(address, PageSize); + ulong endAddrRounded = BitUtils.AlignUp (endAddr, PageSize); + + ulong pagesCount = (endAddrRounded - addressTruncated) / PageSize; + + KernelResult result = DoMmuOperation( + addressTruncated, + pagesCount, + 0, + false, + MemoryPermission.None, + MemoryOperation.Unmap); + + if (result == KernelResult.Success) + { + InsertBlock(addressTruncated, pagesCount, MemoryState.Unmapped); + } + + return result; + } + else + { + return KernelResult.InvalidMemState; + } + } + } + + public KernelResult UnmapIpcRestorePermission(ulong address, ulong size, MemoryState state) + { + ulong endAddr = address + size; + + ulong addressRounded = BitUtils.AlignUp (address, PageSize); + ulong endAddrTruncated = BitUtils.AlignDown(endAddr, PageSize); + + ulong pagesCount = (endAddrTruncated - addressRounded) / PageSize; + + MemoryState stateMask; + + switch (state) + { + case MemoryState.IpcBuffer0: stateMask = MemoryState.IpcSendAllowedType0; break; + case MemoryState.IpcBuffer1: stateMask = MemoryState.IpcSendAllowedType1; break; + case MemoryState.IpcBuffer3: stateMask = MemoryState.IpcSendAllowedType3; break; + + default: return KernelResult.InvalidCombination; + } + + MemoryAttribute attributeMask = + MemoryAttribute.Borrowed | + MemoryAttribute.IpcMapped | + MemoryAttribute.Uncached; + + if (state == MemoryState.IpcBuffer0) + { + attributeMask |= MemoryAttribute.DeviceMapped; + } + + if (!_blockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } + + lock (_blocks) + { + foreach (KMemoryInfo info in IterateOverRange(address, endAddrTruncated)) + { + // Check if the block state matches what we expect. + if ((info.State & stateMask) != stateMask || + (info.Attribute & attributeMask) != MemoryAttribute.IpcMapped) + { + return KernelResult.InvalidMemState; + } + + if (info.Permission != info.SourcePermission && info.IpcRefCount == 1) + { + ulong blockAddress = GetAddrInRange(info, addressRounded); + ulong blockSize = GetSizeInRange(info, addressRounded, endAddrTruncated); + + ulong blockPagesCount = blockSize / PageSize; + + KernelResult result = DoMmuOperation( + blockAddress, + blockPagesCount, + 0, + false, + info.SourcePermission, + MemoryOperation.ChangePermRw); + + if (result != KernelResult.Success) + { + return result; + } + } + } + } + + InsertBlock(address, pagesCount, RestoreIpcMappingPermissions); + + return KernelResult.Success; + } + + public KernelResult UnborrowIpcBuffer(ulong address, ulong size) + { + return ClearAttributesAndChangePermission( + address, + size, + MemoryState.IpcBufferAllowed, + MemoryState.IpcBufferAllowed, + MemoryPermission.None, + MemoryPermission.None, + MemoryAttribute.Mask, + MemoryAttribute.Borrowed, + MemoryPermission.ReadAndWrite, + MemoryAttribute.Borrowed); + } + + private KernelResult ClearAttributesAndChangePermission( + ulong address, + ulong size, + MemoryState stateMask, + MemoryState stateExpected, + MemoryPermission permissionMask, + MemoryPermission permissionExpected, + MemoryAttribute attributeMask, + MemoryAttribute attributeExpected, + MemoryPermission newPermission, + MemoryAttribute attributeClearMask, + KPageList pageList = null) + { + lock (_blocks) + { + if (CheckRange( + address, + size, + stateMask | MemoryState.IsPoolAllocated, + stateExpected | MemoryState.IsPoolAllocated, + permissionMask, + permissionExpected, + attributeMask, + attributeExpected, + MemoryAttribute.IpcAndDeviceMapped, + out MemoryState oldState, + out MemoryPermission oldPermission, + out MemoryAttribute oldAttribute)) + { + ulong pagesCount = size / PageSize; + + if (pageList != null) + { + KPageList currPageList = new KPageList(); + + AddVaRangeToPageList(currPageList, address, pagesCount); + + if (!currPageList.IsEqual(pageList)) + { + return KernelResult.InvalidMemRange; + } + } + + if (!_blockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } + + if (newPermission == MemoryPermission.None) + { + newPermission = oldPermission; + } + + if (newPermission != oldPermission) + { + KernelResult result = DoMmuOperation( + address, + pagesCount, + 0, + false, + newPermission, + MemoryOperation.ChangePermRw); + + if (result != KernelResult.Success) + { + return result; + } + } + + MemoryAttribute newAttribute = oldAttribute & ~attributeClearMask; + + InsertBlock(address, pagesCount, oldState, newPermission, newAttribute); + + return KernelResult.Success; + } + else + { + return KernelResult.InvalidMemState; + } + } + } + + private void AddVaRangeToPageList(KPageList pageList, ulong start, ulong pagesCount) + { + ulong address = start; + + while (address < start + pagesCount * PageSize) + { + if (!ConvertVaToPa(address, out ulong pa)) + { + throw new InvalidOperationException("Unexpected failure translating virtual address."); + } + + pageList.AddRange(pa, 1); + + address += PageSize; + } + } + + private static ulong GetAddrInRange(KMemoryInfo info, ulong start) + { + if (info.Address < start) + { + return start; + } + + return info.Address; + } + + private static ulong GetSizeInRange(KMemoryInfo info, ulong start, ulong end) + { + ulong endAddr = info.Size + info.Address; + ulong size = info.Size; + + if (info.Address < start) + { + size -= start - info.Address; + } + + if (endAddr > end) + { + size -= endAddr - end; + } + + return size; + } + + private bool IsUnmapped(ulong address, ulong size) + { + return CheckRange( + address, + size, + MemoryState.Mask, + MemoryState.Unmapped, + MemoryPermission.Mask, + MemoryPermission.None, + MemoryAttribute.Mask, + MemoryAttribute.None, + MemoryAttribute.IpcAndDeviceMapped, + out _, + out _, + out _); + } + + private bool CheckRange( + ulong address, + ulong size, + MemoryState stateMask, + MemoryState stateExpected, + MemoryPermission permissionMask, + MemoryPermission permissionExpected, + MemoryAttribute attributeMask, + MemoryAttribute attributeExpected, + MemoryAttribute attributeIgnoreMask, + out MemoryState outState, + out MemoryPermission outPermission, + out MemoryAttribute outAttribute) + { + ulong endAddr = address + size; + + LinkedListNode node = FindBlockNode(address); + + KMemoryInfo info = node.Value.GetInfo(); + + MemoryState firstState = info.State; + MemoryPermission firstPermission = info.Permission; + MemoryAttribute firstAttribute = info.Attribute; + + do + { + info = node.Value.GetInfo(); + + // Check if the block state matches what we expect. + if ( firstState != info.State || + firstPermission != info.Permission || + (info.Attribute & attributeMask) != attributeExpected || + (firstAttribute | attributeIgnoreMask) != (info.Attribute | attributeIgnoreMask) || + (firstState & stateMask) != stateExpected || + (firstPermission & permissionMask) != permissionExpected) + { + outState = MemoryState.Unmapped; + outPermission = MemoryPermission.None; + outAttribute = MemoryAttribute.None; + + return false; + } + } + while (info.Address + info.Size - 1 < endAddr - 1 && (node = node.Next) != null); + + outState = firstState; + outPermission = firstPermission; + outAttribute = firstAttribute & ~attributeIgnoreMask; + + return true; + } + + private bool CheckRange( + ulong address, + ulong size, + MemoryState stateMask, + MemoryState stateExpected, + MemoryPermission permissionMask, + MemoryPermission permissionExpected, + MemoryAttribute attributeMask, + MemoryAttribute attributeExpected) + { + foreach (KMemoryInfo info in IterateOverRange(address, address + size)) + { + // Check if the block state matches what we expect. + if ((info.State & stateMask) != stateExpected || + (info.Permission & permissionMask) != permissionExpected || + (info.Attribute & attributeMask) != attributeExpected) + { + return false; + } + } + + return true; + } + + private IEnumerable IterateOverRange(ulong start, ulong end) + { + LinkedListNode node = FindBlockNode(start); + + KMemoryInfo info; + + do + { + info = node.Value.GetInfo(); + + yield return info; + } + while (info.Address + info.Size - 1 < end - 1 && (node = node.Next) != null); + } + + private void InsertBlock( + ulong baseAddress, + ulong pagesCount, + MemoryState oldState, + MemoryPermission oldPermission, + MemoryAttribute oldAttribute, + MemoryState newState, + MemoryPermission newPermission, + MemoryAttribute newAttribute) + { + // Insert new block on the list only on areas where the state + // of the block matches the state specified on the old* state + // arguments, otherwise leave it as is. + int oldCount = _blocks.Count; + + oldAttribute |= MemoryAttribute.IpcAndDeviceMapped; + + ulong endAddr = baseAddress + pagesCount * PageSize; + + LinkedListNode node = _blocks.First; + + while (node != null) + { + KMemoryBlock currBlock = node.Value; + + LinkedListNode nextNode = node.Next; + + ulong currBaseAddr = currBlock.BaseAddress; + ulong currEndAddr = currBlock.PagesCount * PageSize + currBaseAddr; + + if (baseAddress < currEndAddr && currBaseAddr < endAddr) + { + MemoryAttribute currBlockAttr = currBlock.Attribute | MemoryAttribute.IpcAndDeviceMapped; + + if (currBlock.State != oldState || + currBlock.Permission != oldPermission || + currBlockAttr != oldAttribute) + { + node = nextNode; + + continue; + } + + LinkedListNode newNode = node; + + if (baseAddress > currBaseAddr) + { + _blocks.AddBefore(node, currBlock.SplitRightAtAddress(baseAddress)); + } + + if (endAddr < currEndAddr) + { + newNode = _blocks.AddBefore(node, currBlock.SplitRightAtAddress(endAddr)); + } + + newNode.Value.SetState(newPermission, newState, newAttribute); + + MergeEqualStateNeighbors(newNode); + } + + if (currEndAddr - 1 >= endAddr - 1) + { + break; + } + + node = nextNode; + } + + _blockAllocator.Count += _blocks.Count - oldCount; + } + + private void InsertBlock( + ulong baseAddress, + ulong pagesCount, + MemoryState state, + MemoryPermission permission = MemoryPermission.None, + MemoryAttribute attribute = MemoryAttribute.None) + { + // Inserts new block at the list, replacing and splitting + // existing blocks as needed. + int oldCount = _blocks.Count; + + ulong endAddr = baseAddress + pagesCount * PageSize; + + LinkedListNode node = _blocks.First; + + while (node != null) + { + KMemoryBlock currBlock = node.Value; + + LinkedListNode nextNode = node.Next; + + ulong currBaseAddr = currBlock.BaseAddress; + ulong currEndAddr = currBlock.PagesCount * PageSize + currBaseAddr; + + if (baseAddress < currEndAddr && currBaseAddr < endAddr) + { + LinkedListNode newNode = node; + + if (baseAddress > currBaseAddr) + { + _blocks.AddBefore(node, currBlock.SplitRightAtAddress(baseAddress)); + } + + if (endAddr < currEndAddr) + { + newNode = _blocks.AddBefore(node, currBlock.SplitRightAtAddress(endAddr)); + } + + newNode.Value.SetState(permission, state, attribute); + + MergeEqualStateNeighbors(newNode); + } + + if (currEndAddr - 1 >= endAddr - 1) + { + break; + } + + node = nextNode; + } + + _blockAllocator.Count += _blocks.Count - oldCount; + } + + private static void SetIpcMappingPermissions(KMemoryBlock block, MemoryPermission permission) + { + block.SetIpcMappingPermission(permission); + } + + private static void RestoreIpcMappingPermissions(KMemoryBlock block, MemoryPermission permission) + { + block.RestoreIpcMappingPermission(); + } + + private delegate void BlockMutator(KMemoryBlock block, MemoryPermission newPerm); + + private void InsertBlock( + ulong baseAddress, + ulong pagesCount, + BlockMutator blockMutate, + MemoryPermission permission = MemoryPermission.None) + { + // Inserts new block at the list, replacing and splitting + // existing blocks as needed, then calling the callback + // function on the new block. + int oldCount = _blocks.Count; + + ulong endAddr = baseAddress + pagesCount * PageSize; + + LinkedListNode node = _blocks.First; + + while (node != null) + { + KMemoryBlock currBlock = node.Value; + + LinkedListNode nextNode = node.Next; + + ulong currBaseAddr = currBlock.BaseAddress; + ulong currEndAddr = currBlock.PagesCount * PageSize + currBaseAddr; + + if (baseAddress < currEndAddr && currBaseAddr < endAddr) + { + LinkedListNode newNode = node; + + if (baseAddress > currBaseAddr) + { + _blocks.AddBefore(node, currBlock.SplitRightAtAddress(baseAddress)); + } + + if (endAddr < currEndAddr) + { + newNode = _blocks.AddBefore(node, currBlock.SplitRightAtAddress(endAddr)); + } + + KMemoryBlock newBlock = newNode.Value; + + blockMutate(newBlock, permission); + + MergeEqualStateNeighbors(newNode); + } + + if (currEndAddr - 1 >= endAddr - 1) + { + break; + } + + node = nextNode; + } + + _blockAllocator.Count += _blocks.Count - oldCount; + } + + private void MergeEqualStateNeighbors(LinkedListNode node) + { + KMemoryBlock block = node.Value; + + if (node.Previous != null) + { + KMemoryBlock previousBlock = node.Previous.Value; + + if (BlockStateEquals(block, previousBlock)) + { + LinkedListNode previousNode = node.Previous; + + _blocks.Remove(node); + + previousBlock.AddPages(block.PagesCount); + + node = previousNode; + block = previousBlock; + } + } + + if (node.Next != null) + { + KMemoryBlock nextBlock = node.Next.Value; + + if (BlockStateEquals(block, nextBlock)) + { + _blocks.Remove(node.Next); + + block.AddPages(nextBlock.PagesCount); + } + } + } + + private static bool BlockStateEquals(KMemoryBlock lhs, KMemoryBlock rhs) + { + return lhs.State == rhs.State && + lhs.Permission == rhs.Permission && + lhs.Attribute == rhs.Attribute && + lhs.SourcePermission == rhs.SourcePermission && + lhs.DeviceRefCount == rhs.DeviceRefCount && + lhs.IpcRefCount == rhs.IpcRefCount; + } + + private ulong AllocateVa( + ulong regionStart, + ulong regionPagesCount, + ulong neededPagesCount, + int alignment) + { + ulong address = 0; + + ulong regionEndAddr = regionStart + regionPagesCount * PageSize; + + ulong reservedPagesCount = _isKernel ? 1UL : 4UL; + + if (_aslrEnabled) + { + ulong totalNeededSize = (reservedPagesCount + neededPagesCount) * PageSize; + + ulong remainingPages = regionPagesCount - neededPagesCount; + + ulong aslrMaxOffset = ((remainingPages + reservedPagesCount) * PageSize) / (ulong)alignment; + + for (int attempt = 0; attempt < 8; attempt++) + { + address = BitUtils.AlignDown(regionStart + GetRandomValue(0, aslrMaxOffset) * (ulong)alignment, alignment); + + ulong endAddr = address + totalNeededSize; + + KMemoryInfo info = FindBlock(address).GetInfo(); + + if (info.State != MemoryState.Unmapped) + { + continue; + } + + ulong currBaseAddr = info.Address + reservedPagesCount * PageSize; + ulong currEndAddr = info.Address + info.Size; + + if (address >= regionStart && + address >= currBaseAddr && + endAddr - 1 <= regionEndAddr - 1 && + endAddr - 1 <= currEndAddr - 1) + { + break; + } + } + + if (address == 0) + { + ulong aslrPage = GetRandomValue(0, aslrMaxOffset); + + address = FindFirstFit( + regionStart + aslrPage * PageSize, + regionPagesCount - aslrPage, + neededPagesCount, + alignment, + 0, + reservedPagesCount); + } + } + + if (address == 0) + { + address = FindFirstFit( + regionStart, + regionPagesCount, + neededPagesCount, + alignment, + 0, + reservedPagesCount); + } + + return address; + } + + private ulong FindFirstFit( + ulong regionStart, + ulong regionPagesCount, + ulong neededPagesCount, + int alignment, + ulong reservedStart, + ulong reservedPagesCount) + { + ulong reservedSize = reservedPagesCount * PageSize; + + ulong totalNeededSize = reservedSize + neededPagesCount * PageSize; + + ulong regionEndAddr = regionStart + regionPagesCount * PageSize; + + LinkedListNode node = FindBlockNode(regionStart); + + KMemoryInfo info = node.Value.GetInfo(); + + while (regionEndAddr >= info.Address) + { + if (info.State == MemoryState.Unmapped) + { + ulong currBaseAddr = info.Address + reservedSize; + ulong currEndAddr = info.Address + info.Size - 1; + + ulong address = BitUtils.AlignDown(currBaseAddr, alignment) + reservedStart; + + if (currBaseAddr > address) + { + address += (ulong)alignment; + } + + ulong allocationEndAddr = address + totalNeededSize - 1; + + if (allocationEndAddr <= regionEndAddr && + allocationEndAddr <= currEndAddr && + address < allocationEndAddr) + { + return address; + } + } + + node = node.Next; + + if (node == null) + { + break; + } + + info = node.Value.GetInfo(); + } + + return 0; + } + + private KMemoryBlock FindBlock(ulong address) + { + return FindBlockNode(address)?.Value; + } + + private LinkedListNode FindBlockNode(ulong address) + { + lock (_blocks) + { + LinkedListNode node = _blocks.First; + + while (node != null) + { + KMemoryBlock block = node.Value; + + ulong currEndAddr = block.PagesCount * PageSize + block.BaseAddress; + + if (block.BaseAddress <= address && currEndAddr - 1 >= address) + { + return node; + } + + node = node.Next; + } + } + + return null; + } + + private bool ValidateRegionForState(ulong address, ulong size, MemoryState state) + { + ulong endAddr = address + size; + + ulong regionBaseAddr = GetBaseAddrForState(state); + + ulong regionEndAddr = regionBaseAddr + GetSizeForState(state); + + bool InsideRegion() + { + return regionBaseAddr <= address && + endAddr > address && + endAddr - 1 <= regionEndAddr - 1; + } + + bool OutsideHeapRegion() + { + return endAddr <= HeapRegionStart || + address >= HeapRegionEnd; + } + + bool OutsideMapRegion() + { + return endAddr <= AliasRegionStart || + address >= AliasRegionEnd; + } + + switch (state) + { + case MemoryState.Io: + case MemoryState.Normal: + case MemoryState.CodeStatic: + case MemoryState.CodeMutable: + case MemoryState.SharedMemory: + case MemoryState.ModCodeStatic: + case MemoryState.ModCodeMutable: + case MemoryState.Stack: + case MemoryState.ThreadLocal: + case MemoryState.TransferMemoryIsolated: + case MemoryState.TransferMemory: + case MemoryState.ProcessMemory: + case MemoryState.CodeReadOnly: + case MemoryState.CodeWritable: + return InsideRegion() && OutsideHeapRegion() && OutsideMapRegion(); + + case MemoryState.Heap: + return InsideRegion() && OutsideMapRegion(); + + case MemoryState.IpcBuffer0: + case MemoryState.IpcBuffer1: + case MemoryState.IpcBuffer3: + return InsideRegion() && OutsideHeapRegion(); + + case MemoryState.KernelStack: + return InsideRegion(); + } + + throw new ArgumentException($"Invalid state value \"{state}\"."); + } + + private ulong GetBaseAddrForState(MemoryState state) + { + switch (state) + { + case MemoryState.Io: + case MemoryState.Normal: + case MemoryState.ThreadLocal: + return TlsIoRegionStart; + + case MemoryState.CodeStatic: + case MemoryState.CodeMutable: + case MemoryState.SharedMemory: + case MemoryState.ModCodeStatic: + case MemoryState.ModCodeMutable: + case MemoryState.TransferMemoryIsolated: + case MemoryState.TransferMemory: + case MemoryState.ProcessMemory: + case MemoryState.CodeReadOnly: + case MemoryState.CodeWritable: + return GetAddrSpaceBaseAddr(); + + case MemoryState.Heap: + return HeapRegionStart; + + case MemoryState.IpcBuffer0: + case MemoryState.IpcBuffer1: + case MemoryState.IpcBuffer3: + return AliasRegionStart; + + case MemoryState.Stack: + return StackRegionStart; + + case MemoryState.KernelStack: + return AddrSpaceStart; + } + + throw new ArgumentException($"Invalid state value \"{state}\"."); + } + + private ulong GetSizeForState(MemoryState state) + { + switch (state) + { + case MemoryState.Io: + case MemoryState.Normal: + case MemoryState.ThreadLocal: + return TlsIoRegionEnd - TlsIoRegionStart; + + case MemoryState.CodeStatic: + case MemoryState.CodeMutable: + case MemoryState.SharedMemory: + case MemoryState.ModCodeStatic: + case MemoryState.ModCodeMutable: + case MemoryState.TransferMemoryIsolated: + case MemoryState.TransferMemory: + case MemoryState.ProcessMemory: + case MemoryState.CodeReadOnly: + case MemoryState.CodeWritable: + return GetAddrSpaceSize(); + + case MemoryState.Heap: + return HeapRegionEnd - HeapRegionStart; + + case MemoryState.IpcBuffer0: + case MemoryState.IpcBuffer1: + case MemoryState.IpcBuffer3: + return AliasRegionEnd - AliasRegionStart; + + case MemoryState.Stack: + return StackRegionEnd - StackRegionStart; + + case MemoryState.KernelStack: + return AddrSpaceEnd - AddrSpaceStart; + } + + throw new ArgumentException($"Invalid state value \"{state}\"."); + } + + public ulong GetAddrSpaceBaseAddr() + { + if (AddrSpaceWidth == 36 || AddrSpaceWidth == 39) + { + return 0x8000000; + } + else if (AddrSpaceWidth == 32) + { + return 0x200000; + } + else + { + throw new InvalidOperationException("Invalid address space width!"); + } + } + + public ulong GetAddrSpaceSize() + { + if (AddrSpaceWidth == 36) + { + return 0xff8000000; + } + else if (AddrSpaceWidth == 39) + { + return 0x7ff8000000; + } + else if (AddrSpaceWidth == 32) + { + return 0xffe00000; + } + else + { + throw new InvalidOperationException("Invalid address space width!"); + } + } + + private KernelResult MapPages(ulong address, KPageList pageList, MemoryPermission permission) + { + ulong currAddr = address; + + KernelResult result = KernelResult.Success; + + foreach (KPageNode pageNode in pageList) + { + result = DoMmuOperation( + currAddr, + pageNode.PagesCount, + pageNode.Address, + true, + permission, + MemoryOperation.MapPa); + + if (result != KernelResult.Success) + { + KMemoryInfo info = FindBlock(currAddr).GetInfo(); + + ulong pagesCount = (address - currAddr) / PageSize; + + result = MmuUnmap(address, pagesCount); + + break; + } + + currAddr += pageNode.PagesCount * PageSize; + } + + return result; + } + + private KernelResult MmuUnmap(ulong address, ulong pagesCount) + { + return DoMmuOperation( + address, + pagesCount, + 0, + false, + MemoryPermission.None, + MemoryOperation.Unmap); + } + + private KernelResult MmuChangePermission(ulong address, ulong pagesCount, MemoryPermission permission) + { + return DoMmuOperation( + address, + pagesCount, + 0, + false, + permission, + MemoryOperation.ChangePermRw); + } + + private KernelResult DoMmuOperation( + ulong dstVa, + ulong pagesCount, + ulong srcPa, + bool map, + MemoryPermission permission, + MemoryOperation operation) + { + if (map != (operation == MemoryOperation.MapPa)) + { + throw new ArgumentException(nameof(map) + " value is invalid for this operation."); + } + + KernelResult result; + + switch (operation) + { + case MemoryOperation.MapPa: + { + ulong size = pagesCount * PageSize; + + _cpuMemory.Map((long)dstVa, (long)(srcPa - DramMemoryMap.DramBase), (long)size); + + result = KernelResult.Success; + + break; + } + + case MemoryOperation.Allocate: + { + KMemoryRegionManager region = GetMemoryRegionManager(); + + result = region.AllocatePages(pagesCount, _aslrDisabled, out KPageList pageList); + + if (result == KernelResult.Success) + { + result = MmuMapPages(dstVa, pageList); + } + + break; + } + + case MemoryOperation.Unmap: + { + ulong size = pagesCount * PageSize; + + _cpuMemory.Unmap((long)dstVa, (long)size); + + result = KernelResult.Success; + + break; + } + + case MemoryOperation.ChangePermRw: result = KernelResult.Success; break; + case MemoryOperation.ChangePermsAndAttributes: result = KernelResult.Success; break; + + default: throw new ArgumentException($"Invalid operation \"{operation}\"."); + } + + return result; + } + + private KernelResult DoMmuOperation( + ulong address, + ulong pagesCount, + KPageList pageList, + MemoryPermission permission, + MemoryOperation operation) + { + if (operation != MemoryOperation.MapVa) + { + throw new ArgumentException($"Invalid memory operation \"{operation}\" specified."); + } + + return MmuMapPages(address, pageList); + } + + private KMemoryRegionManager GetMemoryRegionManager() + { + return _system.MemoryRegions[(int)_memRegion]; + } + + private KernelResult MmuMapPages(ulong address, KPageList pageList) + { + foreach (KPageNode pageNode in pageList) + { + ulong size = pageNode.PagesCount * PageSize; + + _cpuMemory.Map((long)address, (long)(pageNode.Address - DramMemoryMap.DramBase), (long)size); + + address += size; + } + + return KernelResult.Success; + } + + public ulong GetDramAddressFromVa(ulong va) + { + return (ulong)_cpuMemory.GetPhysicalAddress((long)va); + } + + public bool ConvertVaToPa(ulong va, out ulong pa) + { + pa = DramMemoryMap.DramBase + (ulong)_cpuMemory.GetPhysicalAddress((long)va); + + return true; + } + + public static ulong GetDramAddressFromPa(ulong pa) + { + return pa - DramMemoryMap.DramBase; + } + + public long GetMmUsedPages() + { + lock (_blocks) + { + return BitUtils.DivRoundUp(GetMmUsedSize(), PageSize); + } + } + + private long GetMmUsedSize() + { + return _blocks.Count * KMemoryBlockSize; + } + + public bool IsInvalidRegion(ulong address, ulong size) + { + return address + size - 1 > GetAddrSpaceBaseAddr() + GetAddrSpaceSize() - 1; + } + + public bool InsideAddrSpace(ulong address, ulong size) + { + return AddrSpaceStart <= address && address + size - 1 <= AddrSpaceEnd - 1; + } + + public bool InsideAliasRegion(ulong address, ulong size) + { + return address + size > AliasRegionStart && AliasRegionEnd > address; + } + + public bool InsideHeapRegion(ulong address, ulong size) + { + return address + size > HeapRegionStart && HeapRegionEnd > address; + } + + public bool InsideStackRegion(ulong address, ulong size) + { + return address + size > StackRegionStart && StackRegionEnd > address; + } + + public bool OutsideAliasRegion(ulong address, ulong size) + { + return AliasRegionStart > address || address + size - 1 > AliasRegionEnd - 1; + } + + public bool OutsideAddrSpace(ulong address, ulong size) + { + return AddrSpaceStart > address || address + size - 1 > AddrSpaceEnd - 1; + } + + public bool OutsideStackRegion(ulong address, ulong size) + { + return StackRegionStart > address || address + size - 1 > StackRegionEnd - 1; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryRegionBlock.cs b/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryRegionBlock.cs new file mode 100644 index 0000000000..3334ff439a --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryRegionBlock.cs @@ -0,0 +1,43 @@ +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + class KMemoryRegionBlock + { + public long[][] Masks; + + public ulong FreeCount; + public int MaxLevel; + public ulong StartAligned; + public ulong SizeInBlocksTruncated; + public ulong SizeInBlocksRounded; + public int Order; + public int NextOrder; + + public bool TryCoalesce(int index, int size) + { + long mask = ((1L << size) - 1) << (index & 63); + + index /= 64; + + if ((mask & ~Masks[MaxLevel - 1][index]) != 0) + { + return false; + } + + Masks[MaxLevel - 1][index] &= ~mask; + + for (int level = MaxLevel - 2; level >= 0; level--, index /= 64) + { + Masks[level][index / 64] &= ~(1L << (index & 63)); + + if (Masks[level][index / 64] != 0) + { + break; + } + } + + FreeCount -= (ulong)size; + + return true; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryRegionManager.cs b/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryRegionManager.cs new file mode 100644 index 0000000000..bb4989fcb1 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryRegionManager.cs @@ -0,0 +1,488 @@ +using Ryujinx.Common; +using Ryujinx.HLE.HOS.Kernel.Common; + +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + class KMemoryRegionManager + { + private static readonly int[] BlockOrders = new int[] { 12, 16, 21, 22, 25, 29, 30 }; + + public ulong Address { get; private set; } + public ulong EndAddr { get; private set; } + public ulong Size { get; private set; } + + private int _blockOrdersCount; + + private KMemoryRegionBlock[] _blocks; + + public KMemoryRegionManager(ulong address, ulong size, ulong endAddr) + { + _blocks = new KMemoryRegionBlock[BlockOrders.Length]; + + Address = address; + Size = size; + EndAddr = endAddr; + + _blockOrdersCount = BlockOrders.Length; + + for (int blockIndex = 0; blockIndex < _blockOrdersCount; blockIndex++) + { + _blocks[blockIndex] = new KMemoryRegionBlock(); + + _blocks[blockIndex].Order = BlockOrders[blockIndex]; + + int nextOrder = blockIndex == _blockOrdersCount - 1 ? 0 : BlockOrders[blockIndex + 1]; + + _blocks[blockIndex].NextOrder = nextOrder; + + int currBlockSize = 1 << BlockOrders[blockIndex]; + int nextBlockSize = currBlockSize; + + if (nextOrder != 0) + { + nextBlockSize = 1 << nextOrder; + } + + ulong startAligned = BitUtils.AlignDown(address, nextBlockSize); + ulong endAddrAligned = BitUtils.AlignDown(endAddr, currBlockSize); + + ulong sizeInBlocksTruncated = (endAddrAligned - startAligned) >> BlockOrders[blockIndex]; + + ulong endAddrRounded = BitUtils.AlignUp(address + size, nextBlockSize); + + ulong sizeInBlocksRounded = (endAddrRounded - startAligned) >> BlockOrders[blockIndex]; + + _blocks[blockIndex].StartAligned = startAligned; + _blocks[blockIndex].SizeInBlocksTruncated = sizeInBlocksTruncated; + _blocks[blockIndex].SizeInBlocksRounded = sizeInBlocksRounded; + + ulong currSizeInBlocks = sizeInBlocksRounded; + + int maxLevel = 0; + + do + { + maxLevel++; + } + while ((currSizeInBlocks /= 64) != 0); + + _blocks[blockIndex].MaxLevel = maxLevel; + + _blocks[blockIndex].Masks = new long[maxLevel][]; + + currSizeInBlocks = sizeInBlocksRounded; + + for (int level = maxLevel - 1; level >= 0; level--) + { + currSizeInBlocks = (currSizeInBlocks + 63) / 64; + + _blocks[blockIndex].Masks[level] = new long[currSizeInBlocks]; + } + } + + if (size != 0) + { + FreePages(address, size / KMemoryManager.PageSize); + } + } + + public KernelResult AllocatePages(ulong pagesCount, bool backwards, out KPageList pageList) + { + lock (_blocks) + { + return AllocatePagesImpl(pagesCount, backwards, out pageList); + } + } + + public ulong AllocatePagesContiguous(ulong pagesCount, bool backwards) + { + lock (_blocks) + { + return AllocatePagesContiguousImpl(pagesCount, backwards); + } + } + + private KernelResult AllocatePagesImpl(ulong pagesCount, bool backwards, out KPageList pageList) + { + pageList = new KPageList(); + + if (_blockOrdersCount > 0) + { + if (GetFreePagesImpl() < pagesCount) + { + return KernelResult.OutOfMemory; + } + } + else if (pagesCount != 0) + { + return KernelResult.OutOfMemory; + } + + for (int blockIndex = _blockOrdersCount - 1; blockIndex >= 0; blockIndex--) + { + KMemoryRegionBlock block = _blocks[blockIndex]; + + ulong bestFitBlockSize = 1UL << block.Order; + + ulong blockPagesCount = bestFitBlockSize / KMemoryManager.PageSize; + + // Check if this is the best fit for this page size. + // If so, try allocating as much requested pages as possible. + while (blockPagesCount <= pagesCount) + { + ulong address = AllocatePagesForOrder(blockIndex, backwards, bestFitBlockSize); + + // The address being zero means that no free space was found on that order, + // just give up and try with the next one. + if (address == 0) + { + break; + } + + // Add new allocated page(s) to the pages list. + // If an error occurs, then free all allocated pages and fail. + KernelResult result = pageList.AddRange(address, blockPagesCount); + + if (result != KernelResult.Success) + { + FreePages(address, blockPagesCount); + + foreach (KPageNode pageNode in pageList) + { + FreePages(pageNode.Address, pageNode.PagesCount); + } + + return result; + } + + pagesCount -= blockPagesCount; + } + } + + // Success case, all requested pages were allocated successfully. + if (pagesCount == 0) + { + return KernelResult.Success; + } + + // Error case, free allocated pages and return out of memory. + foreach (KPageNode pageNode in pageList) + { + FreePages(pageNode.Address, pageNode.PagesCount); + } + + pageList = null; + + return KernelResult.OutOfMemory; + } + + private ulong AllocatePagesContiguousImpl(ulong pagesCount, bool backwards) + { + if (pagesCount == 0 || _blocks.Length < 1) + { + return 0; + } + + int blockIndex = 0; + + while ((1UL << _blocks[blockIndex].Order) / KMemoryManager.PageSize < pagesCount) + { + if (++blockIndex >= _blocks.Length) + { + return 0; + } + } + + ulong tightestFitBlockSize = 1UL << _blocks[blockIndex].Order; + + ulong address = AllocatePagesForOrder(blockIndex, backwards, tightestFitBlockSize); + + ulong requiredSize = pagesCount * KMemoryManager.PageSize; + + if (address != 0 && tightestFitBlockSize > requiredSize) + { + FreePages(address + requiredSize, (tightestFitBlockSize - requiredSize) / KMemoryManager.PageSize); + } + + return address; + } + + private ulong AllocatePagesForOrder(int blockIndex, bool backwards, ulong bestFitBlockSize) + { + ulong address = 0; + + KMemoryRegionBlock block = null; + + for (int currBlockIndex = blockIndex; + currBlockIndex < _blockOrdersCount && address == 0; + currBlockIndex++) + { + block = _blocks[currBlockIndex]; + + int index = 0; + + bool zeroMask = false; + + for (int level = 0; level < block.MaxLevel; level++) + { + long mask = block.Masks[level][index]; + + if (mask == 0) + { + zeroMask = true; + + break; + } + + if (backwards) + { + index = (index * 64 + 63) - BitUtils.CountLeadingZeros64(mask); + } + else + { + index = index * 64 + BitUtils.CountLeadingZeros64(BitUtils.ReverseBits64(mask)); + } + } + + if (block.SizeInBlocksTruncated <= (ulong)index || zeroMask) + { + continue; + } + + block.FreeCount--; + + int tempIdx = index; + + for (int level = block.MaxLevel - 1; level >= 0; level--, tempIdx /= 64) + { + block.Masks[level][tempIdx / 64] &= ~(1L << (tempIdx & 63)); + + if (block.Masks[level][tempIdx / 64] != 0) + { + break; + } + } + + address = block.StartAligned + ((ulong)index << block.Order); + } + + for (int currBlockIndex = blockIndex; + currBlockIndex < _blockOrdersCount && address == 0; + currBlockIndex++) + { + block = _blocks[currBlockIndex]; + + int index = 0; + + bool zeroMask = false; + + for (int level = 0; level < block.MaxLevel; level++) + { + long mask = block.Masks[level][index]; + + if (mask == 0) + { + zeroMask = true; + + break; + } + + if (backwards) + { + index = index * 64 + BitUtils.CountLeadingZeros64(BitUtils.ReverseBits64(mask)); + } + else + { + index = (index * 64 + 63) - BitUtils.CountLeadingZeros64(mask); + } + } + + if (block.SizeInBlocksTruncated <= (ulong)index || zeroMask) + { + continue; + } + + block.FreeCount--; + + int tempIdx = index; + + for (int level = block.MaxLevel - 1; level >= 0; level--, tempIdx /= 64) + { + block.Masks[level][tempIdx / 64] &= ~(1L << (tempIdx & 63)); + + if (block.Masks[level][tempIdx / 64] != 0) + { + break; + } + } + + address = block.StartAligned + ((ulong)index << block.Order); + } + + if (address != 0) + { + // If we are using a larger order than best fit, then we should + // split it into smaller blocks. + ulong firstFreeBlockSize = 1UL << block.Order; + + if (firstFreeBlockSize > bestFitBlockSize) + { + FreePages(address + bestFitBlockSize, (firstFreeBlockSize - bestFitBlockSize) / KMemoryManager.PageSize); + } + } + + return address; + } + + public void FreePage(ulong address) + { + lock (_blocks) + { + FreePages(address, 1); + } + } + + public void FreePages(KPageList pageList) + { + lock (_blocks) + { + foreach (KPageNode pageNode in pageList) + { + FreePages(pageNode.Address, pageNode.PagesCount); + } + } + } + + private void FreePages(ulong address, ulong pagesCount) + { + ulong endAddr = address + pagesCount * KMemoryManager.PageSize; + + int blockIndex = _blockOrdersCount - 1; + + ulong addressRounded = 0; + ulong endAddrTruncated = 0; + + for (; blockIndex >= 0; blockIndex--) + { + KMemoryRegionBlock allocInfo = _blocks[blockIndex]; + + int blockSize = 1 << allocInfo.Order; + + addressRounded = BitUtils.AlignUp (address, blockSize); + endAddrTruncated = BitUtils.AlignDown(endAddr, blockSize); + + if (addressRounded < endAddrTruncated) + { + break; + } + } + + void FreeRegion(ulong currAddress) + { + for (int currBlockIndex = blockIndex; + currBlockIndex < _blockOrdersCount && currAddress != 0; + currBlockIndex++) + { + KMemoryRegionBlock block = _blocks[currBlockIndex]; + + block.FreeCount++; + + ulong freedBlocks = (currAddress - block.StartAligned) >> block.Order; + + int index = (int)freedBlocks; + + for (int level = block.MaxLevel - 1; level >= 0; level--, index /= 64) + { + long mask = block.Masks[level][index / 64]; + + block.Masks[level][index / 64] = mask | (1L << (index & 63)); + + if (mask != 0) + { + break; + } + } + + int blockSizeDelta = 1 << (block.NextOrder - block.Order); + + int freedBlocksTruncated = BitUtils.AlignDown((int)freedBlocks, blockSizeDelta); + + if (!block.TryCoalesce(freedBlocksTruncated, blockSizeDelta)) + { + break; + } + + currAddress = block.StartAligned + ((ulong)freedBlocksTruncated << block.Order); + } + } + + // Free inside aligned region. + ulong baseAddress = addressRounded; + + while (baseAddress < endAddrTruncated) + { + ulong blockSize = 1UL << _blocks[blockIndex].Order; + + FreeRegion(baseAddress); + + baseAddress += blockSize; + } + + int nextBlockIndex = blockIndex - 1; + + // Free region between Address and aligned region start. + baseAddress = addressRounded; + + for (blockIndex = nextBlockIndex; blockIndex >= 0; blockIndex--) + { + ulong blockSize = 1UL << _blocks[blockIndex].Order; + + while (baseAddress - blockSize >= address) + { + baseAddress -= blockSize; + + FreeRegion(baseAddress); + } + } + + // Free region between aligned region end and End Address. + baseAddress = endAddrTruncated; + + for (blockIndex = nextBlockIndex; blockIndex >= 0; blockIndex--) + { + ulong blockSize = 1UL << _blocks[blockIndex].Order; + + while (baseAddress + blockSize <= endAddr) + { + FreeRegion(baseAddress); + + baseAddress += blockSize; + } + } + } + + public ulong GetFreePages() + { + lock (_blocks) + { + return GetFreePagesImpl(); + } + } + + private ulong GetFreePagesImpl() + { + ulong availablePages = 0; + + for (int blockIndex = 0; blockIndex < _blockOrdersCount; blockIndex++) + { + KMemoryRegionBlock block = _blocks[blockIndex]; + + ulong blockPagesCount = (1UL << block.Order) / KMemoryManager.PageSize; + + availablePages += blockPagesCount * block.FreeCount; + } + + return availablePages; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Memory/KPageList.cs b/Ryujinx.HLE/HOS/Kernel/Memory/KPageList.cs new file mode 100644 index 0000000000..f0935dcc1c --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Memory/KPageList.cs @@ -0,0 +1,81 @@ +using Ryujinx.HLE.HOS.Kernel.Common; +using System.Collections; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + class KPageList : IEnumerable + { + public LinkedList Nodes { get; private set; } + + public KPageList() + { + Nodes = new LinkedList(); + } + + public KernelResult AddRange(ulong address, ulong pagesCount) + { + if (pagesCount != 0) + { + if (Nodes.Last != null) + { + KPageNode lastNode = Nodes.Last.Value; + + if (lastNode.Address + lastNode.PagesCount * KMemoryManager.PageSize == address) + { + address = lastNode.Address; + pagesCount += lastNode.PagesCount; + + Nodes.RemoveLast(); + } + } + + Nodes.AddLast(new KPageNode(address, pagesCount)); + } + + return KernelResult.Success; + } + + public ulong GetPagesCount() + { + ulong sum = 0; + + foreach (KPageNode node in Nodes) + { + sum += node.PagesCount; + } + + return sum; + } + + public bool IsEqual(KPageList other) + { + LinkedListNode thisNode = Nodes.First; + LinkedListNode otherNode = other.Nodes.First; + + while (thisNode != null && otherNode != null) + { + if (thisNode.Value.Address != otherNode.Value.Address || + thisNode.Value.PagesCount != otherNode.Value.PagesCount) + { + return false; + } + + thisNode = thisNode.Next; + otherNode = otherNode.Next; + } + + return thisNode == null && otherNode == null; + } + + public IEnumerator GetEnumerator() + { + return Nodes.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Memory/KPageNode.cs b/Ryujinx.HLE/HOS/Kernel/Memory/KPageNode.cs new file mode 100644 index 0000000000..ada41687ea --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Memory/KPageNode.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + struct KPageNode + { + public ulong Address; + public ulong PagesCount; + + public KPageNode(ulong address, ulong pagesCount) + { + Address = address; + PagesCount = pagesCount; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Memory/KSharedMemory.cs b/Ryujinx.HLE/HOS/Kernel/Memory/KSharedMemory.cs new file mode 100644 index 0000000000..6b92ed30a7 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Memory/KSharedMemory.cs @@ -0,0 +1,71 @@ +using Ryujinx.Common; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Process; + +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + class KSharedMemory : KAutoObject + { + private KPageList _pageList; + + private long _ownerPid; + + private MemoryPermission _ownerPermission; + private MemoryPermission _userPermission; + + public KSharedMemory( + Horizon system, + KPageList pageList, + long ownerPid, + MemoryPermission ownerPermission, + MemoryPermission userPermission) : base(system) + { + _pageList = pageList; + _ownerPid = ownerPid; + _ownerPermission = ownerPermission; + _userPermission = userPermission; + } + + public KernelResult MapIntoProcess( + KMemoryManager memoryManager, + ulong address, + ulong size, + KProcess process, + MemoryPermission permission) + { + ulong pagesCountRounded = BitUtils.DivRoundUp(size, KMemoryManager.PageSize); + + if (_pageList.GetPagesCount() != pagesCountRounded) + { + return KernelResult.InvalidSize; + } + + MemoryPermission expectedPermission = process.Pid == _ownerPid + ? _ownerPermission + : _userPermission; + + if (permission != expectedPermission) + { + return KernelResult.InvalidPermission; + } + + return memoryManager.MapPages(address, _pageList, MemoryState.SharedMemory, permission); + } + + public KernelResult UnmapFromProcess( + KMemoryManager memoryManager, + ulong address, + ulong size, + KProcess process) + { + ulong pagesCountRounded = BitUtils.DivRoundUp(size, KMemoryManager.PageSize); + + if (_pageList.GetPagesCount() != pagesCountRounded) + { + return KernelResult.InvalidSize; + } + + return memoryManager.UnmapPages(address, _pageList, MemoryState.SharedMemory); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Memory/KSlabHeap.cs b/Ryujinx.HLE/HOS/Kernel/Memory/KSlabHeap.cs new file mode 100644 index 0000000000..9051e84cc2 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Memory/KSlabHeap.cs @@ -0,0 +1,50 @@ +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + class KSlabHeap + { + private LinkedList _items; + + public KSlabHeap(ulong pa, ulong itemSize, ulong size) + { + _items = new LinkedList(); + + int itemsCount = (int)(size / itemSize); + + for (int index = 0; index < itemsCount; index++) + { + _items.AddLast(pa); + + pa += itemSize; + } + } + + public bool TryGetItem(out ulong pa) + { + lock (_items) + { + if (_items.First != null) + { + pa = _items.First.Value; + + _items.RemoveFirst(); + + return true; + } + } + + pa = 0; + + return false; + } + + public void Free(ulong pa) + { + lock (_items) + { + _items.AddFirst(pa); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Memory/KTransferMemory.cs b/Ryujinx.HLE/HOS/Kernel/Memory/KTransferMemory.cs new file mode 100644 index 0000000000..a0929eca00 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Memory/KTransferMemory.cs @@ -0,0 +1,16 @@ +using Ryujinx.HLE.HOS.Kernel.Common; + +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + class KTransferMemory : KAutoObject + { + public ulong Address { get; private set; } + public ulong Size { get; private set; } + + public KTransferMemory(Horizon system, ulong address, ulong size) : base(system) + { + Address = address; + Size = size; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Memory/MemoryAttribute.cs b/Ryujinx.HLE/HOS/Kernel/Memory/MemoryAttribute.cs new file mode 100644 index 0000000000..42407ffe2a --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Memory/MemoryAttribute.cs @@ -0,0 +1,22 @@ +using System; + +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + [Flags] + enum MemoryAttribute : byte + { + None = 0, + Mask = 0xff, + + Borrowed = 1 << 0, + IpcMapped = 1 << 1, + DeviceMapped = 1 << 2, + Uncached = 1 << 3, + + IpcAndDeviceMapped = IpcMapped | DeviceMapped, + + BorrowedAndIpcMapped = Borrowed | IpcMapped, + + DeviceMappedAndUncached = DeviceMapped | Uncached + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Memory/MemoryOperation.cs b/Ryujinx.HLE/HOS/Kernel/Memory/MemoryOperation.cs new file mode 100644 index 0000000000..7f7f29deed --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Memory/MemoryOperation.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + enum MemoryOperation + { + MapPa, + MapVa, + Allocate, + Unmap, + ChangePermRw, + ChangePermsAndAttributes + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Memory/MemoryPermission.cs b/Ryujinx.HLE/HOS/Kernel/Memory/MemoryPermission.cs new file mode 100644 index 0000000000..0ad90abd7c --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Memory/MemoryPermission.cs @@ -0,0 +1,18 @@ +using System; + +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + [Flags] + enum MemoryPermission : byte + { + None = 0, + Mask = 0xff, + + Read = 1 << 0, + Write = 1 << 1, + Execute = 1 << 2, + + ReadAndWrite = Read | Write, + ReadAndExecute = Read | Execute + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Memory/MemoryRegion.cs b/Ryujinx.HLE/HOS/Kernel/Memory/MemoryRegion.cs new file mode 100644 index 0000000000..ad719bdedc --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Memory/MemoryRegion.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + enum MemoryRegion + { + Application = 0, + Applet = 1, + Service = 2, + NvServices = 3 + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Memory/MemoryState.cs b/Ryujinx.HLE/HOS/Kernel/Memory/MemoryState.cs new file mode 100644 index 0000000000..f7161a88c3 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Memory/MemoryState.cs @@ -0,0 +1,49 @@ +using System; + +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + [Flags] + enum MemoryState : uint + { + Unmapped = 0x00000000, + Io = 0x00002001, + Normal = 0x00042002, + CodeStatic = 0x00DC7E03, + CodeMutable = 0x03FEBD04, + Heap = 0x037EBD05, + SharedMemory = 0x00402006, + ModCodeStatic = 0x00DD7E08, + ModCodeMutable = 0x03FFBD09, + IpcBuffer0 = 0x005C3C0A, + Stack = 0x005C3C0B, + ThreadLocal = 0x0040200C, + TransferMemoryIsolated = 0x015C3C0D, + TransferMemory = 0x005C380E, + ProcessMemory = 0x0040380F, + Reserved = 0x00000010, + IpcBuffer1 = 0x005C3811, + IpcBuffer3 = 0x004C2812, + KernelStack = 0x00002013, + CodeReadOnly = 0x00402214, + CodeWritable = 0x00402015, + Mask = 0xffffffff, + + PermissionChangeAllowed = 1 << 8, + ForceReadWritableByDebugSyscalls = 1 << 9, + IpcSendAllowedType0 = 1 << 10, + IpcSendAllowedType3 = 1 << 11, + IpcSendAllowedType1 = 1 << 12, + ProcessPermissionChangeAllowed = 1 << 14, + MapAllowed = 1 << 15, + UnmapProcessCodeMemoryAllowed = 1 << 16, + TransferMemoryAllowed = 1 << 17, + QueryPhysicalAddressAllowed = 1 << 18, + MapDeviceAllowed = 1 << 19, + MapDeviceAlignedAllowed = 1 << 20, + IpcBufferAllowed = 1 << 21, + IsPoolAllocated = 1 << 22, + MapProcessAllowed = 1 << 23, + AttributeChangeAllowed = 1 << 24, + CodeMemoryAllowed = 1 << 25 + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Process/HleProcessDebugger.cs b/Ryujinx.HLE/HOS/Kernel/Process/HleProcessDebugger.cs new file mode 100644 index 0000000000..75f52851de --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Process/HleProcessDebugger.cs @@ -0,0 +1,364 @@ +using ARMeilleure.Memory; +using Ryujinx.Common; +using Ryujinx.HLE.HOS.Diagnostics.Demangler; +using Ryujinx.HLE.HOS.Kernel.Memory; +using Ryujinx.HLE.Loaders.Elf; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Kernel.Process +{ + class HleProcessDebugger + { + private const int Mod0 = 'M' << 0 | 'O' << 8 | 'D' << 16 | '0' << 24; + + private KProcess _owner; + + private class Image + { + public long BaseAddress { get; private set; } + + public ElfSymbol[] Symbols { get; private set; } + + public Image(long baseAddress, ElfSymbol[] symbols) + { + BaseAddress = baseAddress; + Symbols = symbols; + } + } + + private List _images; + + private int _loaded; + + public HleProcessDebugger(KProcess owner) + { + _owner = owner; + + _images = new List(); + } + + public string GetGuestStackTrace(ARMeilleure.State.ExecutionContext context) + { + EnsureLoaded(); + + StringBuilder trace = new StringBuilder(); + + void AppendTrace(long address) + { + Image image = GetImage(address, out int imageIndex); + + if (image == null || !TryGetSubName(image, address, out string subName)) + { + subName = $"Sub{address:x16}"; + } + else if (subName.StartsWith("_Z")) + { + subName = Demangler.Parse(subName); + } + + if (image != null) + { + long offset = address - image.BaseAddress; + + string imageName = GetGuessedNsoNameFromIndex(imageIndex); + + trace.AppendLine($" {imageName}:0x{offset:x8} {subName}"); + } + else + { + trace.AppendLine($" ??? {subName}"); + } + } + + trace.AppendLine($"Process: {_owner.Name}, PID: {_owner.Pid}"); + + if (context.IsAarch32) + { + long framePointer = (long)context.GetX(11); + + while (framePointer != 0) + { + if ((framePointer & 3) != 0 || + !_owner.CpuMemory.IsMapped(framePointer) || + !_owner.CpuMemory.IsMapped(framePointer + 4)) + { + break; + } + + AppendTrace(_owner.CpuMemory.ReadInt32(framePointer + 4)); + + framePointer = _owner.CpuMemory.ReadInt32(framePointer); + } + } + else + { + long framePointer = (long)context.GetX(29); + + while (framePointer != 0) + { + if ((framePointer & 7) != 0 || + !_owner.CpuMemory.IsMapped(framePointer) || + !_owner.CpuMemory.IsMapped(framePointer + 8)) + { + break; + } + + AppendTrace(_owner.CpuMemory.ReadInt64(framePointer + 8)); + + framePointer = _owner.CpuMemory.ReadInt64(framePointer); + } + } + + return trace.ToString(); + } + + private bool TryGetSubName(Image image, long address, out string name) + { + address -= image.BaseAddress; + + int left = 0; + int right = image.Symbols.Length - 1; + + while (left <= right) + { + int size = right - left; + + int middle = left + (size >> 1); + + ElfSymbol symbol = image.Symbols[middle]; + + ulong endAddr = symbol.Value + symbol.Size; + + if ((ulong)address >= symbol.Value && (ulong)address < endAddr) + { + name = symbol.Name; + + return true; + } + + if ((ulong)address < (ulong)symbol.Value) + { + right = middle - 1; + } + else + { + left = middle + 1; + } + } + + name = null; + + return false; + } + + private Image GetImage(long address, out int index) + { + lock (_images) + { + for (index = _images.Count - 1; index >= 0; index--) + { + if ((ulong)address >= (ulong)_images[index].BaseAddress) + { + return _images[index]; + } + } + } + + return null; + } + + private string GetGuessedNsoNameFromIndex(int index) + { + if ((uint)index > 11) + { + return "???"; + } + + if (index == 0) + { + return "rtld"; + } + else if (index == 1) + { + return "main"; + } + else if (index == GetImagesCount() - 1) + { + return "sdk"; + } + else + { + return "subsdk" + (index - 2); + } + } + + private int GetImagesCount() + { + lock (_images) + { + return _images.Count; + } + } + + private void EnsureLoaded() + { + if (Interlocked.CompareExchange(ref _loaded, 1, 0) == 0) + { + ScanMemoryForTextSegments(); + } + } + + private void ScanMemoryForTextSegments() + { + ulong oldAddress = 0; + ulong address = 0; + + while (address >= oldAddress) + { + KMemoryInfo info = _owner.MemoryManager.QueryMemory(address); + + if (info.State == MemoryState.Reserved) + { + break; + } + + if (info.State == MemoryState.CodeStatic && info.Permission == MemoryPermission.ReadAndExecute) + { + LoadMod0Symbols(_owner.CpuMemory, (long)info.Address); + } + + oldAddress = address; + + address = info.Address + info.Size; + } + } + + private void LoadMod0Symbols(MemoryManager memory, long textOffset) + { + long mod0Offset = textOffset + memory.ReadUInt32(textOffset + 4); + + if (mod0Offset < textOffset || !memory.IsMapped(mod0Offset) || (mod0Offset & 3) != 0) + { + return; + } + + Dictionary dynamic = new Dictionary(); + + int mod0Magic = memory.ReadInt32(mod0Offset + 0x0); + + if (mod0Magic != Mod0) + { + return; + } + + long dynamicOffset = memory.ReadInt32(mod0Offset + 0x4) + mod0Offset; + long bssStartOffset = memory.ReadInt32(mod0Offset + 0x8) + mod0Offset; + long bssEndOffset = memory.ReadInt32(mod0Offset + 0xc) + mod0Offset; + long ehHdrStartOffset = memory.ReadInt32(mod0Offset + 0x10) + mod0Offset; + long ehHdrEndOffset = memory.ReadInt32(mod0Offset + 0x14) + mod0Offset; + long modObjOffset = memory.ReadInt32(mod0Offset + 0x18) + mod0Offset; + + bool isAArch32 = memory.ReadUInt64(dynamicOffset) > 0xFFFFFFFF || memory.ReadUInt64(dynamicOffset + 0x10) > 0xFFFFFFFF; + + while (true) + { + long tagVal; + long value; + + if (isAArch32) + { + tagVal = memory.ReadInt32(dynamicOffset + 0); + value = memory.ReadInt32(dynamicOffset + 4); + + dynamicOffset += 0x8; + } + else + { + tagVal = memory.ReadInt64(dynamicOffset + 0); + value = memory.ReadInt64(dynamicOffset + 8); + + dynamicOffset += 0x10; + } + + + ElfDynamicTag tag = (ElfDynamicTag)tagVal; + + if (tag == ElfDynamicTag.DT_NULL) + { + break; + } + + dynamic[tag] = value; + } + + if (!dynamic.TryGetValue(ElfDynamicTag.DT_STRTAB, out long strTab) || + !dynamic.TryGetValue(ElfDynamicTag.DT_SYMTAB, out long symTab) || + !dynamic.TryGetValue(ElfDynamicTag.DT_SYMENT, out long symEntSize)) + { + return; + } + + long strTblAddr = textOffset + strTab; + long symTblAddr = textOffset + symTab; + + List symbols = new List(); + + while ((ulong)symTblAddr < (ulong)strTblAddr) + { + ElfSymbol sym = isAArch32 ? GetSymbol32(memory, symTblAddr, strTblAddr) : GetSymbol64(memory, symTblAddr, strTblAddr); + + symbols.Add(sym); + + symTblAddr += symEntSize; + } + + lock (_images) + { + _images.Add(new Image(textOffset, symbols.OrderBy(x => x.Value).ToArray())); + } + } + + private ElfSymbol GetSymbol64(MemoryManager memory, long address, long strTblAddr) + { + using (BinaryReader inputStream = new BinaryReader(new MemoryStream(memory.ReadBytes(address, Unsafe.SizeOf())))) + { + ElfSymbol64 sym = inputStream.ReadStruct(); + + uint nameIndex = sym.NameOffset; + + string name = string.Empty; + + for (int chr; (chr = memory.ReadByte(strTblAddr + nameIndex++)) != 0;) + { + name += (char)chr; + } + + return new ElfSymbol(name, sym.Info, sym.Other, sym.SectionIndex, sym.ValueAddress, sym.Size); + } + } + + private ElfSymbol GetSymbol32(MemoryManager memory, long address, long strTblAddr) + { + using (BinaryReader inputStream = new BinaryReader(new MemoryStream(memory.ReadBytes(address, Unsafe.SizeOf())))) + { + ElfSymbol32 sym = inputStream.ReadStruct(); + + uint nameIndex = sym.NameOffset; + + string name = string.Empty; + + for (int chr; (chr = memory.ReadByte(strTblAddr + nameIndex++)) != 0;) + { + name += (char)chr; + } + + return new ElfSymbol(name, sym.Info, sym.Other, sym.SectionIndex, sym.ValueAddress, sym.Size); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Process/KContextIdManager.cs b/Ryujinx.HLE/HOS/Kernel/Process/KContextIdManager.cs new file mode 100644 index 0000000000..0392b9304b --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Process/KContextIdManager.cs @@ -0,0 +1,83 @@ +using Ryujinx.Common; +using System; + +namespace Ryujinx.HLE.HOS.Kernel.Process +{ + class KContextIdManager + { + private const int IdMasksCount = 8; + + private int[] _idMasks; + + private int _nextFreeBitHint; + + public KContextIdManager() + { + _idMasks = new int[IdMasksCount]; + } + + public int GetId() + { + lock (_idMasks) + { + int id = 0; + + if (!TestBit(_nextFreeBitHint)) + { + id = _nextFreeBitHint; + } + else + { + for (int index = 0; index < IdMasksCount; index++) + { + int mask = _idMasks[index]; + + int firstFreeBit = BitUtils.CountLeadingZeros32((mask + 1) & ~mask); + + if (firstFreeBit < 32) + { + int baseBit = index * 32 + 31; + + id = baseBit - firstFreeBit; + + break; + } + else if (index == IdMasksCount - 1) + { + throw new InvalidOperationException("Maximum number of Ids reached!"); + } + } + } + + _nextFreeBitHint = id + 1; + + SetBit(id); + + return id; + } + } + + public void PutId(int id) + { + lock (_idMasks) + { + ClearBit(id); + } + } + + private bool TestBit(int bit) + { + return (_idMasks[_nextFreeBitHint / 32] & (1 << (_nextFreeBitHint & 31))) != 0; + } + + private void SetBit(int bit) + { + _idMasks[_nextFreeBitHint / 32] |= (1 << (_nextFreeBitHint & 31)); + } + + private void ClearBit(int bit) + { + _idMasks[_nextFreeBitHint / 32] &= ~(1 << (_nextFreeBitHint & 31)); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Process/KHandleEntry.cs b/Ryujinx.HLE/HOS/Kernel/Process/KHandleEntry.cs new file mode 100644 index 0000000000..b5ca9b5e28 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Process/KHandleEntry.cs @@ -0,0 +1,19 @@ +using Ryujinx.HLE.HOS.Kernel.Common; + +namespace Ryujinx.HLE.HOS.Kernel.Process +{ + class KHandleEntry + { + public KHandleEntry Next { get; set; } + + public int Index { get; private set; } + + public ushort HandleId { get; set; } + public KAutoObject Obj { get; set; } + + public KHandleEntry(int index) + { + Index = index; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Process/KHandleTable.cs b/Ryujinx.HLE/HOS/Kernel/Process/KHandleTable.cs new file mode 100644 index 0000000000..e9dd14b2e6 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Process/KHandleTable.cs @@ -0,0 +1,285 @@ +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Threading; +using System; + +namespace Ryujinx.HLE.HOS.Kernel.Process +{ + class KHandleTable + { + public const int SelfThreadHandle = (0x1ffff << 15) | 0; + public const int SelfProcessHandle = (0x1ffff << 15) | 1; + + private Horizon _system; + + private KHandleEntry[] _table; + + private KHandleEntry _tableHead; + private KHandleEntry _nextFreeEntry; + + private int _activeSlotsCount; + + private int _size; + + private ushort _idCounter; + + public KHandleTable(Horizon system) + { + _system = system; + } + + public KernelResult Initialize(int size) + { + if ((uint)size > 1024) + { + return KernelResult.OutOfMemory; + } + + if (size < 1) + { + size = 1024; + } + + _size = size; + + _idCounter = 1; + + _table = new KHandleEntry[size]; + + _tableHead = new KHandleEntry(0); + + KHandleEntry entry = _tableHead; + + for (int index = 0; index < size; index++) + { + _table[index] = entry; + + entry.Next = new KHandleEntry(index + 1); + + entry = entry.Next; + } + + _table[size - 1].Next = null; + + _nextFreeEntry = _tableHead; + + return KernelResult.Success; + } + + public KernelResult GenerateHandle(KAutoObject obj, out int handle) + { + handle = 0; + + lock (_table) + { + if (_activeSlotsCount >= _size) + { + return KernelResult.HandleTableFull; + } + + KHandleEntry entry = _nextFreeEntry; + + _nextFreeEntry = entry.Next; + + entry.Obj = obj; + entry.HandleId = _idCounter; + + _activeSlotsCount++; + + handle = (_idCounter << 15) | entry.Index; + + obj.IncrementReferenceCount(); + + if ((short)(_idCounter + 1) >= 0) + { + _idCounter++; + } + else + { + _idCounter = 1; + } + } + + return KernelResult.Success; + } + + public KernelResult ReserveHandle(out int handle) + { + handle = 0; + + lock (_table) + { + if (_activeSlotsCount >= _size) + { + return KernelResult.HandleTableFull; + } + + KHandleEntry entry = _nextFreeEntry; + + _nextFreeEntry = entry.Next; + + _activeSlotsCount++; + + handle = (_idCounter << 15) | entry.Index; + + if ((short)(_idCounter + 1) >= 0) + { + _idCounter++; + } + else + { + _idCounter = 1; + } + } + + return KernelResult.Success; + } + + public void CancelHandleReservation(int handle) + { + int index = (handle >> 0) & 0x7fff; + int handleId = (handle >> 15); + + lock (_table) + { + KHandleEntry entry = _table[index]; + + entry.Obj = null; + entry.Next = _nextFreeEntry; + + _nextFreeEntry = entry; + + _activeSlotsCount--; + } + } + + public void SetReservedHandleObj(int handle, KAutoObject obj) + { + int index = (handle >> 0) & 0x7fff; + int handleId = (handle >> 15); + + lock (_table) + { + KHandleEntry entry = _table[index]; + + entry.Obj = obj; + entry.HandleId = (ushort)(handle >> 15); + + obj.IncrementReferenceCount(); + } + } + + public bool CloseHandle(int handle) + { + if ((handle >> 30) != 0 || + handle == SelfThreadHandle || + handle == SelfProcessHandle) + { + return false; + } + + int index = (handle >> 0) & 0x7fff; + int handleId = (handle >> 15); + + KAutoObject obj = null; + + bool result = false; + + lock (_table) + { + if (handleId != 0 && index < _size) + { + KHandleEntry entry = _table[index]; + + if ((obj = entry.Obj) != null && entry.HandleId == handleId) + { + entry.Obj = null; + entry.Next = _nextFreeEntry; + + _nextFreeEntry = entry; + + _activeSlotsCount--; + + result = true; + } + } + } + + if (result) + { + obj.DecrementReferenceCount(); + } + + return result; + } + + public T GetObject(int handle) where T : KAutoObject + { + int index = (handle >> 0) & 0x7fff; + int handleId = (handle >> 15); + + lock (_table) + { + if ((handle >> 30) == 0 && handleId != 0 && index < _size) + { + KHandleEntry entry = _table[index]; + + if (entry.HandleId == handleId && entry.Obj is T obj) + { + return obj; + } + } + } + + return default(T); + } + + public KThread GetKThread(int handle) + { + if (handle == SelfThreadHandle) + { + return _system.Scheduler.GetCurrentThread(); + } + else + { + return GetObject(handle); + } + } + + public KProcess GetKProcess(int handle) + { + if (handle == SelfProcessHandle) + { + return _system.Scheduler.GetCurrentProcess(); + } + else + { + return GetObject(handle); + } + } + + public void Destroy() + { + lock (_table) + { + for (int index = 0; index < _size; index++) + { + KHandleEntry entry = _table[index]; + + if (entry.Obj != null) + { + if (entry.Obj is IDisposable disposableObj) + { + disposableObj.Dispose(); + } + + entry.Obj.DecrementReferenceCount(); + entry.Obj = null; + entry.Next = _nextFreeEntry; + + _nextFreeEntry = entry; + } + } + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs b/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs new file mode 100644 index 0000000000..f987c83c01 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs @@ -0,0 +1,1135 @@ +using ARMeilleure.Memory; +using ARMeilleure.State; +using ARMeilleure.Translation; +using Ryujinx.Common; +using Ryujinx.HLE.Exceptions; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Memory; +using Ryujinx.HLE.HOS.Kernel.SupervisorCall; +using Ryujinx.HLE.HOS.Kernel.Threading; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Kernel.Process +{ + class KProcess : KSynchronizationObject + { + public const int KernelVersionMajor = 10; + public const int KernelVersionMinor = 4; + public const int KernelVersionRevision = 0; + + public const int KernelVersionPacked = + (KernelVersionMajor << 19) | + (KernelVersionMinor << 15) | + (KernelVersionRevision << 0); + + public KMemoryManager MemoryManager { get; private set; } + + private SortedDictionary _fullTlsPages; + private SortedDictionary _freeTlsPages; + + public int DefaultCpuCore { get; set; } + + public bool Debug { get; private set; } + + public KResourceLimit ResourceLimit { get; private set; } + + public ulong PersonalMmHeapPagesCount { get; private set; } + + public ProcessState State { get; private set; } + + private object _processLock; + private object _threadingLock; + + public KAddressArbiter AddressArbiter { get; private set; } + + public long[] RandomEntropy { get; private set; } + + private bool _signaled; + private bool _useSystemMemBlocks; + + public string Name { get; private set; } + + private int _threadCount; + + public int MmuFlags { get; private set; } + + private MemoryRegion _memRegion; + + public KProcessCapabilities Capabilities { get; private set; } + + public ulong TitleId { get; private set; } + public long Pid { get; private set; } + + private long _creationTimestamp; + private ulong _entrypoint; + private ulong _imageSize; + private ulong _mainThreadStackSize; + private ulong _memoryUsageCapacity; + private int _category; + + public KHandleTable HandleTable { get; private set; } + + public ulong UserExceptionContextAddress { get; private set; } + + private LinkedList _threads; + + public bool IsPaused { get; private set; } + + public MemoryManager CpuMemory { get; private set; } + + public Translator Translator { get; private set; } + + private SvcHandler _svcHandler; + + private Horizon _system; + + public HleProcessDebugger Debugger { get; private set; } + + public KProcess(Horizon system) : base(system) + { + _processLock = new object(); + _threadingLock = new object(); + + _system = system; + + AddressArbiter = new KAddressArbiter(system); + + _fullTlsPages = new SortedDictionary(); + _freeTlsPages = new SortedDictionary(); + + Capabilities = new KProcessCapabilities(); + + RandomEntropy = new long[KScheduler.CpuCoresCount]; + + _threads = new LinkedList(); + + _svcHandler = new SvcHandler(system.Device, this); + + Debugger = new HleProcessDebugger(this); + } + + public KernelResult InitializeKip( + ProcessCreationInfo creationInfo, + int[] caps, + KPageList pageList, + KResourceLimit resourceLimit, + MemoryRegion memRegion) + { + ResourceLimit = resourceLimit; + _memRegion = memRegion; + + AddressSpaceType addrSpaceType = (AddressSpaceType)((creationInfo.MmuFlags >> 1) & 7); + + InitializeMemoryManager(addrSpaceType, memRegion); + + bool aslrEnabled = ((creationInfo.MmuFlags >> 5) & 1) != 0; + + ulong codeAddress = creationInfo.CodeAddress; + + ulong codeSize = (ulong)creationInfo.CodePagesCount * KMemoryManager.PageSize; + + KMemoryBlockAllocator memoryBlockAllocator = (MmuFlags & 0x40) != 0 + ? System.LargeMemoryBlockAllocator + : System.SmallMemoryBlockAllocator; + + KernelResult result = MemoryManager.InitializeForProcess( + addrSpaceType, + aslrEnabled, + !aslrEnabled, + memRegion, + codeAddress, + codeSize, + memoryBlockAllocator); + + if (result != KernelResult.Success) + { + return result; + } + + if (!ValidateCodeAddressAndSize(codeAddress, codeSize)) + { + return KernelResult.InvalidMemRange; + } + + result = MemoryManager.MapPages( + codeAddress, + pageList, + MemoryState.CodeStatic, + MemoryPermission.None); + + if (result != KernelResult.Success) + { + return result; + } + + result = Capabilities.InitializeForKernel(caps, MemoryManager); + + if (result != KernelResult.Success) + { + return result; + } + + Pid = System.GetKipId(); + + if (Pid == 0 || (ulong)Pid >= Horizon.InitialProcessId) + { + throw new InvalidOperationException($"Invalid KIP Id {Pid}."); + } + + result = ParseProcessInfo(creationInfo); + + return result; + } + + public KernelResult Initialize( + ProcessCreationInfo creationInfo, + int[] caps, + KResourceLimit resourceLimit, + MemoryRegion memRegion) + { + ResourceLimit = resourceLimit; + _memRegion = memRegion; + + ulong personalMmHeapSize = GetPersonalMmHeapSize((ulong)creationInfo.PersonalMmHeapPagesCount, memRegion); + + ulong codePagesCount = (ulong)creationInfo.CodePagesCount; + + ulong neededSizeForProcess = personalMmHeapSize + codePagesCount * KMemoryManager.PageSize; + + if (neededSizeForProcess != 0 && resourceLimit != null) + { + if (!resourceLimit.Reserve(LimitableResource.Memory, neededSizeForProcess)) + { + return KernelResult.ResLimitExceeded; + } + } + + void CleanUpForError() + { + if (neededSizeForProcess != 0 && resourceLimit != null) + { + resourceLimit.Release(LimitableResource.Memory, neededSizeForProcess); + } + } + + PersonalMmHeapPagesCount = (ulong)creationInfo.PersonalMmHeapPagesCount; + + KMemoryBlockAllocator memoryBlockAllocator; + + if (PersonalMmHeapPagesCount != 0) + { + memoryBlockAllocator = new KMemoryBlockAllocator(PersonalMmHeapPagesCount * KMemoryManager.PageSize); + } + else + { + memoryBlockAllocator = (MmuFlags & 0x40) != 0 + ? System.LargeMemoryBlockAllocator + : System.SmallMemoryBlockAllocator; + } + + AddressSpaceType addrSpaceType = (AddressSpaceType)((creationInfo.MmuFlags >> 1) & 7); + + InitializeMemoryManager(addrSpaceType, memRegion); + + bool aslrEnabled = ((creationInfo.MmuFlags >> 5) & 1) != 0; + + ulong codeAddress = creationInfo.CodeAddress; + + ulong codeSize = codePagesCount * KMemoryManager.PageSize; + + KernelResult result = MemoryManager.InitializeForProcess( + addrSpaceType, + aslrEnabled, + !aslrEnabled, + memRegion, + codeAddress, + codeSize, + memoryBlockAllocator); + + if (result != KernelResult.Success) + { + CleanUpForError(); + + return result; + } + + if (!ValidateCodeAddressAndSize(codeAddress, codeSize)) + { + CleanUpForError(); + + return KernelResult.InvalidMemRange; + } + + result = MemoryManager.MapNewProcessCode( + codeAddress, + codePagesCount, + MemoryState.CodeStatic, + MemoryPermission.None); + + if (result != KernelResult.Success) + { + CleanUpForError(); + + return result; + } + + result = Capabilities.InitializeForUser(caps, MemoryManager); + + if (result != KernelResult.Success) + { + CleanUpForError(); + + return result; + } + + Pid = System.GetProcessId(); + + if (Pid == -1 || (ulong)Pid < Horizon.InitialProcessId) + { + throw new InvalidOperationException($"Invalid Process Id {Pid}."); + } + + result = ParseProcessInfo(creationInfo); + + if (result != KernelResult.Success) + { + CleanUpForError(); + } + + return result; + } + + private bool ValidateCodeAddressAndSize(ulong address, ulong size) + { + ulong codeRegionStart; + ulong codeRegionSize; + + switch (MemoryManager.AddrSpaceWidth) + { + case 32: + codeRegionStart = 0x200000; + codeRegionSize = 0x3fe00000; + break; + + case 36: + codeRegionStart = 0x8000000; + codeRegionSize = 0x78000000; + break; + + case 39: + codeRegionStart = 0x8000000; + codeRegionSize = 0x7ff8000000; + break; + + default: throw new InvalidOperationException("Invalid address space width on memory manager."); + } + + ulong endAddr = address + size; + + ulong codeRegionEnd = codeRegionStart + codeRegionSize; + + if (endAddr <= address || + endAddr - 1 > codeRegionEnd - 1) + { + return false; + } + + if (MemoryManager.InsideHeapRegion (address, size) || + MemoryManager.InsideAliasRegion(address, size)) + { + return false; + } + + return true; + } + + private KernelResult ParseProcessInfo(ProcessCreationInfo creationInfo) + { + // Ensure that the current kernel version is equal or above to the minimum required. + uint requiredKernelVersionMajor = (uint)Capabilities.KernelReleaseVersion >> 19; + uint requiredKernelVersionMinor = ((uint)Capabilities.KernelReleaseVersion >> 15) & 0xf; + + if (System.EnableVersionChecks) + { + if (requiredKernelVersionMajor > KernelVersionMajor) + { + return KernelResult.InvalidCombination; + } + + if (requiredKernelVersionMajor != KernelVersionMajor && requiredKernelVersionMajor < 3) + { + return KernelResult.InvalidCombination; + } + + if (requiredKernelVersionMinor > KernelVersionMinor) + { + return KernelResult.InvalidCombination; + } + } + + KernelResult result = AllocateThreadLocalStorage(out ulong userExceptionContextAddress); + + if (result != KernelResult.Success) + { + return result; + } + + UserExceptionContextAddress = userExceptionContextAddress; + + MemoryHelper.FillWithZeros(CpuMemory, (long)userExceptionContextAddress, KTlsPageInfo.TlsEntrySize); + + Name = creationInfo.Name; + + State = ProcessState.Created; + + _creationTimestamp = PerformanceCounter.ElapsedMilliseconds; + + MmuFlags = creationInfo.MmuFlags; + _category = creationInfo.Category; + TitleId = creationInfo.TitleId; + _entrypoint = creationInfo.CodeAddress; + _imageSize = (ulong)creationInfo.CodePagesCount * KMemoryManager.PageSize; + + _useSystemMemBlocks = ((MmuFlags >> 6) & 1) != 0; + + switch ((AddressSpaceType)((MmuFlags >> 1) & 7)) + { + case AddressSpaceType.Addr32Bits: + case AddressSpaceType.Addr36Bits: + case AddressSpaceType.Addr39Bits: + _memoryUsageCapacity = MemoryManager.HeapRegionEnd - + MemoryManager.HeapRegionStart; + break; + + case AddressSpaceType.Addr32BitsNoMap: + _memoryUsageCapacity = MemoryManager.HeapRegionEnd - + MemoryManager.HeapRegionStart + + MemoryManager.AliasRegionEnd - + MemoryManager.AliasRegionStart; + break; + + default: throw new InvalidOperationException($"Invalid MMU flags value 0x{MmuFlags:x2}."); + } + + GenerateRandomEntropy(); + + return KernelResult.Success; + } + + public KernelResult AllocateThreadLocalStorage(out ulong address) + { + System.CriticalSection.Enter(); + + KernelResult result; + + if (_freeTlsPages.Count > 0) + { + // If we have free TLS pages available, just use the first one. + KTlsPageInfo pageInfo = _freeTlsPages.Values.First(); + + if (!pageInfo.TryGetFreePage(out address)) + { + throw new InvalidOperationException("Unexpected failure getting free TLS page!"); + } + + if (pageInfo.IsFull()) + { + _freeTlsPages.Remove(pageInfo.PageAddr); + + _fullTlsPages.Add(pageInfo.PageAddr, pageInfo); + } + + result = KernelResult.Success; + } + else + { + // Otherwise, we need to create a new one. + result = AllocateTlsPage(out KTlsPageInfo pageInfo); + + if (result == KernelResult.Success) + { + if (!pageInfo.TryGetFreePage(out address)) + { + throw new InvalidOperationException("Unexpected failure getting free TLS page!"); + } + + _freeTlsPages.Add(pageInfo.PageAddr, pageInfo); + } + else + { + address = 0; + } + } + + System.CriticalSection.Leave(); + + return result; + } + + private KernelResult AllocateTlsPage(out KTlsPageInfo pageInfo) + { + pageInfo = default(KTlsPageInfo); + + if (!System.UserSlabHeapPages.TryGetItem(out ulong tlsPagePa)) + { + return KernelResult.OutOfMemory; + } + + ulong regionStart = MemoryManager.TlsIoRegionStart; + ulong regionSize = MemoryManager.TlsIoRegionEnd - regionStart; + + ulong regionPagesCount = regionSize / KMemoryManager.PageSize; + + KernelResult result = MemoryManager.AllocateOrMapPa( + 1, + KMemoryManager.PageSize, + tlsPagePa, + true, + regionStart, + regionPagesCount, + MemoryState.ThreadLocal, + MemoryPermission.ReadAndWrite, + out ulong tlsPageVa); + + if (result != KernelResult.Success) + { + System.UserSlabHeapPages.Free(tlsPagePa); + } + else + { + pageInfo = new KTlsPageInfo(tlsPageVa); + + MemoryHelper.FillWithZeros(CpuMemory, (long)tlsPageVa, KMemoryManager.PageSize); + } + + return result; + } + + public KernelResult FreeThreadLocalStorage(ulong tlsSlotAddr) + { + ulong tlsPageAddr = BitUtils.AlignDown(tlsSlotAddr, KMemoryManager.PageSize); + + System.CriticalSection.Enter(); + + KernelResult result = KernelResult.Success; + + KTlsPageInfo pageInfo = null; + + if (_fullTlsPages.TryGetValue(tlsPageAddr, out pageInfo)) + { + // TLS page was full, free slot and move to free pages tree. + _fullTlsPages.Remove(tlsPageAddr); + + _freeTlsPages.Add(tlsPageAddr, pageInfo); + } + else if (!_freeTlsPages.TryGetValue(tlsPageAddr, out pageInfo)) + { + result = KernelResult.InvalidAddress; + } + + if (pageInfo != null) + { + pageInfo.FreeTlsSlot(tlsSlotAddr); + + if (pageInfo.IsEmpty()) + { + // TLS page is now empty, we should ensure it is removed + // from all trees, and free the memory it was using. + _freeTlsPages.Remove(tlsPageAddr); + + System.CriticalSection.Leave(); + + FreeTlsPage(pageInfo); + + return KernelResult.Success; + } + } + + System.CriticalSection.Leave(); + + return result; + } + + private KernelResult FreeTlsPage(KTlsPageInfo pageInfo) + { + if (!MemoryManager.ConvertVaToPa(pageInfo.PageAddr, out ulong tlsPagePa)) + { + throw new InvalidOperationException("Unexpected failure translating virtual address to physical."); + } + + KernelResult result = MemoryManager.UnmapForKernel(pageInfo.PageAddr, 1, MemoryState.ThreadLocal); + + if (result == KernelResult.Success) + { + System.UserSlabHeapPages.Free(tlsPagePa); + } + + return result; + } + + private void GenerateRandomEntropy() + { + // TODO. + } + + public KernelResult Start(int mainThreadPriority, ulong stackSize) + { + lock (_processLock) + { + if (State > ProcessState.CreatedAttached) + { + return KernelResult.InvalidState; + } + + if (ResourceLimit != null && !ResourceLimit.Reserve(LimitableResource.Thread, 1)) + { + return KernelResult.ResLimitExceeded; + } + + KResourceLimit threadResourceLimit = ResourceLimit; + KResourceLimit memoryResourceLimit = null; + + if (_mainThreadStackSize != 0) + { + throw new InvalidOperationException("Trying to start a process with a invalid state!"); + } + + ulong stackSizeRounded = BitUtils.AlignUp(stackSize, KMemoryManager.PageSize); + + ulong neededSize = stackSizeRounded + _imageSize; + + // Check if the needed size for the code and the stack will fit on the + // memory usage capacity of this Process. Also check for possible overflow + // on the above addition. + if (neededSize > _memoryUsageCapacity || + neededSize < stackSizeRounded) + { + threadResourceLimit?.Release(LimitableResource.Thread, 1); + + return KernelResult.OutOfMemory; + } + + if (stackSizeRounded != 0 && ResourceLimit != null) + { + memoryResourceLimit = ResourceLimit; + + if (!memoryResourceLimit.Reserve(LimitableResource.Memory, stackSizeRounded)) + { + threadResourceLimit?.Release(LimitableResource.Thread, 1); + + return KernelResult.ResLimitExceeded; + } + } + + KernelResult result; + + KThread mainThread = null; + + ulong stackTop = 0; + + void CleanUpForError() + { + HandleTable.Destroy(); + + mainThread?.DecrementReferenceCount(); + + if (_mainThreadStackSize != 0) + { + ulong stackBottom = stackTop - _mainThreadStackSize; + + ulong stackPagesCount = _mainThreadStackSize / KMemoryManager.PageSize; + + MemoryManager.UnmapForKernel(stackBottom, stackPagesCount, MemoryState.Stack); + + _mainThreadStackSize = 0; + } + + memoryResourceLimit?.Release(LimitableResource.Memory, stackSizeRounded); + threadResourceLimit?.Release(LimitableResource.Thread, 1); + } + + if (stackSizeRounded != 0) + { + ulong stackPagesCount = stackSizeRounded / KMemoryManager.PageSize; + + ulong regionStart = MemoryManager.StackRegionStart; + ulong regionSize = MemoryManager.StackRegionEnd - regionStart; + + ulong regionPagesCount = regionSize / KMemoryManager.PageSize; + + result = MemoryManager.AllocateOrMapPa( + stackPagesCount, + KMemoryManager.PageSize, + 0, + false, + regionStart, + regionPagesCount, + MemoryState.Stack, + MemoryPermission.ReadAndWrite, + out ulong stackBottom); + + if (result != KernelResult.Success) + { + CleanUpForError(); + + return result; + } + + _mainThreadStackSize += stackSizeRounded; + + stackTop = stackBottom + stackSizeRounded; + } + + ulong heapCapacity = _memoryUsageCapacity - _mainThreadStackSize - _imageSize; + + result = MemoryManager.SetHeapCapacity(heapCapacity); + + if (result != KernelResult.Success) + { + CleanUpForError(); + + return result; + } + + HandleTable = new KHandleTable(System); + + result = HandleTable.Initialize(Capabilities.HandleTableSize); + + if (result != KernelResult.Success) + { + CleanUpForError(); + + return result; + } + + mainThread = new KThread(System); + + result = mainThread.Initialize( + _entrypoint, + 0, + stackTop, + mainThreadPriority, + DefaultCpuCore, + this); + + if (result != KernelResult.Success) + { + CleanUpForError(); + + return result; + } + + result = HandleTable.GenerateHandle(mainThread, out int mainThreadHandle); + + if (result != KernelResult.Success) + { + CleanUpForError(); + + return result; + } + + mainThread.SetEntryArguments(0, mainThreadHandle); + + ProcessState oldState = State; + ProcessState newState = State != ProcessState.Created + ? ProcessState.Attached + : ProcessState.Started; + + SetState(newState); + + // TODO: We can't call KThread.Start from a non-guest thread. + // We will need to make some changes to allow the creation of + // dummy threads that will be used to initialize the current + // thread on KCoreContext so that GetCurrentThread doesn't fail. + /* Result = MainThread.Start(); + + if (Result != KernelResult.Success) + { + SetState(OldState); + + CleanUpForError(); + } */ + + mainThread.Reschedule(ThreadSchedState.Running); + + if (result == KernelResult.Success) + { + mainThread.IncrementReferenceCount(); + } + + mainThread.DecrementReferenceCount(); + + return result; + } + } + + private void SetState(ProcessState newState) + { + if (State != newState) + { + State = newState; + _signaled = true; + + Signal(); + } + } + + public KernelResult InitializeThread( + KThread thread, + ulong entrypoint, + ulong argsPtr, + ulong stackTop, + int priority, + int cpuCore) + { + lock (_processLock) + { + return thread.Initialize(entrypoint, argsPtr, stackTop, priority, cpuCore, this); + } + } + + public void SubscribeThreadEventHandlers(ARMeilleure.State.ExecutionContext context) + { + context.Interrupt += InterruptHandler; + context.SupervisorCall += _svcHandler.SvcCall; + context.Undefined += UndefinedInstructionHandler; + } + + private void InterruptHandler(object sender, EventArgs e) + { + System.Scheduler.ContextSwitch(); + } + + public void IncrementThreadCount() + { + Interlocked.Increment(ref _threadCount); + + System.ThreadCounter.AddCount(); + } + + public void DecrementThreadCountAndTerminateIfZero() + { + System.ThreadCounter.Signal(); + + if (Interlocked.Decrement(ref _threadCount) == 0) + { + Terminate(); + } + } + + public void DecrementToZeroWhileTerminatingCurrent() + { + System.ThreadCounter.Signal(); + + while (Interlocked.Decrement(ref _threadCount) != 0) + { + Destroy(); + TerminateCurrentProcess(); + } + + // Nintendo panic here because if it reaches this point, the current thread should be already dead. + // As we handle the death of the thread in the post SVC handler and inside the CPU emulator, we don't panic here. + } + + public ulong GetMemoryCapacity() + { + ulong totalCapacity = (ulong)ResourceLimit.GetRemainingValue(LimitableResource.Memory); + + totalCapacity += MemoryManager.GetTotalHeapSize(); + + totalCapacity += GetPersonalMmHeapSize(); + + totalCapacity += _imageSize + _mainThreadStackSize; + + if (totalCapacity <= _memoryUsageCapacity) + { + return totalCapacity; + } + + return _memoryUsageCapacity; + } + + public ulong GetMemoryUsage() + { + return _imageSize + _mainThreadStackSize + MemoryManager.GetTotalHeapSize() + GetPersonalMmHeapSize(); + } + + public ulong GetMemoryCapacityWithoutPersonalMmHeap() + { + return GetMemoryCapacity() - GetPersonalMmHeapSize(); + } + + public ulong GetMemoryUsageWithoutPersonalMmHeap() + { + return GetMemoryUsage() - GetPersonalMmHeapSize(); + } + + private ulong GetPersonalMmHeapSize() + { + return GetPersonalMmHeapSize(PersonalMmHeapPagesCount, _memRegion); + } + + private static ulong GetPersonalMmHeapSize(ulong personalMmHeapPagesCount, MemoryRegion memRegion) + { + if (memRegion == MemoryRegion.Applet) + { + return 0; + } + + return personalMmHeapPagesCount * KMemoryManager.PageSize; + } + + public void AddThread(KThread thread) + { + lock (_threadingLock) + { + thread.ProcessListNode = _threads.AddLast(thread); + } + } + + public void RemoveThread(KThread thread) + { + lock (_threadingLock) + { + _threads.Remove(thread.ProcessListNode); + } + } + + public bool IsCpuCoreAllowed(int core) + { + return (Capabilities.AllowedCpuCoresMask & (1L << core)) != 0; + } + + public bool IsPriorityAllowed(int priority) + { + return (Capabilities.AllowedThreadPriosMask & (1L << priority)) != 0; + } + + public override bool IsSignaled() + { + return _signaled; + } + + public KernelResult Terminate() + { + KernelResult result; + + bool shallTerminate = false; + + System.CriticalSection.Enter(); + + lock (_processLock) + { + if (State >= ProcessState.Started) + { + if (State == ProcessState.Started || + State == ProcessState.Crashed || + State == ProcessState.Attached || + State == ProcessState.DebugSuspended) + { + SetState(ProcessState.Exiting); + + shallTerminate = true; + } + + result = KernelResult.Success; + } + else + { + result = KernelResult.InvalidState; + } + } + + System.CriticalSection.Leave(); + + if (shallTerminate) + { + UnpauseAndTerminateAllThreadsExcept(System.Scheduler.GetCurrentThread()); + + HandleTable.Destroy(); + + SignalExitToDebugTerminated(); + SignalExit(); + } + + return result; + } + + public void TerminateCurrentProcess() + { + bool shallTerminate = false; + + System.CriticalSection.Enter(); + + lock (_processLock) + { + if (State >= ProcessState.Started) + { + if (State == ProcessState.Started || + State == ProcessState.Attached || + State == ProcessState.DebugSuspended) + { + SetState(ProcessState.Exiting); + + shallTerminate = true; + } + } + } + + System.CriticalSection.Leave(); + + if (shallTerminate) + { + UnpauseAndTerminateAllThreadsExcept(System.Scheduler.GetCurrentThread()); + + HandleTable.Destroy(); + + // NOTE: this is supposed to be called in receiving of the mailbox. + SignalExitToDebugExited(); + SignalExit(); + } + } + + private void UnpauseAndTerminateAllThreadsExcept(KThread currentThread) + { + lock (_threadingLock) + { + System.CriticalSection.Enter(); + + foreach (KThread thread in _threads) + { + if ((thread.SchedFlags & ThreadSchedState.LowMask) != ThreadSchedState.TerminationPending) + { + thread.PrepareForTermination(); + } + } + + System.CriticalSection.Leave(); + } + + KThread blockedThread = null; + + lock (_threadingLock) + { + foreach (KThread thread in _threads) + { + if (thread != currentThread && (thread.SchedFlags & ThreadSchedState.LowMask) != ThreadSchedState.TerminationPending) + { + thread.IncrementReferenceCount(); + + blockedThread = thread; + break; + } + } + } + + if (blockedThread != null) + { + blockedThread.Terminate(); + blockedThread.DecrementReferenceCount(); + } + } + + private void SignalExitToDebugTerminated() + { + // TODO: Debug events. + } + + private void SignalExitToDebugExited() + { + // TODO: Debug events. + } + + private void SignalExit() + { + if (ResourceLimit != null) + { + ResourceLimit.Release(LimitableResource.Memory, GetMemoryUsage()); + } + + System.CriticalSection.Enter(); + + SetState(ProcessState.Exited); + + System.CriticalSection.Leave(); + } + + public KernelResult ClearIfNotExited() + { + KernelResult result; + + System.CriticalSection.Enter(); + + lock (_processLock) + { + if (State != ProcessState.Exited && _signaled) + { + _signaled = false; + + result = KernelResult.Success; + } + else + { + result = KernelResult.InvalidState; + } + } + + System.CriticalSection.Leave(); + + return result; + } + + public void StopAllThreads() + { + lock (_threadingLock) + { + foreach (KThread thread in _threads) + { + System.Scheduler.ExitThread(thread); + + System.Scheduler.CoreManager.Set(thread.HostThread); + } + } + } + + private void InitializeMemoryManager(AddressSpaceType addrSpaceType, MemoryRegion memRegion) + { + int addrSpaceBits; + + switch (addrSpaceType) + { + case AddressSpaceType.Addr32Bits: addrSpaceBits = 32; break; + case AddressSpaceType.Addr36Bits: addrSpaceBits = 36; break; + case AddressSpaceType.Addr32BitsNoMap: addrSpaceBits = 32; break; + case AddressSpaceType.Addr39Bits: addrSpaceBits = 39; break; + + default: throw new ArgumentException(nameof(addrSpaceType)); + } + + bool useFlatPageTable = memRegion == MemoryRegion.Application; + + CpuMemory = new MemoryManager(_system.Device.Memory.RamPointer, addrSpaceBits, useFlatPageTable); + + Translator = new Translator(CpuMemory); + + // TODO: This should eventually be removed. + // The GPU shouldn't depend on the CPU memory manager at all. + _system.Device.Gpu.SetVmm(CpuMemory); + + MemoryManager = new KMemoryManager(_system, CpuMemory); + } + + public void PrintCurrentThreadStackTrace() + { + System.Scheduler.GetCurrentThread().PrintGuestStackTrace(); + } + + private void UndefinedInstructionHandler(object sender, InstUndefinedEventArgs e) + { + throw new UndefinedInstructionException(e.Address, e.OpCode); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Process/KProcessCapabilities.cs b/Ryujinx.HLE/HOS/Kernel/Process/KProcessCapabilities.cs new file mode 100644 index 0000000000..2396aea83e --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Process/KProcessCapabilities.cs @@ -0,0 +1,319 @@ +using Ryujinx.Common; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Memory; +using Ryujinx.HLE.HOS.Kernel.Threading; + +namespace Ryujinx.HLE.HOS.Kernel.Process +{ + class KProcessCapabilities + { + public byte[] SvcAccessMask { get; private set; } + public byte[] IrqAccessMask { get; private set; } + + public long AllowedCpuCoresMask { get; private set; } + public long AllowedThreadPriosMask { get; private set; } + + public int DebuggingFlags { get; private set; } + public int HandleTableSize { get; private set; } + public int KernelReleaseVersion { get; private set; } + public int ApplicationType { get; private set; } + + public KProcessCapabilities() + { + SvcAccessMask = new byte[0x10]; + IrqAccessMask = new byte[0x80]; + } + + public KernelResult InitializeForKernel(int[] caps, KMemoryManager memoryManager) + { + AllowedCpuCoresMask = 0xf; + AllowedThreadPriosMask = -1; + DebuggingFlags &= ~3; + KernelReleaseVersion = KProcess.KernelVersionPacked; + + return Parse(caps, memoryManager); + } + + public KernelResult InitializeForUser(int[] caps, KMemoryManager memoryManager) + { + return Parse(caps, memoryManager); + } + + private KernelResult Parse(int[] caps, KMemoryManager memoryManager) + { + int mask0 = 0; + int mask1 = 0; + + for (int index = 0; index < caps.Length; index++) + { + int cap = caps[index]; + + if (((cap + 1) & ~cap) != 0x40) + { + KernelResult result = ParseCapability(cap, ref mask0, ref mask1, memoryManager); + + if (result != KernelResult.Success) + { + return result; + } + } + else + { + if ((uint)index + 1 >= caps.Length) + { + return KernelResult.InvalidCombination; + } + + int prevCap = cap; + + cap = caps[++index]; + + if (((cap + 1) & ~cap) != 0x40) + { + return KernelResult.InvalidCombination; + } + + if ((cap & 0x78000000) != 0) + { + return KernelResult.MaximumExceeded; + } + + if ((cap & 0x7ffff80) == 0) + { + return KernelResult.InvalidSize; + } + + long address = ((long)(uint)prevCap << 5) & 0xffffff000; + long size = ((long)(uint)cap << 5) & 0xfffff000; + + if (((ulong)(address + size - 1) >> 36) != 0) + { + return KernelResult.InvalidAddress; + } + + MemoryPermission perm = (prevCap >> 31) != 0 + ? MemoryPermission.Read + : MemoryPermission.ReadAndWrite; + + KernelResult result; + + if ((cap >> 31) != 0) + { + result = memoryManager.MapNormalMemory(address, size, perm); + } + else + { + result = memoryManager.MapIoMemory(address, size, perm); + } + + if (result != KernelResult.Success) + { + return result; + } + } + } + + return KernelResult.Success; + } + + private KernelResult ParseCapability(int cap, ref int mask0, ref int mask1, KMemoryManager memoryManager) + { + int code = (cap + 1) & ~cap; + + if (code == 1) + { + return KernelResult.InvalidCapability; + } + else if (code == 0) + { + return KernelResult.Success; + } + + int codeMask = 1 << (32 - BitUtils.CountLeadingZeros32(code + 1)); + + // Check if the property was already set. + if (((mask0 & codeMask) & 0x1e008) != 0) + { + return KernelResult.InvalidCombination; + } + + mask0 |= codeMask; + + switch (code) + { + case 8: + { + if (AllowedCpuCoresMask != 0 || AllowedThreadPriosMask != 0) + { + return KernelResult.InvalidCapability; + } + + int lowestCpuCore = (cap >> 16) & 0xff; + int highestCpuCore = (cap >> 24) & 0xff; + + if (lowestCpuCore > highestCpuCore) + { + return KernelResult.InvalidCombination; + } + + int highestThreadPrio = (cap >> 4) & 0x3f; + int lowestThreadPrio = (cap >> 10) & 0x3f; + + if (lowestThreadPrio > highestThreadPrio) + { + return KernelResult.InvalidCombination; + } + + if (highestCpuCore >= KScheduler.CpuCoresCount) + { + return KernelResult.InvalidCpuCore; + } + + AllowedCpuCoresMask = GetMaskFromMinMax(lowestCpuCore, highestCpuCore); + AllowedThreadPriosMask = GetMaskFromMinMax(lowestThreadPrio, highestThreadPrio); + + break; + } + + case 0x10: + { + int slot = (cap >> 29) & 7; + + int svcSlotMask = 1 << slot; + + if ((mask1 & svcSlotMask) != 0) + { + return KernelResult.InvalidCombination; + } + + mask1 |= svcSlotMask; + + int svcMask = (cap >> 5) & 0xffffff; + + int baseSvc = slot * 24; + + for (int index = 0; index < 24; index++) + { + if (((svcMask >> index) & 1) == 0) + { + continue; + } + + int svcId = baseSvc + index; + + if (svcId > 0x7f) + { + return KernelResult.MaximumExceeded; + } + + SvcAccessMask[svcId / 8] |= (byte)(1 << (svcId & 7)); + } + + break; + } + + case 0x80: + { + long address = ((long)(uint)cap << 4) & 0xffffff000; + + memoryManager.MapIoMemory(address, KMemoryManager.PageSize, MemoryPermission.ReadAndWrite); + + break; + } + + case 0x800: + { + // TODO: GIC distributor check. + int irq0 = (cap >> 12) & 0x3ff; + int irq1 = (cap >> 22) & 0x3ff; + + if (irq0 != 0x3ff) + { + IrqAccessMask[irq0 / 8] |= (byte)(1 << (irq0 & 7)); + } + + if (irq1 != 0x3ff) + { + IrqAccessMask[irq1 / 8] |= (byte)(1 << (irq1 & 7)); + } + + break; + } + + case 0x2000: + { + int applicationType = cap >> 14; + + if ((uint)applicationType > 7) + { + return KernelResult.ReservedValue; + } + + ApplicationType = applicationType; + + break; + } + + case 0x4000: + { + // Note: This check is bugged on kernel too, we are just replicating the bug here. + if ((KernelReleaseVersion >> 17) != 0 || cap < 0x80000) + { + return KernelResult.ReservedValue; + } + + KernelReleaseVersion = cap; + + break; + } + + case 0x8000: + { + int handleTableSize = cap >> 26; + + if ((uint)handleTableSize > 0x3ff) + { + return KernelResult.ReservedValue; + } + + HandleTableSize = handleTableSize; + + break; + } + + case 0x10000: + { + int debuggingFlags = cap >> 19; + + if ((uint)debuggingFlags > 3) + { + return KernelResult.ReservedValue; + } + + DebuggingFlags &= ~3; + DebuggingFlags |= debuggingFlags; + + break; + } + + default: return KernelResult.InvalidCapability; + } + + return KernelResult.Success; + } + + private static long GetMaskFromMinMax(int min, int max) + { + int range = max - min + 1; + + if (range == 64) + { + return -1L; + } + + long mask = (1L << range) - 1; + + return mask << min; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Process/KTlsPageInfo.cs b/Ryujinx.HLE/HOS/Kernel/Process/KTlsPageInfo.cs new file mode 100644 index 0000000000..5ce5a299da --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Process/KTlsPageInfo.cs @@ -0,0 +1,75 @@ +using Ryujinx.HLE.HOS.Kernel.Memory; + +namespace Ryujinx.HLE.HOS.Kernel.Process +{ + class KTlsPageInfo + { + public const int TlsEntrySize = 0x200; + + public ulong PageAddr { get; private set; } + + private bool[] _isSlotFree; + + public KTlsPageInfo(ulong pageAddress) + { + PageAddr = pageAddress; + + _isSlotFree = new bool[KMemoryManager.PageSize / TlsEntrySize]; + + for (int index = 0; index < _isSlotFree.Length; index++) + { + _isSlotFree[index] = true; + } + } + + public bool TryGetFreePage(out ulong address) + { + address = PageAddr; + + for (int index = 0; index < _isSlotFree.Length; index++) + { + if (_isSlotFree[index]) + { + _isSlotFree[index] = false; + + return true; + } + + address += TlsEntrySize; + } + + address = 0; + + return false; + } + + public bool IsFull() + { + bool hasFree = false; + + for (int index = 0; index < _isSlotFree.Length; index++) + { + hasFree |= _isSlotFree[index]; + } + + return !hasFree; + } + + public bool IsEmpty() + { + bool allFree = true; + + for (int index = 0; index < _isSlotFree.Length; index++) + { + allFree &= _isSlotFree[index]; + } + + return allFree; + } + + public void FreeTlsSlot(ulong address) + { + _isSlotFree[(address - PageAddr) / TlsEntrySize] = true; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Process/KTlsPageManager.cs b/Ryujinx.HLE/HOS/Kernel/Process/KTlsPageManager.cs new file mode 100644 index 0000000000..03174e5bbc --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Process/KTlsPageManager.cs @@ -0,0 +1,61 @@ +using Ryujinx.HLE.HOS.Kernel.Memory; +using System; + +namespace Ryujinx.HLE.HOS.Kernel.Process +{ + class KTlsPageManager + { + private const int TlsEntrySize = 0x200; + + private long _pagePosition; + + private int _usedSlots; + + private bool[] _slots; + + public bool IsEmpty => _usedSlots == 0; + public bool IsFull => _usedSlots == _slots.Length; + + public KTlsPageManager(long pagePosition) + { + _pagePosition = pagePosition; + + _slots = new bool[KMemoryManager.PageSize / TlsEntrySize]; + } + + public bool TryGetFreeTlsAddr(out long position) + { + position = _pagePosition; + + for (int index = 0; index < _slots.Length; index++) + { + if (!_slots[index]) + { + _slots[index] = true; + + _usedSlots++; + + return true; + } + + position += TlsEntrySize; + } + + position = 0; + + return false; + } + + public void FreeTlsSlot(int slot) + { + if ((uint)slot > _slots.Length) + { + throw new ArgumentOutOfRangeException(nameof(slot)); + } + + _slots[slot] = false; + + _usedSlots--; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Process/ProcessCreationInfo.cs b/Ryujinx.HLE/HOS/Kernel/Process/ProcessCreationInfo.cs new file mode 100644 index 0000000000..7431d7dd25 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Process/ProcessCreationInfo.cs @@ -0,0 +1,37 @@ +namespace Ryujinx.HLE.HOS.Kernel.Process +{ + struct ProcessCreationInfo + { + public string Name { get; private set; } + + public int Category { get; private set; } + public ulong TitleId { get; private set; } + + public ulong CodeAddress { get; private set; } + public int CodePagesCount { get; private set; } + + public int MmuFlags { get; private set; } + public int ResourceLimitHandle { get; private set; } + public int PersonalMmHeapPagesCount { get; private set; } + + public ProcessCreationInfo( + string name, + int category, + ulong titleId, + ulong codeAddress, + int codePagesCount, + int mmuFlags, + int resourceLimitHandle, + int personalMmHeapPagesCount) + { + Name = name; + Category = category; + TitleId = titleId; + CodeAddress = codeAddress; + CodePagesCount = codePagesCount; + MmuFlags = mmuFlags; + ResourceLimitHandle = resourceLimitHandle; + PersonalMmHeapPagesCount = personalMmHeapPagesCount; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Process/ProcessState.cs b/Ryujinx.HLE/HOS/Kernel/Process/ProcessState.cs new file mode 100644 index 0000000000..5ef3077e36 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Process/ProcessState.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.HLE.HOS.Kernel.Process +{ + enum ProcessState : byte + { + Created = 0, + CreatedAttached = 1, + Started = 2, + Crashed = 3, + Attached = 4, + Exiting = 5, + Exited = 6, + DebugSuspended = 7 + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/InvalidSvcException.cs b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/InvalidSvcException.cs new file mode 100644 index 0000000000..b78a228471 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/InvalidSvcException.cs @@ -0,0 +1,9 @@ +using System; + +namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall +{ + public class InvalidSvcException : Exception + { + public InvalidSvcException(string message) : base(message) { } + } +} diff --git a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/RAttribute.cs b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/RAttribute.cs new file mode 100644 index 0000000000..c1d9eeed07 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/RAttribute.cs @@ -0,0 +1,15 @@ +using System; + +namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall +{ + [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)] + public class RAttribute : Attribute + { + public readonly int Index; + + public RAttribute(int index) + { + Index = index; + } + } +} diff --git a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcHandler.cs b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcHandler.cs new file mode 100644 index 0000000000..d5698e2bc5 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcHandler.cs @@ -0,0 +1,44 @@ +using ARMeilleure.State; +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.HLE.HOS.Kernel.Threading; +using System; + +namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall +{ + partial class SvcHandler + { + private Switch _device; + private KProcess _process; + private Horizon _system; + + public SvcHandler(Switch device, KProcess process) + { + _device = device; + _process = process; + _system = device.System; + } + + public void SvcCall(object sender, InstExceptionEventArgs e) + { + ExecutionContext context = (ExecutionContext)sender; + + Action svcFunc = context.IsAarch32 ? SvcTable.SvcTable32[e.Id] : SvcTable.SvcTable64[e.Id]; + + if (svcFunc == null) + { + throw new NotImplementedException($"SVC 0x{e.Id:X4} is not implemented."); + } + + svcFunc(this, context); + + PostSvcHandler(); + } + + private void PostSvcHandler() + { + KThread currentThread = _system.Scheduler.GetCurrentThread(); + + currentThread.HandlePostSyscall(); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcIpc.cs b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcIpc.cs new file mode 100644 index 0000000000..70767733c1 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcIpc.cs @@ -0,0 +1,533 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Ipc; +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.HLE.HOS.Kernel.Threading; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall +{ + partial class SvcHandler + { + private struct HleIpcMessage + { + public KThread Thread { get; private set; } + public KClientSession Session { get; private set; } + public IpcMessage Message { get; private set; } + public long MessagePtr { get; private set; } + + public HleIpcMessage( + KThread thread, + KClientSession session, + IpcMessage message, + long messagePtr) + { + Thread = thread; + Session = session; + Message = message; + MessagePtr = messagePtr; + } + } + + public KernelResult ConnectToNamedPort64([R(1)] ulong namePtr, [R(1)] out int handle) + { + return ConnectToNamedPort(namePtr, out handle); + } + + private KernelResult ConnectToNamedPort(ulong namePtr, out int handle) + { + handle = 0; + + if (!KernelTransfer.UserToKernelString(_system, namePtr, 12, out string name)) + { + return KernelResult.UserCopyFailed; + } + + if (name.Length > 11) + { + return KernelResult.MaximumExceeded; + } + + KAutoObject autoObj = KAutoObject.FindNamedObject(_system, name); + + if (!(autoObj is KClientPort clientPort)) + { + return KernelResult.NotFound; + } + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + KernelResult result = currentProcess.HandleTable.ReserveHandle(out handle); + + if (result != KernelResult.Success) + { + return result; + } + + result = clientPort.Connect(out KClientSession clientSession); + + if (result != KernelResult.Success) + { + currentProcess.HandleTable.CancelHandleReservation(handle); + + return result; + } + + currentProcess.HandleTable.SetReservedHandleObj(handle, clientSession); + + clientSession.DecrementReferenceCount(); + + return result; + } + + public KernelResult SendSyncRequest64([R(0)] int handle) + { + return SendSyncRequest((ulong)_system.Scheduler.GetCurrentThread().Context.Tpidr, 0x100, handle); + } + + public KernelResult SendSyncRequestWithUserBuffer64([R(0)] ulong messagePtr, [R(1)] ulong size, [R(2)] int handle) + { + return SendSyncRequest(messagePtr, size, handle); + } + + private KernelResult SendSyncRequest(ulong messagePtr, ulong size, int handle) + { + byte[] messageData = _process.CpuMemory.ReadBytes((long)messagePtr, (long)size); + + KClientSession clientSession = _process.HandleTable.GetObject(handle); + + if (clientSession == null || clientSession.Service == null) + { + return SendSyncRequest_(handle); + } + + if (clientSession != null) + { + _system.CriticalSection.Enter(); + + KThread currentThread = _system.Scheduler.GetCurrentThread(); + + currentThread.SignaledObj = null; + currentThread.ObjSyncResult = KernelResult.Success; + + currentThread.Reschedule(ThreadSchedState.Paused); + + IpcMessage message = new IpcMessage(messageData, (long)messagePtr); + + ThreadPool.QueueUserWorkItem(ProcessIpcRequest, new HleIpcMessage( + currentThread, + clientSession, + message, + (long)messagePtr)); + + _system.ThreadCounter.AddCount(); + + _system.CriticalSection.Leave(); + + return currentThread.ObjSyncResult; + } + else + { + Logger.PrintWarning(LogClass.KernelSvc, $"Invalid session handle 0x{handle:x8}!"); + + return KernelResult.InvalidHandle; + } + } + + private void ProcessIpcRequest(object state) + { + HleIpcMessage ipcMessage = (HleIpcMessage)state; + + ipcMessage.Thread.ObjSyncResult = IpcHandler.IpcCall( + _device, + _process, + _process.CpuMemory, + ipcMessage.Thread, + ipcMessage.Session, + ipcMessage.Message, + ipcMessage.MessagePtr); + + _system.ThreadCounter.Signal(); + + ipcMessage.Thread.Reschedule(ThreadSchedState.Running); + } + + private KernelResult SendSyncRequest_(int handle) + { + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + KClientSession session = currentProcess.HandleTable.GetObject(handle); + + if (session == null) + { + return KernelResult.InvalidHandle; + } + + return session.SendSyncRequest(); + } + + public KernelResult CreateSession64( + [R(2)] bool isLight, + [R(3)] ulong namePtr, + [R(1)] out int serverSessionHandle, + [R(2)] out int clientSessionHandle) + { + return CreateSession(isLight, namePtr, out serverSessionHandle, out clientSessionHandle); + } + + private KernelResult CreateSession( + bool isLight, + ulong namePtr, + out int serverSessionHandle, + out int clientSessionHandle) + { + serverSessionHandle = 0; + clientSessionHandle = 0; + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + KResourceLimit resourceLimit = currentProcess.ResourceLimit; + + KernelResult result = KernelResult.Success; + + if (resourceLimit != null && !resourceLimit.Reserve(LimitableResource.Session, 1)) + { + return KernelResult.ResLimitExceeded; + } + + if (isLight) + { + KLightSession session = new KLightSession(_system); + + result = currentProcess.HandleTable.GenerateHandle(session.ServerSession, out serverSessionHandle); + + if (result == KernelResult.Success) + { + result = currentProcess.HandleTable.GenerateHandle(session.ClientSession, out clientSessionHandle); + + if (result != KernelResult.Success) + { + currentProcess.HandleTable.CloseHandle(serverSessionHandle); + + serverSessionHandle = 0; + } + } + + session.ServerSession.DecrementReferenceCount(); + session.ClientSession.DecrementReferenceCount(); + } + else + { + KSession session = new KSession(_system); + + result = currentProcess.HandleTable.GenerateHandle(session.ServerSession, out serverSessionHandle); + + if (result == KernelResult.Success) + { + result = currentProcess.HandleTable.GenerateHandle(session.ClientSession, out clientSessionHandle); + + if (result != KernelResult.Success) + { + currentProcess.HandleTable.CloseHandle(serverSessionHandle); + + serverSessionHandle = 0; + } + } + + session.ServerSession.DecrementReferenceCount(); + session.ClientSession.DecrementReferenceCount(); + } + + return result; + } + + public KernelResult AcceptSession64([R(1)] int portHandle, [R(1)] out int sessionHandle) + { + return AcceptSession(portHandle, out sessionHandle); + } + + private KernelResult AcceptSession(int portHandle, out int sessionHandle) + { + sessionHandle = 0; + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + KServerPort serverPort = currentProcess.HandleTable.GetObject(portHandle); + + if (serverPort == null) + { + return KernelResult.InvalidHandle; + } + + KernelResult result = currentProcess.HandleTable.ReserveHandle(out int handle); + + if (result != KernelResult.Success) + { + return result; + } + + KAutoObject session; + + if (serverPort.IsLight) + { + session = serverPort.AcceptIncomingLightConnection(); + } + else + { + session = serverPort.AcceptIncomingConnection(); + } + + if (session != null) + { + currentProcess.HandleTable.SetReservedHandleObj(handle, session); + + session.DecrementReferenceCount(); + + sessionHandle = handle; + + result = KernelResult.Success; + } + else + { + currentProcess.HandleTable.CancelHandleReservation(handle); + + result = KernelResult.NotFound; + } + + return result; + } + + public KernelResult ReplyAndReceive64( + [R(1)] ulong handlesPtr, + [R(2)] int handlesCount, + [R(3)] int replyTargetHandle, + [R(4)] long timeout, + [R(1)] out int handleIndex) + { + handleIndex = 0; + + if ((uint)handlesCount > 0x40) + { + return KernelResult.MaximumExceeded; + } + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + ulong copySize = (ulong)((long)handlesCount * 4); + + if (!currentProcess.MemoryManager.InsideAddrSpace(handlesPtr, copySize)) + { + return KernelResult.UserCopyFailed; + } + + if (handlesPtr + copySize < handlesPtr) + { + return KernelResult.UserCopyFailed; + } + + int[] handles = new int[handlesCount]; + + if (!KernelTransfer.UserToKernelInt32Array(_system, handlesPtr, handles)) + { + return KernelResult.UserCopyFailed; + } + + KSynchronizationObject[] syncObjs = new KSynchronizationObject[handlesCount]; + + for (int index = 0; index < handlesCount; index++) + { + KSynchronizationObject obj = currentProcess.HandleTable.GetObject(handles[index]); + + if (obj == null) + { + return KernelResult.InvalidHandle; + } + + syncObjs[index] = obj; + } + + KernelResult result; + + if (replyTargetHandle != 0) + { + KServerSession replyTarget = currentProcess.HandleTable.GetObject(replyTargetHandle); + + if (replyTarget == null) + { + return KernelResult.InvalidHandle; + } + + result = replyTarget.Reply(); + + if (result != KernelResult.Success) + { + return result; + } + } + + while ((result = _system.Synchronization.WaitFor(syncObjs, timeout, out handleIndex)) == KernelResult.Success) + { + KServerSession session = currentProcess.HandleTable.GetObject(handles[handleIndex]); + + if (session == null) + { + break; + } + + if ((result = session.Receive()) != KernelResult.NotFound) + { + break; + } + } + + return result; + } + + public KernelResult CreatePort64( + [R(2)] int maxSessions, + [R(3)] bool isLight, + [R(4)] ulong namePtr, + [R(1)] out int serverPortHandle, + [R(2)] out int clientPortHandle) + { + return CreatePort(maxSessions, isLight, namePtr, out serverPortHandle, out clientPortHandle); + } + + private KernelResult CreatePort( + int maxSessions, + bool isLight, + ulong namePtr, + out int serverPortHandle, + out int clientPortHandle) + { + serverPortHandle = clientPortHandle = 0; + + if (maxSessions < 1) + { + return KernelResult.MaximumExceeded; + } + + KPort port = new KPort(_system, maxSessions, isLight, (long)namePtr); + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + KernelResult result = currentProcess.HandleTable.GenerateHandle(port.ClientPort, out clientPortHandle); + + if (result != KernelResult.Success) + { + return result; + } + + result = currentProcess.HandleTable.GenerateHandle(port.ServerPort, out serverPortHandle); + + if (result != KernelResult.Success) + { + currentProcess.HandleTable.CloseHandle(clientPortHandle); + } + + return result; + } + + public KernelResult ManageNamedPort64([R(1)] ulong namePtr, [R(2)] int maxSessions, [R(1)] out int handle) + { + return ManageNamedPort(namePtr, maxSessions, out handle); + } + + private KernelResult ManageNamedPort(ulong namePtr, int maxSessions, out int handle) + { + handle = 0; + + if (!KernelTransfer.UserToKernelString(_system, namePtr, 12, out string name)) + { + return KernelResult.UserCopyFailed; + } + + if (maxSessions < 0 || name.Length > 11) + { + return KernelResult.MaximumExceeded; + } + + if (maxSessions == 0) + { + return KClientPort.RemoveName(_system, name); + } + + KPort port = new KPort(_system, maxSessions, false, 0); + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + KernelResult result = currentProcess.HandleTable.GenerateHandle(port.ServerPort, out handle); + + if (result != KernelResult.Success) + { + return result; + } + + result = port.ClientPort.SetName(name); + + if (result != KernelResult.Success) + { + currentProcess.HandleTable.CloseHandle(handle); + } + + return result; + } + + public KernelResult ConnectToPort64([R(1)] int clientPortHandle, [R(1)] out int clientSessionHandle) + { + return ConnectToPort(clientPortHandle, out clientSessionHandle); + } + + private KernelResult ConnectToPort(int clientPortHandle, out int clientSessionHandle) + { + clientSessionHandle = 0; + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + KClientPort clientPort = currentProcess.HandleTable.GetObject(clientPortHandle); + + if (clientPort == null) + { + return KernelResult.InvalidHandle; + } + + KernelResult result = currentProcess.HandleTable.ReserveHandle(out int handle); + + if (result != KernelResult.Success) + { + return result; + } + + KAutoObject session; + + if (clientPort.IsLight) + { + result = clientPort.ConnectLight(out KLightClientSession clientSession); + + session = clientSession; + } + else + { + result = clientPort.Connect(out KClientSession clientSession); + + session = clientSession; + } + + if (result != KernelResult.Success) + { + currentProcess.HandleTable.CancelHandleReservation(handle); + + return result; + } + + currentProcess.HandleTable.SetReservedHandleObj(handle, session); + + session.DecrementReferenceCount(); + + clientSessionHandle = handle; + + return result; + } + } +} diff --git a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcMemory.cs b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcMemory.cs new file mode 100644 index 0000000000..42be266b3a --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcMemory.cs @@ -0,0 +1,517 @@ +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Memory; +using Ryujinx.HLE.HOS.Kernel.Process; + +namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall +{ + partial class SvcHandler + { + public KernelResult SetHeapSize64([R(1)] ulong size, [R(1)] out ulong position) + { + return SetHeapSize(size, out position); + } + + private KernelResult SetHeapSize(ulong size, out ulong position) + { + if ((size & 0xfffffffe001fffff) != 0) + { + position = 0; + + return KernelResult.InvalidSize; + } + + return _process.MemoryManager.SetHeapSize(size, out position); + } + + public KernelResult SetMemoryAttribute64( + [R(0)] ulong position, + [R(1)] ulong size, + [R(2)] MemoryAttribute attributeMask, + [R(3)] MemoryAttribute attributeValue) + { + return SetMemoryAttribute(position, size, attributeMask, attributeValue); + } + + private KernelResult SetMemoryAttribute( + ulong position, + ulong size, + MemoryAttribute attributeMask, + MemoryAttribute attributeValue) + { + if (!PageAligned(position)) + { + return KernelResult.InvalidAddress; + } + + if (!PageAligned(size) || size == 0) + { + return KernelResult.InvalidSize; + } + + MemoryAttribute attributes = attributeMask | attributeValue; + + if (attributes != attributeMask || + (attributes | MemoryAttribute.Uncached) != MemoryAttribute.Uncached) + { + return KernelResult.InvalidCombination; + } + + KernelResult result = _process.MemoryManager.SetMemoryAttribute( + position, + size, + attributeMask, + attributeValue); + + return result; + } + + public KernelResult MapMemory64([R(0)] ulong dst, [R(1)] ulong src, [R(2)] ulong size) + { + return MapMemory(dst, src, size); + } + + private KernelResult MapMemory(ulong dst, ulong src, ulong size) + { + if (!PageAligned(src | dst)) + { + return KernelResult.InvalidAddress; + } + + if (!PageAligned(size) || size == 0) + { + return KernelResult.InvalidSize; + } + + if (src + size <= src || dst + size <= dst) + { + return KernelResult.InvalidMemState; + } + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + if (!currentProcess.MemoryManager.InsideAddrSpace(src, size)) + { + return KernelResult.InvalidMemState; + } + + if (currentProcess.MemoryManager.OutsideStackRegion(dst, size) || + currentProcess.MemoryManager.InsideHeapRegion (dst, size) || + currentProcess.MemoryManager.InsideAliasRegion (dst, size)) + { + return KernelResult.InvalidMemRange; + } + + return _process.MemoryManager.Map(dst, src, size); + } + + public KernelResult UnmapMemory64([R(0)] ulong dst, [R(1)] ulong src, [R(2)] ulong size) + { + return UnmapMemory(dst, src, size); + } + + private KernelResult UnmapMemory(ulong dst, ulong src, ulong size) + { + if (!PageAligned(src | dst)) + { + return KernelResult.InvalidAddress; + } + + if (!PageAligned(size) || size == 0) + { + return KernelResult.InvalidSize; + } + + if (src + size <= src || dst + size <= dst) + { + return KernelResult.InvalidMemState; + } + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + if (!currentProcess.MemoryManager.InsideAddrSpace(src, size)) + { + return KernelResult.InvalidMemState; + } + + if (currentProcess.MemoryManager.OutsideStackRegion(dst, size) || + currentProcess.MemoryManager.InsideHeapRegion (dst, size) || + currentProcess.MemoryManager.InsideAliasRegion (dst, size)) + { + return KernelResult.InvalidMemRange; + } + + return _process.MemoryManager.Unmap(dst, src, size); + } + + public KernelResult QueryMemory64([R(0)] ulong infoPtr, [R(2)] ulong position, [R(1)] out ulong pageInfo) + { + return QueryMemory(infoPtr, position, out pageInfo); + } + + private KernelResult QueryMemory(ulong infoPtr, ulong position, out ulong pageInfo) + { + KMemoryInfo blkInfo = _process.MemoryManager.QueryMemory(position); + + _process.CpuMemory.WriteUInt64((long)infoPtr + 0x00, blkInfo.Address); + _process.CpuMemory.WriteUInt64((long)infoPtr + 0x08, blkInfo.Size); + _process.CpuMemory.WriteInt32 ((long)infoPtr + 0x10, (int)blkInfo.State & 0xff); + _process.CpuMemory.WriteInt32 ((long)infoPtr + 0x14, (int)blkInfo.Attribute); + _process.CpuMemory.WriteInt32 ((long)infoPtr + 0x18, (int)blkInfo.Permission); + _process.CpuMemory.WriteInt32 ((long)infoPtr + 0x1c, blkInfo.IpcRefCount); + _process.CpuMemory.WriteInt32 ((long)infoPtr + 0x20, blkInfo.DeviceRefCount); + _process.CpuMemory.WriteInt32 ((long)infoPtr + 0x24, 0); + + pageInfo = 0; + + return KernelResult.Success; + } + + public KernelResult MapSharedMemory64([R(0)] int handle, [R(1)] ulong address, [R(2)] ulong size, [R(3)] MemoryPermission permission) + { + return MapSharedMemory(handle, address, size, permission); + } + + private KernelResult MapSharedMemory(int handle, ulong address, ulong size, MemoryPermission permission) + { + if (!PageAligned(address)) + { + return KernelResult.InvalidAddress; + } + + if (!PageAligned(size) || size == 0) + { + return KernelResult.InvalidSize; + } + + if (address + size <= address) + { + return KernelResult.InvalidMemState; + } + + if ((permission | MemoryPermission.Write) != MemoryPermission.ReadAndWrite) + { + return KernelResult.InvalidPermission; + } + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + KSharedMemory sharedMemory = currentProcess.HandleTable.GetObject(handle); + + if (sharedMemory == null) + { + return KernelResult.InvalidHandle; + } + + if (currentProcess.MemoryManager.IsInvalidRegion (address, size) || + currentProcess.MemoryManager.InsideHeapRegion (address, size) || + currentProcess.MemoryManager.InsideAliasRegion(address, size)) + { + return KernelResult.InvalidMemRange; + } + + return sharedMemory.MapIntoProcess( + currentProcess.MemoryManager, + address, + size, + currentProcess, + permission); + } + + public KernelResult UnmapSharedMemory64([R(0)] int handle, [R(1)] ulong address, [R(2)] ulong size) + { + return UnmapSharedMemory(handle, address, size); + } + + private KernelResult UnmapSharedMemory(int handle, ulong address, ulong size) + { + if (!PageAligned(address)) + { + return KernelResult.InvalidAddress; + } + + if (!PageAligned(size) || size == 0) + { + return KernelResult.InvalidSize; + } + + if (address + size <= address) + { + return KernelResult.InvalidMemState; + } + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + KSharedMemory sharedMemory = currentProcess.HandleTable.GetObject(handle); + + if (sharedMemory == null) + { + return KernelResult.InvalidHandle; + } + + if (currentProcess.MemoryManager.IsInvalidRegion (address, size) || + currentProcess.MemoryManager.InsideHeapRegion (address, size) || + currentProcess.MemoryManager.InsideAliasRegion(address, size)) + { + return KernelResult.InvalidMemRange; + } + + return sharedMemory.UnmapFromProcess( + currentProcess.MemoryManager, + address, + size, + currentProcess); + } + + public KernelResult CreateTransferMemory64( + [R(1)] ulong address, + [R(2)] ulong size, + [R(3)] MemoryPermission permission, + [R(1)] out int handle) + { + return CreateTransferMemory(address, size, permission, out handle); + } + + private KernelResult CreateTransferMemory(ulong address, ulong size, MemoryPermission permission, out int handle) + { + handle = 0; + + if (!PageAligned(address)) + { + return KernelResult.InvalidAddress; + } + + if (!PageAligned(size) || size == 0) + { + return KernelResult.InvalidSize; + } + + if (address + size <= address) + { + return KernelResult.InvalidMemState; + } + + if (permission > MemoryPermission.ReadAndWrite || permission == MemoryPermission.Write) + { + return KernelResult.InvalidPermission; + } + + KernelResult result = _process.MemoryManager.ReserveTransferMemory(address, size, permission); + + if (result != KernelResult.Success) + { + return result; + } + + KTransferMemory transferMemory = new KTransferMemory(_system, address, size); + + return _process.HandleTable.GenerateHandle(transferMemory, out handle); + } + + public KernelResult MapPhysicalMemory64([R(0)] ulong address, [R(1)] ulong size) + { + return MapPhysicalMemory(address, size); + } + + private KernelResult MapPhysicalMemory(ulong address, ulong size) + { + if (!PageAligned(address)) + { + return KernelResult.InvalidAddress; + } + + if (!PageAligned(size) || size == 0) + { + return KernelResult.InvalidSize; + } + + if (address + size <= address) + { + return KernelResult.InvalidMemRange; + } + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + if ((currentProcess.PersonalMmHeapPagesCount & 0xfffffffffffff) == 0) + { + return KernelResult.InvalidState; + } + + if (!currentProcess.MemoryManager.InsideAddrSpace (address, size) || + currentProcess.MemoryManager.OutsideAliasRegion(address, size)) + { + return KernelResult.InvalidMemRange; + } + + return _process.MemoryManager.MapPhysicalMemory(address, size); + } + + public KernelResult UnmapPhysicalMemory64([R(0)] ulong address, [R(1)] ulong size) + { + return UnmapPhysicalMemory(address, size); + } + + private KernelResult UnmapPhysicalMemory(ulong address, ulong size) + { + if (!PageAligned(address)) + { + return KernelResult.InvalidAddress; + } + + if (!PageAligned(size) || size == 0) + { + return KernelResult.InvalidSize; + } + + if (address + size <= address) + { + return KernelResult.InvalidMemRange; + } + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + if ((currentProcess.PersonalMmHeapPagesCount & 0xfffffffffffff) == 0) + { + return KernelResult.InvalidState; + } + + if (!currentProcess.MemoryManager.InsideAddrSpace (address, size) || + currentProcess.MemoryManager.OutsideAliasRegion(address, size)) + { + return KernelResult.InvalidMemRange; + } + + return _process.MemoryManager.UnmapPhysicalMemory(address, size); + } + + public KernelResult MapProcessCodeMemory64([R(0)] int handle, [R(1)] ulong dst, [R(2)] ulong src, [R(3)] ulong size) + { + return MapProcessCodeMemory(handle, dst, src, size); + } + + public KernelResult MapProcessCodeMemory(int handle, ulong dst, ulong src, ulong size) + { + if (!PageAligned(dst) || !PageAligned(src)) + { + return KernelResult.InvalidAddress; + } + + if (!PageAligned(size) || size == 0) + { + return KernelResult.InvalidSize; + } + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + KProcess targetProcess = currentProcess.HandleTable.GetObject(handle); + + if (targetProcess == null) + { + return KernelResult.InvalidHandle; + } + + if (targetProcess.MemoryManager.OutsideAddrSpace(dst, size) || + targetProcess.MemoryManager.OutsideAddrSpace(src, size) || + targetProcess.MemoryManager.InsideAliasRegion(dst, size) || + targetProcess.MemoryManager.InsideHeapRegion(dst, size)) + { + return KernelResult.InvalidMemRange; + } + + if (size + dst <= dst || size + src <= src) + { + return KernelResult.InvalidMemState; + } + + return targetProcess.MemoryManager.MapProcessCodeMemory(dst, src, size); + } + + public KernelResult UnmapProcessCodeMemory64([R(0)] int handle, [R(1)] ulong dst, [R(2)] ulong src, [R(3)] ulong size) + { + return UnmapProcessCodeMemory(handle, dst, src, size); + } + + public KernelResult UnmapProcessCodeMemory(int handle, ulong dst, ulong src, ulong size) + { + if (!PageAligned(dst) || !PageAligned(src)) + { + return KernelResult.InvalidAddress; + } + + if (!PageAligned(size) || size == 0) + { + return KernelResult.InvalidSize; + } + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + KProcess targetProcess = currentProcess.HandleTable.GetObject(handle); + + if (targetProcess == null) + { + return KernelResult.InvalidHandle; + } + + if (targetProcess.MemoryManager.OutsideAddrSpace(dst, size) || + targetProcess.MemoryManager.OutsideAddrSpace(src, size) || + targetProcess.MemoryManager.InsideAliasRegion(dst, size) || + targetProcess.MemoryManager.InsideHeapRegion(dst, size)) + { + return KernelResult.InvalidMemRange; + } + + if (size + dst <= dst || size + src <= src) + { + return KernelResult.InvalidMemState; + } + + return targetProcess.MemoryManager.UnmapProcessCodeMemory(dst, src, size); + } + + public KernelResult SetProcessMemoryPermission64([R(0)] int handle, [R(1)] ulong src, [R(2)] ulong size, [R(3)] MemoryPermission permission) + { + return SetProcessMemoryPermission(handle, src, size, permission); + } + + public KernelResult SetProcessMemoryPermission(int handle, ulong src, ulong size, MemoryPermission permission) + { + if (!PageAligned(src)) + { + return KernelResult.InvalidAddress; + } + + if (!PageAligned(size) || size == 0) + { + return KernelResult.InvalidSize; + } + + if (permission != MemoryPermission.None && + permission != MemoryPermission.Read && + permission != MemoryPermission.ReadAndWrite && + permission != MemoryPermission.ReadAndExecute) + { + return KernelResult.InvalidPermission; + } + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + KProcess targetProcess = currentProcess.HandleTable.GetObject(handle); + + if (targetProcess == null) + { + return KernelResult.InvalidHandle; + } + + if (targetProcess.MemoryManager.OutsideAddrSpace(src, size)) + { + return KernelResult.InvalidMemState; + } + + return targetProcess.MemoryManager.SetProcessMemoryPermission(src, size, permission); + } + + private static bool PageAligned(ulong position) + { + return (position & (KMemoryManager.PageSize - 1)) == 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcSystem.cs b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcSystem.cs new file mode 100644 index 0000000000..dad9612cc6 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcSystem.cs @@ -0,0 +1,624 @@ +using ARMeilleure.Memory; +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.Exceptions; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Ipc; +using Ryujinx.HLE.HOS.Kernel.Memory; +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.HLE.HOS.Kernel.Threading; + +namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall +{ + partial class SvcHandler + { + public void ExitProcess64() + { + ExitProcess(); + } + + public KernelResult TerminateProcess64([R(0)] int handle) + { + return TerminateProcess(handle); + } + + private KernelResult TerminateProcess(int handle) + { + KProcess process = _process.HandleTable.GetObject(handle); + + KernelResult result; + + if (process != null) + { + if (process == _system.Scheduler.GetCurrentProcess()) + { + result = KernelResult.Success; + process.DecrementToZeroWhileTerminatingCurrent(); + } + else + { + result = process.Terminate(); + process.DecrementReferenceCount(); + } + } + else + { + result = KernelResult.InvalidHandle; + } + + return result; + } + + private void ExitProcess() + { + _system.Scheduler.GetCurrentProcess().TerminateCurrentProcess(); + } + + public KernelResult SignalEvent64([R(0)] int handle) + { + return SignalEvent(handle); + } + + private KernelResult SignalEvent(int handle) + { + KWritableEvent writableEvent = _process.HandleTable.GetObject(handle); + + KernelResult result; + + if (writableEvent != null) + { + writableEvent.Signal(); + + result = KernelResult.Success; + } + else + { + result = KernelResult.InvalidHandle; + } + + return result; + } + + public KernelResult ClearEvent64([R(0)] int handle) + { + return ClearEvent(handle); + } + + private KernelResult ClearEvent(int handle) + { + KernelResult result; + + KWritableEvent writableEvent = _process.HandleTable.GetObject(handle); + + if (writableEvent == null) + { + KReadableEvent readableEvent = _process.HandleTable.GetObject(handle); + + result = readableEvent?.Clear() ?? KernelResult.InvalidHandle; + } + else + { + result = writableEvent.Clear(); + } + + return result; + } + + public KernelResult CloseHandle64([R(0)] int handle) + { + return CloseHandle(handle); + } + + private KernelResult CloseHandle(int handle) + { + KAutoObject obj = _process.HandleTable.GetObject(handle); + + _process.HandleTable.CloseHandle(handle); + + if (obj == null) + { + return KernelResult.InvalidHandle; + } + + if (obj is KSession session) + { + session.Dispose(); + } + else if (obj is KTransferMemory transferMemory) + { + _process.MemoryManager.ResetTransferMemory( + transferMemory.Address, + transferMemory.Size); + } + + return KernelResult.Success; + } + + public KernelResult ResetSignal64([R(0)] int handle) + { + return ResetSignal(handle); + } + + private KernelResult ResetSignal(int handle) + { + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + KReadableEvent readableEvent = currentProcess.HandleTable.GetObject(handle); + + KernelResult result; + + if (readableEvent != null) + { + result = readableEvent.ClearIfSignaled(); + } + else + { + KProcess process = currentProcess.HandleTable.GetKProcess(handle); + + if (process != null) + { + result = process.ClearIfNotExited(); + } + else + { + result = KernelResult.InvalidHandle; + } + } + + return result; + } + + public ulong GetSystemTick64() + { + return _system.Scheduler.GetCurrentThread().Context.CntpctEl0; + } + + public KernelResult GetProcessId64([R(1)] int handle, [R(1)] out long pid) + { + return GetProcessId(handle, out pid); + } + + private KernelResult GetProcessId(int handle, out long pid) + { + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + KProcess process = currentProcess.HandleTable.GetKProcess(handle); + + if (process == null) + { + KThread thread = currentProcess.HandleTable.GetKThread(handle); + + if (thread != null) + { + process = thread.Owner; + } + + // TODO: KDebugEvent. + } + + pid = process?.Pid ?? 0; + + return process != null + ? KernelResult.Success + : KernelResult.InvalidHandle; + } + + public void Break64([R(0)] ulong reason, [R(1)] ulong x1, [R(2)] ulong info) + { + Break(reason); + } + + private void Break(ulong reason) + { + KThread currentThread = _system.Scheduler.GetCurrentThread(); + + if ((reason & (1UL << 31)) == 0) + { + currentThread.PrintGuestStackTrace(); + + // As the process is exiting, this is probably caused by emulation termination. + if (currentThread.Owner.State == ProcessState.Exiting) + { + return; + } + + // TODO: Debug events. + currentThread.Owner.TerminateCurrentProcess(); + + throw new GuestBrokeExecutionException(); + } + else + { + Logger.PrintInfo(LogClass.KernelSvc, "Debugger triggered."); + + currentThread.PrintGuestStackTrace(); + } + } + + public void OutputDebugString64([R(0)] ulong strPtr, [R(1)] ulong size) + { + OutputDebugString(strPtr, size); + } + + private void OutputDebugString(ulong strPtr, ulong size) + { + string str = MemoryHelper.ReadAsciiString(_process.CpuMemory, (long)strPtr, (long)size); + + Logger.PrintWarning(LogClass.KernelSvc, str); + } + + public KernelResult GetInfo64([R(1)] uint id, [R(2)] int handle, [R(3)] long subId, [R(1)] out long value) + { + return GetInfo(id, handle, subId, out value); + } + + private KernelResult GetInfo(uint id, int handle, long subId, out long value) + { + value = 0; + + switch (id) + { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + case 12: + case 13: + case 14: + case 15: + case 16: + case 17: + case 18: + case 20: + case 21: + case 22: + { + if (subId != 0) + { + return KernelResult.InvalidCombination; + } + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + KProcess process = currentProcess.HandleTable.GetKProcess(handle); + + if (process == null) + { + return KernelResult.InvalidHandle; + } + + switch (id) + { + case 0: value = process.Capabilities.AllowedCpuCoresMask; break; + case 1: value = process.Capabilities.AllowedThreadPriosMask; break; + + case 2: value = (long)process.MemoryManager.AliasRegionStart; break; + case 3: value = (long)(process.MemoryManager.AliasRegionEnd - + process.MemoryManager.AliasRegionStart); break; + + case 4: value = (long)process.MemoryManager.HeapRegionStart; break; + case 5: value = (long)(process.MemoryManager.HeapRegionEnd - + process.MemoryManager.HeapRegionStart); break; + + case 6: value = (long)process.GetMemoryCapacity(); break; + + case 7: value = (long)process.GetMemoryUsage(); break; + + case 12: value = (long)process.MemoryManager.GetAddrSpaceBaseAddr(); break; + + case 13: value = (long)process.MemoryManager.GetAddrSpaceSize(); break; + + case 14: value = (long)process.MemoryManager.StackRegionStart; break; + case 15: value = (long)(process.MemoryManager.StackRegionEnd - + process.MemoryManager.StackRegionStart); break; + + case 16: value = (long)process.PersonalMmHeapPagesCount * KMemoryManager.PageSize; break; + + case 17: + if (process.PersonalMmHeapPagesCount != 0) + { + value = process.MemoryManager.GetMmUsedPages() * KMemoryManager.PageSize; + } + + break; + + case 18: value = (long)process.TitleId; break; + + case 20: value = (long)process.UserExceptionContextAddress; break; + + case 21: value = (long)process.GetMemoryCapacityWithoutPersonalMmHeap(); break; + + case 22: value = (long)process.GetMemoryUsageWithoutPersonalMmHeap(); break; + } + + break; + } + + case 8: + { + if (handle != 0) + { + return KernelResult.InvalidHandle; + } + + if (subId != 0) + { + return KernelResult.InvalidCombination; + } + + value = _system.Scheduler.GetCurrentProcess().Debug ? 1 : 0; + + break; + } + + case 9: + { + if (handle != 0) + { + return KernelResult.InvalidHandle; + } + + if (subId != 0) + { + return KernelResult.InvalidCombination; + } + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + if (currentProcess.ResourceLimit != null) + { + KHandleTable handleTable = currentProcess.HandleTable; + KResourceLimit resourceLimit = currentProcess.ResourceLimit; + + KernelResult result = handleTable.GenerateHandle(resourceLimit, out int resLimHandle); + + if (result != KernelResult.Success) + { + return result; + } + + value = (uint)resLimHandle; + } + + break; + } + + case 10: + { + if (handle != 0) + { + return KernelResult.InvalidHandle; + } + + int currentCore = _system.Scheduler.GetCurrentThread().CurrentCore; + + if (subId != -1 && subId != currentCore) + { + return KernelResult.InvalidCombination; + } + + value = _system.Scheduler.CoreContexts[currentCore].TotalIdleTimeTicks; + + break; + } + + case 11: + { + if (handle != 0) + { + return KernelResult.InvalidHandle; + } + + if ((ulong)subId > 3) + { + return KernelResult.InvalidCombination; + } + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + + value = currentProcess.RandomEntropy[subId]; + + break; + } + + case 0xf0000002u: + { + if (subId < -1 || subId > 3) + { + return KernelResult.InvalidCombination; + } + + KThread thread = _system.Scheduler.GetCurrentProcess().HandleTable.GetKThread(handle); + + if (thread == null) + { + return KernelResult.InvalidHandle; + } + + KThread currentThread = _system.Scheduler.GetCurrentThread(); + + int currentCore = currentThread.CurrentCore; + + if (subId != -1 && subId != currentCore) + { + return KernelResult.Success; + } + + KCoreContext coreContext = _system.Scheduler.CoreContexts[currentCore]; + + long timeDelta = PerformanceCounter.ElapsedMilliseconds - coreContext.LastContextSwitchTime; + + if (subId != -1) + { + value = KTimeManager.ConvertMillisecondsToTicks(timeDelta); + } + else + { + long totalTimeRunning = thread.TotalTimeRunning; + + if (thread == currentThread) + { + totalTimeRunning += timeDelta; + } + + value = KTimeManager.ConvertMillisecondsToTicks(totalTimeRunning); + } + + break; + } + + default: return KernelResult.InvalidEnumValue; + } + + return KernelResult.Success; + } + + public KernelResult CreateEvent64([R(1)] out int wEventHandle, [R(2)] out int rEventHandle) + { + return CreateEvent(out wEventHandle, out rEventHandle); + } + + private KernelResult CreateEvent(out int wEventHandle, out int rEventHandle) + { + KEvent Event = new KEvent(_system); + + KernelResult result = _process.HandleTable.GenerateHandle(Event.WritableEvent, out wEventHandle); + + if (result == KernelResult.Success) + { + result = _process.HandleTable.GenerateHandle(Event.ReadableEvent, out rEventHandle); + + if (result != KernelResult.Success) + { + _process.HandleTable.CloseHandle(wEventHandle); + } + } + else + { + rEventHandle = 0; + } + + return result; + } + + public KernelResult GetProcessList64([R(1)] ulong address, [R(2)] int maxCount, [R(1)] out int count) + { + return GetProcessList(address, maxCount, out count); + } + + private KernelResult GetProcessList(ulong address, int maxCount, out int count) + { + count = 0; + + if ((maxCount >> 28) != 0) + { + return KernelResult.MaximumExceeded; + } + + if (maxCount != 0) + { + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + ulong copySize = (ulong)maxCount * 8; + + if (address + copySize <= address) + { + return KernelResult.InvalidMemState; + } + + if (currentProcess.MemoryManager.OutsideAddrSpace(address, copySize)) + { + return KernelResult.InvalidMemState; + } + } + + int copyCount = 0; + + lock (_system.Processes) + { + foreach (KProcess process in _system.Processes.Values) + { + if (copyCount < maxCount) + { + if (!KernelTransfer.KernelToUserInt64(_system, address + (ulong)copyCount * 8, process.Pid)) + { + return KernelResult.UserCopyFailed; + } + } + + copyCount++; + } + } + + count = copyCount; + + return KernelResult.Success; + } + + public KernelResult GetSystemInfo64([R(1)] uint id, [R(2)] int handle, [R(3)] long subId, [R(1)] out long value) + { + return GetSystemInfo(id, handle, subId, out value); + } + + private KernelResult GetSystemInfo(uint id, int handle, long subId, out long value) + { + value = 0; + + if (id > 2) + { + return KernelResult.InvalidEnumValue; + } + + if (handle != 0) + { + return KernelResult.InvalidHandle; + } + + if (id < 2) + { + if ((ulong)subId > 3) + { + return KernelResult.InvalidCombination; + } + + KMemoryRegionManager region = _system.MemoryRegions[subId]; + + switch (id) + { + // Memory region capacity. + case 0: value = (long)region.Size; break; + + // Memory region free space. + case 1: + { + ulong freePagesCount = region.GetFreePages(); + + value = (long)(freePagesCount * KMemoryManager.PageSize); + + break; + } + } + } + else /* if (Id == 2) */ + { + if ((ulong)subId > 1) + { + return KernelResult.InvalidCombination; + } + + switch (subId) + { + case 0: value = _system.PrivilegedProcessLowestId; break; + case 1: value = _system.PrivilegedProcessHighestId; break; + } + } + + return KernelResult.Success; + } + } +} diff --git a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcTable.cs b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcTable.cs new file mode 100644 index 0000000000..27aa70fc71 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcTable.cs @@ -0,0 +1,404 @@ +using ARMeilleure.State; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Kernel.Common; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; + +namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall +{ + static class SvcTable + { + private const int SvcFuncMaxArguments64 = 8; + private const int SvcFuncMaxArguments32 = 4; + private const int SvcMax = 0x80; + + public static Action[] SvcTable32 { get; } + public static Action[] SvcTable64 { get; } + + static SvcTable() + { + SvcTable32 = new Action[SvcMax]; + SvcTable64 = new Action[SvcMax]; + + Dictionary svcFuncs64 = new Dictionary + { + { 0x01, nameof(SvcHandler.SetHeapSize64) }, + { 0x03, nameof(SvcHandler.SetMemoryAttribute64) }, + { 0x04, nameof(SvcHandler.MapMemory64) }, + { 0x05, nameof(SvcHandler.UnmapMemory64) }, + { 0x06, nameof(SvcHandler.QueryMemory64) }, + { 0x07, nameof(SvcHandler.ExitProcess64) }, + { 0x08, nameof(SvcHandler.CreateThread64) }, + { 0x09, nameof(SvcHandler.StartThread64) }, + { 0x0a, nameof(SvcHandler.ExitThread64) }, + { 0x0b, nameof(SvcHandler.SleepThread64) }, + { 0x0c, nameof(SvcHandler.GetThreadPriority64) }, + { 0x0d, nameof(SvcHandler.SetThreadPriority64) }, + { 0x0e, nameof(SvcHandler.GetThreadCoreMask64) }, + { 0x0f, nameof(SvcHandler.SetThreadCoreMask64) }, + { 0x10, nameof(SvcHandler.GetCurrentProcessorNumber64) }, + { 0x11, nameof(SvcHandler.SignalEvent64) }, + { 0x12, nameof(SvcHandler.ClearEvent64) }, + { 0x13, nameof(SvcHandler.MapSharedMemory64) }, + { 0x14, nameof(SvcHandler.UnmapSharedMemory64) }, + { 0x15, nameof(SvcHandler.CreateTransferMemory64) }, + { 0x16, nameof(SvcHandler.CloseHandle64) }, + { 0x17, nameof(SvcHandler.ResetSignal64) }, + { 0x18, nameof(SvcHandler.WaitSynchronization64) }, + { 0x19, nameof(SvcHandler.CancelSynchronization64) }, + { 0x1a, nameof(SvcHandler.ArbitrateLock64) }, + { 0x1b, nameof(SvcHandler.ArbitrateUnlock64) }, + { 0x1c, nameof(SvcHandler.WaitProcessWideKeyAtomic64) }, + { 0x1d, nameof(SvcHandler.SignalProcessWideKey64) }, + { 0x1e, nameof(SvcHandler.GetSystemTick64) }, + { 0x1f, nameof(SvcHandler.ConnectToNamedPort64) }, + { 0x21, nameof(SvcHandler.SendSyncRequest64) }, + { 0x22, nameof(SvcHandler.SendSyncRequestWithUserBuffer64) }, + { 0x24, nameof(SvcHandler.GetProcessId64) }, + { 0x25, nameof(SvcHandler.GetThreadId64) }, + { 0x26, nameof(SvcHandler.Break64) }, + { 0x27, nameof(SvcHandler.OutputDebugString64) }, + { 0x29, nameof(SvcHandler.GetInfo64) }, + { 0x2c, nameof(SvcHandler.MapPhysicalMemory64) }, + { 0x2d, nameof(SvcHandler.UnmapPhysicalMemory64) }, + { 0x32, nameof(SvcHandler.SetThreadActivity64) }, + { 0x33, nameof(SvcHandler.GetThreadContext364) }, + { 0x34, nameof(SvcHandler.WaitForAddress64) }, + { 0x35, nameof(SvcHandler.SignalToAddress64) }, + { 0x40, nameof(SvcHandler.CreateSession64) }, + { 0x41, nameof(SvcHandler.AcceptSession64) }, + { 0x43, nameof(SvcHandler.ReplyAndReceive64) }, + { 0x45, nameof(SvcHandler.CreateEvent64) }, + { 0x65, nameof(SvcHandler.GetProcessList64) }, + { 0x6f, nameof(SvcHandler.GetSystemInfo64) }, + { 0x70, nameof(SvcHandler.CreatePort64) }, + { 0x71, nameof(SvcHandler.ManageNamedPort64) }, + { 0x72, nameof(SvcHandler.ConnectToPort64) }, + { 0x73, nameof(SvcHandler.SetProcessMemoryPermission64) }, + { 0x77, nameof(SvcHandler.MapProcessCodeMemory64) }, + { 0x78, nameof(SvcHandler.UnmapProcessCodeMemory64) }, + { 0x7B, nameof(SvcHandler.TerminateProcess64) } + }; + + foreach (KeyValuePair value in svcFuncs64) + { + SvcTable64[value.Key] = GenerateMethod(value.Value, SvcFuncMaxArguments64); + } + + Dictionary svcFuncs32 = new Dictionary + { + // TODO + }; + + foreach (KeyValuePair value in svcFuncs32) + { + SvcTable32[value.Key] = GenerateMethod(value.Value, SvcFuncMaxArguments32); + } + } + + private static Action GenerateMethod(string svcName, int registerCleanCount) + { + Type[] argTypes = new Type[] { typeof(SvcHandler), typeof(ExecutionContext) }; + + DynamicMethod method = new DynamicMethod(svcName, null, argTypes); + + MethodInfo methodInfo = typeof(SvcHandler).GetMethod(svcName); + + ParameterInfo[] methodArgs = methodInfo.GetParameters(); + + ILGenerator generator = method.GetILGenerator(); + + void ConvertToArgType(Type sourceType) + { + CheckIfTypeIsSupported(sourceType, svcName); + + switch (Type.GetTypeCode(sourceType)) + { + case TypeCode.UInt32: generator.Emit(OpCodes.Conv_U4); break; + case TypeCode.Int32: generator.Emit(OpCodes.Conv_I4); break; + case TypeCode.UInt16: generator.Emit(OpCodes.Conv_U2); break; + case TypeCode.Int16: generator.Emit(OpCodes.Conv_I2); break; + case TypeCode.Byte: generator.Emit(OpCodes.Conv_U1); break; + case TypeCode.SByte: generator.Emit(OpCodes.Conv_I1); break; + + case TypeCode.Boolean: + generator.Emit(OpCodes.Conv_I4); + generator.Emit(OpCodes.Ldc_I4_1); + generator.Emit(OpCodes.And); + break; + } + } + + void ConvertToFieldType(Type sourceType) + { + CheckIfTypeIsSupported(sourceType, svcName); + + switch (Type.GetTypeCode(sourceType)) + { + case TypeCode.UInt32: + case TypeCode.Int32: + case TypeCode.UInt16: + case TypeCode.Int16: + case TypeCode.Byte: + case TypeCode.SByte: + case TypeCode.Boolean: + generator.Emit(OpCodes.Conv_U8); + break; + } + } + + RAttribute GetRegisterAttribute(ParameterInfo parameterInfo) + { + RAttribute argumentAttribute = (RAttribute)parameterInfo.GetCustomAttribute(typeof(RAttribute)); + + if (argumentAttribute == null) + { + throw new InvalidOperationException($"Method \"{svcName}\" is missing a {typeof(RAttribute).Name} attribute on parameter \"{parameterInfo.Name}\""); + } + + return argumentAttribute; + } + + // For functions returning output values, the first registers + // are used to hold pointers where the value will be stored, + // so they can't be used to pass argument and we must + // skip them. + int byRefArgsCount = 0; + + for (int index = 0; index < methodArgs.Length; index++) + { + if (methodArgs[index].ParameterType.IsByRef) + { + byRefArgsCount++; + } + } + + BindingFlags staticNonPublic = BindingFlags.NonPublic | BindingFlags.Static; + + // Print all the arguments for debugging purposes. + int inputArgsCount = methodArgs.Length - byRefArgsCount; + + if (inputArgsCount != 0) + { + generator.Emit(OpCodes.Ldc_I4, inputArgsCount); + + generator.Emit(OpCodes.Newarr, typeof(object)); + + string argsFormat = svcName; + + for (int index = 0; index < methodArgs.Length; index++) + { + Type argType = methodArgs[index].ParameterType; + + // Ignore out argument for printing + if (argType.IsByRef) + { + continue; + } + + RAttribute registerAttribute = GetRegisterAttribute(methodArgs[index]); + + argsFormat += $" {methodArgs[index].Name}: 0x{{{index}:X8}},"; + + generator.Emit(OpCodes.Dup); + generator.Emit(OpCodes.Ldc_I4, index); + + generator.Emit(OpCodes.Ldarg_1); + generator.Emit(OpCodes.Ldc_I4, registerAttribute.Index); + + MethodInfo info = typeof(ExecutionContext).GetMethod(nameof(ExecutionContext.GetX)); + + generator.Emit(OpCodes.Call, info); + + generator.Emit(OpCodes.Box, typeof(ulong)); + + generator.Emit(OpCodes.Stelem_Ref); + } + + argsFormat = argsFormat.Substring(0, argsFormat.Length - 1); + + generator.Emit(OpCodes.Ldstr, argsFormat); + } + else + { + generator.Emit(OpCodes.Ldnull); + + generator.Emit(OpCodes.Ldstr, svcName); + } + + MethodInfo printArgsMethod = typeof(SvcTable).GetMethod(nameof(PrintArguments), staticNonPublic); + + generator.Emit(OpCodes.Call, printArgsMethod); + + // Call the SVC function handler. + generator.Emit(OpCodes.Ldarg_0); + + List<(LocalBuilder, RAttribute)> locals = new List<(LocalBuilder, RAttribute)>(); + + for (int index = 0; index < methodArgs.Length; index++) + { + Type argType = methodArgs[index].ParameterType; + RAttribute registerAttribute = GetRegisterAttribute(methodArgs[index]); + + if (argType.IsByRef) + { + argType = argType.GetElementType(); + + LocalBuilder local = generator.DeclareLocal(argType); + + locals.Add((local, registerAttribute)); + + if (!methodArgs[index].IsOut) + { + generator.Emit(OpCodes.Ldarg_1); + generator.Emit(OpCodes.Ldc_I4, registerAttribute.Index); + + MethodInfo info = typeof(ExecutionContext).GetMethod(nameof(ExecutionContext.GetX)); + + generator.Emit(OpCodes.Call, info); + + ConvertToArgType(argType); + + generator.Emit(OpCodes.Stloc, local); + } + + generator.Emit(OpCodes.Ldloca, local); + } + else + { + generator.Emit(OpCodes.Ldarg_1); + generator.Emit(OpCodes.Ldc_I4, registerAttribute.Index); + + MethodInfo info = typeof(ExecutionContext).GetMethod(nameof(ExecutionContext.GetX)); + + generator.Emit(OpCodes.Call, info); + + ConvertToArgType(argType); + } + } + + generator.Emit(OpCodes.Call, methodInfo); + + Type retType = methodInfo.ReturnType; + + // Print result code. + if (retType == typeof(KernelResult)) + { + MethodInfo printResultMethod = typeof(SvcTable).GetMethod(nameof(PrintResult), staticNonPublic); + + generator.Emit(OpCodes.Dup); + generator.Emit(OpCodes.Ldstr, svcName); + generator.Emit(OpCodes.Call, printResultMethod); + } + + uint registerInUse = 0; + + // Save return value into register X0 (when the method has a return value). + if (retType != typeof(void)) + { + CheckIfTypeIsSupported(retType, svcName); + + LocalBuilder tempLocal = generator.DeclareLocal(retType); + + generator.Emit(OpCodes.Stloc, tempLocal); + generator.Emit(OpCodes.Ldarg_1); + generator.Emit(OpCodes.Ldc_I4, 0); + generator.Emit(OpCodes.Ldloc, tempLocal); + + ConvertToFieldType(retType); + + MethodInfo info = typeof(ExecutionContext).GetMethod(nameof(ExecutionContext.SetX)); + + generator.Emit(OpCodes.Call, info); + + registerInUse |= 1u << 0; + } + + for (int index = 0; index < locals.Count; index++) + { + (LocalBuilder local, RAttribute attribute) = locals[index]; + generator.Emit(OpCodes.Ldarg_1); + generator.Emit(OpCodes.Ldc_I4, attribute.Index); + generator.Emit(OpCodes.Ldloc, local); + + ConvertToFieldType(local.LocalType); + + MethodInfo info = typeof(ExecutionContext).GetMethod(nameof(ExecutionContext.SetX)); + + generator.Emit(OpCodes.Call, info); + + registerInUse |= 1u << attribute.Index; + } + + // Zero out the remaining unused registers. + for (int i = 0; i < registerCleanCount; i++) + { + if ((registerInUse & (1u << i)) != 0) + { + continue; + } + + generator.Emit(OpCodes.Ldarg_1); + generator.Emit(OpCodes.Ldc_I4, i); + generator.Emit(OpCodes.Ldc_I8, 0L); + + MethodInfo info = typeof(ExecutionContext).GetMethod(nameof(ExecutionContext.SetX)); + + generator.Emit(OpCodes.Call, info); + } + + generator.Emit(OpCodes.Ret); + + return (Action)method.CreateDelegate(typeof(Action)); + } + + private static void CheckIfTypeIsSupported(Type type, string svcName) + { + switch (Type.GetTypeCode(type)) + { + case TypeCode.UInt64: + case TypeCode.Int64: + case TypeCode.UInt32: + case TypeCode.Int32: + case TypeCode.UInt16: + case TypeCode.Int16: + case TypeCode.Byte: + case TypeCode.SByte: + case TypeCode.Boolean: + return; + } + + throw new InvalidSvcException($"Method \"{svcName}\" has a invalid ref type \"{type.Name}\"."); + } + + private static void PrintArguments(object[] argValues, string formatOrSvcName) + { + if (argValues != null) + { + Logger.PrintDebug(LogClass.KernelSvc, string.Format(formatOrSvcName, argValues)); + } + else + { + Logger.PrintDebug(LogClass.KernelSvc, formatOrSvcName); + } + } + + private static void PrintResult(KernelResult result, string svcName) + { + if (result != KernelResult.Success && + result != KernelResult.TimedOut && + result != KernelResult.Cancelled && + result != KernelResult.InvalidState) + { + Logger.PrintWarning(LogClass.KernelSvc, $"{svcName} returned error {result}."); + } + else + { + Logger.PrintDebug(LogClass.KernelSvc, $"{svcName} returned result {result}."); + } + } + } +} diff --git a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcThread.cs b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcThread.cs new file mode 100644 index 0000000000..9e681c80f2 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcThread.cs @@ -0,0 +1,438 @@ +using ARMeilleure.Memory; +using ARMeilleure.State; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.HLE.HOS.Kernel.Threading; + +namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall +{ + partial class SvcHandler + { + public KernelResult CreateThread64( + [R(1)] ulong entrypoint, + [R(2)] ulong argsPtr, + [R(3)] ulong stackTop, + [R(4)] int priority, + [R(5)] int cpuCore, + [R(1)] out int handle) + { + return CreateThread(entrypoint, argsPtr, stackTop, priority, cpuCore, out handle); + } + + private KernelResult CreateThread( + ulong entrypoint, + ulong argsPtr, + ulong stackTop, + int priority, + int cpuCore, + out int handle) + { + handle = 0; + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + if (cpuCore == -2) + { + cpuCore = currentProcess.DefaultCpuCore; + } + + if ((uint)cpuCore >= KScheduler.CpuCoresCount || !currentProcess.IsCpuCoreAllowed(cpuCore)) + { + return KernelResult.InvalidCpuCore; + } + + if ((uint)priority >= KScheduler.PrioritiesCount || !currentProcess.IsPriorityAllowed(priority)) + { + return KernelResult.InvalidPriority; + } + + long timeout = KTimeManager.ConvertMillisecondsToNanoseconds(100); + + if (currentProcess.ResourceLimit != null && + !currentProcess.ResourceLimit.Reserve(LimitableResource.Thread, 1, timeout)) + { + return KernelResult.ResLimitExceeded; + } + + KThread thread = new KThread(_system); + + KernelResult result = currentProcess.InitializeThread( + thread, + entrypoint, + argsPtr, + stackTop, + priority, + cpuCore); + + if (result == KernelResult.Success) + { + result = _process.HandleTable.GenerateHandle(thread, out handle); + } + else + { + currentProcess.ResourceLimit?.Release(LimitableResource.Thread, 1); + } + + thread.DecrementReferenceCount(); + + return result; + } + + public KernelResult StartThread64([R(0)] int handle) + { + return StartThread(handle); + } + + private KernelResult StartThread(int handle) + { + KThread thread = _process.HandleTable.GetKThread(handle); + + if (thread != null) + { + thread.IncrementReferenceCount(); + + KernelResult result = thread.Start(); + + if (result == KernelResult.Success) + { + thread.IncrementReferenceCount(); + } + + thread.DecrementReferenceCount(); + + return result; + } + else + { + return KernelResult.InvalidHandle; + } + } + + public void ExitThread64() + { + ExitThread(); + } + + private void ExitThread() + { + KThread currentThread = _system.Scheduler.GetCurrentThread(); + + _system.Scheduler.ExitThread(currentThread); + + currentThread.Exit(); + } + + public void SleepThread64([R(0)] long timeout) + { + SleepThread(timeout); + } + + private void SleepThread(long timeout) + { + KThread currentThread = _system.Scheduler.GetCurrentThread(); + + if (timeout < 1) + { + switch (timeout) + { + case 0: currentThread.Yield(); break; + case -1: currentThread.YieldWithLoadBalancing(); break; + case -2: currentThread.YieldAndWaitForLoadBalancing(); break; + } + } + else + { + currentThread.Sleep(timeout); + } + } + + public KernelResult GetThreadPriority64([R(1)] int handle, [R(1)] out int priority) + { + return GetThreadPriority(handle, out priority); + } + + private KernelResult GetThreadPriority(int handle, out int priority) + { + KThread thread = _process.HandleTable.GetKThread(handle); + + if (thread != null) + { + priority = thread.DynamicPriority; + + return KernelResult.Success; + } + else + { + priority = 0; + + return KernelResult.InvalidHandle; + } + } + + public KernelResult SetThreadPriority64([R(0)] int handle, [R(1)] int priority) + { + return SetThreadPriority(handle, priority); + } + + public KernelResult SetThreadPriority(int handle, int priority) + { + // TODO: NPDM check. + + KThread thread = _process.HandleTable.GetKThread(handle); + + if (thread == null) + { + return KernelResult.InvalidHandle; + } + + thread.SetPriority(priority); + + return KernelResult.Success; + } + + public KernelResult GetThreadCoreMask64([R(2)] int handle, [R(1)] out int preferredCore, [R(2)] out long affinityMask) + { + return GetThreadCoreMask(handle, out preferredCore, out affinityMask); + } + + private KernelResult GetThreadCoreMask(int handle, out int preferredCore, out long affinityMask) + { + KThread thread = _process.HandleTable.GetKThread(handle); + + if (thread != null) + { + preferredCore = thread.PreferredCore; + affinityMask = thread.AffinityMask; + + return KernelResult.Success; + } + else + { + preferredCore = 0; + affinityMask = 0; + + return KernelResult.InvalidHandle; + } + } + + public KernelResult SetThreadCoreMask64([R(0)] int handle, [R(1)] int preferredCore, [R(2)] long affinityMask) + { + return SetThreadCoreMask(handle, preferredCore, affinityMask); + } + + private KernelResult SetThreadCoreMask(int handle, int preferredCore, long affinityMask) + { + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + if (preferredCore == -2) + { + preferredCore = currentProcess.DefaultCpuCore; + + affinityMask = 1 << preferredCore; + } + else + { + if ((currentProcess.Capabilities.AllowedCpuCoresMask | affinityMask) != + currentProcess.Capabilities.AllowedCpuCoresMask) + { + return KernelResult.InvalidCpuCore; + } + + if (affinityMask == 0) + { + return KernelResult.InvalidCombination; + } + + if ((uint)preferredCore > 3) + { + if ((preferredCore | 2) != -1) + { + return KernelResult.InvalidCpuCore; + } + } + else if ((affinityMask & (1 << preferredCore)) == 0) + { + return KernelResult.InvalidCombination; + } + } + + KThread thread = _process.HandleTable.GetKThread(handle); + + if (thread == null) + { + return KernelResult.InvalidHandle; + } + + return thread.SetCoreAndAffinityMask(preferredCore, affinityMask); + } + + public int GetCurrentProcessorNumber64() + { + return _system.Scheduler.GetCurrentThread().CurrentCore; + } + + public KernelResult GetThreadId64([R(1)] int handle, [R(1)] out long threadUid) + { + return GetThreadId(handle, out threadUid); + } + + private KernelResult GetThreadId(int handle, out long threadUid) + { + KThread thread = _process.HandleTable.GetKThread(handle); + + if (thread != null) + { + threadUid = thread.ThreadUid; + + return KernelResult.Success; + } + else + { + threadUid = 0; + + return KernelResult.InvalidHandle; + } + } + + public KernelResult SetThreadActivity64([R(0)] int handle, [R(1)] bool pause) + { + return SetThreadActivity(handle, pause); + } + + private KernelResult SetThreadActivity(int handle, bool pause) + { + KThread thread = _process.HandleTable.GetObject(handle); + + if (thread == null) + { + return KernelResult.InvalidHandle; + } + + if (thread.Owner != _system.Scheduler.GetCurrentProcess()) + { + return KernelResult.InvalidHandle; + } + + if (thread == _system.Scheduler.GetCurrentThread()) + { + return KernelResult.InvalidThread; + } + + return thread.SetActivity(pause); + } + + public KernelResult GetThreadContext364([R(0)] ulong address, [R(1)] int handle) + { + return GetThreadContext3(address, handle); + } + + private KernelResult GetThreadContext3(ulong address, int handle) + { + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + KThread currentThread = _system.Scheduler.GetCurrentThread(); + + KThread thread = _process.HandleTable.GetObject(handle); + + if (thread == null) + { + return KernelResult.InvalidHandle; + } + + if (thread.Owner != currentProcess) + { + return KernelResult.InvalidHandle; + } + + if (currentThread == thread) + { + return KernelResult.InvalidThread; + } + + MemoryManager memory = currentProcess.CpuMemory; + + memory.WriteUInt64((long)address + 0x0, thread.Context.GetX(0)); + memory.WriteUInt64((long)address + 0x8, thread.Context.GetX(1)); + memory.WriteUInt64((long)address + 0x10, thread.Context.GetX(2)); + memory.WriteUInt64((long)address + 0x18, thread.Context.GetX(3)); + memory.WriteUInt64((long)address + 0x20, thread.Context.GetX(4)); + memory.WriteUInt64((long)address + 0x28, thread.Context.GetX(5)); + memory.WriteUInt64((long)address + 0x30, thread.Context.GetX(6)); + memory.WriteUInt64((long)address + 0x38, thread.Context.GetX(7)); + memory.WriteUInt64((long)address + 0x40, thread.Context.GetX(8)); + memory.WriteUInt64((long)address + 0x48, thread.Context.GetX(9)); + memory.WriteUInt64((long)address + 0x50, thread.Context.GetX(10)); + memory.WriteUInt64((long)address + 0x58, thread.Context.GetX(11)); + memory.WriteUInt64((long)address + 0x60, thread.Context.GetX(12)); + memory.WriteUInt64((long)address + 0x68, thread.Context.GetX(13)); + memory.WriteUInt64((long)address + 0x70, thread.Context.GetX(14)); + memory.WriteUInt64((long)address + 0x78, thread.Context.GetX(15)); + memory.WriteUInt64((long)address + 0x80, thread.Context.GetX(16)); + memory.WriteUInt64((long)address + 0x88, thread.Context.GetX(17)); + memory.WriteUInt64((long)address + 0x90, thread.Context.GetX(18)); + memory.WriteUInt64((long)address + 0x98, thread.Context.GetX(19)); + memory.WriteUInt64((long)address + 0xa0, thread.Context.GetX(20)); + memory.WriteUInt64((long)address + 0xa8, thread.Context.GetX(21)); + memory.WriteUInt64((long)address + 0xb0, thread.Context.GetX(22)); + memory.WriteUInt64((long)address + 0xb8, thread.Context.GetX(23)); + memory.WriteUInt64((long)address + 0xc0, thread.Context.GetX(24)); + memory.WriteUInt64((long)address + 0xc8, thread.Context.GetX(25)); + memory.WriteUInt64((long)address + 0xd0, thread.Context.GetX(26)); + memory.WriteUInt64((long)address + 0xd8, thread.Context.GetX(27)); + memory.WriteUInt64((long)address + 0xe0, thread.Context.GetX(28)); + memory.WriteUInt64((long)address + 0xe8, thread.Context.GetX(29)); + memory.WriteUInt64((long)address + 0xf0, thread.Context.GetX(30)); + memory.WriteUInt64((long)address + 0xf8, thread.Context.GetX(31)); + + memory.WriteInt64((long)address + 0x100, thread.LastPc); + + memory.WriteUInt64((long)address + 0x108, (ulong)GetPsr(thread.Context)); + + memory.WriteVector128((long)address + 0x110, thread.Context.GetV(0)); + memory.WriteVector128((long)address + 0x120, thread.Context.GetV(1)); + memory.WriteVector128((long)address + 0x130, thread.Context.GetV(2)); + memory.WriteVector128((long)address + 0x140, thread.Context.GetV(3)); + memory.WriteVector128((long)address + 0x150, thread.Context.GetV(4)); + memory.WriteVector128((long)address + 0x160, thread.Context.GetV(5)); + memory.WriteVector128((long)address + 0x170, thread.Context.GetV(6)); + memory.WriteVector128((long)address + 0x180, thread.Context.GetV(7)); + memory.WriteVector128((long)address + 0x190, thread.Context.GetV(8)); + memory.WriteVector128((long)address + 0x1a0, thread.Context.GetV(9)); + memory.WriteVector128((long)address + 0x1b0, thread.Context.GetV(10)); + memory.WriteVector128((long)address + 0x1c0, thread.Context.GetV(11)); + memory.WriteVector128((long)address + 0x1d0, thread.Context.GetV(12)); + memory.WriteVector128((long)address + 0x1e0, thread.Context.GetV(13)); + memory.WriteVector128((long)address + 0x1f0, thread.Context.GetV(14)); + memory.WriteVector128((long)address + 0x200, thread.Context.GetV(15)); + memory.WriteVector128((long)address + 0x210, thread.Context.GetV(16)); + memory.WriteVector128((long)address + 0x220, thread.Context.GetV(17)); + memory.WriteVector128((long)address + 0x230, thread.Context.GetV(18)); + memory.WriteVector128((long)address + 0x240, thread.Context.GetV(19)); + memory.WriteVector128((long)address + 0x250, thread.Context.GetV(20)); + memory.WriteVector128((long)address + 0x260, thread.Context.GetV(21)); + memory.WriteVector128((long)address + 0x270, thread.Context.GetV(22)); + memory.WriteVector128((long)address + 0x280, thread.Context.GetV(23)); + memory.WriteVector128((long)address + 0x290, thread.Context.GetV(24)); + memory.WriteVector128((long)address + 0x2a0, thread.Context.GetV(25)); + memory.WriteVector128((long)address + 0x2b0, thread.Context.GetV(26)); + memory.WriteVector128((long)address + 0x2c0, thread.Context.GetV(27)); + memory.WriteVector128((long)address + 0x2d0, thread.Context.GetV(28)); + memory.WriteVector128((long)address + 0x2e0, thread.Context.GetV(29)); + memory.WriteVector128((long)address + 0x2f0, thread.Context.GetV(30)); + memory.WriteVector128((long)address + 0x300, thread.Context.GetV(31)); + + memory.WriteInt32((long)address + 0x310, (int)thread.Context.Fpcr); + memory.WriteInt32((long)address + 0x314, (int)thread.Context.Fpsr); + memory.WriteInt64((long)address + 0x318, thread.Context.Tpidr); + + return KernelResult.Success; + } + + private static int GetPsr(ExecutionContext context) + { + return (context.GetPstateFlag(PState.NFlag) ? (1 << 31) : 0) | + (context.GetPstateFlag(PState.ZFlag) ? (1 << 30) : 0) | + (context.GetPstateFlag(PState.CFlag) ? (1 << 29) : 0) | + (context.GetPstateFlag(PState.VFlag) ? (1 << 28) : 0); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcThreadSync.cs b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcThreadSync.cs new file mode 100644 index 0000000000..5eeecd932b --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcThreadSync.cs @@ -0,0 +1,250 @@ +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.HLE.HOS.Kernel.Threading; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall +{ + partial class SvcHandler + { + public KernelResult WaitSynchronization64([R(1)] ulong handlesPtr, [R(2)] int handlesCount, [R(3)] long timeout, [R(1)] out int handleIndex) + { + return WaitSynchronization(handlesPtr, handlesCount, timeout, out handleIndex); + } + + private KernelResult WaitSynchronization(ulong handlesPtr, int handlesCount, long timeout, out int handleIndex) + { + handleIndex = 0; + + if ((uint)handlesCount > 0x40) + { + return KernelResult.MaximumExceeded; + } + + List syncObjs = new List(); + + for (int index = 0; index < handlesCount; index++) + { + int handle = _process.CpuMemory.ReadInt32((long)handlesPtr + index * 4); + + KSynchronizationObject syncObj = _process.HandleTable.GetObject(handle); + + if (syncObj == null) + { + break; + } + + syncObjs.Add(syncObj); + } + + return _system.Synchronization.WaitFor(syncObjs.ToArray(), timeout, out handleIndex); + } + + public KernelResult CancelSynchronization64([R(0)] int handle) + { + return CancelSynchronization(handle); + } + + private KernelResult CancelSynchronization(int handle) + { + KThread thread = _process.HandleTable.GetKThread(handle); + + if (thread == null) + { + return KernelResult.InvalidHandle; + } + + thread.CancelSynchronization(); + + return KernelResult.Success; + } + + public KernelResult ArbitrateLock64([R(0)] int ownerHandle, [R(1)] ulong mutexAddress, [R(2)] int requesterHandle) + { + return ArbitrateLock(ownerHandle, mutexAddress, requesterHandle); + } + + private KernelResult ArbitrateLock(int ownerHandle, ulong mutexAddress, int requesterHandle) + { + if (IsPointingInsideKernel(mutexAddress)) + { + return KernelResult.InvalidMemState; + } + + if (IsAddressNotWordAligned(mutexAddress)) + { + return KernelResult.InvalidAddress; + } + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + return currentProcess.AddressArbiter.ArbitrateLock(ownerHandle, mutexAddress, requesterHandle); + } + + public KernelResult ArbitrateUnlock64([R(0)] ulong mutexAddress) + { + return ArbitrateUnlock(mutexAddress); + } + + private KernelResult ArbitrateUnlock(ulong mutexAddress) + { + if (IsPointingInsideKernel(mutexAddress)) + { + return KernelResult.InvalidMemState; + } + + if (IsAddressNotWordAligned(mutexAddress)) + { + return KernelResult.InvalidAddress; + } + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + return currentProcess.AddressArbiter.ArbitrateUnlock(mutexAddress); + } + + public KernelResult WaitProcessWideKeyAtomic64( + [R(0)] ulong mutexAddress, + [R(1)] ulong condVarAddress, + [R(2)] int handle, + [R(3)] long timeout) + { + return WaitProcessWideKeyAtomic(mutexAddress, condVarAddress, handle, timeout); + } + + private KernelResult WaitProcessWideKeyAtomic( + ulong mutexAddress, + ulong condVarAddress, + int handle, + long timeout) + { + if (IsPointingInsideKernel(mutexAddress)) + { + return KernelResult.InvalidMemState; + } + + if (IsAddressNotWordAligned(mutexAddress)) + { + return KernelResult.InvalidAddress; + } + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + return currentProcess.AddressArbiter.WaitProcessWideKeyAtomic( + mutexAddress, + condVarAddress, + handle, + timeout); + } + + public KernelResult SignalProcessWideKey64([R(0)] ulong address, [R(1)] int count) + { + return SignalProcessWideKey(address, count); + } + + private KernelResult SignalProcessWideKey(ulong address, int count) + { + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + currentProcess.AddressArbiter.SignalProcessWideKey(address, count); + + return KernelResult.Success; + } + + public KernelResult WaitForAddress64([R(0)] ulong address, [R(1)] ArbitrationType type, [R(2)] int value, [R(3)] long timeout) + { + return WaitForAddress(address, type, value, timeout); + } + + private KernelResult WaitForAddress(ulong address, ArbitrationType type, int value, long timeout) + { + if (IsPointingInsideKernel(address)) + { + return KernelResult.InvalidMemState; + } + + if (IsAddressNotWordAligned(address)) + { + return KernelResult.InvalidAddress; + } + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + KernelResult result; + + switch (type) + { + case ArbitrationType.WaitIfLessThan: + result = currentProcess.AddressArbiter.WaitForAddressIfLessThan(address, value, false, timeout); + break; + + case ArbitrationType.DecrementAndWaitIfLessThan: + result = currentProcess.AddressArbiter.WaitForAddressIfLessThan(address, value, true, timeout); + break; + + case ArbitrationType.WaitIfEqual: + result = currentProcess.AddressArbiter.WaitForAddressIfEqual(address, value, timeout); + break; + + default: + result = KernelResult.InvalidEnumValue; + break; + } + + return result; + } + + public KernelResult SignalToAddress64([R(0)] ulong address, [R(1)] SignalType type, [R(2)] int value, [R(3)] int count) + { + return SignalToAddress(address, type, value, count); + } + + private KernelResult SignalToAddress(ulong address, SignalType type, int value, int count) + { + if (IsPointingInsideKernel(address)) + { + return KernelResult.InvalidMemState; + } + + if (IsAddressNotWordAligned(address)) + { + return KernelResult.InvalidAddress; + } + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + KernelResult result; + + switch (type) + { + case SignalType.Signal: + result = currentProcess.AddressArbiter.Signal(address, count); + break; + + case SignalType.SignalAndIncrementIfEqual: + result = currentProcess.AddressArbiter.SignalAndIncrementIfEqual(address, value, count); + break; + + case SignalType.SignalAndModifyIfEqual: + result = currentProcess.AddressArbiter.SignalAndModifyIfEqual(address, value, count); + break; + + default: + result = KernelResult.InvalidEnumValue; + break; + } + + return result; + } + + private bool IsPointingInsideKernel(ulong address) + { + return (address + 0x1000000000) < 0xffffff000; + } + + private bool IsAddressNotWordAligned(ulong address) + { + return (address & 3) != 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/ArbitrationType.cs b/Ryujinx.HLE/HOS/Kernel/Threading/ArbitrationType.cs new file mode 100644 index 0000000000..89c1bf1fa0 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Threading/ArbitrationType.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Kernel.Threading +{ + enum ArbitrationType + { + WaitIfLessThan = 0, + DecrementAndWaitIfLessThan = 1, + WaitIfEqual = 2 + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/HleCoreManager.cs b/Ryujinx.HLE/HOS/Kernel/Threading/HleCoreManager.cs new file mode 100644 index 0000000000..c25979904d --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Threading/HleCoreManager.cs @@ -0,0 +1,66 @@ +using System.Collections.Concurrent; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Kernel.Threading +{ + class HleCoreManager + { + private class PausableThread + { + public ManualResetEvent Event { get; private set; } + + public bool IsExiting { get; set; } + + public PausableThread() + { + Event = new ManualResetEvent(false); + } + } + + private ConcurrentDictionary _threads; + + public HleCoreManager() + { + _threads = new ConcurrentDictionary(); + } + + public void Set(Thread thread) + { + GetThread(thread).Event.Set(); + } + + public void Reset(Thread thread) + { + GetThread(thread).Event.Reset(); + } + + public void Wait(Thread thread) + { + PausableThread pausableThread = GetThread(thread); + + if (!pausableThread.IsExiting) + { + pausableThread.Event.WaitOne(); + } + } + + public void Exit(Thread thread) + { + GetThread(thread).IsExiting = true; + } + + private PausableThread GetThread(Thread thread) + { + return _threads.GetOrAdd(thread, (key) => new PausableThread()); + } + + public void RemoveThread(Thread thread) + { + if (_threads.TryRemove(thread, out PausableThread pausableThread)) + { + pausableThread.Event.Set(); + pausableThread.Event.Dispose(); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/HleScheduler.cs b/Ryujinx.HLE/HOS/Kernel/Threading/HleScheduler.cs new file mode 100644 index 0000000000..1a213b924f --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Threading/HleScheduler.cs @@ -0,0 +1,150 @@ +using System; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Kernel.Threading +{ + partial class KScheduler + { + private const int RoundRobinTimeQuantumMs = 10; + + private int _currentCore; + + public bool MultiCoreScheduling { get; set; } + + public HleCoreManager CoreManager { get; private set; } + + private bool _keepPreempting; + + public void StartAutoPreemptionThread() + { + Thread preemptionThread = new Thread(PreemptCurrentThread) + { + Name = "HLE.PreemptionThread" + }; + + _keepPreempting = true; + + preemptionThread.Start(); + } + + public void ContextSwitch() + { + lock (CoreContexts) + { + if (MultiCoreScheduling) + { + int selectedCount = 0; + + for (int core = 0; core < CpuCoresCount; core++) + { + KCoreContext coreContext = CoreContexts[core]; + + if (coreContext.ContextSwitchNeeded && (coreContext.CurrentThread?.IsCurrentHostThread() ?? false)) + { + coreContext.ContextSwitch(); + } + + if (coreContext.CurrentThread?.IsCurrentHostThread() ?? false) + { + selectedCount++; + } + } + + if (selectedCount == 0) + { + CoreManager.Reset(Thread.CurrentThread); + } + else if (selectedCount == 1) + { + CoreManager.Set(Thread.CurrentThread); + } + else + { + throw new InvalidOperationException("Thread scheduled in more than one core!"); + } + } + else + { + KThread currentThread = CoreContexts[_currentCore].CurrentThread; + + bool hasThreadExecuting = currentThread != null; + + if (hasThreadExecuting) + { + // If this is not the thread that is currently executing, we need + // to request an interrupt to allow safely starting another thread. + if (!currentThread.IsCurrentHostThread()) + { + currentThread.Context.RequestInterrupt(); + + return; + } + + CoreManager.Reset(currentThread.HostThread); + } + + // Advance current core and try picking a thread, + // keep advancing if it is null. + for (int core = 0; core < 4; core++) + { + _currentCore = (_currentCore + 1) % CpuCoresCount; + + KCoreContext coreContext = CoreContexts[_currentCore]; + + coreContext.UpdateCurrentThread(); + + if (coreContext.CurrentThread != null) + { + CoreManager.Set(coreContext.CurrentThread.HostThread); + + coreContext.CurrentThread.Execute(); + + break; + } + } + + // If nothing was running before, then we are on a "external" + // HLE thread, we don't need to wait. + if (!hasThreadExecuting) + { + return; + } + } + } + + CoreManager.Wait(Thread.CurrentThread); + } + + private void PreemptCurrentThread() + { + // Preempts current thread every 10 milliseconds on a round-robin fashion, + // when multi core scheduling is disabled, to try ensuring that all threads + // gets a chance to run. + while (_keepPreempting) + { + lock (CoreContexts) + { + KThread currentThread = CoreContexts[_currentCore].CurrentThread; + + currentThread?.Context.RequestInterrupt(); + } + + PreemptThreads(); + + Thread.Sleep(RoundRobinTimeQuantumMs); + } + } + + public void ExitThread(KThread thread) + { + thread.Context.Running = false; + + CoreManager.Exit(thread.HostThread); + } + + public void RemoveThread(KThread thread) + { + CoreManager.RemoveThread(thread.HostThread); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/KAddressArbiter.cs b/Ryujinx.HLE/HOS/Kernel/Threading/KAddressArbiter.cs new file mode 100644 index 0000000000..166cf064c1 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Threading/KAddressArbiter.cs @@ -0,0 +1,595 @@ +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Process; +using System.Collections.Generic; +using System.Linq; + +namespace Ryujinx.HLE.HOS.Kernel.Threading +{ + class KAddressArbiter + { + private const int HasListenersMask = 0x40000000; + + private Horizon _system; + + public List CondVarThreads; + public List ArbiterThreads; + + public KAddressArbiter(Horizon system) + { + _system = system; + + CondVarThreads = new List(); + ArbiterThreads = new List(); + } + + public KernelResult ArbitrateLock(int ownerHandle, ulong mutexAddress, int requesterHandle) + { + KThread currentThread = _system.Scheduler.GetCurrentThread(); + + _system.CriticalSection.Enter(); + + currentThread.SignaledObj = null; + currentThread.ObjSyncResult = KernelResult.Success; + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + if (!KernelTransfer.UserToKernelInt32(_system, mutexAddress, out int mutexValue)) + { + _system.CriticalSection.Leave(); + + return KernelResult.InvalidMemState; + } + + if (mutexValue != (ownerHandle | HasListenersMask)) + { + _system.CriticalSection.Leave(); + + return 0; + } + + KThread mutexOwner = currentProcess.HandleTable.GetObject(ownerHandle); + + if (mutexOwner == null) + { + _system.CriticalSection.Leave(); + + return KernelResult.InvalidHandle; + } + + currentThread.MutexAddress = mutexAddress; + currentThread.ThreadHandleForUserMutex = requesterHandle; + + mutexOwner.AddMutexWaiter(currentThread); + + currentThread.Reschedule(ThreadSchedState.Paused); + + _system.CriticalSection.Leave(); + _system.CriticalSection.Enter(); + + if (currentThread.MutexOwner != null) + { + currentThread.MutexOwner.RemoveMutexWaiter(currentThread); + } + + _system.CriticalSection.Leave(); + + return (KernelResult)currentThread.ObjSyncResult; + } + + public KernelResult ArbitrateUnlock(ulong mutexAddress) + { + _system.CriticalSection.Enter(); + + KThread currentThread = _system.Scheduler.GetCurrentThread(); + + (KernelResult result, KThread newOwnerThread) = MutexUnlock(currentThread, mutexAddress); + + if (result != KernelResult.Success && newOwnerThread != null) + { + newOwnerThread.SignaledObj = null; + newOwnerThread.ObjSyncResult = result; + } + + _system.CriticalSection.Leave(); + + return result; + } + + public KernelResult WaitProcessWideKeyAtomic( + ulong mutexAddress, + ulong condVarAddress, + int threadHandle, + long timeout) + { + _system.CriticalSection.Enter(); + + KThread currentThread = _system.Scheduler.GetCurrentThread(); + + currentThread.SignaledObj = null; + currentThread.ObjSyncResult = KernelResult.TimedOut; + + if (currentThread.ShallBeTerminated || + currentThread.SchedFlags == ThreadSchedState.TerminationPending) + { + _system.CriticalSection.Leave(); + + return KernelResult.ThreadTerminating; + } + + (KernelResult result, _) = MutexUnlock(currentThread, mutexAddress); + + if (result != KernelResult.Success) + { + _system.CriticalSection.Leave(); + + return result; + } + + currentThread.MutexAddress = mutexAddress; + currentThread.ThreadHandleForUserMutex = threadHandle; + currentThread.CondVarAddress = condVarAddress; + + CondVarThreads.Add(currentThread); + + if (timeout != 0) + { + currentThread.Reschedule(ThreadSchedState.Paused); + + if (timeout > 0) + { + _system.TimeManager.ScheduleFutureInvocation(currentThread, timeout); + } + } + + _system.CriticalSection.Leave(); + + if (timeout > 0) + { + _system.TimeManager.UnscheduleFutureInvocation(currentThread); + } + + _system.CriticalSection.Enter(); + + if (currentThread.MutexOwner != null) + { + currentThread.MutexOwner.RemoveMutexWaiter(currentThread); + } + + CondVarThreads.Remove(currentThread); + + _system.CriticalSection.Leave(); + + return (KernelResult)currentThread.ObjSyncResult; + } + + private (KernelResult, KThread) MutexUnlock(KThread currentThread, ulong mutexAddress) + { + KThread newOwnerThread = currentThread.RelinquishMutex(mutexAddress, out int count); + + int mutexValue = 0; + + if (newOwnerThread != null) + { + mutexValue = newOwnerThread.ThreadHandleForUserMutex; + + if (count >= 2) + { + mutexValue |= HasListenersMask; + } + + newOwnerThread.SignaledObj = null; + newOwnerThread.ObjSyncResult = KernelResult.Success; + + newOwnerThread.ReleaseAndResume(); + } + + KernelResult result = KernelResult.Success; + + if (!KernelTransfer.KernelToUserInt32(_system, mutexAddress, mutexValue)) + { + result = KernelResult.InvalidMemState; + } + + return (result, newOwnerThread); + } + + public void SignalProcessWideKey(ulong address, int count) + { + Queue signaledThreads = new Queue(); + + _system.CriticalSection.Enter(); + + IOrderedEnumerable sortedThreads = CondVarThreads.OrderBy(x => x.DynamicPriority); + + foreach (KThread thread in sortedThreads.Where(x => x.CondVarAddress == address)) + { + TryAcquireMutex(thread); + + signaledThreads.Enqueue(thread); + + // If the count is <= 0, we should signal all threads waiting. + if (count >= 1 && --count == 0) + { + break; + } + } + + while (signaledThreads.TryDequeue(out KThread thread)) + { + CondVarThreads.Remove(thread); + } + + _system.CriticalSection.Leave(); + } + + private KThread TryAcquireMutex(KThread requester) + { + ulong address = requester.MutexAddress; + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + int mutexValue, newMutexValue; + + do + { + if (!KernelTransfer.UserToKernelInt32(_system, address, out mutexValue)) + { + // Invalid address. + requester.SignaledObj = null; + requester.ObjSyncResult = KernelResult.InvalidMemState; + + return null; + } + + if (mutexValue != 0) + { + // Update value to indicate there is a mutex waiter now. + newMutexValue = mutexValue | HasListenersMask; + } + else + { + // No thread owning the mutex, assign to requesting thread. + newMutexValue = requester.ThreadHandleForUserMutex; + } + } + while (!currentProcess.CpuMemory.AtomicCompareExchangeInt32((long)address, mutexValue, newMutexValue)); + + if (mutexValue == 0) + { + // We now own the mutex. + requester.SignaledObj = null; + requester.ObjSyncResult = KernelResult.Success; + + requester.ReleaseAndResume(); + + return null; + } + + mutexValue &= ~HasListenersMask; + + KThread mutexOwner = currentProcess.HandleTable.GetObject(mutexValue); + + if (mutexOwner != null) + { + // Mutex already belongs to another thread, wait for it. + mutexOwner.AddMutexWaiter(requester); + } + else + { + // Invalid mutex owner. + requester.SignaledObj = null; + requester.ObjSyncResult = KernelResult.InvalidHandle; + + requester.ReleaseAndResume(); + } + + return mutexOwner; + } + + public KernelResult WaitForAddressIfEqual(ulong address, int value, long timeout) + { + KThread currentThread = _system.Scheduler.GetCurrentThread(); + + _system.CriticalSection.Enter(); + + if (currentThread.ShallBeTerminated || + currentThread.SchedFlags == ThreadSchedState.TerminationPending) + { + _system.CriticalSection.Leave(); + + return KernelResult.ThreadTerminating; + } + + currentThread.SignaledObj = null; + currentThread.ObjSyncResult = KernelResult.TimedOut; + + if (!KernelTransfer.UserToKernelInt32(_system, address, out int currentValue)) + { + _system.CriticalSection.Leave(); + + return KernelResult.InvalidMemState; + } + + if (currentValue == value) + { + if (timeout == 0) + { + _system.CriticalSection.Leave(); + + return KernelResult.TimedOut; + } + + currentThread.MutexAddress = address; + currentThread.WaitingInArbitration = true; + + InsertSortedByPriority(ArbiterThreads, currentThread); + + currentThread.Reschedule(ThreadSchedState.Paused); + + if (timeout > 0) + { + _system.TimeManager.ScheduleFutureInvocation(currentThread, timeout); + } + + _system.CriticalSection.Leave(); + + if (timeout > 0) + { + _system.TimeManager.UnscheduleFutureInvocation(currentThread); + } + + _system.CriticalSection.Enter(); + + if (currentThread.WaitingInArbitration) + { + ArbiterThreads.Remove(currentThread); + + currentThread.WaitingInArbitration = false; + } + + _system.CriticalSection.Leave(); + + return (KernelResult)currentThread.ObjSyncResult; + } + + _system.CriticalSection.Leave(); + + return KernelResult.InvalidState; + } + + public KernelResult WaitForAddressIfLessThan( + ulong address, + int value, + bool shouldDecrement, + long timeout) + { + KThread currentThread = _system.Scheduler.GetCurrentThread(); + + _system.CriticalSection.Enter(); + + if (currentThread.ShallBeTerminated || + currentThread.SchedFlags == ThreadSchedState.TerminationPending) + { + _system.CriticalSection.Leave(); + + return KernelResult.ThreadTerminating; + } + + currentThread.SignaledObj = null; + currentThread.ObjSyncResult = KernelResult.TimedOut; + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + if (!KernelTransfer.UserToKernelInt32(_system, address, out int currentValue)) + { + _system.CriticalSection.Leave(); + + return KernelResult.InvalidMemState; + } + + if (shouldDecrement) + { + currentValue = currentProcess.CpuMemory.AtomicDecrementInt32((long)address) + 1; + } + + if (currentValue < value) + { + if (timeout == 0) + { + _system.CriticalSection.Leave(); + + return KernelResult.TimedOut; + } + + currentThread.MutexAddress = address; + currentThread.WaitingInArbitration = true; + + InsertSortedByPriority(ArbiterThreads, currentThread); + + currentThread.Reschedule(ThreadSchedState.Paused); + + if (timeout > 0) + { + _system.TimeManager.ScheduleFutureInvocation(currentThread, timeout); + } + + _system.CriticalSection.Leave(); + + if (timeout > 0) + { + _system.TimeManager.UnscheduleFutureInvocation(currentThread); + } + + _system.CriticalSection.Enter(); + + if (currentThread.WaitingInArbitration) + { + ArbiterThreads.Remove(currentThread); + + currentThread.WaitingInArbitration = false; + } + + _system.CriticalSection.Leave(); + + return (KernelResult)currentThread.ObjSyncResult; + } + + _system.CriticalSection.Leave(); + + return KernelResult.InvalidState; + } + + private void InsertSortedByPriority(List threads, KThread thread) + { + int nextIndex = -1; + + for (int index = 0; index < threads.Count; index++) + { + if (threads[index].DynamicPriority > thread.DynamicPriority) + { + nextIndex = index; + + break; + } + } + + if (nextIndex != -1) + { + threads.Insert(nextIndex, thread); + } + else + { + threads.Add(thread); + } + } + + public KernelResult Signal(ulong address, int count) + { + _system.CriticalSection.Enter(); + + WakeArbiterThreads(address, count); + + _system.CriticalSection.Leave(); + + return KernelResult.Success; + } + + public KernelResult SignalAndIncrementIfEqual(ulong address, int value, int count) + { + _system.CriticalSection.Enter(); + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + int currentValue; + + do + { + if (!KernelTransfer.UserToKernelInt32(_system, address, out currentValue)) + { + _system.CriticalSection.Leave(); + + return KernelResult.InvalidMemState; + } + + if (currentValue != value) + { + _system.CriticalSection.Leave(); + + return KernelResult.InvalidState; + } + } + while (!currentProcess.CpuMemory.AtomicCompareExchangeInt32((long)address, currentValue, currentValue + 1)); + + WakeArbiterThreads(address, count); + + _system.CriticalSection.Leave(); + + return KernelResult.Success; + } + + public KernelResult SignalAndModifyIfEqual(ulong address, int value, int count) + { + _system.CriticalSection.Enter(); + + int offset; + + // The value is decremented if the number of threads waiting is less + // or equal to the Count of threads to be signaled, or Count is zero + // or negative. It is incremented if there are no threads waiting. + int waitingCount = 0; + + foreach (KThread thread in ArbiterThreads.Where(x => x.MutexAddress == address)) + { + if (++waitingCount > count) + { + break; + } + } + + if (waitingCount > 0) + { + offset = waitingCount <= count || count <= 0 ? -1 : 0; + } + else + { + offset = 1; + } + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + int currentValue; + + do + { + if (!KernelTransfer.UserToKernelInt32(_system, address, out currentValue)) + { + _system.CriticalSection.Leave(); + + return KernelResult.InvalidMemState; + } + + if (currentValue != value) + { + _system.CriticalSection.Leave(); + + return KernelResult.InvalidState; + } + } + while (!currentProcess.CpuMemory.AtomicCompareExchangeInt32((long)address, currentValue, currentValue + offset)); + + WakeArbiterThreads(address, count); + + _system.CriticalSection.Leave(); + + return KernelResult.Success; + } + + private void WakeArbiterThreads(ulong address, int count) + { + Queue signaledThreads = new Queue(); + + foreach (KThread thread in ArbiterThreads.Where(x => x.MutexAddress == address)) + { + signaledThreads.Enqueue(thread); + + // If the count is <= 0, we should signal all threads waiting. + if (count >= 1 && --count == 0) + { + break; + } + } + + while (signaledThreads.TryDequeue(out KThread thread)) + { + thread.SignaledObj = null; + thread.ObjSyncResult = KernelResult.Success; + + thread.ReleaseAndResume(); + + thread.WaitingInArbitration = false; + + ArbiterThreads.Remove(thread); + } + } + } +} diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/KConditionVariable.cs b/Ryujinx.HLE/HOS/Kernel/Threading/KConditionVariable.cs new file mode 100644 index 0000000000..414736438a --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Threading/KConditionVariable.cs @@ -0,0 +1,71 @@ +using System.Collections.Generic; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Kernel.Threading +{ + static class KConditionVariable + { + public static void Wait(Horizon system, LinkedList threadList, object mutex, long timeout) + { + KThread currentThread = system.Scheduler.GetCurrentThread(); + + system.CriticalSection.Enter(); + + Monitor.Exit(mutex); + + currentThread.Withholder = threadList; + + currentThread.Reschedule(ThreadSchedState.Paused); + + currentThread.WithholderNode = threadList.AddLast(currentThread); + + if (currentThread.ShallBeTerminated || + currentThread.SchedFlags == ThreadSchedState.TerminationPending) + { + threadList.Remove(currentThread.WithholderNode); + + currentThread.Reschedule(ThreadSchedState.Running); + + currentThread.Withholder = null; + + system.CriticalSection.Leave(); + } + else + { + if (timeout > 0) + { + system.TimeManager.ScheduleFutureInvocation(currentThread, timeout); + } + + system.CriticalSection.Leave(); + + if (timeout > 0) + { + system.TimeManager.UnscheduleFutureInvocation(currentThread); + } + } + + Monitor.Enter(mutex); + } + + public static void NotifyAll(Horizon system, LinkedList threadList) + { + system.CriticalSection.Enter(); + + LinkedListNode node = threadList.First; + + for (; node != null; node = threadList.First) + { + KThread thread = node.Value; + + threadList.Remove(thread.WithholderNode); + + thread.Withholder = null; + + thread.Reschedule(ThreadSchedState.Running); + } + + system.CriticalSection.Leave(); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/KCoreContext.cs b/Ryujinx.HLE/HOS/Kernel/Threading/KCoreContext.cs new file mode 100644 index 0000000000..0aa12b0ddc --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Threading/KCoreContext.cs @@ -0,0 +1,79 @@ +using Ryujinx.Common; + +namespace Ryujinx.HLE.HOS.Kernel.Threading +{ + class KCoreContext + { + private KScheduler _scheduler; + + private HleCoreManager _coreManager; + + public bool ContextSwitchNeeded { get; private set; } + + public long LastContextSwitchTime { get; private set; } + + public long TotalIdleTimeTicks { get; private set; } //TODO + + public KThread CurrentThread { get; private set; } + public KThread SelectedThread { get; private set; } + + public KCoreContext(KScheduler scheduler, HleCoreManager coreManager) + { + _scheduler = scheduler; + _coreManager = coreManager; + } + + public void SelectThread(KThread thread) + { + SelectedThread = thread; + + if (SelectedThread != CurrentThread) + { + ContextSwitchNeeded = true; + } + } + + public void UpdateCurrentThread() + { + ContextSwitchNeeded = false; + + LastContextSwitchTime = PerformanceCounter.ElapsedMilliseconds; + + CurrentThread = SelectedThread; + + if (CurrentThread != null) + { + long currentTime = PerformanceCounter.ElapsedMilliseconds; + + CurrentThread.TotalTimeRunning += currentTime - CurrentThread.LastScheduledTime; + CurrentThread.LastScheduledTime = currentTime; + } + } + + public void ContextSwitch() + { + ContextSwitchNeeded = false; + + LastContextSwitchTime = PerformanceCounter.ElapsedMilliseconds; + + if (CurrentThread != null) + { + _coreManager.Reset(CurrentThread.HostThread); + } + + CurrentThread = SelectedThread; + + if (CurrentThread != null) + { + long currentTime = PerformanceCounter.ElapsedMilliseconds; + + CurrentThread.TotalTimeRunning += currentTime - CurrentThread.LastScheduledTime; + CurrentThread.LastScheduledTime = currentTime; + + _coreManager.Set(CurrentThread.HostThread); + + CurrentThread.Execute(); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/KCriticalSection.cs b/Ryujinx.HLE/HOS/Kernel/Threading/KCriticalSection.cs new file mode 100644 index 0000000000..b7013bb7b1 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Threading/KCriticalSection.cs @@ -0,0 +1,93 @@ +using ARMeilleure; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Kernel.Threading +{ + class KCriticalSection + { + private Horizon _system; + + public object LockObj { get; private set; } + + private int _recursionCount; + + public KCriticalSection(Horizon system) + { + _system = system; + + LockObj = new object(); + } + + public void Enter() + { + Monitor.Enter(LockObj); + + _recursionCount++; + } + + public void Leave() + { + if (_recursionCount == 0) + { + return; + } + + bool doContextSwitch = false; + + if (--_recursionCount == 0) + { + if (_system.Scheduler.ThreadReselectionRequested) + { + _system.Scheduler.SelectThreads(); + } + + Monitor.Exit(LockObj); + + if (_system.Scheduler.MultiCoreScheduling) + { + lock (_system.Scheduler.CoreContexts) + { + for (int core = 0; core < KScheduler.CpuCoresCount; core++) + { + KCoreContext coreContext = _system.Scheduler.CoreContexts[core]; + + if (coreContext.ContextSwitchNeeded) + { + KThread currentThread = coreContext.CurrentThread; + + if (currentThread == null) + { + // Nothing is running, we can perform the context switch immediately. + coreContext.ContextSwitch(); + } + else if (currentThread.IsCurrentHostThread()) + { + // Thread running on the current core, context switch will block. + doContextSwitch = true; + } + else + { + // Thread running on another core, request a interrupt. + currentThread.Context.RequestInterrupt(); + } + } + } + } + } + else + { + doContextSwitch = true; + } + } + else + { + Monitor.Exit(LockObj); + } + + if (doContextSwitch) + { + _system.Scheduler.ContextSwitch(); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/KEvent.cs b/Ryujinx.HLE/HOS/Kernel/Threading/KEvent.cs new file mode 100644 index 0000000000..dd9822918c --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Threading/KEvent.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.HLE.HOS.Kernel.Threading +{ + class KEvent + { + public KReadableEvent ReadableEvent { get; private set; } + public KWritableEvent WritableEvent { get; private set; } + + public KEvent(Horizon system) + { + ReadableEvent = new KReadableEvent(system, this); + WritableEvent = new KWritableEvent(system, this); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/KReadableEvent.cs b/Ryujinx.HLE/HOS/Kernel/Threading/KReadableEvent.cs new file mode 100644 index 0000000000..9821de35b1 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Threading/KReadableEvent.cs @@ -0,0 +1,64 @@ +using Ryujinx.HLE.HOS.Kernel.Common; + +namespace Ryujinx.HLE.HOS.Kernel.Threading +{ + class KReadableEvent : KSynchronizationObject + { + private KEvent _parent; + + private bool _signaled; + + public KReadableEvent(Horizon system, KEvent parent) : base(system) + { + _parent = parent; + } + + public override void Signal() + { + System.CriticalSection.Enter(); + + if (!_signaled) + { + _signaled = true; + + base.Signal(); + } + + System.CriticalSection.Leave(); + } + + public KernelResult Clear() + { + _signaled = false; + + return KernelResult.Success; + } + + public KernelResult ClearIfSignaled() + { + KernelResult result; + + System.CriticalSection.Enter(); + + if (_signaled) + { + _signaled = false; + + result = KernelResult.Success; + } + else + { + result = KernelResult.InvalidState; + } + + System.CriticalSection.Leave(); + + return result; + } + + public override bool IsSignaled() + { + return _signaled; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs b/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs new file mode 100644 index 0000000000..dd5422b8e7 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs @@ -0,0 +1,254 @@ +using Ryujinx.HLE.HOS.Kernel.Process; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Ryujinx.HLE.HOS.Kernel.Threading +{ + partial class KScheduler : IDisposable + { + public const int PrioritiesCount = 64; + public const int CpuCoresCount = 4; + + private const int PreemptionPriorityCores012 = 59; + private const int PreemptionPriorityCore3 = 63; + + private Horizon _system; + + public KSchedulingData SchedulingData { get; private set; } + + public KCoreContext[] CoreContexts { get; private set; } + + public bool ThreadReselectionRequested { get; set; } + + public KScheduler(Horizon system) + { + _system = system; + + SchedulingData = new KSchedulingData(); + + CoreManager = new HleCoreManager(); + + CoreContexts = new KCoreContext[CpuCoresCount]; + + for (int core = 0; core < CpuCoresCount; core++) + { + CoreContexts[core] = new KCoreContext(this, CoreManager); + } + } + + private void PreemptThreads() + { + _system.CriticalSection.Enter(); + + PreemptThread(PreemptionPriorityCores012, 0); + PreemptThread(PreemptionPriorityCores012, 1); + PreemptThread(PreemptionPriorityCores012, 2); + PreemptThread(PreemptionPriorityCore3, 3); + + _system.CriticalSection.Leave(); + } + + private void PreemptThread(int prio, int core) + { + IEnumerable scheduledThreads = SchedulingData.ScheduledThreads(core); + + KThread selectedThread = scheduledThreads.FirstOrDefault(x => x.DynamicPriority == prio); + + // Yield priority queue. + if (selectedThread != null) + { + SchedulingData.Reschedule(prio, core, selectedThread); + } + + IEnumerable SuitableCandidates() + { + foreach (KThread thread in SchedulingData.SuggestedThreads(core)) + { + int srcCore = thread.CurrentCore; + + if (srcCore >= 0) + { + KThread highestPrioSrcCore = SchedulingData.ScheduledThreads(srcCore).FirstOrDefault(); + + if (highestPrioSrcCore != null && highestPrioSrcCore.DynamicPriority < 2) + { + break; + } + + if (highestPrioSrcCore == thread) + { + continue; + } + } + + // If the candidate was scheduled after the current thread, then it's not worth it. + if (selectedThread == null || selectedThread.LastScheduledTime >= thread.LastScheduledTime) + { + yield return thread; + } + } + } + + // Select candidate threads that could run on this core. + // Only take into account threads that are not yet selected. + KThread dst = SuitableCandidates().FirstOrDefault(x => x.DynamicPriority == prio); + + if (dst != null) + { + SchedulingData.TransferToCore(prio, core, dst); + + selectedThread = dst; + } + + // If the priority of the currently selected thread is lower than preemption priority, + // then allow threads with lower priorities to be selected aswell. + if (selectedThread != null && selectedThread.DynamicPriority > prio) + { + Func predicate = x => x.DynamicPriority >= selectedThread.DynamicPriority; + + dst = SuitableCandidates().FirstOrDefault(predicate); + + if (dst != null) + { + SchedulingData.TransferToCore(dst.DynamicPriority, core, dst); + } + } + + ThreadReselectionRequested = true; + } + + public void SelectThreads() + { + ThreadReselectionRequested = false; + + for (int core = 0; core < CpuCoresCount; core++) + { + KThread thread = SchedulingData.ScheduledThreads(core).FirstOrDefault(); + + CoreContexts[core].SelectThread(thread); + } + + for (int core = 0; core < CpuCoresCount; core++) + { + // If the core is not idle (there's already a thread running on it), + // then we don't need to attempt load balancing. + if (SchedulingData.ScheduledThreads(core).Any()) + { + continue; + } + + int[] srcCoresHighestPrioThreads = new int[CpuCoresCount]; + + int srcCoresHighestPrioThreadsCount = 0; + + KThread dst = null; + + // Select candidate threads that could run on this core. + // Give preference to threads that are not yet selected. + foreach (KThread thread in SchedulingData.SuggestedThreads(core)) + { + if (thread.CurrentCore < 0 || thread != CoreContexts[thread.CurrentCore].SelectedThread) + { + dst = thread; + + break; + } + + srcCoresHighestPrioThreads[srcCoresHighestPrioThreadsCount++] = thread.CurrentCore; + } + + // Not yet selected candidate found. + if (dst != null) + { + // Priorities < 2 are used for the kernel message dispatching + // threads, we should skip load balancing entirely. + if (dst.DynamicPriority >= 2) + { + SchedulingData.TransferToCore(dst.DynamicPriority, core, dst); + + CoreContexts[core].SelectThread(dst); + } + + continue; + } + + // All candidates are already selected, choose the best one + // (the first one that doesn't make the source core idle if moved). + for (int index = 0; index < srcCoresHighestPrioThreadsCount; index++) + { + int srcCore = srcCoresHighestPrioThreads[index]; + + KThread src = SchedulingData.ScheduledThreads(srcCore).ElementAtOrDefault(1); + + if (src != null) + { + // Run the second thread on the queue on the source core, + // move the first one to the current core. + KThread origSelectedCoreSrc = CoreContexts[srcCore].SelectedThread; + + CoreContexts[srcCore].SelectThread(src); + + SchedulingData.TransferToCore(origSelectedCoreSrc.DynamicPriority, core, origSelectedCoreSrc); + + CoreContexts[core].SelectThread(origSelectedCoreSrc); + } + } + } + } + + public KThread GetCurrentThread() + { + lock (CoreContexts) + { + for (int core = 0; core < CpuCoresCount; core++) + { + if (CoreContexts[core].CurrentThread?.IsCurrentHostThread() ?? false) + { + return CoreContexts[core].CurrentThread; + } + } + } + + return GetDummyThread(); + + throw new InvalidOperationException("Current thread is not scheduled!"); + } + + private KThread _dummyThread; + + private KThread GetDummyThread() + { + if (_dummyThread != null) + { + return _dummyThread; + } + + KProcess dummyProcess = new KProcess(_system); + + KThread dummyThread = new KThread(_system); + + dummyThread.Initialize(0, 0, 0, 44, 0, dummyProcess, ThreadType.Dummy); + + return _dummyThread = dummyThread; + } + + public KProcess GetCurrentProcess() + { + return GetCurrentThread().Owner; + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _keepPreempting = false; + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/KSchedulingData.cs b/Ryujinx.HLE/HOS/Kernel/Threading/KSchedulingData.cs new file mode 100644 index 0000000000..83c4a0791f --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Threading/KSchedulingData.cs @@ -0,0 +1,207 @@ +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Kernel.Threading +{ + class KSchedulingData + { + private LinkedList[][] _scheduledThreadsPerPrioPerCore; + private LinkedList[][] _suggestedThreadsPerPrioPerCore; + + private long[] _scheduledPrioritiesPerCore; + private long[] _suggestedPrioritiesPerCore; + + public KSchedulingData() + { + _suggestedThreadsPerPrioPerCore = new LinkedList[KScheduler.PrioritiesCount][]; + _scheduledThreadsPerPrioPerCore = new LinkedList[KScheduler.PrioritiesCount][]; + + for (int prio = 0; prio < KScheduler.PrioritiesCount; prio++) + { + _suggestedThreadsPerPrioPerCore[prio] = new LinkedList[KScheduler.CpuCoresCount]; + _scheduledThreadsPerPrioPerCore[prio] = new LinkedList[KScheduler.CpuCoresCount]; + + for (int core = 0; core < KScheduler.CpuCoresCount; core++) + { + _suggestedThreadsPerPrioPerCore[prio][core] = new LinkedList(); + _scheduledThreadsPerPrioPerCore[prio][core] = new LinkedList(); + } + } + + _scheduledPrioritiesPerCore = new long[KScheduler.CpuCoresCount]; + _suggestedPrioritiesPerCore = new long[KScheduler.CpuCoresCount]; + } + + public IEnumerable SuggestedThreads(int core) + { + return Iterate(_suggestedThreadsPerPrioPerCore, _suggestedPrioritiesPerCore, core); + } + + public IEnumerable ScheduledThreads(int core) + { + return Iterate(_scheduledThreadsPerPrioPerCore, _scheduledPrioritiesPerCore, core); + } + + private IEnumerable Iterate(LinkedList[][] listPerPrioPerCore, long[] prios, int core) + { + long prioMask = prios[core]; + + int prio = CountTrailingZeros(prioMask); + + prioMask &= ~(1L << prio); + + while (prio < KScheduler.PrioritiesCount) + { + LinkedList list = listPerPrioPerCore[prio][core]; + + LinkedListNode node = list.First; + + while (node != null) + { + yield return node.Value; + + node = node.Next; + } + + prio = CountTrailingZeros(prioMask); + + prioMask &= ~(1L << prio); + } + } + + private int CountTrailingZeros(long value) + { + int count = 0; + + while (((value >> count) & 0xf) == 0 && count < 64) + { + count += 4; + } + + while (((value >> count) & 1) == 0 && count < 64) + { + count++; + } + + return count; + } + + public void TransferToCore(int prio, int dstCore, KThread thread) + { + bool schedulable = thread.DynamicPriority < KScheduler.PrioritiesCount; + + int srcCore = thread.CurrentCore; + + thread.CurrentCore = dstCore; + + if (srcCore == dstCore || !schedulable) + { + return; + } + + if (srcCore >= 0) + { + Unschedule(prio, srcCore, thread); + } + + if (dstCore >= 0) + { + Unsuggest(prio, dstCore, thread); + Schedule(prio, dstCore, thread); + } + + if (srcCore >= 0) + { + Suggest(prio, srcCore, thread); + } + } + + public void Suggest(int prio, int core, KThread thread) + { + if (prio >= KScheduler.PrioritiesCount) + { + return; + } + + thread.SiblingsPerCore[core] = SuggestedQueue(prio, core).AddFirst(thread); + + _suggestedPrioritiesPerCore[core] |= 1L << prio; + } + + public void Unsuggest(int prio, int core, KThread thread) + { + if (prio >= KScheduler.PrioritiesCount) + { + return; + } + + LinkedList queue = SuggestedQueue(prio, core); + + queue.Remove(thread.SiblingsPerCore[core]); + + if (queue.First == null) + { + _suggestedPrioritiesPerCore[core] &= ~(1L << prio); + } + } + + public void Schedule(int prio, int core, KThread thread) + { + if (prio >= KScheduler.PrioritiesCount) + { + return; + } + + thread.SiblingsPerCore[core] = ScheduledQueue(prio, core).AddLast(thread); + + _scheduledPrioritiesPerCore[core] |= 1L << prio; + } + + public void SchedulePrepend(int prio, int core, KThread thread) + { + if (prio >= KScheduler.PrioritiesCount) + { + return; + } + + thread.SiblingsPerCore[core] = ScheduledQueue(prio, core).AddFirst(thread); + + _scheduledPrioritiesPerCore[core] |= 1L << prio; + } + + public void Reschedule(int prio, int core, KThread thread) + { + LinkedList queue = ScheduledQueue(prio, core); + + queue.Remove(thread.SiblingsPerCore[core]); + + thread.SiblingsPerCore[core] = queue.AddLast(thread); + } + + public void Unschedule(int prio, int core, KThread thread) + { + if (prio >= KScheduler.PrioritiesCount) + { + return; + } + + LinkedList queue = ScheduledQueue(prio, core); + + queue.Remove(thread.SiblingsPerCore[core]); + + if (queue.First == null) + { + _scheduledPrioritiesPerCore[core] &= ~(1L << prio); + } + } + + private LinkedList SuggestedQueue(int prio, int core) + { + return _suggestedThreadsPerPrioPerCore[prio][core]; + } + + private LinkedList ScheduledQueue(int prio, int core) + { + return _scheduledThreadsPerPrioPerCore[prio][core]; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/KSynchronization.cs b/Ryujinx.HLE/HOS/Kernel/Threading/KSynchronization.cs new file mode 100644 index 0000000000..865551a2d4 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Threading/KSynchronization.cs @@ -0,0 +1,136 @@ +using Ryujinx.HLE.HOS.Kernel.Common; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Kernel.Threading +{ + class KSynchronization + { + private Horizon _system; + + public KSynchronization(Horizon system) + { + _system = system; + } + + public KernelResult WaitFor(KSynchronizationObject[] syncObjs, long timeout, out int handleIndex) + { + handleIndex = 0; + + KernelResult result = KernelResult.TimedOut; + + _system.CriticalSection.Enter(); + + // Check if objects are already signaled before waiting. + for (int index = 0; index < syncObjs.Length; index++) + { + if (!syncObjs[index].IsSignaled()) + { + continue; + } + + handleIndex = index; + + _system.CriticalSection.Leave(); + + return KernelResult.Success; + } + + if (timeout == 0) + { + _system.CriticalSection.Leave(); + + return result; + } + + KThread currentThread = _system.Scheduler.GetCurrentThread(); + + if (currentThread.ShallBeTerminated || + currentThread.SchedFlags == ThreadSchedState.TerminationPending) + { + result = KernelResult.ThreadTerminating; + } + else if (currentThread.SyncCancelled) + { + currentThread.SyncCancelled = false; + + result = KernelResult.Cancelled; + } + else + { + LinkedListNode[] syncNodes = new LinkedListNode[syncObjs.Length]; + + for (int index = 0; index < syncObjs.Length; index++) + { + syncNodes[index] = syncObjs[index].AddWaitingThread(currentThread); + } + + currentThread.WaitingSync = true; + currentThread.SignaledObj = null; + currentThread.ObjSyncResult = result; + + currentThread.Reschedule(ThreadSchedState.Paused); + + if (timeout > 0) + { + _system.TimeManager.ScheduleFutureInvocation(currentThread, timeout); + } + + _system.CriticalSection.Leave(); + + currentThread.WaitingSync = false; + + if (timeout > 0) + { + _system.TimeManager.UnscheduleFutureInvocation(currentThread); + } + + _system.CriticalSection.Enter(); + + result = currentThread.ObjSyncResult; + + handleIndex = -1; + + for (int index = 0; index < syncObjs.Length; index++) + { + syncObjs[index].RemoveWaitingThread(syncNodes[index]); + + if (syncObjs[index] == currentThread.SignaledObj) + { + handleIndex = index; + } + } + } + + _system.CriticalSection.Leave(); + + return result; + } + + public void SignalObject(KSynchronizationObject syncObj) + { + _system.CriticalSection.Enter(); + + if (syncObj.IsSignaled()) + { + LinkedListNode node = syncObj.WaitingThreads.First; + + while (node != null) + { + KThread thread = node.Value; + + if ((thread.SchedFlags & ThreadSchedState.LowMask) == ThreadSchedState.Paused) + { + thread.SignaledObj = syncObj; + thread.ObjSyncResult = KernelResult.Success; + + thread.Reschedule(ThreadSchedState.Running); + } + + node = node.Next; + } + } + + _system.CriticalSection.Leave(); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs b/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs new file mode 100644 index 0000000000..c4bd781d4e --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs @@ -0,0 +1,1210 @@ +using ARMeilleure.Memory; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Process; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Kernel.Threading +{ + class KThread : KSynchronizationObject, IKFutureSchedulerObject + { + private int _hostThreadRunning; + + public Thread HostThread { get; private set; } + + public ARMeilleure.State.ExecutionContext Context { get; private set; } + + public long AffinityMask { get; set; } + + public long ThreadUid { get; private set; } + + public long TotalTimeRunning { get; set; } + + public KSynchronizationObject SignaledObj { get; set; } + + public ulong CondVarAddress { get; set; } + + private ulong _entrypoint; + + public ulong MutexAddress { get; set; } + + public KProcess Owner { get; private set; } + + private ulong _tlsAddress; + + public ulong TlsAddress => _tlsAddress; + public ulong TlsDramAddress { get; private set; } + + public long LastScheduledTime { get; set; } + + public LinkedListNode[] SiblingsPerCore { get; private set; } + + public LinkedList Withholder { get; set; } + public LinkedListNode WithholderNode { get; set; } + + public LinkedListNode ProcessListNode { get; set; } + + private LinkedList _mutexWaiters; + private LinkedListNode _mutexWaiterNode; + + public KThread MutexOwner { get; private set; } + + public int ThreadHandleForUserMutex { get; set; } + + private ThreadSchedState _forcePauseFlags; + + public KernelResult ObjSyncResult { get; set; } + + public int DynamicPriority { get; set; } + public int CurrentCore { get; set; } + public int BasePriority { get; set; } + public int PreferredCore { get; set; } + + private long _affinityMaskOverride; + private int _preferredCoreOverride; + private int _affinityOverrideCount; + + public ThreadSchedState SchedFlags { get; private set; } + + private int _shallBeTerminated; + + public bool ShallBeTerminated { get => _shallBeTerminated != 0; set => _shallBeTerminated = value ? 1 : 0; } + + public bool SyncCancelled { get; set; } + public bool WaitingSync { get; set; } + + private bool _hasExited; + private bool _hasBeenInitialized; + private bool _hasBeenReleased; + + public bool WaitingInArbitration { get; set; } + + private KScheduler _scheduler; + + private KSchedulingData _schedulingData; + + public long LastPc { get; set; } + + public KThread(Horizon system) : base(system) + { + _scheduler = system.Scheduler; + _schedulingData = system.Scheduler.SchedulingData; + + SiblingsPerCore = new LinkedListNode[KScheduler.CpuCoresCount]; + + _mutexWaiters = new LinkedList(); + } + + public KernelResult Initialize( + ulong entrypoint, + ulong argsPtr, + ulong stackTop, + int priority, + int defaultCpuCore, + KProcess owner, + ThreadType type = ThreadType.User, + ThreadStart customHostThreadStart = null) + { + if ((uint)type > 3) + { + throw new ArgumentException($"Invalid thread type \"{type}\"."); + } + + PreferredCore = defaultCpuCore; + + AffinityMask |= 1L << defaultCpuCore; + + SchedFlags = type == ThreadType.Dummy + ? ThreadSchedState.Running + : ThreadSchedState.None; + + CurrentCore = PreferredCore; + + DynamicPriority = priority; + BasePriority = priority; + + ObjSyncResult = KernelResult.ThreadNotStarted; + + _entrypoint = entrypoint; + + if (type == ThreadType.User) + { + if (owner.AllocateThreadLocalStorage(out _tlsAddress) != KernelResult.Success) + { + return KernelResult.OutOfMemory; + } + + TlsDramAddress = owner.MemoryManager.GetDramAddressFromVa(_tlsAddress); + + MemoryHelper.FillWithZeros(owner.CpuMemory, (long)_tlsAddress, KTlsPageInfo.TlsEntrySize); + } + + bool is64Bits; + + if (owner != null) + { + Owner = owner; + + owner.IncrementReferenceCount(); + owner.IncrementThreadCount(); + + is64Bits = (owner.MmuFlags & 1) != 0; + } + else + { + is64Bits = true; + } + + HostThread = new Thread(customHostThreadStart ?? (() => ThreadStart(entrypoint))); + + Context = new ARMeilleure.State.ExecutionContext(); + + bool isAarch32 = (Owner.MmuFlags & 1) == 0; + + Context.IsAarch32 = isAarch32; + + Context.SetX(0, argsPtr); + + if (isAarch32) + { + Context.SetX(13, (uint)stackTop); + } + else + { + Context.SetX(31, stackTop); + } + + Context.CntfrqEl0 = 19200000; + Context.Tpidr = (long)_tlsAddress; + + owner.SubscribeThreadEventHandlers(Context); + + ThreadUid = System.GetThreadUid(); + + HostThread.Name = $"HLE.HostThread.{ThreadUid}"; + + _hasBeenInitialized = true; + + if (owner != null) + { + owner.AddThread(this); + + if (owner.IsPaused) + { + System.CriticalSection.Enter(); + + if (ShallBeTerminated || SchedFlags == ThreadSchedState.TerminationPending) + { + System.CriticalSection.Leave(); + + return KernelResult.Success; + } + + _forcePauseFlags |= ThreadSchedState.ProcessPauseFlag; + + CombineForcePauseFlags(); + + System.CriticalSection.Leave(); + } + } + + return KernelResult.Success; + } + + public KernelResult Start() + { + if (!System.KernelInitialized) + { + System.CriticalSection.Enter(); + + if (!ShallBeTerminated && SchedFlags != ThreadSchedState.TerminationPending) + { + _forcePauseFlags |= ThreadSchedState.KernelInitPauseFlag; + + CombineForcePauseFlags(); + } + + System.CriticalSection.Leave(); + } + + KernelResult result = KernelResult.ThreadTerminating; + + System.CriticalSection.Enter(); + + if (!ShallBeTerminated) + { + KThread currentThread = System.Scheduler.GetCurrentThread(); + + while (SchedFlags != ThreadSchedState.TerminationPending && + currentThread.SchedFlags != ThreadSchedState.TerminationPending && + !currentThread.ShallBeTerminated) + { + if ((SchedFlags & ThreadSchedState.LowMask) != ThreadSchedState.None) + { + result = KernelResult.InvalidState; + + break; + } + + if (currentThread._forcePauseFlags == ThreadSchedState.None) + { + if (Owner != null && _forcePauseFlags != ThreadSchedState.None) + { + CombineForcePauseFlags(); + } + + SetNewSchedFlags(ThreadSchedState.Running); + + result = KernelResult.Success; + + break; + } + else + { + currentThread.CombineForcePauseFlags(); + + System.CriticalSection.Leave(); + System.CriticalSection.Enter(); + + if (currentThread.ShallBeTerminated) + { + break; + } + } + } + } + + System.CriticalSection.Leave(); + + return result; + } + + public void Exit() + { + // TODO: Debug event. + + if (Owner != null) + { + Owner.ResourceLimit?.Release(LimitableResource.Thread, 0, 1); + + _hasBeenReleased = true; + } + + System.CriticalSection.Enter(); + + _forcePauseFlags &= ~ThreadSchedState.ForcePauseMask; + + ExitImpl(); + + System.CriticalSection.Leave(); + + DecrementReferenceCount(); + } + + public ThreadSchedState PrepareForTermination() + { + System.CriticalSection.Enter(); + + ThreadSchedState result; + + if (Interlocked.CompareExchange(ref _shallBeTerminated, 1, 0) == 0) + { + if ((SchedFlags & ThreadSchedState.LowMask) == ThreadSchedState.None) + { + SchedFlags = ThreadSchedState.TerminationPending; + } + else + { + if (_forcePauseFlags != ThreadSchedState.None) + { + _forcePauseFlags &= ~ThreadSchedState.ThreadPauseFlag; + + ThreadSchedState oldSchedFlags = SchedFlags; + + SchedFlags &= ThreadSchedState.LowMask; + + AdjustScheduling(oldSchedFlags); + } + + if (BasePriority >= 0x10) + { + SetPriority(0xF); + } + + if ((SchedFlags & ThreadSchedState.LowMask) == ThreadSchedState.Running) + { + // TODO: GIC distributor stuffs (sgir changes ect) + } + + SignaledObj = null; + ObjSyncResult = KernelResult.ThreadTerminating; + + ReleaseAndResume(); + } + } + + result = SchedFlags; + + System.CriticalSection.Leave(); + + return result & ThreadSchedState.LowMask; + } + + public void Terminate() + { + ThreadSchedState state = PrepareForTermination(); + + if (state != ThreadSchedState.TerminationPending) + { + System.Synchronization.WaitFor(new KSynchronizationObject[] { this }, -1, out _); + } + } + + public void HandlePostSyscall() + { + ThreadSchedState state; + + do + { + if (ShallBeTerminated || SchedFlags == ThreadSchedState.TerminationPending) + { + System.Scheduler.ExitThread(this); + Exit(); + + // As the death of the thread is handled by the CPU emulator, we differ from the official kernel and return here. + break; + } + + System.CriticalSection.Enter(); + + if (ShallBeTerminated || SchedFlags == ThreadSchedState.TerminationPending) + { + state = ThreadSchedState.TerminationPending; + } + else + { + if (_forcePauseFlags != ThreadSchedState.None) + { + CombineForcePauseFlags(); + } + + state = ThreadSchedState.Running; + } + + System.CriticalSection.Leave(); + } while (state == ThreadSchedState.TerminationPending); + } + + private void ExitImpl() + { + System.CriticalSection.Enter(); + + SetNewSchedFlags(ThreadSchedState.TerminationPending); + + _hasExited = true; + + Signal(); + + System.CriticalSection.Leave(); + } + + public KernelResult Sleep(long timeout) + { + System.CriticalSection.Enter(); + + if (ShallBeTerminated || SchedFlags == ThreadSchedState.TerminationPending) + { + System.CriticalSection.Leave(); + + return KernelResult.ThreadTerminating; + } + + SetNewSchedFlags(ThreadSchedState.Paused); + + if (timeout > 0) + { + System.TimeManager.ScheduleFutureInvocation(this, timeout); + } + + System.CriticalSection.Leave(); + + if (timeout > 0) + { + System.TimeManager.UnscheduleFutureInvocation(this); + } + + return 0; + } + + public void Yield() + { + System.CriticalSection.Enter(); + + if (SchedFlags != ThreadSchedState.Running) + { + System.CriticalSection.Leave(); + + System.Scheduler.ContextSwitch(); + + return; + } + + if (DynamicPriority < KScheduler.PrioritiesCount) + { + // Move current thread to the end of the queue. + _schedulingData.Reschedule(DynamicPriority, CurrentCore, this); + } + + _scheduler.ThreadReselectionRequested = true; + + System.CriticalSection.Leave(); + + System.Scheduler.ContextSwitch(); + } + + public void YieldWithLoadBalancing() + { + System.CriticalSection.Enter(); + + if (SchedFlags != ThreadSchedState.Running) + { + System.CriticalSection.Leave(); + + System.Scheduler.ContextSwitch(); + + return; + } + + int prio = DynamicPriority; + int core = CurrentCore; + + KThread nextThreadOnCurrentQueue = null; + + if (DynamicPriority < KScheduler.PrioritiesCount) + { + // Move current thread to the end of the queue. + _schedulingData.Reschedule(prio, core, this); + + Func predicate = x => x.DynamicPriority == prio; + + nextThreadOnCurrentQueue = _schedulingData.ScheduledThreads(core).FirstOrDefault(predicate); + } + + IEnumerable SuitableCandidates() + { + foreach (KThread thread in _schedulingData.SuggestedThreads(core)) + { + int srcCore = thread.CurrentCore; + + if (srcCore >= 0) + { + KThread selectedSrcCore = _scheduler.CoreContexts[srcCore].SelectedThread; + + if (selectedSrcCore == thread || ((selectedSrcCore?.DynamicPriority ?? 2) < 2)) + { + continue; + } + } + + // If the candidate was scheduled after the current thread, then it's not worth it, + // unless the priority is higher than the current one. + if (nextThreadOnCurrentQueue.LastScheduledTime >= thread.LastScheduledTime || + nextThreadOnCurrentQueue.DynamicPriority < thread.DynamicPriority) + { + yield return thread; + } + } + } + + KThread dst = SuitableCandidates().FirstOrDefault(x => x.DynamicPriority <= prio); + + if (dst != null) + { + _schedulingData.TransferToCore(dst.DynamicPriority, core, dst); + + _scheduler.ThreadReselectionRequested = true; + } + + if (this != nextThreadOnCurrentQueue) + { + _scheduler.ThreadReselectionRequested = true; + } + + System.CriticalSection.Leave(); + + System.Scheduler.ContextSwitch(); + } + + public void YieldAndWaitForLoadBalancing() + { + System.CriticalSection.Enter(); + + if (SchedFlags != ThreadSchedState.Running) + { + System.CriticalSection.Leave(); + + System.Scheduler.ContextSwitch(); + + return; + } + + int core = CurrentCore; + + _schedulingData.TransferToCore(DynamicPriority, -1, this); + + KThread selectedThread = null; + + if (!_schedulingData.ScheduledThreads(core).Any()) + { + foreach (KThread thread in _schedulingData.SuggestedThreads(core)) + { + if (thread.CurrentCore < 0) + { + continue; + } + + KThread firstCandidate = _schedulingData.ScheduledThreads(thread.CurrentCore).FirstOrDefault(); + + if (firstCandidate == thread) + { + continue; + } + + if (firstCandidate == null || firstCandidate.DynamicPriority >= 2) + { + _schedulingData.TransferToCore(thread.DynamicPriority, core, thread); + + selectedThread = thread; + } + + break; + } + } + + if (selectedThread != this) + { + _scheduler.ThreadReselectionRequested = true; + } + + System.CriticalSection.Leave(); + + System.Scheduler.ContextSwitch(); + } + + public void SetPriority(int priority) + { + System.CriticalSection.Enter(); + + BasePriority = priority; + + UpdatePriorityInheritance(); + + System.CriticalSection.Leave(); + } + + public KernelResult SetActivity(bool pause) + { + KernelResult result = KernelResult.Success; + + System.CriticalSection.Enter(); + + ThreadSchedState lowNibble = SchedFlags & ThreadSchedState.LowMask; + + if (lowNibble != ThreadSchedState.Paused && lowNibble != ThreadSchedState.Running) + { + System.CriticalSection.Leave(); + + return KernelResult.InvalidState; + } + + System.CriticalSection.Enter(); + + if (!ShallBeTerminated && SchedFlags != ThreadSchedState.TerminationPending) + { + if (pause) + { + // Pause, the force pause flag should be clear (thread is NOT paused). + if ((_forcePauseFlags & ThreadSchedState.ThreadPauseFlag) == 0) + { + _forcePauseFlags |= ThreadSchedState.ThreadPauseFlag; + + CombineForcePauseFlags(); + } + else + { + result = KernelResult.InvalidState; + } + } + else + { + // Unpause, the force pause flag should be set (thread is paused). + if ((_forcePauseFlags & ThreadSchedState.ThreadPauseFlag) != 0) + { + ThreadSchedState oldForcePauseFlags = _forcePauseFlags; + + _forcePauseFlags &= ~ThreadSchedState.ThreadPauseFlag; + + if ((oldForcePauseFlags & ~ThreadSchedState.ThreadPauseFlag) == ThreadSchedState.None) + { + ThreadSchedState oldSchedFlags = SchedFlags; + + SchedFlags &= ThreadSchedState.LowMask; + + AdjustScheduling(oldSchedFlags); + } + } + else + { + result = KernelResult.InvalidState; + } + } + } + + System.CriticalSection.Leave(); + System.CriticalSection.Leave(); + + return result; + } + + public void CancelSynchronization() + { + System.CriticalSection.Enter(); + + if ((SchedFlags & ThreadSchedState.LowMask) != ThreadSchedState.Paused || !WaitingSync) + { + SyncCancelled = true; + } + else if (Withholder != null) + { + Withholder.Remove(WithholderNode); + + SetNewSchedFlags(ThreadSchedState.Running); + + Withholder = null; + + SyncCancelled = true; + } + else + { + SignaledObj = null; + ObjSyncResult = KernelResult.Cancelled; + + SetNewSchedFlags(ThreadSchedState.Running); + + SyncCancelled = false; + } + + System.CriticalSection.Leave(); + } + + public KernelResult SetCoreAndAffinityMask(int newCore, long newAffinityMask) + { + System.CriticalSection.Enter(); + + bool useOverride = _affinityOverrideCount != 0; + + // The value -3 is "do not change the preferred core". + if (newCore == -3) + { + newCore = useOverride ? _preferredCoreOverride : PreferredCore; + + if ((newAffinityMask & (1 << newCore)) == 0) + { + System.CriticalSection.Leave(); + + return KernelResult.InvalidCombination; + } + } + + if (useOverride) + { + _preferredCoreOverride = newCore; + _affinityMaskOverride = newAffinityMask; + } + else + { + long oldAffinityMask = AffinityMask; + + PreferredCore = newCore; + AffinityMask = newAffinityMask; + + if (oldAffinityMask != newAffinityMask) + { + int oldCore = CurrentCore; + + if (CurrentCore >= 0 && ((AffinityMask >> CurrentCore) & 1) == 0) + { + if (PreferredCore < 0) + { + CurrentCore = HighestSetCore(AffinityMask); + } + else + { + CurrentCore = PreferredCore; + } + } + + AdjustSchedulingForNewAffinity(oldAffinityMask, oldCore); + } + } + + System.CriticalSection.Leave(); + + return KernelResult.Success; + } + + private static int HighestSetCore(long mask) + { + for (int core = KScheduler.CpuCoresCount - 1; core >= 0; core--) + { + if (((mask >> core) & 1) != 0) + { + return core; + } + } + + return -1; + } + + private void CombineForcePauseFlags() + { + ThreadSchedState oldFlags = SchedFlags; + ThreadSchedState lowNibble = SchedFlags & ThreadSchedState.LowMask; + + SchedFlags = lowNibble | _forcePauseFlags; + + AdjustScheduling(oldFlags); + } + + private void SetNewSchedFlags(ThreadSchedState newFlags) + { + System.CriticalSection.Enter(); + + ThreadSchedState oldFlags = SchedFlags; + + SchedFlags = (oldFlags & ThreadSchedState.HighMask) | newFlags; + + if ((oldFlags & ThreadSchedState.LowMask) != newFlags) + { + AdjustScheduling(oldFlags); + } + + System.CriticalSection.Leave(); + } + + public void ReleaseAndResume() + { + System.CriticalSection.Enter(); + + if ((SchedFlags & ThreadSchedState.LowMask) == ThreadSchedState.Paused) + { + if (Withholder != null) + { + Withholder.Remove(WithholderNode); + + SetNewSchedFlags(ThreadSchedState.Running); + + Withholder = null; + } + else + { + SetNewSchedFlags(ThreadSchedState.Running); + } + } + + System.CriticalSection.Leave(); + } + + public void Reschedule(ThreadSchedState newFlags) + { + System.CriticalSection.Enter(); + + ThreadSchedState oldFlags = SchedFlags; + + SchedFlags = (oldFlags & ThreadSchedState.HighMask) | + (newFlags & ThreadSchedState.LowMask); + + AdjustScheduling(oldFlags); + + System.CriticalSection.Leave(); + } + + public void AddMutexWaiter(KThread requester) + { + AddToMutexWaitersList(requester); + + requester.MutexOwner = this; + + UpdatePriorityInheritance(); + } + + public void RemoveMutexWaiter(KThread thread) + { + if (thread._mutexWaiterNode?.List != null) + { + _mutexWaiters.Remove(thread._mutexWaiterNode); + } + + thread.MutexOwner = null; + + UpdatePriorityInheritance(); + } + + public KThread RelinquishMutex(ulong mutexAddress, out int count) + { + count = 0; + + if (_mutexWaiters.First == null) + { + return null; + } + + KThread newMutexOwner = null; + + LinkedListNode currentNode = _mutexWaiters.First; + + do + { + // Skip all threads that are not waiting for this mutex. + while (currentNode != null && currentNode.Value.MutexAddress != mutexAddress) + { + currentNode = currentNode.Next; + } + + if (currentNode == null) + { + break; + } + + LinkedListNode nextNode = currentNode.Next; + + _mutexWaiters.Remove(currentNode); + + currentNode.Value.MutexOwner = newMutexOwner; + + if (newMutexOwner != null) + { + // New owner was already selected, re-insert on new owner list. + newMutexOwner.AddToMutexWaitersList(currentNode.Value); + } + else + { + // New owner not selected yet, use current thread. + newMutexOwner = currentNode.Value; + } + + count++; + + currentNode = nextNode; + } + while (currentNode != null); + + if (newMutexOwner != null) + { + UpdatePriorityInheritance(); + + newMutexOwner.UpdatePriorityInheritance(); + } + + return newMutexOwner; + } + + private void UpdatePriorityInheritance() + { + // If any of the threads waiting for the mutex has + // higher priority than the current thread, then + // the current thread inherits that priority. + int highestPriority = BasePriority; + + if (_mutexWaiters.First != null) + { + int waitingDynamicPriority = _mutexWaiters.First.Value.DynamicPriority; + + if (waitingDynamicPriority < highestPriority) + { + highestPriority = waitingDynamicPriority; + } + } + + if (highestPriority != DynamicPriority) + { + int oldPriority = DynamicPriority; + + DynamicPriority = highestPriority; + + AdjustSchedulingForNewPriority(oldPriority); + + if (MutexOwner != null) + { + // Remove and re-insert to ensure proper sorting based on new priority. + MutexOwner._mutexWaiters.Remove(_mutexWaiterNode); + + MutexOwner.AddToMutexWaitersList(this); + + MutexOwner.UpdatePriorityInheritance(); + } + } + } + + private void AddToMutexWaitersList(KThread thread) + { + LinkedListNode nextPrio = _mutexWaiters.First; + + int currentPriority = thread.DynamicPriority; + + while (nextPrio != null && nextPrio.Value.DynamicPriority <= currentPriority) + { + nextPrio = nextPrio.Next; + } + + if (nextPrio != null) + { + thread._mutexWaiterNode = _mutexWaiters.AddBefore(nextPrio, thread); + } + else + { + thread._mutexWaiterNode = _mutexWaiters.AddLast(thread); + } + } + + private void AdjustScheduling(ThreadSchedState oldFlags) + { + if (oldFlags == SchedFlags) + { + return; + } + + if (oldFlags == ThreadSchedState.Running) + { + // Was running, now it's stopped. + if (CurrentCore >= 0) + { + _schedulingData.Unschedule(DynamicPriority, CurrentCore, this); + } + + for (int core = 0; core < KScheduler.CpuCoresCount; core++) + { + if (core != CurrentCore && ((AffinityMask >> core) & 1) != 0) + { + _schedulingData.Unsuggest(DynamicPriority, core, this); + } + } + } + else if (SchedFlags == ThreadSchedState.Running) + { + // Was stopped, now it's running. + if (CurrentCore >= 0) + { + _schedulingData.Schedule(DynamicPriority, CurrentCore, this); + } + + for (int core = 0; core < KScheduler.CpuCoresCount; core++) + { + if (core != CurrentCore && ((AffinityMask >> core) & 1) != 0) + { + _schedulingData.Suggest(DynamicPriority, core, this); + } + } + } + + _scheduler.ThreadReselectionRequested = true; + } + + private void AdjustSchedulingForNewPriority(int oldPriority) + { + if (SchedFlags != ThreadSchedState.Running) + { + return; + } + + // Remove thread from the old priority queues. + if (CurrentCore >= 0) + { + _schedulingData.Unschedule(oldPriority, CurrentCore, this); + } + + for (int core = 0; core < KScheduler.CpuCoresCount; core++) + { + if (core != CurrentCore && ((AffinityMask >> core) & 1) != 0) + { + _schedulingData.Unsuggest(oldPriority, core, this); + } + } + + // Add thread to the new priority queues. + KThread currentThread = _scheduler.GetCurrentThread(); + + if (CurrentCore >= 0) + { + if (currentThread == this) + { + _schedulingData.SchedulePrepend(DynamicPriority, CurrentCore, this); + } + else + { + _schedulingData.Schedule(DynamicPriority, CurrentCore, this); + } + } + + for (int core = 0; core < KScheduler.CpuCoresCount; core++) + { + if (core != CurrentCore && ((AffinityMask >> core) & 1) != 0) + { + _schedulingData.Suggest(DynamicPriority, core, this); + } + } + + _scheduler.ThreadReselectionRequested = true; + } + + private void AdjustSchedulingForNewAffinity(long oldAffinityMask, int oldCore) + { + if (SchedFlags != ThreadSchedState.Running || DynamicPriority >= KScheduler.PrioritiesCount) + { + return; + } + + // Remove thread from the old priority queues. + for (int core = 0; core < KScheduler.CpuCoresCount; core++) + { + if (((oldAffinityMask >> core) & 1) != 0) + { + if (core == oldCore) + { + _schedulingData.Unschedule(DynamicPriority, core, this); + } + else + { + _schedulingData.Unsuggest(DynamicPriority, core, this); + } + } + } + + // Add thread to the new priority queues. + for (int core = 0; core < KScheduler.CpuCoresCount; core++) + { + if (((AffinityMask >> core) & 1) != 0) + { + if (core == CurrentCore) + { + _schedulingData.Schedule(DynamicPriority, core, this); + } + else + { + _schedulingData.Suggest(DynamicPriority, core, this); + } + } + } + + _scheduler.ThreadReselectionRequested = true; + } + + public void SetEntryArguments(long argsPtr, int threadHandle) + { + Context.SetX(0, (ulong)argsPtr); + Context.SetX(1, (ulong)threadHandle); + } + + public void TimeUp() + { + ReleaseAndResume(); + } + + public string GetGuestStackTrace() + { + return Owner.Debugger.GetGuestStackTrace(Context); + } + + public void PrintGuestStackTrace() + { + StringBuilder trace = new StringBuilder(); + + trace.AppendLine("Guest stack trace:"); + trace.AppendLine(GetGuestStackTrace()); + + Logger.PrintInfo(LogClass.Cpu, trace.ToString()); + } + + public void Execute() + { + if (Interlocked.CompareExchange(ref _hostThreadRunning, 1, 0) == 0) + { + HostThread.Start(); + } + } + + private void ThreadStart(ulong entrypoint) + { + Owner.Translator.Execute(Context, entrypoint); + + ThreadExit(); + } + + private void ThreadExit() + { + System.Scheduler.ExitThread(this); + System.Scheduler.RemoveThread(this); + } + + public bool IsCurrentHostThread() + { + return Thread.CurrentThread == HostThread; + } + + public override bool IsSignaled() + { + return _hasExited; + } + + protected override void Destroy() + { + if (_hasBeenInitialized) + { + FreeResources(); + + bool released = Owner != null || _hasBeenReleased; + + if (Owner != null) + { + Owner.ResourceLimit?.Release(LimitableResource.Thread, 1, released ? 0 : 1); + + Owner.DecrementReferenceCount(); + } + else + { + System.ResourceLimit.Release(LimitableResource.Thread, 1, released ? 0 : 1); + } + } + } + + private void FreeResources() + { + Owner?.RemoveThread(this); + + if (_tlsAddress != 0 && Owner.FreeThreadLocalStorage(_tlsAddress) != KernelResult.Success) + { + throw new InvalidOperationException("Unexpected failure freeing thread local storage."); + } + + System.CriticalSection.Enter(); + + // Wake up all threads that may be waiting for a mutex being held by this thread. + foreach (KThread thread in _mutexWaiters) + { + thread.MutexOwner = null; + thread._preferredCoreOverride = 0; + thread.ObjSyncResult = KernelResult.InvalidState; + + thread.ReleaseAndResume(); + } + + System.CriticalSection.Leave(); + + Owner?.DecrementThreadCountAndTerminateIfZero(); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/KWritableEvent.cs b/Ryujinx.HLE/HOS/Kernel/Threading/KWritableEvent.cs new file mode 100644 index 0000000000..1db88995d8 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Threading/KWritableEvent.cs @@ -0,0 +1,24 @@ +using Ryujinx.HLE.HOS.Kernel.Common; + +namespace Ryujinx.HLE.HOS.Kernel.Threading +{ + class KWritableEvent : KAutoObject + { + private KEvent _parent; + + public KWritableEvent(Horizon system, KEvent parent) : base(system) + { + _parent = parent; + } + + public void Signal() + { + _parent.ReadableEvent.Signal(); + } + + public KernelResult Clear() + { + return _parent.ReadableEvent.Clear(); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/SignalType.cs b/Ryujinx.HLE/HOS/Kernel/Threading/SignalType.cs new file mode 100644 index 0000000000..e72b719b4c --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Threading/SignalType.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Kernel.Threading +{ + enum SignalType + { + Signal = 0, + SignalAndIncrementIfEqual = 1, + SignalAndModifyIfEqual = 2 + } +} diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/ThreadSchedState.cs b/Ryujinx.HLE/HOS/Kernel/Threading/ThreadSchedState.cs new file mode 100644 index 0000000000..c9eaa6b333 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Threading/ThreadSchedState.cs @@ -0,0 +1,19 @@ +namespace Ryujinx.HLE.HOS.Kernel.Threading +{ + enum ThreadSchedState : ushort + { + LowMask = 0xf, + HighMask = 0xfff0, + ForcePauseMask = 0x70, + + ProcessPauseFlag = 1 << 4, + ThreadPauseFlag = 1 << 5, + ProcessDebugPauseFlag = 1 << 6, + KernelInitPauseFlag = 1 << 8, + + None = 0, + Paused = 1, + Running = 2, + TerminationPending = 3 + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/ThreadType.cs b/Ryujinx.HLE/HOS/Kernel/Threading/ThreadType.cs new file mode 100644 index 0000000000..0b44b57ff1 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Threading/ThreadType.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Kernel.Threading +{ + enum ThreadType + { + Dummy, + Kernel, + Kernel2, + User + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/ProgramLoader.cs b/Ryujinx.HLE/HOS/ProgramLoader.cs new file mode 100644 index 0000000000..19c77380d4 --- /dev/null +++ b/Ryujinx.HLE/HOS/ProgramLoader.cs @@ -0,0 +1,312 @@ +using ARMeilleure.Memory; +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Memory; +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.HLE.Loaders.Executables; +using Ryujinx.HLE.Loaders.Npdm; + +namespace Ryujinx.HLE.HOS +{ + class ProgramLoader + { + private const bool AslrEnabled = true; + + private const int ArgsHeaderSize = 8; + private const int ArgsDataSize = 0x9000; + private const int ArgsTotalSize = ArgsHeaderSize + ArgsDataSize; + + public static bool LoadKernelInitalProcess(Horizon system, KernelInitialProcess kip) + { + int endOffset = kip.DataOffset + kip.Data.Length; + + if (kip.BssSize != 0) + { + endOffset = kip.BssOffset + kip.BssSize; + } + + int codeSize = BitUtils.AlignUp(kip.TextOffset + endOffset, KMemoryManager.PageSize); + + int codePagesCount = codeSize / KMemoryManager.PageSize; + + ulong codeBaseAddress = kip.Addr39Bits ? 0x8000000UL : 0x200000UL; + + ulong codeAddress = codeBaseAddress + (ulong)kip.TextOffset; + + int mmuFlags = 0; + + if (AslrEnabled) + { + // TODO: Randomization. + + mmuFlags |= 0x20; + } + + if (kip.Addr39Bits) + { + mmuFlags |= (int)AddressSpaceType.Addr39Bits << 1; + } + + if (kip.Is64Bits) + { + mmuFlags |= 1; + } + + ProcessCreationInfo creationInfo = new ProcessCreationInfo( + kip.Name, + kip.ProcessCategory, + kip.TitleId, + codeAddress, + codePagesCount, + mmuFlags, + 0, + 0); + + MemoryRegion memoryRegion = kip.IsService + ? MemoryRegion.Service + : MemoryRegion.Application; + + KMemoryRegionManager region = system.MemoryRegions[(int)memoryRegion]; + + KernelResult result = region.AllocatePages((ulong)codePagesCount, false, out KPageList pageList); + + if (result != KernelResult.Success) + { + Logger.PrintError(LogClass.Loader, $"Process initialization returned error \"{result}\"."); + + return false; + } + + KProcess process = new KProcess(system); + + result = process.InitializeKip( + creationInfo, + kip.Capabilities, + pageList, + system.ResourceLimit, + memoryRegion); + + if (result != KernelResult.Success) + { + Logger.PrintError(LogClass.Loader, $"Process initialization returned error \"{result}\"."); + + return false; + } + + result = LoadIntoMemory(process, kip, codeBaseAddress); + + if (result != KernelResult.Success) + { + Logger.PrintError(LogClass.Loader, $"Process initialization returned error \"{result}\"."); + + return false; + } + + process.DefaultCpuCore = kip.DefaultProcessorId; + + result = process.Start(kip.MainThreadPriority, (ulong)kip.MainThreadStackSize); + + if (result != KernelResult.Success) + { + Logger.PrintError(LogClass.Loader, $"Process start returned error \"{result}\"."); + + return false; + } + + system.Processes.Add(process.Pid, process); + + return true; + } + + public static bool LoadStaticObjects( + Horizon system, + Npdm metaData, + IExecutable[] staticObjects, + byte[] arguments = null) + { + if (!metaData.Is64Bits) + { + Logger.PrintWarning(LogClass.Loader, "32-bits application detected."); + } + + ulong argsStart = 0; + int argsSize = 0; + ulong codeStart = metaData.Is64Bits ? 0x8000000UL : 0x200000UL; + int codeSize = 0; + + ulong[] nsoBase = new ulong[staticObjects.Length]; + + for (int index = 0; index < staticObjects.Length; index++) + { + IExecutable staticObject = staticObjects[index]; + + int textEnd = staticObject.TextOffset + staticObject.Text.Length; + int roEnd = staticObject.RoOffset + staticObject.Ro.Length; + int dataEnd = staticObject.DataOffset + staticObject.Data.Length + staticObject.BssSize; + + int nsoSize = textEnd; + + if ((uint)nsoSize < (uint)roEnd) + { + nsoSize = roEnd; + } + + if ((uint)nsoSize < (uint)dataEnd) + { + nsoSize = dataEnd; + } + + nsoSize = BitUtils.AlignUp(nsoSize, KMemoryManager.PageSize); + + nsoBase[index] = codeStart + (ulong)codeSize; + + codeSize += nsoSize; + + if (arguments != null && argsSize == 0) + { + argsStart = (ulong)codeSize; + + argsSize = BitUtils.AlignDown(arguments.Length * 2 + ArgsTotalSize - 1, KMemoryManager.PageSize); + + codeSize += argsSize; + } + } + + int codePagesCount = codeSize / KMemoryManager.PageSize; + + int personalMmHeapPagesCount = metaData.PersonalMmHeapSize / KMemoryManager.PageSize; + + ProcessCreationInfo creationInfo = new ProcessCreationInfo( + metaData.TitleName, + metaData.ProcessCategory, + metaData.Aci0.TitleId, + codeStart, + codePagesCount, + metaData.MmuFlags, + 0, + personalMmHeapPagesCount); + + KernelResult result; + + KResourceLimit resourceLimit = new KResourceLimit(system); + + long applicationRgSize = (long)system.MemoryRegions[(int)MemoryRegion.Application].Size; + + result = resourceLimit.SetLimitValue(LimitableResource.Memory, applicationRgSize); + result |= resourceLimit.SetLimitValue(LimitableResource.Thread, 608); + result |= resourceLimit.SetLimitValue(LimitableResource.Event, 700); + result |= resourceLimit.SetLimitValue(LimitableResource.TransferMemory, 128); + result |= resourceLimit.SetLimitValue(LimitableResource.Session, 894); + + if (result != KernelResult.Success) + { + Logger.PrintError(LogClass.Loader, $"Process initialization failed setting resource limit values."); + + return false; + } + + KProcess process = new KProcess(system); + + MemoryRegion memoryRegion = (MemoryRegion)((metaData.Acid.Flags >> 2) & 0xf); + + if (memoryRegion > MemoryRegion.NvServices) + { + Logger.PrintError(LogClass.Loader, $"Process initialization failed due to invalid ACID flags."); + + return false; + } + + result = process.Initialize( + creationInfo, + metaData.Aci0.KernelAccessControl.Capabilities, + resourceLimit, + memoryRegion); + + if (result != KernelResult.Success) + { + Logger.PrintError(LogClass.Loader, $"Process initialization returned error \"{result}\"."); + + return false; + } + + for (int index = 0; index < staticObjects.Length; index++) + { + Logger.PrintInfo(LogClass.Loader, $"Loading image {index} at 0x{nsoBase[index]:x16}..."); + + result = LoadIntoMemory(process, staticObjects[index], nsoBase[index]); + + if (result != KernelResult.Success) + { + Logger.PrintError(LogClass.Loader, $"Process initialization returned error \"{result}\"."); + + return false; + } + } + + process.DefaultCpuCore = metaData.DefaultCpuId; + + result = process.Start(metaData.MainThreadPriority, (ulong)metaData.MainThreadStackSize); + + if (result != KernelResult.Success) + { + Logger.PrintError(LogClass.Loader, $"Process start returned error \"{result}\"."); + + return false; + } + + system.Processes.Add(process.Pid, process); + + return true; + } + + private static KernelResult LoadIntoMemory(KProcess process, IExecutable image, ulong baseAddress) + { + ulong textStart = baseAddress + (ulong)image.TextOffset; + ulong roStart = baseAddress + (ulong)image.RoOffset; + ulong dataStart = baseAddress + (ulong)image.DataOffset; + ulong bssStart = baseAddress + (ulong)image.BssOffset; + + ulong end = dataStart + (ulong)image.Data.Length; + + if (image.BssSize != 0) + { + end = bssStart + (ulong)image.BssSize; + } + + process.CpuMemory.WriteBytes((long)textStart, image.Text); + process.CpuMemory.WriteBytes((long)roStart, image.Ro); + process.CpuMemory.WriteBytes((long)dataStart, image.Data); + + MemoryHelper.FillWithZeros(process.CpuMemory, (long)bssStart, image.BssSize); + + KernelResult SetProcessMemoryPermission(ulong address, ulong size, MemoryPermission permission) + { + if (size == 0) + { + return KernelResult.Success; + } + + size = BitUtils.AlignUp(size, KMemoryManager.PageSize); + + return process.MemoryManager.SetProcessMemoryPermission(address, size, permission); + } + + KernelResult result = SetProcessMemoryPermission(textStart, (ulong)image.Text.Length, MemoryPermission.ReadAndExecute); + + if (result != KernelResult.Success) + { + return result; + } + + result = SetProcessMemoryPermission(roStart, (ulong)image.Ro.Length, MemoryPermission.Read); + + if (result != KernelResult.Success) + { + return result; + } + + return SetProcessMemoryPermission(dataStart, end - dataStart, MemoryPermission.ReadAndWrite); + } + } +} diff --git a/Ryujinx.HLE/HOS/ResultCode.cs b/Ryujinx.HLE/HOS/ResultCode.cs new file mode 100644 index 0000000000..4004357b51 --- /dev/null +++ b/Ryujinx.HLE/HOS/ResultCode.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.HLE.HOS +{ + public enum ResultCode + { + OsModuleId = 3, + ErrorCodeShift = 9, + + Success = 0, + + NotAllocated = (1023 << ErrorCodeShift) | OsModuleId + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/ServiceCtx.cs b/Ryujinx.HLE/HOS/ServiceCtx.cs new file mode 100644 index 0000000000..fa6cfca3a8 --- /dev/null +++ b/Ryujinx.HLE/HOS/ServiceCtx.cs @@ -0,0 +1,44 @@ +using ARMeilleure.Memory; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Ipc; +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.HLE.HOS.Kernel.Threading; +using System.IO; + +namespace Ryujinx.HLE.HOS +{ + class ServiceCtx + { + public Switch Device { get; } + public KProcess Process { get; } + public MemoryManager Memory { get; } + public KThread Thread { get; } + public KClientSession Session { get; } + public IpcMessage Request { get; } + public IpcMessage Response { get; } + public BinaryReader RequestData { get; } + public BinaryWriter ResponseData { get; } + + public ServiceCtx( + Switch device, + KProcess process, + MemoryManager memory, + KThread thread, + KClientSession session, + IpcMessage request, + IpcMessage response, + BinaryReader requestData, + BinaryWriter responseData) + { + Device = device; + Process = process; + Memory = memory; + Thread = thread; + Session = session; + Request = request; + Response = response; + RequestData = requestData; + ResponseData = responseData; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/AccountUtils.cs b/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/AccountUtils.cs new file mode 100644 index 0000000000..7a70025ad0 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/AccountUtils.cs @@ -0,0 +1,67 @@ +using Ryujinx.HLE.Utilities; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; + +namespace Ryujinx.HLE.HOS.Services.Account.Acc +{ + public class AccountUtils + { + private ConcurrentDictionary _profiles; + + internal UserProfile LastOpenedUser { get; private set; } + + public AccountUtils() + { + _profiles = new ConcurrentDictionary(); + } + + public void AddUser(UInt128 userId, string name) + { + UserProfile profile = new UserProfile(userId, name); + + _profiles.AddOrUpdate(userId.ToString(), profile, (key, old) => profile); + } + + public void OpenUser(UInt128 userId) + { + if (_profiles.TryGetValue(userId.ToString(), out UserProfile profile)) + { + (LastOpenedUser = profile).AccountState = AccountState.Open; + } + } + + public void CloseUser(UInt128 userId) + { + if (_profiles.TryGetValue(userId.ToString(), out UserProfile profile)) + { + profile.AccountState = AccountState.Closed; + } + } + + public int GetUserCount() + { + return _profiles.Count; + } + + internal bool TryGetUser(UInt128 userId, out UserProfile profile) + { + return _profiles.TryGetValue(userId.ToString(), out profile); + } + + internal IEnumerable GetAllUsers() + { + return _profiles.Values; + } + + internal IEnumerable GetOpenedUsers() + { + return _profiles.Values.Where(x => x.AccountState == AccountState.Open); + } + + internal UserProfile GetFirst() + { + return _profiles.First().Value; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForAdministrator.cs b/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForAdministrator.cs new file mode 100644 index 0000000000..9fb3fb9ba4 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForAdministrator.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Account.Acc +{ + [Service("acc:su")] + class IAccountServiceForAdministrator : IpcService + { + public IAccountServiceForAdministrator(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForApplication.cs b/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForApplication.cs new file mode 100644 index 0000000000..ec26d11faf --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForApplication.cs @@ -0,0 +1,297 @@ +using ARMeilleure.Memory; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Services.Arp; +using Ryujinx.HLE.Utilities; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Services.Account.Acc +{ + [Service("acc:u0")] + class IAccountServiceForApplication : IpcService + { + private bool _userRegistrationRequestPermitted = false; + + private ApplicationLaunchProperty _applicationLaunchProperty; + + public IAccountServiceForApplication(ServiceCtx context) { } + + [Command(0)] + // GetUserCount() -> i32 + public ResultCode GetUserCount(ServiceCtx context) + { + context.ResponseData.Write(context.Device.System.State.Account.GetUserCount()); + + return ResultCode.Success; + } + + [Command(1)] + // GetUserExistence(nn::account::Uid) -> bool + public ResultCode GetUserExistence(ServiceCtx context) + { + UInt128 userId = new UInt128(context.RequestData.ReadBytes(0x10)); + + if (userId.IsNull) + { + return ResultCode.NullArgument; + } + + context.ResponseData.Write(context.Device.System.State.Account.TryGetUser(userId, out _)); + + return ResultCode.Success; + } + + [Command(2)] + // ListAllUsers() -> array + public ResultCode ListAllUsers(ServiceCtx context) + { + return WriteUserList(context, context.Device.System.State.Account.GetAllUsers()); + } + + [Command(3)] + // ListOpenUsers() -> array + public ResultCode ListOpenUsers(ServiceCtx context) + { + return WriteUserList(context, context.Device.System.State.Account.GetOpenedUsers()); + } + + private ResultCode WriteUserList(ServiceCtx context, IEnumerable profiles) + { + if (context.Request.RecvListBuff.Count == 0) + { + return ResultCode.InvalidInputBuffer; + } + + long outputPosition = context.Request.RecvListBuff[0].Position; + long outputSize = context.Request.RecvListBuff[0].Size; + + MemoryHelper.FillWithZeros(context.Memory, outputPosition, (int)outputSize); + + ulong offset = 0; + + foreach (UserProfile userProfile in profiles) + { + if (offset + 0x10 > (ulong)outputSize) + { + break; + } + + context.Memory.WriteInt64(outputPosition + (long)offset, userProfile.UserId.Low); + context.Memory.WriteInt64(outputPosition + (long)offset + 8, userProfile.UserId.High); + + offset += 0x10; + } + + return ResultCode.Success; + } + + [Command(4)] + // GetLastOpenedUser() -> nn::account::Uid + public ResultCode GetLastOpenedUser(ServiceCtx context) + { + context.Device.System.State.Account.LastOpenedUser.UserId.Write(context.ResponseData); + + return ResultCode.Success; + } + + [Command(5)] + // GetProfile(nn::account::Uid) -> object + public ResultCode GetProfile(ServiceCtx context) + { + UInt128 userId = new UInt128(context.RequestData.ReadBytes(0x10)); + + if (!context.Device.System.State.Account.TryGetUser(userId, out UserProfile userProfile)) + { + Logger.PrintWarning(LogClass.ServiceAcc, $"User 0x{userId} not found!"); + + return ResultCode.UserNotFound; + } + + MakeObject(context, new IProfile(userProfile)); + + // Doesn't occur in our case. + // return ResultCode.NullObject; + + return ResultCode.Success; + } + + [Command(50)] + // IsUserRegistrationRequestPermitted(u64, pid) -> bool + public ResultCode IsUserRegistrationRequestPermitted(ServiceCtx context) + { + // The u64 argument seems to be unused by account. + context.ResponseData.Write(_userRegistrationRequestPermitted); + + return ResultCode.Success; + } + + [Command(51)] + // TrySelectUserWithoutInteraction(bool) -> nn::account::Uid + public ResultCode TrySelectUserWithoutInteraction(ServiceCtx context) + { + if (context.Device.System.State.Account.GetUserCount() != 1) + { + // Invalid UserId. + new UInt128(0, 0).Write(context.ResponseData); + + return 0; + } + + bool baasCheck = context.RequestData.ReadBoolean(); + + if (baasCheck) + { + // This checks something related to baas (online), and then return an invalid UserId if the check in baas returns an error code. + // In our case, we can just log it for now. + + Logger.PrintStub(LogClass.ServiceAcc, new { baasCheck }); + } + + // As we returned an invalid UserId if there is more than one user earlier, now we can return only the first one. + context.Device.System.State.Account.GetFirst().UserId.Write(context.ResponseData); + + return ResultCode.Success; + } + + [Command(100)] + [Command(140)] // 6.0.0+ + // InitializeApplicationInfo(u64, pid) + // Both calls (100, 140) use the same submethod, maybe there's something different further along when arp:r is called? + public ResultCode InitializeApplicationInfo(ServiceCtx context) + { + if (_applicationLaunchProperty != null) + { + return ResultCode.ApplicationLaunchPropertyAlreadyInit; + } + + // The u64 argument seems to be unused by account. + long unknown = context.RequestData.ReadInt64(); + + // TODO: Account actually calls nn::arp::detail::IReader::GetApplicationLaunchProperty() with the current PID and store the result (ApplicationLaunchProperty) internally. + // For now we can hardcode values, and fix it after GetApplicationLaunchProperty is implemented. + + /* + if (nn::arp::detail::IReader::GetApplicationLaunchProperty() == 0xCC9D) // InvalidProcessId + { + _applicationLaunchProperty = ApplicationLaunchProperty.Default; + + return ResultCode.InvalidArgument; + } + else + */ + { + _applicationLaunchProperty = ApplicationLaunchProperty.GetByPid(context); + } + + Logger.PrintStub(LogClass.ServiceAcc, new { unknown }); + + return ResultCode.Success; + } + + [Command(101)] + // GetBaasAccountManagerForApplication(nn::account::Uid) -> object + public ResultCode GetBaasAccountManagerForApplication(ServiceCtx context) + { + UInt128 userId = new UInt128(context.RequestData.ReadBytes(0x10)); + + if (userId.IsNull) + { + return ResultCode.NullArgument; + } + + if (_applicationLaunchProperty == null) + { + return ResultCode.InvalidArgument; + } + + MakeObject(context, new IManagerForApplication(userId, _applicationLaunchProperty)); + + // Doesn't occur in our case. + // return ResultCode.NullObject; + + return ResultCode.Success; + } + + [Command(110)] + // StoreSaveDataThumbnail(nn::account::Uid, buffer) + public ResultCode StoreSaveDataThumbnail(ServiceCtx context) + { + if (_applicationLaunchProperty == null) + { + return ResultCode.InvalidArgument; + } + + UInt128 userId = new UInt128(context.RequestData.ReadBytes(0x10)); + + if (userId.IsNull) + { + return ResultCode.NullArgument; + } + + if (context.Request.SendBuff.Count == 0) + { + return ResultCode.InvalidInputBuffer; + } + + long inputPosition = context.Request.SendBuff[0].Position; + long inputSize = context.Request.SendBuff[0].Size; + + if (inputSize != 0x24000) + { + return ResultCode.InvalidInputBufferSize; + } + + byte[] thumbnailBuffer = context.Memory.ReadBytes(inputPosition, inputSize); + + // TODO: Store thumbnailBuffer somewhere, in save data 0x8000000000000010 ? + + Logger.PrintStub(LogClass.ServiceAcc); + + return ResultCode.Success; + } + + [Command(111)] + // ClearSaveDataThumbnail(nn::account::Uid) + public ResultCode ClearSaveDataThumbnail(ServiceCtx context) + { + if (_applicationLaunchProperty == null) + { + return ResultCode.InvalidArgument; + } + + UInt128 userId = new UInt128(context.RequestData.ReadBytes(0x10)); + + if (userId.IsNull) + { + return ResultCode.NullArgument; + } + + // TODO: Clear the Thumbnail somewhere, in save data 0x8000000000000010 ? + + Logger.PrintStub(LogClass.ServiceAcc); + + return ResultCode.Success; + } + + [Command(150)] // 6.0.0+ + // IsUserAccountSwitchLocked() -> bool + public ResultCode IsUserAccountSwitchLocked(ServiceCtx context) + { + // TODO : Validate the following check. + /* + if (_applicationLaunchProperty != null) + { + return ResultCode.ApplicationLaunchPropertyAlreadyInit; + } + */ + + // Account actually calls nn::arp::detail::IReader::GetApplicationControlProperty() with the current PID and store the result (NACP File) internally. + // But since we use LibHac and we load one Application at a time, it's not necessary. + + context.ResponseData.Write(context.Device.System.ControlData.Value.UserAccountSwitchLock); + + Logger.PrintStub(LogClass.ServiceAcc); + + return ResultCode.Success; + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForSystemService.cs b/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForSystemService.cs new file mode 100644 index 0000000000..f1972f6386 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForSystemService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Account.Acc +{ + [Service("acc:u1")] + class IAccountServiceForSystemService : IpcService + { + public IAccountServiceForSystemService(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Account/Acc/IBaasAccessTokenAccessor.cs b/Ryujinx.HLE/HOS/Services/Account/Acc/IBaasAccessTokenAccessor.cs new file mode 100644 index 0000000000..d28ea2753f --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Account/Acc/IBaasAccessTokenAccessor.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Account.Acc +{ + [Service("acc:aa")] + class IBaasAccessTokenAccessor : IpcService + { + public IBaasAccessTokenAccessor(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Account/Acc/IManagerForApplication.cs b/Ryujinx.HLE/HOS/Services/Account/Acc/IManagerForApplication.cs new file mode 100644 index 0000000000..aa9e07bde6 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Account/Acc/IManagerForApplication.cs @@ -0,0 +1,40 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Services.Arp; +using Ryujinx.HLE.Utilities; + +namespace Ryujinx.HLE.HOS.Services.Account.Acc +{ + class IManagerForApplication : IpcService + { + private UInt128 _userId; + private ApplicationLaunchProperty _applicationLaunchProperty; + + public IManagerForApplication(UInt128 userId, ApplicationLaunchProperty applicationLaunchProperty) + { + _userId = userId; + _applicationLaunchProperty = applicationLaunchProperty; + } + + [Command(0)] + // CheckAvailability() + public ResultCode CheckAvailability(ServiceCtx context) + { + Logger.PrintStub(LogClass.ServiceAcc); + + return ResultCode.Success; + } + + [Command(1)] + // GetAccountId() -> nn::account::NetworkServiceAccountId + public ResultCode GetAccountId(ServiceCtx context) + { + long networkServiceAccountId = 0xcafe; + + Logger.PrintStub(LogClass.ServiceAcc, new { networkServiceAccountId }); + + context.ResponseData.Write(networkServiceAccountId); + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Account/Acc/IProfile.cs b/Ryujinx.HLE/HOS/Services/Account/Acc/IProfile.cs new file mode 100644 index 0000000000..0470832b1a --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Account/Acc/IProfile.cs @@ -0,0 +1,80 @@ +using ARMeilleure.Memory; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.Utilities; +using System.IO; +using System.Reflection; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Account.Acc +{ + class IProfile : IpcService + { + private UserProfile _profile; + private Stream _profilePictureStream; + + public IProfile(UserProfile profile) + { + _profile = profile; + _profilePictureStream = Assembly.GetCallingAssembly().GetManifestResourceStream("Ryujinx.HLE.RyujinxProfileImage.jpg"); + } + + [Command(0)] + // Get() -> (nn::account::profile::ProfileBase, buffer) + public ResultCode Get(ServiceCtx context) + { + Logger.PrintStub(LogClass.ServiceAcc); + + long position = context.Request.ReceiveBuff[0].Position; + + MemoryHelper.FillWithZeros(context.Memory, position, 0x80); + + context.Memory.WriteInt32(position, 0); + context.Memory.WriteInt32(position + 4, 1); + context.Memory.WriteInt64(position + 8, 1); + + return GetBase(context); + } + + [Command(1)] + // GetBase() -> nn::account::profile::ProfileBase + public ResultCode GetBase(ServiceCtx context) + { + _profile.UserId.Write(context.ResponseData); + + context.ResponseData.Write(_profile.LastModifiedTimestamp); + + byte[] username = StringUtils.GetFixedLengthBytes(_profile.Name, 0x20, Encoding.UTF8); + + context.ResponseData.Write(username); + + return ResultCode.Success; + } + + [Command(10)] + // GetImageSize() -> u32 + public ResultCode GetImageSize(ServiceCtx context) + { + context.ResponseData.Write(_profilePictureStream.Length); + + return ResultCode.Success; + } + + [Command(11)] + // LoadImage() -> (u32, buffer) + public ResultCode LoadImage(ServiceCtx context) + { + long bufferPosition = context.Request.ReceiveBuff[0].Position; + long bufferLen = context.Request.ReceiveBuff[0].Size; + + byte[] profilePictureData = new byte[bufferLen]; + + _profilePictureStream.Read(profilePictureData, 0, profilePictureData.Length); + + context.Memory.WriteBytes(bufferPosition, profilePictureData); + + context.ResponseData.Write(_profilePictureStream.Length); + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Account/Acc/Types/AccountState.cs b/Ryujinx.HLE/HOS/Services/Account/Acc/Types/AccountState.cs new file mode 100644 index 0000000000..2382a25546 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Account/Acc/Types/AccountState.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Account.Acc +{ + public enum AccountState + { + Closed, + Open + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Account/Acc/Types/UserProfile.cs b/Ryujinx.HLE/HOS/Services/Account/Acc/Types/UserProfile.cs new file mode 100644 index 0000000000..25004c2435 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Account/Acc/Types/UserProfile.cs @@ -0,0 +1,37 @@ +using Ryujinx.HLE.Utilities; +using System; + +namespace Ryujinx.HLE.HOS.Services.Account.Acc +{ + class UserProfile + { + private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + public UInt128 UserId { get; private set; } + + public string Name { get; private set; } + + public long LastModifiedTimestamp { get; private set; } + + public AccountState AccountState { get; set; } + public AccountState OnlinePlayState { get; set; } + + public UserProfile(UInt128 userId, string name) + { + UserId = userId; + Name = name; + + LastModifiedTimestamp = 0; + + AccountState = AccountState.Closed; + OnlinePlayState = AccountState.Closed; + + UpdateTimestamp(); + } + + private void UpdateTimestamp() + { + LastModifiedTimestamp = (long)(DateTime.Now - Epoch).TotalSeconds; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Account/Dauth/IService.cs b/Ryujinx.HLE/HOS/Services/Account/Dauth/IService.cs new file mode 100644 index 0000000000..7230134929 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Account/Dauth/IService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Account.Dauth +{ + [Service("dauth:0")] // 5.0.0+ + class IService : IpcService + { + public IService(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Account/ResultCode.cs b/Ryujinx.HLE/HOS/Services/Account/ResultCode.cs new file mode 100644 index 0000000000..e56732ab17 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Account/ResultCode.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.HLE.HOS.Services.Account +{ + enum ResultCode + { + ModuleId = 124, + ErrorCodeShift = 9, + + Success = 0, + + NullArgument = (20 << ErrorCodeShift) | ModuleId, + InvalidArgument = (22 << ErrorCodeShift) | ModuleId, + NullInputBuffer = (30 << ErrorCodeShift) | ModuleId, + InvalidInputBufferSize = (31 << ErrorCodeShift) | ModuleId, + InvalidInputBuffer = (32 << ErrorCodeShift) | ModuleId, + ApplicationLaunchPropertyAlreadyInit = (41 << ErrorCodeShift) | ModuleId, + UserNotFound = (100 << ErrorCodeShift) | ModuleId, + NullObject = (302 << ErrorCodeShift) | ModuleId, + UnknownError1 = (341 << ErrorCodeShift) | ModuleId + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/ISystemAppletProxy.cs b/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/ISystemAppletProxy.cs new file mode 100644 index 0000000000..ecd5076f46 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/ISystemAppletProxy.cs @@ -0,0 +1,99 @@ +using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService +{ + class ISystemAppletProxy : IpcService + { + public ISystemAppletProxy() { } + + [Command(0)] + // GetCommonStateGetter() -> object + public ResultCode GetCommonStateGetter(ServiceCtx context) + { + MakeObject(context, new ICommonStateGetter(context.Device.System)); + + return ResultCode.Success; + } + + [Command(1)] + // GetSelfController() -> object + public ResultCode GetSelfController(ServiceCtx context) + { + MakeObject(context, new ISelfController(context.Device.System)); + + return ResultCode.Success; + } + + [Command(2)] + // GetWindowController() -> object + public ResultCode GetWindowController(ServiceCtx context) + { + MakeObject(context, new IWindowController()); + + return ResultCode.Success; + } + + [Command(3)] + // GetAudioController() -> object + public ResultCode GetAudioController(ServiceCtx context) + { + MakeObject(context, new IAudioController()); + + return ResultCode.Success; + } + + [Command(4)] + // GetDisplayController() -> object + public ResultCode GetDisplayController(ServiceCtx context) + { + MakeObject(context, new IDisplayController()); + + return ResultCode.Success; + } + + [Command(11)] + // GetLibraryAppletCreator() -> object + public ResultCode GetLibraryAppletCreator(ServiceCtx context) + { + MakeObject(context, new ILibraryAppletCreator()); + + return ResultCode.Success; + } + + [Command(20)] + // GetHomeMenuFunctions() -> object + public ResultCode GetHomeMenuFunctions(ServiceCtx context) + { + MakeObject(context, new IHomeMenuFunctions(context.Device.System)); + + return ResultCode.Success; + } + + [Command(21)] + // GetGlobalStateController() -> object + public ResultCode GetGlobalStateController(ServiceCtx context) + { + MakeObject(context, new IGlobalStateController()); + + return ResultCode.Success; + } + + [Command(22)] + // GetApplicationCreator() -> object + public ResultCode GetApplicationCreator(ServiceCtx context) + { + MakeObject(context, new IApplicationCreator()); + + return ResultCode.Success; + } + + [Command(1000)] + // GetDebugFunctions() -> object + public ResultCode GetDebugFunctions(ServiceCtx context) + { + MakeObject(context, new IDebugFunctions()); + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletCreator/ILibraryAppletAccessor.cs b/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletCreator/ILibraryAppletAccessor.cs new file mode 100644 index 0000000000..9ebb0b99bd --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletCreator/ILibraryAppletAccessor.cs @@ -0,0 +1,165 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Applets; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Threading; +using System; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.LibraryAppletCreator +{ + class ILibraryAppletAccessor : IpcService + { + private IApplet _applet; + + private AppletSession _normalSession; + private AppletSession _interactiveSession; + + private KEvent _stateChangedEvent; + private KEvent _normalOutDataEvent; + private KEvent _interactiveOutDataEvent; + + public ILibraryAppletAccessor(AppletId appletId, Horizon system) + { + _stateChangedEvent = new KEvent(system); + _normalOutDataEvent = new KEvent(system); + _interactiveOutDataEvent = new KEvent(system); + + _applet = AppletManager.Create(appletId, system); + + _normalSession = new AppletSession(); + _interactiveSession = new AppletSession(); + + _applet.AppletStateChanged += OnAppletStateChanged; + _normalSession.DataAvailable += OnNormalOutData; + _interactiveSession.DataAvailable += OnInteractiveOutData; + + Logger.PrintInfo(LogClass.ServiceAm, $"Applet '{appletId}' created."); + } + + private void OnAppletStateChanged(object sender, EventArgs e) + { + _stateChangedEvent.WritableEvent.Signal(); + } + + private void OnNormalOutData(object sender, EventArgs e) + { + _normalOutDataEvent.WritableEvent.Signal(); + } + + private void OnInteractiveOutData(object sender, EventArgs e) + { + _interactiveOutDataEvent.WritableEvent.Signal(); + } + + [Command(0)] + // GetAppletStateChangedEvent() -> handle + public ResultCode GetAppletStateChangedEvent(ServiceCtx context) + { + if (context.Process.HandleTable.GenerateHandle(_stateChangedEvent.ReadableEvent, out int handle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle); + + return ResultCode.Success; + } + + [Command(10)] + // Start() + public ResultCode Start(ServiceCtx context) + { + return (ResultCode)_applet.Start(_normalSession.GetConsumer(), + _interactiveSession.GetConsumer()); + } + + [Command(30)] + // GetResult() + public ResultCode GetResult(ServiceCtx context) + { + return (ResultCode)_applet.GetResult(); + } + + [Command(100)] + // PushInData(object) + public ResultCode PushInData(ServiceCtx context) + { + IStorage data = GetObject(context, 0); + + _normalSession.Push(data.Data); + + return ResultCode.Success; + } + + [Command(101)] + // PopOutData() -> object + public ResultCode PopOutData(ServiceCtx context) + { + if(_normalSession.TryPop(out byte[] data)) + { + MakeObject(context, new IStorage(data)); + + _normalOutDataEvent.WritableEvent.Clear(); + + return ResultCode.Success; + } + + return ResultCode.NotAvailable; + } + + [Command(103)] + // PushInteractiveInData(object) + public ResultCode PushInteractiveInData(ServiceCtx context) + { + IStorage data = GetObject(context, 0); + + _interactiveSession.Push(data.Data); + + return ResultCode.Success; + } + + [Command(104)] + // PopInteractiveOutData() -> object + public ResultCode PopInteractiveOutData(ServiceCtx context) + { + if(_interactiveSession.TryPop(out byte[] data)) + { + MakeObject(context, new IStorage(data)); + + _interactiveOutDataEvent.WritableEvent.Clear(); + + return ResultCode.Success; + } + + return ResultCode.NotAvailable; + } + + [Command(105)] + // GetPopOutDataEvent() -> handle + public ResultCode GetPopOutDataEvent(ServiceCtx context) + { + if (context.Process.HandleTable.GenerateHandle(_normalOutDataEvent.ReadableEvent, out int handle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle); + + return ResultCode.Success; + } + + [Command(106)] + // GetPopInteractiveOutDataEvent() -> handle + public ResultCode GetPopInteractiveOutDataEvent(ServiceCtx context) + { + if (context.Process.HandleTable.GenerateHandle(_interactiveOutDataEvent.ReadableEvent, out int handle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle); + + return ResultCode.Success; + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IApplicationCreator.cs b/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IApplicationCreator.cs new file mode 100644 index 0000000000..79e5b05075 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IApplicationCreator.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy +{ + class IApplicationCreator : IpcService + { + public IApplicationCreator() { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IAudioController.cs b/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IAudioController.cs new file mode 100644 index 0000000000..e630c80d94 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IAudioController.cs @@ -0,0 +1,66 @@ +using Ryujinx.Common.Logging; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy +{ + class IAudioController : IpcService + { + public IAudioController() { } + + [Command(0)] + // SetExpectedMasterVolume(f32, f32) + public ResultCode SetExpectedMasterVolume(ServiceCtx context) + { + float appletVolume = context.RequestData.ReadSingle(); + float libraryAppletVolume = context.RequestData.ReadSingle(); + + Logger.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [Command(1)] + // GetMainAppletExpectedMasterVolume() -> f32 + public ResultCode GetMainAppletExpectedMasterVolume(ServiceCtx context) + { + context.ResponseData.Write(1f); + + Logger.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [Command(2)] + // GetLibraryAppletExpectedMasterVolume() -> f32 + public ResultCode GetLibraryAppletExpectedMasterVolume(ServiceCtx context) + { + context.ResponseData.Write(1f); + + Logger.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [Command(3)] + // ChangeMainAppletMasterVolume(f32, u64) + public ResultCode ChangeMainAppletMasterVolume(ServiceCtx context) + { + float unknown0 = context.RequestData.ReadSingle(); + long unknown1 = context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [Command(4)] + // SetTransparentVolumeRate(f32) + public ResultCode SetTransparentVolumeRate(ServiceCtx context) + { + float unknown0 = context.RequestData.ReadSingle(); + + Logger.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ICommonStateGetter.cs b/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ICommonStateGetter.cs new file mode 100644 index 0000000000..085d9fe600 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ICommonStateGetter.cs @@ -0,0 +1,142 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Apm; +using System; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy +{ + class ICommonStateGetter : IpcService + { + private KEvent _displayResolutionChangeEvent; + + private CpuBoostMode _cpuBoostMode = CpuBoostMode.Disabled; + + public ICommonStateGetter(Horizon system) + { + _displayResolutionChangeEvent = new KEvent(system); + } + + [Command(0)] + // GetEventHandle() -> handle + public ResultCode GetEventHandle(ServiceCtx context) + { + KEvent Event = context.Device.System.AppletState.MessageEvent; + + if (context.Process.HandleTable.GenerateHandle(Event.ReadableEvent, out int handle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle); + + return ResultCode.Success; + } + + [Command(1)] + // ReceiveMessage() -> nn::am::AppletMessage + public ResultCode ReceiveMessage(ServiceCtx context) + { + if (!context.Device.System.AppletState.TryDequeueMessage(out MessageInfo message)) + { + return ResultCode.NoMessages; + } + + context.ResponseData.Write((int)message); + + return ResultCode.Success; + } + + [Command(5)] + // GetOperationMode() -> u8 + public ResultCode GetOperationMode(ServiceCtx context) + { + OperationMode mode = context.Device.System.State.DockedMode + ? OperationMode.Docked + : OperationMode.Handheld; + + context.ResponseData.Write((byte)mode); + + return ResultCode.Success; + } + + [Command(6)] + // GetPerformanceMode() -> u32 + public ResultCode GetPerformanceMode(ServiceCtx context) + { + PerformanceMode mode = context.Device.System.State.DockedMode + ? PerformanceMode.Docked + : PerformanceMode.Handheld; + + context.ResponseData.Write((int)mode); + + return ResultCode.Success; + } + + [Command(8)] + // GetBootMode() -> u8 + public ResultCode GetBootMode(ServiceCtx context) + { + context.ResponseData.Write((byte)0); //Unknown value. + + Logger.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [Command(9)] + // GetCurrentFocusState() -> u8 + public ResultCode GetCurrentFocusState(ServiceCtx context) + { + context.ResponseData.Write((byte)context.Device.System.AppletState.FocusState); + + return ResultCode.Success; + } + + [Command(60)] // 3.0.0+ + // GetDefaultDisplayResolution() -> (u32, u32) + public ResultCode GetDefaultDisplayResolution(ServiceCtx context) + { + context.ResponseData.Write(1280); + context.ResponseData.Write(720); + + return ResultCode.Success; + } + + [Command(61)] // 3.0.0+ + // GetDefaultDisplayResolutionChangeEvent() -> handle + public ResultCode GetDefaultDisplayResolutionChangeEvent(ServiceCtx context) + { + if (context.Process.HandleTable.GenerateHandle(_displayResolutionChangeEvent.ReadableEvent, out int handle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle); + + Logger.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [Command(66)] // 6.0.0+ + // SetCpuBoostMode(u32 cpu_boost_mode) + public ResultCode SetCpuBoostMode(ServiceCtx context) + { + uint cpuBoostMode = context.RequestData.ReadUInt32(); + + if (cpuBoostMode > 1) + { + return ResultCode.CpuBoostModeInvalid; + } + + _cpuBoostMode = (CpuBoostMode)cpuBoostMode; + + // NOTE: There is a condition variable after the assignment, probably waiting something with apm:sys service (SetCpuBoostMode call?). + // Since we will probably never support CPU boost things, it's not needed to implement more. + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IDebugFunctions.cs b/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IDebugFunctions.cs new file mode 100644 index 0000000000..51a112fd06 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IDebugFunctions.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy +{ + class IDebugFunctions : IpcService + { + public IDebugFunctions() { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IDisplayController.cs b/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IDisplayController.cs new file mode 100644 index 0000000000..2b04dbb5ac --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IDisplayController.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy +{ + class IDisplayController : IpcService + { + public IDisplayController() { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IGlobalStateController.cs b/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IGlobalStateController.cs new file mode 100644 index 0000000000..24eeefb988 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IGlobalStateController.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy +{ + class IGlobalStateController : IpcService + { + public IGlobalStateController() { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IHomeMenuFunctions.cs b/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IHomeMenuFunctions.cs new file mode 100644 index 0000000000..a5819132db --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IHomeMenuFunctions.cs @@ -0,0 +1,44 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Threading; +using System; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy +{ + class IHomeMenuFunctions : IpcService + { + private KEvent _channelEvent; + + public IHomeMenuFunctions(Horizon system) + { + // TODO: Signal this Event somewhere in future. + _channelEvent = new KEvent(system); + } + + [Command(10)] + // RequestToGetForeground() + public ResultCode RequestToGetForeground(ServiceCtx context) + { + Logger.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [Command(21)] + // GetPopFromGeneralChannelEvent() -> handle + public ResultCode GetPopFromGeneralChannelEvent(ServiceCtx context) + { + if (context.Process.HandleTable.GenerateHandle(_channelEvent.ReadableEvent, out int handle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle); + + Logger.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ILibraryAppletCreator.cs b/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ILibraryAppletCreator.cs new file mode 100644 index 0000000000..564bde0971 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ILibraryAppletCreator.cs @@ -0,0 +1,47 @@ +using Ryujinx.HLE.HOS.Applets; +using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.LibraryAppletCreator; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy +{ + class ILibraryAppletCreator : IpcService + { + public ILibraryAppletCreator() { } + + [Command(0)] + // CreateLibraryApplet(u32, u32) -> object + public ResultCode CreateLibraryApplet(ServiceCtx context) + { + AppletId appletId = (AppletId)context.RequestData.ReadInt32(); + int libraryAppletMode = context.RequestData.ReadInt32(); + + MakeObject(context, new ILibraryAppletAccessor(appletId, context.Device.System)); + + return ResultCode.Success; + } + + [Command(10)] + // CreateStorage(u64) -> object + public ResultCode CreateStorage(ServiceCtx context) + { + long size = context.RequestData.ReadInt64(); + + MakeObject(context, new IStorage(new byte[size])); + + return ResultCode.Success; + } + + [Command(11)] + // CreateTransferMemoryStorage(b8, u64, handle) -> object + public ResultCode CreateTransferMemoryStorage(ServiceCtx context) + { + bool unknown = context.RequestData.ReadBoolean(); + long size = context.RequestData.ReadInt64(); + + // NOTE: We don't support TransferMemory for now. + + MakeObject(context, new IStorage(new byte[size])); + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ISelfController.cs b/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ISelfController.cs new file mode 100644 index 0000000000..62f1beec23 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ISelfController.cs @@ -0,0 +1,200 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Threading; +using System; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy +{ + class ISelfController : IpcService + { + private KEvent _libraryAppletLaunchableEvent; + + private KEvent _accumulatedSuspendedTickChangedEvent; + private int _accumulatedSuspendedTickChangedEventHandle = 0; + + private int _idleTimeDetectionExtension; + + public ISelfController(Horizon system) + { + _libraryAppletLaunchableEvent = new KEvent(system); + } + + [Command(0)] + // Exit() + public ResultCode Exit(ServiceCtx context) + { + Logger.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [Command(1)] + // LockExit() + public ResultCode LockExit(ServiceCtx context) + { + Logger.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [Command(2)] + // UnlockExit() + public ResultCode UnlockExit(ServiceCtx context) + { + Logger.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [Command(9)] + // GetLibraryAppletLaunchableEvent() -> handle + public ResultCode GetLibraryAppletLaunchableEvent(ServiceCtx context) + { + _libraryAppletLaunchableEvent.ReadableEvent.Signal(); + + if (context.Process.HandleTable.GenerateHandle(_libraryAppletLaunchableEvent.ReadableEvent, out int handle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle); + + Logger.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [Command(10)] + // SetScreenShotPermission(u32) + public ResultCode SetScreenShotPermission(ServiceCtx context) + { + bool enable = context.RequestData.ReadByte() != 0; + + Logger.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [Command(11)] + // SetOperationModeChangedNotification(b8) + public ResultCode SetOperationModeChangedNotification(ServiceCtx context) + { + bool enable = context.RequestData.ReadByte() != 0; + + Logger.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [Command(12)] + // SetPerformanceModeChangedNotification(b8) + public ResultCode SetPerformanceModeChangedNotification(ServiceCtx context) + { + bool enable = context.RequestData.ReadByte() != 0; + + Logger.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [Command(13)] + // SetFocusHandlingMode(b8, b8, b8) + public ResultCode SetFocusHandlingMode(ServiceCtx context) + { + bool flag1 = context.RequestData.ReadByte() != 0; + bool flag2 = context.RequestData.ReadByte() != 0; + bool flag3 = context.RequestData.ReadByte() != 0; + + Logger.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [Command(14)] + // SetRestartMessageEnabled(b8) + public ResultCode SetRestartMessageEnabled(ServiceCtx context) + { + bool enable = context.RequestData.ReadByte() != 0; + + Logger.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [Command(16)] // 2.0.0+ + // SetOutOfFocusSuspendingEnabled(b8) + public ResultCode SetOutOfFocusSuspendingEnabled(ServiceCtx context) + { + bool enable = context.RequestData.ReadByte() != 0; + + Logger.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [Command(19)] // 3.0.0+ + public ResultCode SetScreenShotImageOrientation(ServiceCtx context) + { + int orientation = context.RequestData.ReadInt32(); + + Logger.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [Command(50)] + // SetHandlesRequestToDisplay(b8) + public ResultCode SetHandlesRequestToDisplay(ServiceCtx context) + { + bool enable = context.RequestData.ReadByte() != 0; + + Logger.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [Command(62)] + // SetIdleTimeDetectionExtension(u32) + public ResultCode SetIdleTimeDetectionExtension(ServiceCtx context) + { + _idleTimeDetectionExtension = context.RequestData.ReadInt32(); + + Logger.PrintStub(LogClass.ServiceAm, new { _idleTimeDetectionExtension }); + + return ResultCode.Success; + } + + [Command(63)] + // GetIdleTimeDetectionExtension() -> u32 + public ResultCode GetIdleTimeDetectionExtension(ServiceCtx context) + { + context.ResponseData.Write(_idleTimeDetectionExtension); + + Logger.PrintStub(LogClass.ServiceAm, new { _idleTimeDetectionExtension }); + + return ResultCode.Success; + } + + [Command(91)] // 6.0.0+ + // GetAccumulatedSuspendedTickChangedEvent() -> handle + public ResultCode GetAccumulatedSuspendedTickChangedEvent(ServiceCtx context) + { + if (_accumulatedSuspendedTickChangedEventHandle == 0) + { + _accumulatedSuspendedTickChangedEvent = new KEvent(context.Device.System); + + _accumulatedSuspendedTickChangedEvent.ReadableEvent.Signal(); + + if (context.Process.HandleTable.GenerateHandle(_accumulatedSuspendedTickChangedEvent.ReadableEvent, out _accumulatedSuspendedTickChangedEventHandle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_accumulatedSuspendedTickChangedEventHandle); + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IWindowController.cs b/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IWindowController.cs new file mode 100644 index 0000000000..449658cfed --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IWindowController.cs @@ -0,0 +1,29 @@ +using Ryujinx.Common.Logging; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy +{ + class IWindowController : IpcService + { + public IWindowController() { } + + [Command(1)] + // GetAppletResourceUserId() -> nn::applet::AppletResourceUserId + public ResultCode GetAppletResourceUserId(ServiceCtx context) + { + Logger.PrintStub(LogClass.ServiceAm); + + context.ResponseData.Write(0L); + + return ResultCode.Success; + } + + [Command(10)] + // AcquireForegroundRights() + public ResultCode AcquireForegroundRights(ServiceCtx context) + { + Logger.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/FocusState.cs b/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/FocusState.cs new file mode 100644 index 0000000000..dfd7d7f2c2 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/FocusState.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy +{ + enum FocusState + { + InFocus = 1, + OutOfFocus = 2 + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Am/MessageInfo.cs b/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/MessageInfo.cs similarity index 61% rename from Ryujinx.HLE/OsHle/Services/Am/MessageInfo.cs rename to Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/MessageInfo.cs index bae985fbaf..ff699315a1 100644 --- a/Ryujinx.HLE/OsHle/Services/Am/MessageInfo.cs +++ b/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/MessageInfo.cs @@ -1,4 +1,4 @@ -namespace Ryujinx.HLE.OsHle.Services.Am +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy { enum MessageInfo { diff --git a/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/OperationMode.cs b/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/OperationMode.cs new file mode 100644 index 0000000000..a82ed476ef --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/OperationMode.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy +{ + enum OperationMode + { + Handheld = 0, + Docked = 1 + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Am/AppletAE/AppletFifo.cs b/Ryujinx.HLE/HOS/Services/Am/AppletAE/AppletFifo.cs new file mode 100644 index 0000000000..fb16c86e76 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Am/AppletAE/AppletFifo.cs @@ -0,0 +1,120 @@ +using System; +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE +{ + internal class AppletFifo : IAppletFifo + { + private ConcurrentQueue _dataQueue; + + public event EventHandler DataAvailable; + + public bool IsSynchronized + { + get { return ((ICollection)_dataQueue).IsSynchronized; } + } + + public object SyncRoot + { + get { return ((ICollection)_dataQueue).SyncRoot; } + } + + public int Count + { + get { return _dataQueue.Count; } + } + + public AppletFifo() + { + _dataQueue = new ConcurrentQueue(); + } + + public void Push(T item) + { + _dataQueue.Enqueue(item); + + DataAvailable?.Invoke(this, null); + } + + public bool TryAdd(T item) + { + try + { + this.Push(item); + + return true; + } + catch + { + return false; + } + } + + public T Pop() + { + if (_dataQueue.TryDequeue(out T result)) + { + return result; + } + + throw new InvalidOperationException("FIFO empty."); + } + + public bool TryPop(out T result) + { + return _dataQueue.TryDequeue(out result); + } + + public bool TryTake(out T item) + { + return this.TryPop(out item); + } + + public T Peek() + { + if (_dataQueue.TryPeek(out T result)) + { + return result; + } + + throw new InvalidOperationException("FIFO empty."); + } + + public bool TryPeek(out T result) + { + return _dataQueue.TryPeek(out result); + } + + public void Clear() + { + _dataQueue.Clear(); + } + + public T[] ToArray() + { + return _dataQueue.ToArray(); + } + + public void CopyTo(T[] array, int arrayIndex) + { + _dataQueue.CopyTo(array, arrayIndex); + } + + public void CopyTo(Array array, int index) + { + this.CopyTo((T[])array, index); + } + + public IEnumerator GetEnumerator() + { + return _dataQueue.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return _dataQueue.GetEnumerator(); + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Am/AppletAE/AppletSession.cs b/Ryujinx.HLE/HOS/Services/Am/AppletAE/AppletSession.cs new file mode 100644 index 0000000000..6c9197b3ed --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Am/AppletAE/AppletSession.cs @@ -0,0 +1,77 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE +{ + internal class AppletSession + { + private IAppletFifo _inputData; + private IAppletFifo _outputData; + + public event EventHandler DataAvailable; + + public int Length + { + get { return _inputData.Count; } + } + + public AppletSession() + : this(new AppletFifo(), + new AppletFifo()) + { } + + public AppletSession( + IAppletFifo inputData, + IAppletFifo outputData) + { + _inputData = inputData; + _outputData = outputData; + + _inputData.DataAvailable += OnDataAvailable; + } + + private void OnDataAvailable(object sender, EventArgs e) + { + DataAvailable?.Invoke(this, null); + } + + public void Push(byte[] item) + { + if (!this.TryPush(item)) + { + // TODO(jduncanator): Throw a proper exception + throw new InvalidOperationException(); + } + } + + public bool TryPush(byte[] item) + { + return _outputData.TryAdd(item); + } + + public byte[] Pop() + { + if (this.TryPop(out byte[] item)) + { + return item; + } + + throw new InvalidOperationException("Input data empty."); + } + + public bool TryPop(out byte[] item) + { + return _inputData.TryTake(out item); + } + + /// + /// This returns an AppletSession that can be used at the + /// other end of the pipe. Pushing data into this new session + /// will put it in the first session's input buffer, and vice + /// versa. + /// + public AppletSession GetConsumer() + { + return new AppletSession(this._outputData, this._inputData); + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Am/AppletAE/IAllSystemAppletProxiesService.cs b/Ryujinx.HLE/HOS/Services/Am/AppletAE/IAllSystemAppletProxiesService.cs new file mode 100644 index 0000000000..d29a8da443 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Am/AppletAE/IAllSystemAppletProxiesService.cs @@ -0,0 +1,19 @@ +using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE +{ + [Service("appletAE")] + class IAllSystemAppletProxiesService : IpcService + { + public IAllSystemAppletProxiesService(ServiceCtx context) { } + + [Command(100)] + // OpenSystemAppletProxy(u64, pid, handle) -> object + public ResultCode OpenSystemAppletProxy(ServiceCtx context) + { + MakeObject(context, new ISystemAppletProxy()); + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Am/AppletAE/IAppletFifo.cs b/Ryujinx.HLE/HOS/Services/Am/AppletAE/IAppletFifo.cs new file mode 100644 index 0000000000..ca79bac7af --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Am/AppletAE/IAppletFifo.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Concurrent; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE +{ + interface IAppletFifo : IProducerConsumerCollection + { + event EventHandler DataAvailable; + } +} diff --git a/Ryujinx.HLE/HOS/Services/Am/AppletAE/IStorage.cs b/Ryujinx.HLE/HOS/Services/Am/AppletAE/IStorage.cs new file mode 100644 index 0000000000..37514275db --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Am/AppletAE/IStorage.cs @@ -0,0 +1,21 @@ +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE +{ + class IStorage : IpcService + { + public byte[] Data { get; private set; } + + public IStorage(byte[] data) + { + Data = data; + } + + [Command(0)] + // Open() -> object + public ResultCode Open(ServiceCtx context) + { + MakeObject(context, new IStorageAccessor(this)); + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Am/AppletAE/IStorageAccessor.cs b/Ryujinx.HLE/HOS/Services/Am/AppletAE/IStorageAccessor.cs new file mode 100644 index 0000000000..5013e2e3d0 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Am/AppletAE/IStorageAccessor.cs @@ -0,0 +1,79 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE +{ + class IStorageAccessor : IpcService + { + private IStorage _storage; + + public IStorageAccessor(IStorage storage) + { + _storage = storage; + } + + [Command(0)] + // GetSize() -> u64 + public ResultCode GetSize(ServiceCtx context) + { + context.ResponseData.Write((long)_storage.Data.Length); + + return ResultCode.Success; + } + + [Command(10)] + // Write(u64, buffer) + public ResultCode Write(ServiceCtx context) + { + long writePosition = context.RequestData.ReadInt64(); + + if (writePosition > _storage.Data.Length) + { + return ResultCode.OutOfBounds; + } + + (long position, long size) = context.Request.GetBufferType0x21(); + + size = Math.Min(size, _storage.Data.Length - writePosition); + + if (size > 0) + { + long maxSize = _storage.Data.Length - writePosition; + + if (size > maxSize) + { + size = maxSize; + } + + byte[] data = context.Memory.ReadBytes(position, size); + + Buffer.BlockCopy(data, 0, _storage.Data, (int)writePosition, (int)size); + } + + return ResultCode.Success; + } + + [Command(11)] + // Read(u64) -> buffer + public ResultCode Read(ServiceCtx context) + { + long readPosition = context.RequestData.ReadInt64(); + + if (readPosition > _storage.Data.Length) + { + return ResultCode.OutOfBounds; + } + + (long position, long size) = context.Request.GetBufferType0x22(); + + size = Math.Min(size, _storage.Data.Length - readPosition); + + byte[] data = new byte[size]; + + Buffer.BlockCopy(_storage.Data, (int)readPosition, data, 0, (int)size); + + context.Memory.WriteBytes(position, data); + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Am/AppletAE/Storage/StorageHelper.cs b/Ryujinx.HLE/HOS/Services/Am/AppletAE/Storage/StorageHelper.cs new file mode 100644 index 0000000000..9c96221e2e --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Am/AppletAE/Storage/StorageHelper.cs @@ -0,0 +1,27 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.Storage +{ + class StorageHelper + { + private const uint LaunchParamsMagic = 0xc79497ca; + + public static byte[] MakeLaunchParams() + { + // Size needs to be at least 0x88 bytes otherwise application errors. + using (MemoryStream ms = new MemoryStream()) + { + BinaryWriter writer = new BinaryWriter(ms); + + ms.SetLength(0x88); + + writer.Write(LaunchParamsMagic); + writer.Write(1); // IsAccountSelected? Only lower 8 bits actually used. + writer.Write(1L); // User Id Low (note: User Id needs to be != 0) + writer.Write(0L); // User Id High + + return ms.ToArray(); + } + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/AppletId.cs b/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/AppletId.cs new file mode 100644 index 0000000000..917f68658b --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/AppletId.cs @@ -0,0 +1,27 @@ +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE +{ + enum AppletId + { + Application = 0x01, + OverlayDisplay = 0x02, + QLaunch = 0x03, + Starter = 0x04, + Auth = 0x0A, + Cabinet = 0x0B, + Controller = 0x0C, + DataErase = 0x0D, + Error = 0x0E, + NetConnect = 0x0F, + PlayerSelect = 0x10, + SoftwareKeyboard = 0x11, + MiiEdit = 0x12, + LibAppletWeb = 0x13, + LibAppletShop = 0x14, + PhotoViewer = 0x15, + Settings = 0x16, + LibAppletOff = 0x17, + LibAppletWhitelisted = 0x18, + LibAppletAuth = 0x19, + MyPage = 0x1A + } +} diff --git a/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs b/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs new file mode 100644 index 0000000000..904264aa87 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs @@ -0,0 +1,186 @@ +using LibHac; +using LibHac.Account; +using LibHac.Common; +using LibHac.Ncm; +using LibHac.Ns; +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Am.AppletAE.Storage; +using Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService; +using System; + +using static LibHac.Fs.ApplicationSaveDataManagement; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy +{ + class IApplicationFunctions : IpcService + { + private KEvent _gpuErrorDetectedSystemEvent; + + public IApplicationFunctions(Horizon system) + { + _gpuErrorDetectedSystemEvent = new KEvent(system); + } + + [Command(1)] + // PopLaunchParameter(u32) -> object + public ResultCode PopLaunchParameter(ServiceCtx context) + { + // Only the first 0x18 bytes of the Data seems to be actually used. + MakeObject(context, new AppletAE.IStorage(StorageHelper.MakeLaunchParams())); + + return ResultCode.Success; + } + + [Command(20)] + // EnsureSaveData(nn::account::Uid) -> u64 + public ResultCode EnsureSaveData(ServiceCtx context) + { + Uid userId = context.RequestData.ReadStruct(); + TitleId titleId = new TitleId(context.Process.TitleId); + + BlitStruct controlHolder = context.Device.System.ControlData; + + ref ApplicationControlProperty control = ref controlHolder.Value; + + if (Util.IsEmpty(controlHolder.ByteSpan)) + { + // If the current application doesn't have a loaded control property, create a dummy one + // and set the savedata sizes so a user savedata will be created. + control = ref new BlitStruct(1).Value; + + // The set sizes don't actually matter as long as they're non-zero because we use directory savedata. + control.UserAccountSaveDataSize = 0x4000; + control.UserAccountSaveDataJournalSize = 0x4000; + + Logger.PrintWarning(LogClass.ServiceAm, + "No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games."); + } + + Result result = EnsureApplicationSaveData(context.Device.System.FsClient, out long requiredSize, titleId, + ref context.Device.System.ControlData.Value, ref userId); + + context.ResponseData.Write(requiredSize); + + return (ResultCode)result.Value; + } + + [Command(21)] + // GetDesiredLanguage() -> nn::settings::LanguageCode + public ResultCode GetDesiredLanguage(ServiceCtx context) + { + context.ResponseData.Write(context.Device.System.State.DesiredLanguageCode); + + return ResultCode.Success; + } + + [Command(22)] + // SetTerminateResult(u32) + public ResultCode SetTerminateResult(ServiceCtx context) + { + int errorCode = context.RequestData.ReadInt32(); + string result = GetFormattedErrorCode(errorCode); + + Logger.PrintInfo(LogClass.ServiceAm, $"Result = 0x{errorCode:x8} ({result})."); + + return ResultCode.Success; + } + + private string GetFormattedErrorCode(int errorCode) + { + int module = (errorCode >> 0) & 0x1ff; + int description = (errorCode >> 9) & 0x1fff; + + return $"{(2000 + module):d4}-{description:d4}"; + } + + [Command(23)] + // GetDisplayVersion() -> nn::oe::DisplayVersion + public ResultCode GetDisplayVersion(ServiceCtx context) + { + // FIXME: Need to check correct version on a switch. + context.ResponseData.Write(1L); + context.ResponseData.Write(0L); + + return ResultCode.Success; + } + + [Command(40)] + // NotifyRunning() -> b8 + public ResultCode NotifyRunning(ServiceCtx context) + { + context.ResponseData.Write(1); + + return ResultCode.Success; + } + + [Command(50)] // 2.0.0+ + // GetPseudoDeviceId() -> nn::util::Uuid + public ResultCode GetPseudoDeviceId(ServiceCtx context) + { + context.ResponseData.Write(0L); + context.ResponseData.Write(0L); + + Logger.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [Command(66)] // 3.0.0+ + // InitializeGamePlayRecording(u64, handle) + public ResultCode InitializeGamePlayRecording(ServiceCtx context) + { + Logger.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [Command(67)] // 3.0.0+ + // SetGamePlayRecordingState(u32) + public ResultCode SetGamePlayRecordingState(ServiceCtx context) + { + int state = context.RequestData.ReadInt32(); + + Logger.PrintStub(LogClass.ServiceAm, new { state }); + + return ResultCode.Success; + } + + [Command(110)] // 5.0.0+ + // QueryApplicationPlayStatistics(buffer title_id_list) -> (buffer entries, s32 entries_count) + public ResultCode QueryApplicationPlayStatistics(ServiceCtx context) + { + // TODO: Call pdm:qry cmd 13 when IPC call between services will be implemented. + return (ResultCode)QueryPlayStatisticsManager.GetPlayStatistics(context); + } + + [Command(111)] // 6.0.0+ + // QueryApplicationPlayStatisticsByUid(nn::account::Uid, buffer title_id_list) -> (buffer entries, s32 entries_count) + public ResultCode QueryApplicationPlayStatisticsByUid(ServiceCtx context) + { + // TODO: Call pdm:qry cmd 16 when IPC call between services will be implemented. + return (ResultCode)QueryPlayStatisticsManager.GetPlayStatistics(context, true); + } + + [Command(130)] // 8.0.0+ + // GetGpuErrorDetectedSystemEvent() -> handle + public ResultCode GetGpuErrorDetectedSystemEvent(ServiceCtx context) + { + if (context.Process.HandleTable.GenerateHandle(_gpuErrorDetectedSystemEvent.ReadableEvent, out int gpuErrorDetectedSystemEventHandle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(gpuErrorDetectedSystemEventHandle); + + // NOTE: This is used by "sdk" NSO during applet-application initialization. + // A seperate thread is setup where event-waiting is handled. + // When the Event is signaled, official sw will assert. + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/IApplicationProxy.cs b/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/IApplicationProxy.cs new file mode 100644 index 0000000000..eda73e22fb --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/IApplicationProxy.cs @@ -0,0 +1,82 @@ +using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy; +using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService +{ + class IApplicationProxy : IpcService + { + public IApplicationProxy() { } + + [Command(0)] + // GetCommonStateGetter() -> object + public ResultCode GetCommonStateGetter(ServiceCtx context) + { + MakeObject(context, new ICommonStateGetter(context.Device.System)); + + return ResultCode.Success; + } + + [Command(1)] + // GetSelfController() -> object + public ResultCode GetSelfController(ServiceCtx context) + { + MakeObject(context, new ISelfController(context.Device.System)); + + return ResultCode.Success; + } + + [Command(2)] + // GetWindowController() -> object + public ResultCode GetWindowController(ServiceCtx context) + { + MakeObject(context, new IWindowController()); + + return ResultCode.Success; + } + + [Command(3)] + // GetAudioController() -> object + public ResultCode GetAudioController(ServiceCtx context) + { + MakeObject(context, new IAudioController()); + + return ResultCode.Success; + } + + [Command(4)] + // GetDisplayController() -> object + public ResultCode GetDisplayController(ServiceCtx context) + { + MakeObject(context, new IDisplayController()); + + return ResultCode.Success; + } + + [Command(11)] + // GetLibraryAppletCreator() -> object + public ResultCode GetLibraryAppletCreator(ServiceCtx context) + { + MakeObject(context, new ILibraryAppletCreator()); + + return ResultCode.Success; + } + + [Command(20)] + // GetApplicationFunctions() -> object + public ResultCode GetApplicationFunctions(ServiceCtx context) + { + MakeObject(context, new IApplicationFunctions(context.Device.System)); + + return ResultCode.Success; + } + + [Command(1000)] + // GetDebugFunctions() -> object + public ResultCode GetDebugFunctions(ServiceCtx context) + { + MakeObject(context, new IDebugFunctions()); + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Am/AppletOE/IApplicationProxyService.cs b/Ryujinx.HLE/HOS/Services/Am/AppletOE/IApplicationProxyService.cs new file mode 100644 index 0000000000..0aee24fe93 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Am/AppletOE/IApplicationProxyService.cs @@ -0,0 +1,19 @@ +using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService; + +namespace Ryujinx.HLE.HOS.Services.Am +{ + [Service("appletOE")] + class IApplicationProxyService : IpcService + { + public IApplicationProxyService(ServiceCtx context) { } + + [Command(0)] + // OpenApplicationProxy(u64, pid, handle) -> object + public ResultCode OpenApplicationProxy(ServiceCtx context) + { + MakeObject(context, new IApplicationProxy()); + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Am/Idle/IPolicyManagerSystem.cs b/Ryujinx.HLE/HOS/Services/Am/Idle/IPolicyManagerSystem.cs new file mode 100644 index 0000000000..8c72319c3e --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Am/Idle/IPolicyManagerSystem.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Am.Idle +{ + [Service("idle:sys")] + class IPolicyManagerSystem : IpcService + { + public IPolicyManagerSystem(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Am/Omm/IOperationModeManager.cs b/Ryujinx.HLE/HOS/Services/Am/Omm/IOperationModeManager.cs new file mode 100644 index 0000000000..2856e6d7b3 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Am/Omm/IOperationModeManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Am.Omm +{ + [Service("omm")] + class IOperationModeManager : IpcService + { + public IOperationModeManager(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Am/ResultCode.cs b/Ryujinx.HLE/HOS/Services/Am/ResultCode.cs new file mode 100644 index 0000000000..d8979f4af9 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Am/ResultCode.cs @@ -0,0 +1,16 @@ +namespace Ryujinx.HLE.HOS.Services.Am +{ + enum ResultCode + { + ModuleId = 128, + ErrorCodeShift = 9, + + Success = 0, + + NotAvailable = (2 << ErrorCodeShift) | ModuleId, + NoMessages = (3 << ErrorCodeShift) | ModuleId, + ObjectInvalid = (500 << ErrorCodeShift) | ModuleId, + OutOfBounds = (503 << ErrorCodeShift) | ModuleId, + CpuBoostModeInvalid = (506 << ErrorCodeShift) | ModuleId + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Am/Spsm/IPowerStateInterface.cs b/Ryujinx.HLE/HOS/Services/Am/Spsm/IPowerStateInterface.cs new file mode 100644 index 0000000000..a393f76be4 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Am/Spsm/IPowerStateInterface.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Am.Spsm +{ + [Service("spsm")] + class IPowerStateInterface : IpcService + { + public IPowerStateInterface(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Am/Tcap/IManager.cs b/Ryujinx.HLE/HOS/Services/Am/Tcap/IManager.cs new file mode 100644 index 0000000000..b31ccf8a36 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Am/Tcap/IManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Am.Tcap +{ + [Service("tcap")] + class IManager : IpcService + { + public IManager(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Apm/IManager.cs b/Ryujinx.HLE/HOS/Services/Apm/IManager.cs new file mode 100644 index 0000000000..19fbcd44b8 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Apm/IManager.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.HLE.HOS.Services.Apm +{ + [Service("apm")] // 8.0.0+ + class IManager : IpcService + { + public IManager(ServiceCtx context) { } + + [Command(0)] + // OpenSession() -> object + public ResultCode OpenSession(ServiceCtx context) + { + MakeObject(context, new ISession()); + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Apm/ISession.cs b/Ryujinx.HLE/HOS/Services/Apm/ISession.cs new file mode 100644 index 0000000000..10c92e40d8 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Apm/ISession.cs @@ -0,0 +1,32 @@ +using Ryujinx.Common.Logging; + +namespace Ryujinx.HLE.HOS.Services.Apm +{ + class ISession : IpcService + { + public ISession() { } + + [Command(0)] + // SetPerformanceConfiguration(nn::apm::PerformanceMode, nn::apm::PerformanceConfiguration) + public ResultCode SetPerformanceConfiguration(ServiceCtx context) + { + PerformanceMode perfMode = (PerformanceMode)context.RequestData.ReadInt32(); + PerformanceConfiguration perfConfig = (PerformanceConfiguration)context.RequestData.ReadInt32(); + + return ResultCode.Success; + } + + [Command(1)] + // GetPerformanceConfiguration(nn::apm::PerformanceMode) -> nn::apm::PerformanceConfiguration + public ResultCode GetPerformanceConfiguration(ServiceCtx context) + { + PerformanceMode perfMode = (PerformanceMode)context.RequestData.ReadInt32(); + + context.ResponseData.Write((uint)PerformanceConfiguration.PerformanceConfiguration1); + + Logger.PrintStub(LogClass.ServiceApm); + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Apm/Types/CpuBoostMode.cs b/Ryujinx.HLE/HOS/Services/Apm/Types/CpuBoostMode.cs new file mode 100644 index 0000000000..a4c87d3cc3 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Apm/Types/CpuBoostMode.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Apm +{ + enum CpuBoostMode + { + Disabled = 0, + Mode1 = 1, // Use PerformanceConfiguration13 and PerformanceConfiguration14, or PerformanceConfiguration15 and PerformanceConfiguration16 + Mode2 = 2 // Use PerformanceConfiguration15 and PerformanceConfiguration16. + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Apm/Types/PerformanceConfiguration.cs b/Ryujinx.HLE/HOS/Services/Apm/Types/PerformanceConfiguration.cs new file mode 100644 index 0000000000..e42edebcc6 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Apm/Types/PerformanceConfiguration.cs @@ -0,0 +1,22 @@ +namespace Ryujinx.HLE.HOS.Services.Apm +{ + enum PerformanceConfiguration : uint // Clocks are all in MHz. + { // CPU | GPU | RAM | NOTE + PerformanceConfiguration1 = 0x00010000, // 1020 | 384 | 1600 | Only available while docked. + PerformanceConfiguration2 = 0x00010001, // 1020 | 768 | 1600 | Only available while docked. + PerformanceConfiguration3 = 0x00010002, // 1224 | 691.2 | 1600 | Only available for SDEV units. + PerformanceConfiguration4 = 0x00020000, // 1020 | 230.4 | 1600 | Only available for SDEV units. + PerformanceConfiguration5 = 0x00020001, // 1020 | 307.2 | 1600 | + PerformanceConfiguration6 = 0x00020002, // 1224 | 230.4 | 1600 | + PerformanceConfiguration7 = 0x00020003, // 1020 | 307 | 1331.2 | + PerformanceConfiguration8 = 0x00020004, // 1020 | 384 | 1331.2 | + PerformanceConfiguration9 = 0x00020005, // 1020 | 307.2 | 1065.6 | + PerformanceConfiguration10 = 0x00020006, // 1020 | 384 | 1065.6 | + PerformanceConfiguration11 = 0x92220007, // 1020 | 460.8 | 1600 | + PerformanceConfiguration12 = 0x92220008, // 1020 | 460.8 | 1331.2 | + PerformanceConfiguration13 = 0x92220009, // 1785 | 768 | 1600 | 7.0.0+ + PerformanceConfiguration14 = 0x9222000A, // 1785 | 768 | 1331.2 | 7.0.0+ + PerformanceConfiguration15 = 0x9222000B, // 1020 | 768 | 1600 | 7.0.0+ + PerformanceConfiguration16 = 0x9222000C // 1020 | 768 | 1331.2 | 7.0.0+ + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Apm/PerformanceMode.cs b/Ryujinx.HLE/HOS/Services/Apm/Types/PerformanceMode.cs similarity index 64% rename from Ryujinx.HLE/OsHle/Services/Apm/PerformanceMode.cs rename to Ryujinx.HLE/HOS/Services/Apm/Types/PerformanceMode.cs index d89e27607e..a7a0dfad26 100644 --- a/Ryujinx.HLE/OsHle/Services/Apm/PerformanceMode.cs +++ b/Ryujinx.HLE/HOS/Services/Apm/Types/PerformanceMode.cs @@ -1,8 +1,8 @@ -namespace Ryujinx.HLE.OsHle.Services.Apm +namespace Ryujinx.HLE.HOS.Services.Apm { enum PerformanceMode { Handheld = 0, Docked = 1 } -} +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Arp/ApplicationLaunchProperty.cs b/Ryujinx.HLE/HOS/Services/Arp/ApplicationLaunchProperty.cs new file mode 100644 index 0000000000..686577d7e3 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Arp/ApplicationLaunchProperty.cs @@ -0,0 +1,41 @@ +using Ryujinx.HLE.FileSystem; + +namespace Ryujinx.HLE.HOS.Services.Arp +{ + class ApplicationLaunchProperty + { + public ulong TitleId; + public int Version; + public byte BaseGameStorageId; + public byte UpdateGameStorageId; + public short Padding; + + public static ApplicationLaunchProperty Default + { + get + { + return new ApplicationLaunchProperty + { + TitleId = 0x00, + Version = 0x00, + BaseGameStorageId = (byte)StorageId.NandSystem, + UpdateGameStorageId = (byte)StorageId.None + }; + } + } + + public static ApplicationLaunchProperty GetByPid(ServiceCtx context) + { + // TODO: Handle ApplicationLaunchProperty as array when pid will be supported and return the right item. + // For now we can hardcode values, and fix it after GetApplicationLaunchProperty is implemented. + + return new ApplicationLaunchProperty + { + TitleId = context.Device.System.TitleId, + Version = 0x00, + BaseGameStorageId = (byte)StorageId.NandSystem, + UpdateGameStorageId = (byte)StorageId.None + }; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Arp/IReader.cs b/Ryujinx.HLE/HOS/Services/Arp/IReader.cs new file mode 100644 index 0000000000..5d1e2fff23 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Arp/IReader.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + [Service("arp:r")] + class IReader : IpcService + { + public IReader(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Arp/IWriter.cs b/Ryujinx.HLE/HOS/Services/Arp/IWriter.cs new file mode 100644 index 0000000000..8d13f0fb8d --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Arp/IWriter.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Arp +{ + [Service("arp:w")] + class IWriter : IpcService + { + public IWriter(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioOutManager/IAudioOut.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioOutManager/IAudioOut.cs new file mode 100644 index 0000000000..11d8036cd1 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/AudioOutManager/IAudioOut.cs @@ -0,0 +1,189 @@ +using ARMeilleure.Memory; +using Ryujinx.Audio; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Threading; +using System; + +namespace Ryujinx.HLE.HOS.Services.Audio.AudioOutManager +{ + class IAudioOut : IpcService, IDisposable + { + private IAalOutput _audioOut; + private KEvent _releaseEvent; + private int _track; + + public IAudioOut(IAalOutput audioOut, KEvent releaseEvent, int track) + { + _audioOut = audioOut; + _releaseEvent = releaseEvent; + _track = track; + } + + [Command(0)] + // GetAudioOutState() -> u32 state + public ResultCode GetAudioOutState(ServiceCtx context) + { + context.ResponseData.Write((int)_audioOut.GetState(_track)); + + return ResultCode.Success; + } + + [Command(1)] + // StartAudioOut() + public ResultCode StartAudioOut(ServiceCtx context) + { + _audioOut.Start(_track); + + return ResultCode.Success; + } + + [Command(2)] + // StopAudioOut() + public ResultCode StopAudioOut(ServiceCtx context) + { + _audioOut.Stop(_track); + + return ResultCode.Success; + } + + [Command(3)] + // AppendAudioOutBuffer(u64 tag, buffer) + public ResultCode AppendAudioOutBuffer(ServiceCtx context) + { + return AppendAudioOutBufferImpl(context, context.Request.SendBuff[0].Position); + } + + [Command(4)] + // RegisterBufferEvent() -> handle + public ResultCode RegisterBufferEvent(ServiceCtx context) + { + if (context.Process.HandleTable.GenerateHandle(_releaseEvent.ReadableEvent, out int handle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle); + + return ResultCode.Success; + } + + [Command(5)] + // GetReleasedAudioOutBuffer() -> (u32 count, buffer) + public ResultCode GetReleasedAudioOutBuffer(ServiceCtx context) + { + long position = context.Request.ReceiveBuff[0].Position; + long size = context.Request.ReceiveBuff[0].Size; + + return GetReleasedAudioOutBufferImpl(context, position, size); + } + + [Command(6)] + // ContainsAudioOutBuffer(u64 tag) -> b8 + public ResultCode ContainsAudioOutBuffer(ServiceCtx context) + { + long tag = context.RequestData.ReadInt64(); + + context.ResponseData.Write(_audioOut.ContainsBuffer(_track, tag) ? 1 : 0); + + return 0; + } + + [Command(7)] // 3.0.0+ + // AppendAudioOutBufferAuto(u64 tag, buffer) + public ResultCode AppendAudioOutBufferAuto(ServiceCtx context) + { + (long position, long size) = context.Request.GetBufferType0x21(); + + return AppendAudioOutBufferImpl(context, position); + } + + public ResultCode AppendAudioOutBufferImpl(ServiceCtx context, long position) + { + long tag = context.RequestData.ReadInt64(); + + AudioOutData data = MemoryHelper.Read( + context.Memory, + position); + + byte[] buffer = context.Memory.ReadBytes( + data.SampleBufferPtr, + data.SampleBufferSize); + + _audioOut.AppendBuffer(_track, tag, buffer); + + return ResultCode.Success; + } + + [Command(8)] // 3.0.0+ + // GetReleasedAudioOutBufferAuto() -> (u32 count, buffer) + public ResultCode GetReleasedAudioOutBufferAuto(ServiceCtx context) + { + (long position, long size) = context.Request.GetBufferType0x22(); + + return GetReleasedAudioOutBufferImpl(context, position, size); + } + + public ResultCode GetReleasedAudioOutBufferImpl(ServiceCtx context, long position, long size) + { + uint count = (uint)((ulong)size >> 3); + + long[] releasedBuffers = _audioOut.GetReleasedBuffers(_track, (int)count); + + for (uint index = 0; index < count; index++) + { + long tag = 0; + + if (index < releasedBuffers.Length) + { + tag = releasedBuffers[index]; + } + + context.Memory.WriteInt64(position + index * 8, tag); + } + + context.ResponseData.Write(releasedBuffers.Length); + + return ResultCode.Success; + } + + [Command(12)] // 6.0.0+ + // SetAudioOutVolume(s32) + public ResultCode SetAudioOutVolume(ServiceCtx context) + { + // Games send a gain value here, so we need to apply it on the current volume value. + + float gain = context.RequestData.ReadSingle(); + float currentVolume = _audioOut.GetVolume(); + float newVolume = Math.Clamp(currentVolume + gain, 0.0f, 1.0f); + + _audioOut.SetVolume(newVolume); + + return ResultCode.Success; + } + + [Command(13)] // 6.0.0+ + // GetAudioOutVolume() -> s32 + public ResultCode GetAudioOutVolume(ServiceCtx context) + { + float volume = _audioOut.GetVolume(); + + context.ResponseData.Write(volume); + + return ResultCode.Success; + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _audioOut.CloseTrack(_track); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Aud/AudioOutData.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioOutManager/Types/AudioOutData.cs similarity index 84% rename from Ryujinx.HLE/OsHle/Services/Aud/AudioOutData.cs rename to Ryujinx.HLE/HOS/Services/Audio/AudioOutManager/Types/AudioOutData.cs index 9d68c24ab2..2598d0f8b0 100644 --- a/Ryujinx.HLE/OsHle/Services/Aud/AudioOutData.cs +++ b/Ryujinx.HLE/HOS/Services/Audio/AudioOutManager/Types/AudioOutData.cs @@ -1,6 +1,6 @@ using System.Runtime.InteropServices; -namespace Ryujinx.HLE.OsHle.Services.Aud +namespace Ryujinx.HLE.HOS.Services.Audio.AudioOutManager { [StructLayout(LayoutKind.Sequential)] struct AudioOutData diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/AudioRendererCommon.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/AudioRendererCommon.cs new file mode 100644 index 0000000000..c884b46507 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/AudioRendererCommon.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager +{ + static class AudioRendererCommon + { + public static bool CheckValidRevision(AudioRendererParameter parameters) => GetRevisionVersion(parameters.Revision) <= AudioRendererConsts.Revision; + public static bool CheckFeatureSupported(int revision, int supportedRevision) => revision >= supportedRevision; + public static int GetRevisionVersion(int revision) => (revision - AudioRendererConsts.Rev0Magic) >> 24; + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/BehaviorInfo.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/BehaviorInfo.cs new file mode 100644 index 0000000000..461e433707 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/BehaviorInfo.cs @@ -0,0 +1,30 @@ +namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager +{ + class BehaviorInfo + { + private const int _revision = AudioRendererConsts.Revision; + + private int _userRevision = 0; + + public BehaviorInfo() + { + /* TODO: this class got a size of 0xC0 + 0x00 - uint - Internal Revision + 0x04 - uint - User Revision + 0x08 - ... unknown ... + */ + } + + public bool IsSplitterSupported() => AudioRendererCommon.CheckFeatureSupported(_userRevision, SupportTags.Splitter); + public bool IsSplitterBugFixed() => AudioRendererCommon.CheckFeatureSupported(_userRevision, SupportTags.SplitterBugFix); + public bool IsVariadicCommandBufferSizeSupported() => AudioRendererCommon.CheckFeatureSupported(_userRevision, SupportTags.VariadicCommandBufferSize); + public bool IsElapsedFrameCountSupported() => AudioRendererCommon.CheckFeatureSupported(_userRevision, SupportTags.ElapsedFrameCount); + + public int GetPerformanceMetricsDataFormat() => AudioRendererCommon.CheckFeatureSupported(_userRevision, SupportTags.PerformanceMetricsDataFormatVersion2) ? 2 : 1; + + public void SetUserLibRevision(int revision) + { + _userRevision = AudioRendererCommon.GetRevisionVersion(revision); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/CommandGenerator.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/CommandGenerator.cs new file mode 100644 index 0000000000..b09b990b60 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/CommandGenerator.cs @@ -0,0 +1,16 @@ +namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager +{ + static class CommandGenerator + { + public static long CalculateCommandBufferSize(AudioRendererParameter parameters) + { + return parameters.EffectCount * 0x840 + + parameters.SubMixCount * 0x5A38 + + parameters.SinkCount * 0x148 + + parameters.SplitterDestinationDataCount * 0x540 + + (parameters.SplitterCount * 0x68 + 0x2E0) * parameters.VoiceCount + + ((parameters.VoiceCount + parameters.SubMixCount + parameters.EffectCount + parameters.SinkCount + 0x65) << 6) + + 0x3F8; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/EdgeMatrix.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/EdgeMatrix.cs new file mode 100644 index 0000000000..3f87ae04f1 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/EdgeMatrix.cs @@ -0,0 +1,19 @@ +using Ryujinx.Common; + +namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager +{ + static class EdgeMatrix + { + public static int GetWorkBufferSize(int totalMixCount) + { + int size = BitUtils.AlignUp(totalMixCount * totalMixCount, AudioRendererConsts.BufferAlignment); + + if (size < 0) + { + size |= 7; + } + + return size / 8; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/IAudioDevice.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/IAudioDevice.cs new file mode 100644 index 0000000000..ab4aee7638 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/IAudioDevice.cs @@ -0,0 +1,236 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.SystemState; +using System; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager +{ + class IAudioDevice : IpcService + { + private KEvent _systemEvent; + + public IAudioDevice(Horizon system) + { + _systemEvent = new KEvent(system); + + // TODO: We shouldn't be signaling this here. + _systemEvent.ReadableEvent.Signal(); + } + + [Command(0)] + // ListAudioDeviceName() -> (u32, buffer) + public ResultCode ListAudioDeviceName(ServiceCtx context) + { + string[] deviceNames = SystemStateMgr.AudioOutputs; + + context.ResponseData.Write(deviceNames.Length); + + long position = context.Request.ReceiveBuff[0].Position; + long size = context.Request.ReceiveBuff[0].Size; + + long basePosition = position; + + foreach (string name in deviceNames) + { + byte[] buffer = Encoding.ASCII.GetBytes(name + "\0"); + + if ((position - basePosition) + buffer.Length > size) + { + Logger.PrintError(LogClass.ServiceAudio, $"Output buffer size {size} too small!"); + + break; + } + + context.Memory.WriteBytes(position, buffer); + + position += buffer.Length; + } + + return ResultCode.Success; + } + + [Command(1)] + // SetAudioDeviceOutputVolume(u32, buffer) + public ResultCode SetAudioDeviceOutputVolume(ServiceCtx context) + { + float volume = context.RequestData.ReadSingle(); + + long position = context.Request.SendBuff[0].Position; + long size = context.Request.SendBuff[0].Size; + + byte[] deviceNameBuffer = context.Memory.ReadBytes(position, size); + + string deviceName = Encoding.ASCII.GetString(deviceNameBuffer); + + Logger.PrintStub(LogClass.ServiceAudio); + + return ResultCode.Success; + } + + [Command(3)] + // GetActiveAudioDeviceName() -> buffer + public ResultCode GetActiveAudioDeviceName(ServiceCtx context) + { + string name = context.Device.System.State.ActiveAudioOutput; + + long position = context.Request.ReceiveBuff[0].Position; + long size = context.Request.ReceiveBuff[0].Size; + + byte[] deviceNameBuffer = Encoding.ASCII.GetBytes(name + "\0"); + + if ((ulong)deviceNameBuffer.Length <= (ulong)size) + { + context.Memory.WriteBytes(position, deviceNameBuffer); + } + else + { + Logger.PrintError(LogClass.ServiceAudio, $"Output buffer size {size} too small!"); + } + + return ResultCode.Success; + } + + [Command(4)] + // QueryAudioDeviceSystemEvent() -> handle + public ResultCode QueryAudioDeviceSystemEvent(ServiceCtx context) + { + if (context.Process.HandleTable.GenerateHandle(_systemEvent.ReadableEvent, out int handle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle); + + Logger.PrintStub(LogClass.ServiceAudio); + + return ResultCode.Success; + } + + [Command(5)] + // GetActiveChannelCount() -> u32 + public ResultCode GetActiveChannelCount(ServiceCtx context) + { + context.ResponseData.Write(2); + + Logger.PrintStub(LogClass.ServiceAudio); + + return ResultCode.Success; + } + + [Command(6)] + // ListAudioDeviceNameAuto() -> (u32, buffer) + public ResultCode ListAudioDeviceNameAuto(ServiceCtx context) + { + string[] deviceNames = SystemStateMgr.AudioOutputs; + + context.ResponseData.Write(deviceNames.Length); + + (long position, long size) = context.Request.GetBufferType0x22(); + + long basePosition = position; + + foreach (string name in deviceNames) + { + byte[] buffer = Encoding.UTF8.GetBytes(name + '\0'); + + if ((position - basePosition) + buffer.Length > size) + { + Logger.PrintError(LogClass.ServiceAudio, $"Output buffer size {size} too small!"); + + break; + } + + context.Memory.WriteBytes(position, buffer); + + position += buffer.Length; + } + + return ResultCode.Success; + } + + [Command(7)] + // SetAudioDeviceOutputVolumeAuto(u32, buffer) + public ResultCode SetAudioDeviceOutputVolumeAuto(ServiceCtx context) + { + float volume = context.RequestData.ReadSingle(); + + (long position, long size) = context.Request.GetBufferType0x21(); + + byte[] deviceNameBuffer = context.Memory.ReadBytes(position, size); + + string deviceName = Encoding.UTF8.GetString(deviceNameBuffer); + + Logger.PrintStub(LogClass.ServiceAudio); + + return ResultCode.Success; + } + + [Command(8)] + // GetAudioDeviceOutputVolumeAuto(buffer) -> u32 + public ResultCode GetAudioDeviceOutputVolumeAuto(ServiceCtx context) + { + context.ResponseData.Write(1f); + + Logger.PrintStub(LogClass.ServiceAudio); + + return ResultCode.Success; + } + + [Command(10)] + // GetActiveAudioDeviceNameAuto() -> buffer + public ResultCode GetActiveAudioDeviceNameAuto(ServiceCtx context) + { + string name = context.Device.System.State.ActiveAudioOutput; + + (long position, long size) = context.Request.GetBufferType0x22(); + + byte[] deviceNameBuffer = Encoding.UTF8.GetBytes(name + '\0'); + + if ((ulong)deviceNameBuffer.Length <= (ulong)size) + { + context.Memory.WriteBytes(position, deviceNameBuffer); + } + else + { + Logger.PrintError(LogClass.ServiceAudio, $"Output buffer size {size} too small!"); + } + + return ResultCode.Success; + } + + [Command(11)] + // QueryAudioDeviceInputEvent() -> handle + public ResultCode QueryAudioDeviceInputEvent(ServiceCtx context) + { + if (context.Process.HandleTable.GenerateHandle(_systemEvent.ReadableEvent, out int handle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle); + + Logger.PrintStub(LogClass.ServiceAudio); + + return ResultCode.Success; + } + + [Command(12)] + // QueryAudioDeviceOutputEvent() -> handle + public ResultCode QueryAudioDeviceOutputEvent(ServiceCtx context) + { + if (context.Process.HandleTable.GenerateHandle(_systemEvent.ReadableEvent, out int handle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle); + + Logger.PrintStub(LogClass.ServiceAudio); + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/IAudioRenderer.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/IAudioRenderer.cs new file mode 100644 index 0000000000..aa9b6e516e --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/IAudioRenderer.cs @@ -0,0 +1,416 @@ +using ARMeilleure.Memory; +using Ryujinx.Audio; +using Ryujinx.Audio.Adpcm; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.Utilities; +using System; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; + +namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager +{ + class IAudioRenderer : IpcService, IDisposable + { + // This is the amount of samples that are going to be appended + // each time that RequestUpdateAudioRenderer is called. Ideally, + // this value shouldn't be neither too small (to avoid the player + // starving due to running out of samples) or too large (to avoid + // high latency). + private const int MixBufferSamplesCount = 960; + + private KEvent _updateEvent; + + private MemoryManager _memory; + + private IAalOutput _audioOut; + + private AudioRendererParameter _params; + + private MemoryPoolContext[] _memoryPools; + + private VoiceContext[] _voices; + + private int _track; + + private PlayState _playState; + + public IAudioRenderer( + Horizon system, + MemoryManager memory, + IAalOutput audioOut, + AudioRendererParameter Params) + { + _updateEvent = new KEvent(system); + + _memory = memory; + _audioOut = audioOut; + _params = Params; + + _track = audioOut.OpenTrack( + AudioRendererConsts.HostSampleRate, + AudioRendererConsts.HostChannelsCount, + AudioCallback); + + _memoryPools = CreateArray(Params.EffectCount + Params.VoiceCount * 4); + + _voices = CreateArray(Params.VoiceCount); + + InitializeAudioOut(); + + _playState = PlayState.Stopped; + } + + [Command(0)] + // GetSampleRate() -> u32 + public ResultCode GetSampleRate(ServiceCtx context) + { + context.ResponseData.Write(_params.SampleRate); + + return ResultCode.Success; + } + + [Command(1)] + // GetSampleCount() -> u32 + public ResultCode GetSampleCount(ServiceCtx context) + { + context.ResponseData.Write(_params.SampleCount); + + return ResultCode.Success; + } + + [Command(2)] + // GetMixBufferCount() -> u32 + public ResultCode GetMixBufferCount(ServiceCtx context) + { + context.ResponseData.Write(_params.SubMixCount); + + return ResultCode.Success; + } + + [Command(3)] + // GetState() -> u32 + public ResultCode GetState(ServiceCtx context) + { + context.ResponseData.Write((int)_playState); + + Logger.PrintStub(LogClass.ServiceAudio, new { State = Enum.GetName(typeof(PlayState), _playState) }); + + return ResultCode.Success; + } + + private void AudioCallback() + { + _updateEvent.ReadableEvent.Signal(); + } + + private static T[] CreateArray(int size) where T : new() + { + T[] output = new T[size]; + + for (int index = 0; index < size; index++) + { + output[index] = new T(); + } + + return output; + } + + private void InitializeAudioOut() + { + AppendMixedBuffer(0); + AppendMixedBuffer(1); + AppendMixedBuffer(2); + + _audioOut.Start(_track); + } + + [Command(4)] + // RequestUpdateAudioRenderer(buffer) + // -> (buffer, buffer) + public ResultCode RequestUpdateAudioRenderer(ServiceCtx context) + { + long outputPosition = context.Request.ReceiveBuff[0].Position; + long outputSize = context.Request.ReceiveBuff[0].Size; + + MemoryHelper.FillWithZeros(context.Memory, outputPosition, (int)outputSize); + + long inputPosition = context.Request.SendBuff[0].Position; + + StructReader reader = new StructReader(context.Memory, inputPosition); + StructWriter writer = new StructWriter(context.Memory, outputPosition); + + UpdateDataHeader inputHeader = reader.Read(); + + BehaviorInfo behaviorInfo = new BehaviorInfo(); + + behaviorInfo.SetUserLibRevision(inputHeader.Revision); + + reader.Read(inputHeader.BehaviorSize); + + MemoryPoolIn[] memoryPoolsIn = reader.Read(inputHeader.MemoryPoolSize); + + for (int index = 0; index < memoryPoolsIn.Length; index++) + { + MemoryPoolIn memoryPool = memoryPoolsIn[index]; + + if (memoryPool.State == MemoryPoolState.RequestAttach) + { + _memoryPools[index].OutStatus.State = MemoryPoolState.Attached; + } + else if (memoryPool.State == MemoryPoolState.RequestDetach) + { + _memoryPools[index].OutStatus.State = MemoryPoolState.Detached; + } + } + + reader.Read(inputHeader.VoiceResourceSize); + + VoiceIn[] voicesIn = reader.Read(inputHeader.VoiceSize); + + for (int index = 0; index < voicesIn.Length; index++) + { + VoiceIn voice = voicesIn[index]; + + VoiceContext voiceCtx = _voices[index]; + + voiceCtx.SetAcquireState(voice.Acquired != 0); + + if (voice.Acquired == 0) + { + continue; + } + + if (voice.FirstUpdate != 0) + { + voiceCtx.AdpcmCtx = GetAdpcmDecoderContext( + voice.AdpcmCoeffsPosition, + voice.AdpcmCoeffsSize); + + voiceCtx.SampleFormat = voice.SampleFormat; + voiceCtx.SampleRate = voice.SampleRate; + voiceCtx.ChannelsCount = voice.ChannelsCount; + + voiceCtx.SetBufferIndex(voice.BaseWaveBufferIndex); + } + + voiceCtx.WaveBuffers[0] = voice.WaveBuffer0; + voiceCtx.WaveBuffers[1] = voice.WaveBuffer1; + voiceCtx.WaveBuffers[2] = voice.WaveBuffer2; + voiceCtx.WaveBuffers[3] = voice.WaveBuffer3; + voiceCtx.Volume = voice.Volume; + voiceCtx.PlayState = voice.PlayState; + } + + UpdateAudio(); + + UpdateDataHeader outputHeader = new UpdateDataHeader(); + + int updateHeaderSize = Marshal.SizeOf(); + + outputHeader.Revision = AudioRendererConsts.RevMagic; + outputHeader.BehaviorSize = 0xb0; + outputHeader.MemoryPoolSize = (_params.EffectCount + _params.VoiceCount * 4) * 0x10; + outputHeader.VoiceSize = _params.VoiceCount * 0x10; + outputHeader.EffectSize = _params.EffectCount * 0x10; + outputHeader.SinkSize = _params.SinkCount * 0x20; + outputHeader.PerformanceManagerSize = 0x10; + + if (behaviorInfo.IsElapsedFrameCountSupported()) + { + outputHeader.ElapsedFrameCountInfoSize = 0x10; + } + + outputHeader.TotalSize = updateHeaderSize + + outputHeader.BehaviorSize + + outputHeader.MemoryPoolSize + + outputHeader.VoiceSize + + outputHeader.EffectSize + + outputHeader.SinkSize + + outputHeader.PerformanceManagerSize + + outputHeader.ElapsedFrameCountInfoSize; + + writer.Write(outputHeader); + + foreach (MemoryPoolContext memoryPool in _memoryPools) + { + writer.Write(memoryPool.OutStatus); + } + + foreach (VoiceContext voice in _voices) + { + writer.Write(voice.OutStatus); + } + + return ResultCode.Success; + } + + [Command(5)] + // Start() + public ResultCode StartAudioRenderer(ServiceCtx context) + { + Logger.PrintStub(LogClass.ServiceAudio); + + _playState = PlayState.Playing; + + return ResultCode.Success; + } + + [Command(6)] + // Stop() + public ResultCode StopAudioRenderer(ServiceCtx context) + { + Logger.PrintStub(LogClass.ServiceAudio); + + _playState = PlayState.Stopped; + + return ResultCode.Success; + } + + [Command(7)] + // QuerySystemEvent() -> handle + public ResultCode QuerySystemEvent(ServiceCtx context) + { + if (context.Process.HandleTable.GenerateHandle(_updateEvent.ReadableEvent, out int handle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle); + + return ResultCode.Success; + } + + private AdpcmDecoderContext GetAdpcmDecoderContext(long position, long size) + { + if (size == 0) + { + return null; + } + + AdpcmDecoderContext context = new AdpcmDecoderContext + { + Coefficients = new short[size >> 1] + }; + + for (int offset = 0; offset < size; offset += 2) + { + context.Coefficients[offset >> 1] = _memory.ReadInt16(position + offset); + } + + return context; + } + + private void UpdateAudio() + { + long[] released = _audioOut.GetReleasedBuffers(_track, 2); + + for (int index = 0; index < released.Length; index++) + { + AppendMixedBuffer(released[index]); + } + } + + private void AppendMixedBuffer(long tag) + { + int[] mixBuffer = new int[MixBufferSamplesCount * AudioRendererConsts.HostChannelsCount]; + + foreach (VoiceContext voice in _voices) + { + if (!voice.Playing || voice.CurrentWaveBuffer.Size == 0) + { + continue; + } + + int outOffset = 0; + int pendingSamples = MixBufferSamplesCount; + float volume = voice.Volume; + + while (pendingSamples > 0) + { + int[] samples = voice.GetBufferData(_memory, pendingSamples, out int returnedSamples); + + if (returnedSamples == 0) + { + break; + } + + pendingSamples -= returnedSamples; + + for (int offset = 0; offset < samples.Length; offset++) + { + mixBuffer[outOffset++] += (int)(samples[offset] * voice.Volume); + } + } + } + + _audioOut.AppendBuffer(_track, tag, GetFinalBuffer(mixBuffer)); + } + + private unsafe static short[] GetFinalBuffer(int[] buffer) + { + short[] output = new short[buffer.Length]; + + int offset = 0; + + // Perform Saturation using SSE2 if supported + if (Sse2.IsSupported) + { + fixed (int* inptr = buffer) + fixed (short* outptr = output) + { + for (; offset + 32 <= buffer.Length; offset += 32) + { + // Unroll the loop a little to ensure the CPU pipeline + // is always full. + Vector128 block1A = Sse2.LoadVector128(inptr + offset + 0); + Vector128 block1B = Sse2.LoadVector128(inptr + offset + 4); + + Vector128 block2A = Sse2.LoadVector128(inptr + offset + 8); + Vector128 block2B = Sse2.LoadVector128(inptr + offset + 12); + + Vector128 block3A = Sse2.LoadVector128(inptr + offset + 16); + Vector128 block3B = Sse2.LoadVector128(inptr + offset + 20); + + Vector128 block4A = Sse2.LoadVector128(inptr + offset + 24); + Vector128 block4B = Sse2.LoadVector128(inptr + offset + 28); + + Vector128 output1 = Sse2.PackSignedSaturate(block1A, block1B); + Vector128 output2 = Sse2.PackSignedSaturate(block2A, block2B); + Vector128 output3 = Sse2.PackSignedSaturate(block3A, block3B); + Vector128 output4 = Sse2.PackSignedSaturate(block4A, block4B); + + Sse2.Store(outptr + offset + 0, output1); + Sse2.Store(outptr + offset + 8, output2); + Sse2.Store(outptr + offset + 16, output3); + Sse2.Store(outptr + offset + 24, output4); + } + } + } + + // Process left overs + for (; offset < buffer.Length; offset++) + { + output[offset] = DspUtils.Saturate(buffer[offset]); + } + + return output; + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _audioOut.CloseTrack(_track); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/MemoryPoolContext.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/MemoryPoolContext.cs new file mode 100644 index 0000000000..3f48114c69 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/MemoryPoolContext.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager +{ + class MemoryPoolContext + { + public MemoryPoolOut OutStatus; + + public MemoryPoolContext() + { + OutStatus.State = MemoryPoolState.Detached; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/NodeStates.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/NodeStates.cs new file mode 100644 index 0000000000..7ae9aeea50 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/NodeStates.cs @@ -0,0 +1,19 @@ +using Ryujinx.Common; + +namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager +{ + static class NodeStates + { + public static long GetWorkBufferSize(int totalMixCount) + { + int size = BitUtils.AlignUp(totalMixCount, AudioRendererConsts.BufferAlignment); + + if (size < 0) + { + size |= 7; + } + + return 4 * (totalMixCount * totalMixCount) + 12 * totalMixCount + 2 * (size / 8); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/PerformanceManager.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/PerformanceManager.cs new file mode 100644 index 0000000000..1b8c8a7cc1 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/PerformanceManager.cs @@ -0,0 +1,30 @@ +using Ryujinx.Common.Logging; + +namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager +{ + static class PerformanceManager + { + public static long GetRequiredBufferSizeForPerformanceMetricsPerFrame(BehaviorInfo behaviorInfo, AudioRendererParameter parameters) + { + int performanceMetricsDataFormat = behaviorInfo.GetPerformanceMetricsDataFormat(); + + if (performanceMetricsDataFormat == 2) + { + return 24 * (parameters.VoiceCount + + parameters.EffectCount + + parameters.SubMixCount + + parameters.SinkCount + 1) + 0x990; + } + + if (performanceMetricsDataFormat != 1) + { + Logger.PrintWarning(LogClass.ServiceAudio, $"PerformanceMetricsDataFormat: {performanceMetricsDataFormat} is not supported!"); + } + + return (((parameters.VoiceCount + + parameters.EffectCount + + parameters.SubMixCount + + parameters.SinkCount + 1) << 32) >> 0x1C) + 0x658; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Resampler.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Resampler.cs new file mode 100644 index 0000000000..936e7f506b --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Resampler.cs @@ -0,0 +1,191 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager +{ + static class Resampler + { +#region "LookUp Tables" + private static short[] _curveLut0 = 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[] _curveLut1 = 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[] _curveLut2 = 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 + + public static int[] Resample2Ch( + int[] buffer, + int srcSampleRate, + int dstSampleRate, + int samplesCount, + ref int fracPart) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + if (srcSampleRate <= 0) + { + throw new ArgumentOutOfRangeException(nameof(srcSampleRate)); + } + + if (dstSampleRate <= 0) + { + throw new ArgumentOutOfRangeException(nameof(dstSampleRate)); + } + + double ratio = (double)srcSampleRate / dstSampleRate; + + int newSamplesCount = (int)(samplesCount / ratio); + + int step = (int)(ratio * 0x8000); + + int[] output = new int[newSamplesCount * 2]; + + short[] lut; + + if (step > 0xaaaa) + { + lut = _curveLut0; + } + else if (step <= 0x8000) + { + lut = _curveLut1; + } + else + { + lut = _curveLut2; + } + + int inOffs = 0; + + for (int outOffs = 0; outOffs < output.Length; outOffs += 2) + { + int lutIndex = (fracPart >> 8) * 4; + + int sample0 = buffer[(inOffs + 0) * 2 + 0] * lut[lutIndex + 0] + + buffer[(inOffs + 1) * 2 + 0] * lut[lutIndex + 1] + + buffer[(inOffs + 2) * 2 + 0] * lut[lutIndex + 2] + + buffer[(inOffs + 3) * 2 + 0] * lut[lutIndex + 3]; + + int sample1 = buffer[(inOffs + 0) * 2 + 1] * lut[lutIndex + 0] + + buffer[(inOffs + 1) * 2 + 1] * lut[lutIndex + 1] + + buffer[(inOffs + 2) * 2 + 1] * lut[lutIndex + 2] + + buffer[(inOffs + 3) * 2 + 1] * lut[lutIndex + 3]; + + int newOffset = fracPart + step; + + inOffs += newOffset >> 15; + + fracPart = newOffset & 0x7fff; + + output[outOffs + 0] = sample0 >> 15; + output[outOffs + 1] = sample1 >> 15; + } + + return output; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/SplitterContext.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/SplitterContext.cs new file mode 100644 index 0000000000..e46af4432d --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/SplitterContext.cs @@ -0,0 +1,25 @@ +using Ryujinx.Common; + +namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager +{ + class SplitterContext + { + public static long CalcWorkBufferSize(BehaviorInfo behaviorInfo, AudioRendererParameter parameters) + { + if (!behaviorInfo.IsSplitterSupported()) + { + return 0; + } + + long size = parameters.SplitterDestinationDataCount * 0xE0 + + parameters.SplitterCount * 0x20; + + if (!behaviorInfo.IsSplitterBugFixed()) + { + size += BitUtils.AlignUp(4 * parameters.SplitterDestinationDataCount, 16); + } + + return size; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/AudioRendererConsts.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/AudioRendererConsts.cs new file mode 100644 index 0000000000..7e8b89bd23 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/AudioRendererConsts.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager +{ + static class AudioRendererConsts + { + // Revision Consts + public const int Revision = 7; + public const int Rev0Magic = ('R' << 0) | ('E' << 8) | ('V' << 16) | ('0' << 24); + public const int RevMagic = Rev0Magic + (Revision << 24); + + // Misc Consts + public const int BufferAlignment = 0x40; + + // Host Consts + public const int HostSampleRate = 48000; + public const int HostChannelsCount = 2; + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Aud/AudioRendererParameter.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/AudioRendererParameter.cs similarity index 59% rename from Ryujinx.HLE/OsHle/Services/Aud/AudioRendererParameter.cs rename to Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/AudioRendererParameter.cs index 0a0792ec5b..2cfbc4b70e 100644 --- a/Ryujinx.HLE/OsHle/Services/Aud/AudioRendererParameter.cs +++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/AudioRendererParameter.cs @@ -1,22 +1,22 @@ using System.Runtime.InteropServices; -namespace Ryujinx.HLE.OsHle.Services.Aud +namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager { [StructLayout(LayoutKind.Sequential)] struct AudioRendererParameter { public int SampleRate; public int SampleCount; - public int Unknown8; - public int UnknownC; + public int MixBufferCount; + public int SubMixCount; public int VoiceCount; public int SinkCount; public int EffectCount; - public int Unknown1C; - public int Unknown20; + public int PerformanceManagerCount; + public int VoiceDropEnable; public int SplitterCount; - public int Unknown28; + public int SplitterDestinationDataCount; public int Unknown2C; public int Revision; } -} +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/BehaviorIn.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/BehaviorIn.cs new file mode 100644 index 0000000000..953b4ce3ab --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/BehaviorIn.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager +{ + [StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 4)] + struct BehaviorIn + { + public long Unknown0; + public long Unknown8; + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/BiquadFilter.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/BiquadFilter.cs new file mode 100644 index 0000000000..d0d8ed9bb7 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/BiquadFilter.cs @@ -0,0 +1,16 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager +{ + [StructLayout(LayoutKind.Sequential, Size = 0xc, Pack = 1)] + struct BiquadFilter + { + public byte Enable; + public byte Padding; + public short B0; + public short B1; + public short B2; + public short A1; + public short A2; + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/MemoryPoolIn.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/MemoryPoolIn.cs new file mode 100644 index 0000000000..8dc53929a8 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/MemoryPoolIn.cs @@ -0,0 +1,14 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager +{ + [StructLayout(LayoutKind.Sequential, Size = 0x20, Pack = 4)] + struct MemoryPoolIn + { + public long Address; + public long Size; + public MemoryPoolState State; + public int Unknown14; + public long Unknown18; + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/MemoryPoolOut.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/MemoryPoolOut.cs new file mode 100644 index 0000000000..7581e8a756 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/MemoryPoolOut.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager +{ + [StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 4)] + struct MemoryPoolOut + { + public MemoryPoolState State; + public int Unknown14; + public long Unknown18; + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Aud/MemoryPoolState.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/MemoryPoolState.cs similarity index 68% rename from Ryujinx.HLE/OsHle/Services/Aud/MemoryPoolState.cs rename to Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/MemoryPoolState.cs index 892cde49e3..a82747b829 100644 --- a/Ryujinx.HLE/OsHle/Services/Aud/MemoryPoolState.cs +++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/MemoryPoolState.cs @@ -1,6 +1,6 @@ -namespace Ryujinx.HLE.OsHle.Services.Aud +namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager { - enum MemoryPoolState : int + enum MemoryPoolState { Invalid = 0, Unknown = 1, @@ -10,4 +10,4 @@ Attached = 5, Released = 6 } -} +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/PlayState.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/PlayState.cs new file mode 100644 index 0000000000..d63df971dc --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/PlayState.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager +{ + enum PlayState : byte + { + Playing = 0, + Stopped = 1, + Paused = 2 + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/SupportTags.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/SupportTags.cs new file mode 100644 index 0000000000..a418d89e64 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/SupportTags.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager +{ + static class SupportTags + { + public const int Splitter = 2; + public const int SplitterBugFix = 5; + public const int PerformanceMetricsDataFormatVersion2 = 5; + public const int VariadicCommandBufferSize = 5; + public const int ElapsedFrameCount = 5; + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Aud/UpdateDataHeader.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/UpdateDataHeader.cs similarity index 57% rename from Ryujinx.HLE/OsHle/Services/Aud/UpdateDataHeader.cs rename to Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/UpdateDataHeader.cs index f944b302e0..1c5b2903ff 100644 --- a/Ryujinx.HLE/OsHle/Services/Aud/UpdateDataHeader.cs +++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/UpdateDataHeader.cs @@ -1,22 +1,22 @@ -namespace Ryujinx.HLE.OsHle.Services.Aud +namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager { struct UpdateDataHeader { public int Revision; public int BehaviorSize; - public int MemoryPoolsSize; - public int VoicesSize; + public int MemoryPoolSize; + public int VoiceSize; public int VoiceResourceSize; - public int EffectsSize; - public int MixesSize; - public int SinksSize; + public int EffectSize; + public int MixeSize; + public int SinkSize; public int PerformanceManagerSize; public int Unknown24; - public int Unknown28; + public int ElapsedFrameCountInfoSize; public int Unknown2C; public int Unknown30; public int Unknown34; public int Unknown38; public int TotalSize; } -} +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/VoiceChannelResourceIn.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/VoiceChannelResourceIn.cs new file mode 100644 index 0000000000..4871713e9d --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/VoiceChannelResourceIn.cs @@ -0,0 +1,10 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager +{ + [StructLayout(LayoutKind.Sequential, Size = 0x70, Pack = 1)] + struct VoiceChannelResourceIn + { + // ??? + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/VoiceIn.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/VoiceIn.cs new file mode 100644 index 0000000000..dbcd55586d --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/VoiceIn.cs @@ -0,0 +1,49 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager +{ + [StructLayout(LayoutKind.Sequential, Size = 0x170, Pack = 1)] + struct VoiceIn + { + public int VoiceSlot; + public int NodeId; + + public byte FirstUpdate; + public byte Acquired; + + public PlayState PlayState; + + public SampleFormat SampleFormat; + + public int SampleRate; + + public int Priority; + + public int Unknown14; + + public int ChannelsCount; + + public float Pitch; + public float Volume; + + public BiquadFilter BiquadFilter0; + public BiquadFilter BiquadFilter1; + + public int AppendedWaveBuffersCount; + + public int BaseWaveBufferIndex; + + public int Unknown44; + + public long AdpcmCoeffsPosition; + public long AdpcmCoeffsSize; + + public int VoiceDestination; + public int Padding; + + public WaveBuffer WaveBuffer0; + public WaveBuffer WaveBuffer1; + public WaveBuffer WaveBuffer2; + public WaveBuffer WaveBuffer3; + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/VoiceOut.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/VoiceOut.cs new file mode 100644 index 0000000000..3a295971ee --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/VoiceOut.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager +{ + [StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 4)] + struct VoiceOut + { + public long PlayedSamplesCount; + public int PlayedWaveBuffersCount; + public int VoiceDropsCount; //? + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/WaveBuffer.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/WaveBuffer.cs new file mode 100644 index 0000000000..1c0d5630b0 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/Types/WaveBuffer.cs @@ -0,0 +1,20 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager +{ + [StructLayout(LayoutKind.Sequential, Size = 0x38, Pack = 1)] + struct WaveBuffer + { + public long Position; + public long Size; + public int FirstSampleOffset; + public int LastSampleOffset; + public byte Looping; + public byte LastBuffer; + public short Unknown1A; + public int Unknown1C; + public long AdpcmLoopContextPosition; + public long AdpcmLoopContextSize; + public long Unknown30; + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/VoiceContext.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/VoiceContext.cs new file mode 100644 index 0000000000..60fe2e2342 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager/VoiceContext.cs @@ -0,0 +1,199 @@ +using ARMeilleure.Memory; +using Ryujinx.Audio.Adpcm; +using System; + +namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager +{ + class VoiceContext + { + private bool _acquired; + private bool _bufferReload; + + private int _resamplerFracPart; + + private int _bufferIndex; + private int _offset; + + public int SampleRate { get; set; } + public int ChannelsCount { get; set; } + + public float Volume { get; set; } + + public PlayState PlayState { get; set; } + + public SampleFormat SampleFormat { get; set; } + + public AdpcmDecoderContext AdpcmCtx { get; set; } + + public WaveBuffer[] WaveBuffers { get; } + + public WaveBuffer CurrentWaveBuffer => WaveBuffers[_bufferIndex]; + + private VoiceOut _outStatus; + + public VoiceOut OutStatus => _outStatus; + + private int[] _samples; + + public bool Playing => _acquired && PlayState == PlayState.Playing; + + public VoiceContext() + { + WaveBuffers = new WaveBuffer[4]; + } + + public void SetAcquireState(bool newState) + { + if (_acquired && !newState) + { + // Release. + Reset(); + } + + _acquired = newState; + } + + private void Reset() + { + _bufferReload = true; + + _bufferIndex = 0; + _offset = 0; + + _outStatus.PlayedSamplesCount = 0; + _outStatus.PlayedWaveBuffersCount = 0; + _outStatus.VoiceDropsCount = 0; + } + + public int[] GetBufferData(MemoryManager memory, int maxSamples, out int samplesCount) + { + if (!Playing) + { + samplesCount = 0; + + return null; + } + + if (_bufferReload) + { + _bufferReload = false; + + UpdateBuffer(memory); + } + + WaveBuffer wb = WaveBuffers[_bufferIndex]; + + int maxSize = _samples.Length - _offset; + + int size = maxSamples * AudioRendererConsts.HostChannelsCount; + + if (size > maxSize) + { + size = maxSize; + } + + int[] output = new int[size]; + + Array.Copy(_samples, _offset, output, 0, size); + + samplesCount = size / AudioRendererConsts.HostChannelsCount; + + _outStatus.PlayedSamplesCount += samplesCount; + + _offset += size; + + if (_offset == _samples.Length) + { + _offset = 0; + + if (wb.Looping == 0) + { + SetBufferIndex((_bufferIndex + 1) & 3); + } + + _outStatus.PlayedWaveBuffersCount++; + + if (wb.LastBuffer != 0) + { + PlayState = PlayState.Paused; + } + } + + return output; + } + + private void UpdateBuffer(MemoryManager memory) + { + // TODO: Implement conversion for formats other + // than interleaved stereo (2 channels). + // As of now, it assumes that HostChannelsCount == 2. + WaveBuffer wb = WaveBuffers[_bufferIndex]; + + if (wb.Position == 0) + { + _samples = new int[0]; + + return; + } + + if (SampleFormat == SampleFormat.PcmInt16) + { + int samplesCount = (int)(wb.Size / (sizeof(short) * ChannelsCount)); + + _samples = new int[samplesCount * AudioRendererConsts.HostChannelsCount]; + + if (ChannelsCount == 1) + { + for (int index = 0; index < samplesCount; index++) + { + short sample = memory.ReadInt16(wb.Position + index * 2); + + _samples[index * 2 + 0] = sample; + _samples[index * 2 + 1] = sample; + } + } + else + { + for (int index = 0; index < samplesCount * 2; index++) + { + _samples[index] = memory.ReadInt16(wb.Position + index * 2); + } + } + } + else if (SampleFormat == SampleFormat.Adpcm) + { + byte[] buffer = memory.ReadBytes(wb.Position, wb.Size); + + _samples = AdpcmDecoder.Decode(buffer, AdpcmCtx); + } + else + { + throw new InvalidOperationException(); + } + + if (SampleRate != AudioRendererConsts.HostSampleRate) + { + // TODO: We should keep the frames being discarded (see the 4 below) + // on a buffer and include it on the next samples buffer, to allow + // the resampler to do seamless interpolation between wave buffers. + int samplesCount = _samples.Length / AudioRendererConsts.HostChannelsCount; + + samplesCount = Math.Max(samplesCount - 4, 0); + + _samples = Resampler.Resample2Ch( + _samples, + SampleRate, + AudioRendererConsts.HostSampleRate, + samplesCount, + ref _resamplerFracPart); + } + } + + public void SetBufferIndex(int index) + { + _bufferIndex = index & 3; + + _bufferReload = true; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/IHardwareOpusDecoder.cs b/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/IHardwareOpusDecoder.cs new file mode 100644 index 0000000000..079f2ae75e --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/IHardwareOpusDecoder.cs @@ -0,0 +1,232 @@ +using Concentus; +using Concentus.Enums; +using Concentus.Structs; +using Ryujinx.HLE.HOS.Services.Audio.Types; +using System; +using System.IO; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Audio.HardwareOpusDecoderManager +{ + class IHardwareOpusDecoder : IpcService + { + private int _sampleRate; + private int _channelsCount; + private bool _reset; + + private OpusDecoder _decoder; + + public IHardwareOpusDecoder(int sampleRate, int channelsCount) + { + _sampleRate = sampleRate; + _channelsCount = channelsCount; + _reset = false; + + _decoder = new OpusDecoder(sampleRate, channelsCount); + } + + private ResultCode GetPacketNumSamples(out int numSamples, byte[] packet) + { + int result = OpusPacketInfo.GetNumSamples(_decoder, packet, 0, packet.Length); + + numSamples = result; + + if (result == OpusError.OPUS_INVALID_PACKET) + { + return ResultCode.OpusInvalidInput; + } + else if (result == OpusError.OPUS_BAD_ARG) + { + return ResultCode.OpusInvalidInput; + } + + return ResultCode.Success; + } + + private ResultCode DecodeInterleavedInternal(BinaryReader input, out short[] outPcmData, long outputSize, out uint outConsumed, out int outSamples) + { + outPcmData = null; + outConsumed = 0; + outSamples = 0; + + long streamSize = input.BaseStream.Length; + + if (streamSize < Marshal.SizeOf()) + { + return ResultCode.OpusInvalidInput; + } + + OpusPacketHeader header = OpusPacketHeader.FromStream(input); + + uint totalSize = header.length + (uint)Marshal.SizeOf(); + + if (totalSize > streamSize) + { + return ResultCode.OpusInvalidInput; + } + + byte[] opusData = input.ReadBytes((int)header.length); + + ResultCode result = GetPacketNumSamples(out int numSamples, opusData); + + if (result == ResultCode.Success) + { + if ((uint)numSamples * (uint)_channelsCount * sizeof(short) > outputSize) + { + return ResultCode.OpusInvalidInput; + } + + outPcmData = new short[numSamples * _channelsCount]; + + if (_reset) + { + _reset = false; + + _decoder.ResetState(); + } + + try + { + outSamples = _decoder.Decode(opusData, 0, opusData.Length, outPcmData, 0, outPcmData.Length / _channelsCount); + outConsumed = totalSize; + } + catch (OpusException) + { + // TODO: as OpusException doesn't provide us the exact error code, this is kind of inaccurate in some cases... + return ResultCode.OpusInvalidInput; + } + } + + return ResultCode.Success; + } + + [Command(0)] + // DecodeInterleaved(buffer) -> (u32, u32, buffer) + public ResultCode DecodeInterleavedOriginal(ServiceCtx context) + { + ResultCode result; + + long inPosition = context.Request.SendBuff[0].Position; + long inSize = context.Request.SendBuff[0].Size; + long outputPosition = context.Request.ReceiveBuff[0].Position; + long outputSize = context.Request.ReceiveBuff[0].Size; + + using (BinaryReader inputStream = new BinaryReader(new MemoryStream(context.Memory.ReadBytes(inPosition, inSize)))) + { + result = DecodeInterleavedInternal(inputStream, out short[] outPcmData, outputSize, out uint outConsumed, out int outSamples); + + if (result == ResultCode.Success) + { + byte[] pcmDataBytes = new byte[outPcmData.Length * sizeof(short)]; + Buffer.BlockCopy(outPcmData, 0, pcmDataBytes, 0, pcmDataBytes.Length); + context.Memory.WriteBytes(outputPosition, pcmDataBytes); + + context.ResponseData.Write(outConsumed); + context.ResponseData.Write(outSamples); + } + } + + return result; + } + + [Command(4)] // 6.0.0+ + // DecodeInterleavedWithPerfOld(buffer) -> (u32, u32, u64, buffer) + public ResultCode DecodeInterleavedWithPerfOld(ServiceCtx context) + { + ResultCode result; + + long inPosition = context.Request.SendBuff[0].Position; + long inSize = context.Request.SendBuff[0].Size; + long outputPosition = context.Request.ReceiveBuff[0].Position; + long outputSize = context.Request.ReceiveBuff[0].Size; + + using (BinaryReader inputStream = new BinaryReader(new MemoryStream(context.Memory.ReadBytes(inPosition, inSize)))) + { + result = DecodeInterleavedInternal(inputStream, out short[] outPcmData, outputSize, out uint outConsumed, out int outSamples); + + if (result == ResultCode.Success) + { + byte[] pcmDataBytes = new byte[outPcmData.Length * sizeof(short)]; + Buffer.BlockCopy(outPcmData, 0, pcmDataBytes, 0, pcmDataBytes.Length); + context.Memory.WriteBytes(outputPosition, pcmDataBytes); + + context.ResponseData.Write(outConsumed); + context.ResponseData.Write(outSamples); + + // This is the time the DSP took to process the request, TODO: fill this. + context.ResponseData.Write(0); + } + } + + return result; + } + + [Command(6)] // 6.0.0+ + // DecodeInterleavedOld(bool reset, buffer) -> (u32, u32, u64, buffer) + public ResultCode DecodeInterleavedOld(ServiceCtx context) + { + ResultCode result; + + _reset = context.RequestData.ReadBoolean(); + + long inPosition = context.Request.SendBuff[0].Position; + long inSize = context.Request.SendBuff[0].Size; + long outputPosition = context.Request.ReceiveBuff[0].Position; + long outputSize = context.Request.ReceiveBuff[0].Size; + + using (BinaryReader inputStream = new BinaryReader(new MemoryStream(context.Memory.ReadBytes(inPosition, inSize)))) + { + result = DecodeInterleavedInternal(inputStream, out short[] outPcmData, outputSize, out uint outConsumed, out int outSamples); + + if (result == ResultCode.Success) + { + byte[] pcmDataBytes = new byte[outPcmData.Length * sizeof(short)]; + Buffer.BlockCopy(outPcmData, 0, pcmDataBytes, 0, pcmDataBytes.Length); + context.Memory.WriteBytes(outputPosition, pcmDataBytes); + + context.ResponseData.Write(outConsumed); + context.ResponseData.Write(outSamples); + + // This is the time the DSP took to process the request, TODO: fill this. + context.ResponseData.Write(0); + } + } + + return result; + } + + [Command(8)] // 7.0.0+ + // DecodeInterleaved(bool reset, buffer) -> (u32, u32, u64, buffer) + public ResultCode DecodeInterleaved(ServiceCtx context) + { + ResultCode result; + + _reset = context.RequestData.ReadBoolean(); + + long inPosition = context.Request.SendBuff[0].Position; + long inSize = context.Request.SendBuff[0].Size; + long outputPosition = context.Request.ReceiveBuff[0].Position; + long outputSize = context.Request.ReceiveBuff[0].Size; + + using (BinaryReader inputStream = new BinaryReader(new MemoryStream(context.Memory.ReadBytes(inPosition, inSize)))) + { + result = DecodeInterleavedInternal(inputStream, out short[] outPcmData, outputSize, out uint outConsumed, out int outSamples); + + if (result == ResultCode.Success) + { + byte[] pcmDataBytes = new byte[outPcmData.Length * sizeof(short)]; + Buffer.BlockCopy(outPcmData, 0, pcmDataBytes, 0, pcmDataBytes.Length); + context.Memory.WriteBytes(outputPosition, pcmDataBytes); + + context.ResponseData.Write(outConsumed); + context.ResponseData.Write(outSamples); + + // This is the time the DSP took to process the request, TODO: fill this. + context.ResponseData.Write(0); + } + } + + return result; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/IAudioController.cs b/Ryujinx.HLE/HOS/Services/Audio/IAudioController.cs new file mode 100644 index 0000000000..1bd2e31deb --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/IAudioController.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Audio +{ + [Service("audctl")] + class IAudioController : IpcService + { + public IAudioController(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/IAudioInManager.cs b/Ryujinx.HLE/HOS/Services/Audio/IAudioInManager.cs new file mode 100644 index 0000000000..d8e1f468e2 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/IAudioInManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Audio +{ + [Service("audin:u")] + class IAudioInManager : IpcService + { + public IAudioInManager(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/IAudioInManagerForApplet.cs b/Ryujinx.HLE/HOS/Services/Audio/IAudioInManagerForApplet.cs new file mode 100644 index 0000000000..37d9a8fe8c --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/IAudioInManagerForApplet.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Audio +{ + [Service("audin:a")] + class IAudioInManagerForApplet : IpcService + { + public IAudioInManagerForApplet(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/IAudioInManagerForDebugger.cs b/Ryujinx.HLE/HOS/Services/Audio/IAudioInManagerForDebugger.cs new file mode 100644 index 0000000000..1a497efb62 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/IAudioInManagerForDebugger.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Audio +{ + [Service("audin:d")] + class IAudioInManagerForDebugger : IpcService + { + public IAudioInManagerForDebugger(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManager.cs b/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManager.cs new file mode 100644 index 0000000000..19ee806745 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManager.cs @@ -0,0 +1,162 @@ +using ARMeilleure.Memory; +using Ryujinx.Audio; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Audio.AudioOutManager; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Audio +{ + [Service("audout:u")] + class IAudioOutManager : IpcService + { + private const string DefaultAudioOutput = "DeviceOut"; + private const int DefaultSampleRate = 48000; + private const int DefaultChannelsCount = 2; + + public IAudioOutManager(ServiceCtx context) { } + + [Command(0)] + // ListAudioOuts() -> (u32 count, buffer) + public ResultCode ListAudioOuts(ServiceCtx context) + { + return ListAudioOutsImpl( + context, + context.Request.ReceiveBuff[0].Position, + context.Request.ReceiveBuff[0].Size); + } + + [Command(1)] + // OpenAudioOut(u32 sample_rate, u16 unused, u16 channel_count, nn::applet::AppletResourceUserId, pid, handle, buffer name_in) + // -> (u32 sample_rate, u32 channel_count, u32 pcm_format, u32, object, buffer name_out) + public ResultCode OpenAudioOut(ServiceCtx context) + { + return OpenAudioOutImpl( + context, + context.Request.SendBuff[0].Position, + context.Request.SendBuff[0].Size, + context.Request.ReceiveBuff[0].Position, + context.Request.ReceiveBuff[0].Size); + } + + [Command(2)] // 3.0.0+ + // ListAudioOutsAuto() -> (u32 count, buffer) + public ResultCode ListAudioOutsAuto(ServiceCtx context) + { + (long recvPosition, long recvSize) = context.Request.GetBufferType0x22(); + + return ListAudioOutsImpl(context, recvPosition, recvSize); + } + + [Command(3)] // 3.0.0+ + // OpenAudioOutAuto(u32 sample_rate, u16 unused, u16 channel_count, nn::applet::AppletResourceUserId, pid, handle, buffer) + // -> (u32 sample_rate, u32 channel_count, u32 pcm_format, u32, object, buffer name_out) + public ResultCode OpenAudioOutAuto(ServiceCtx context) + { + (long sendPosition, long sendSize) = context.Request.GetBufferType0x21(); + (long recvPosition, long recvSize) = context.Request.GetBufferType0x22(); + + return OpenAudioOutImpl( + context, + sendPosition, + sendSize, + recvPosition, + recvSize); + } + + private ResultCode ListAudioOutsImpl(ServiceCtx context, long position, long size) + { + int nameCount = 0; + + byte[] deviceNameBuffer = Encoding.ASCII.GetBytes(DefaultAudioOutput + "\0"); + + if ((ulong)deviceNameBuffer.Length <= (ulong)size) + { + context.Memory.WriteBytes(position, deviceNameBuffer); + + nameCount++; + } + else + { + Logger.PrintError(LogClass.ServiceAudio, $"Output buffer size {size} too small!"); + } + + context.ResponseData.Write(nameCount); + + return ResultCode.Success; + } + + private ResultCode OpenAudioOutImpl(ServiceCtx context, long sendPosition, long sendSize, long receivePosition, long receiveSize) + { + string deviceName = MemoryHelper.ReadAsciiString( + context.Memory, + sendPosition, + sendSize); + + if (deviceName == string.Empty) + { + deviceName = DefaultAudioOutput; + } + + if (deviceName != DefaultAudioOutput) + { + Logger.PrintWarning(LogClass.Audio, "Invalid device name!"); + + return ResultCode.DeviceNotFound; + } + + byte[] deviceNameBuffer = Encoding.ASCII.GetBytes(deviceName + "\0"); + + if ((ulong)deviceNameBuffer.Length <= (ulong)receiveSize) + { + context.Memory.WriteBytes(receivePosition, deviceNameBuffer); + } + else + { + Logger.PrintError(LogClass.ServiceAudio, $"Output buffer size {receiveSize} too small!"); + } + + int sampleRate = context.RequestData.ReadInt32(); + int channels = context.RequestData.ReadInt32(); + + if (sampleRate == 0) + { + sampleRate = DefaultSampleRate; + } + + if (sampleRate != DefaultSampleRate) + { + Logger.PrintWarning(LogClass.Audio, "Invalid sample rate!"); + + return ResultCode.UnsupportedSampleRate; + } + + channels = (ushort)channels; + + if (channels == 0) + { + channels = DefaultChannelsCount; + } + + KEvent releaseEvent = new KEvent(context.Device.System); + + ReleaseCallback callback = () => + { + releaseEvent.ReadableEvent.Signal(); + }; + + IAalOutput audioOut = context.Device.AudioOut; + + int track = audioOut.OpenTrack(sampleRate, channels, callback); + + MakeObject(context, new IAudioOut(audioOut, releaseEvent, track)); + + context.ResponseData.Write(sampleRate); + context.ResponseData.Write(channels); + context.ResponseData.Write((int)SampleFormat.PcmInt16); + context.ResponseData.Write((int)PlaybackState.Stopped); + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManagerForApplet.cs b/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManagerForApplet.cs new file mode 100644 index 0000000000..4b41b0cfcf --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManagerForApplet.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Audio +{ + [Service("audout:a")] + class IAudioOutManagerForApplet : IpcService + { + public IAudioOutManagerForApplet(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManagerForDebugger.cs b/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManagerForDebugger.cs new file mode 100644 index 0000000000..41cde97269 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManagerForDebugger.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Audio +{ + [Service("audout:d")] + class IAudioOutManagerForDebugger : IpcService + { + public IAudioOutManagerForDebugger(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManager.cs b/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManager.cs new file mode 100644 index 0000000000..5ca968553b --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManager.cs @@ -0,0 +1,148 @@ +using Ryujinx.Audio; +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager; + +namespace Ryujinx.HLE.HOS.Services.Audio +{ + [Service("audren:u")] + class IAudioRendererManager : IpcService + { + public IAudioRendererManager(ServiceCtx context) { } + + [Command(0)] + // OpenAudioRenderer(nn::audio::detail::AudioRendererParameterInternal, u64, nn::applet::AppletResourceUserId, pid, handle, handle) + // -> object + public ResultCode OpenAudioRenderer(ServiceCtx context) + { + IAalOutput audioOut = context.Device.AudioOut; + + AudioRendererParameter Params = GetAudioRendererParameter(context); + + MakeObject(context, new IAudioRenderer( + context.Device.System, + context.Memory, + audioOut, + Params)); + + return ResultCode.Success; + } + + [Command(1)] + // GetWorkBufferSize(nn::audio::detail::AudioRendererParameterInternal) -> u64 + public ResultCode GetAudioRendererWorkBufferSize(ServiceCtx context) + { + AudioRendererParameter parameters = GetAudioRendererParameter(context); + + if (AudioRendererCommon.CheckValidRevision(parameters)) + { + BehaviorInfo behaviorInfo = new BehaviorInfo(); + + behaviorInfo.SetUserLibRevision(parameters.Revision); + + long size; + + int totalMixCount = parameters.SubMixCount + 1; + + size = BitUtils.AlignUp(parameters.MixBufferCount * 4, AudioRendererConsts.BufferAlignment) + + parameters.SubMixCount * 0x400 + + totalMixCount * 0x940 + + parameters.VoiceCount * 0x3F0 + + BitUtils.AlignUp(totalMixCount * 8, 16) + + BitUtils.AlignUp(parameters.VoiceCount * 8, 16) + + BitUtils.AlignUp(((parameters.SinkCount + parameters.SubMixCount) * 0x3C0 + parameters.SampleCount * 4) * + (parameters.MixBufferCount + 6), AudioRendererConsts.BufferAlignment) + + (parameters.SinkCount + parameters.SubMixCount) * 0x2C0 + + (parameters.EffectCount + parameters.VoiceCount * 4) * 0x30 + + 0x50; + + if (behaviorInfo.IsSplitterSupported()) + { + size += BitUtils.AlignUp(NodeStates.GetWorkBufferSize(totalMixCount) + EdgeMatrix.GetWorkBufferSize(totalMixCount), 16); + } + + size = parameters.SinkCount * 0x170 + + (parameters.SinkCount + parameters.SubMixCount) * 0x280 + + parameters.EffectCount * 0x4C0 + + ((size + SplitterContext.CalcWorkBufferSize(behaviorInfo, parameters) + 0x30 * parameters.EffectCount + (4 * parameters.VoiceCount) + 0x8F) & ~0x3FL) + + ((parameters.VoiceCount << 8) | 0x40); + + if (parameters.PerformanceManagerCount >= 1) + { + size += (PerformanceManager.GetRequiredBufferSizeForPerformanceMetricsPerFrame(behaviorInfo, parameters) * + (parameters.PerformanceManagerCount + 1) + 0xFF) & ~0x3FL; + } + + if (behaviorInfo.IsVariadicCommandBufferSizeSupported()) + { + size += CommandGenerator.CalculateCommandBufferSize(parameters) + 0x7E; + } + else + { + size += 0x1807E; + } + + size = BitUtils.AlignUp(size, 0x1000); + + context.ResponseData.Write(size); + + Logger.PrintDebug(LogClass.ServiceAudio, $"WorkBufferSize is 0x{size:x16}."); + + return ResultCode.Success; + } + else + { + context.ResponseData.Write(0L); + + Logger.PrintWarning(LogClass.ServiceAudio, $"Library Revision REV{AudioRendererCommon.GetRevisionVersion(parameters.Revision)} is not supported!"); + + return ResultCode.UnsupportedRevision; + } + } + + private AudioRendererParameter GetAudioRendererParameter(ServiceCtx context) + { + AudioRendererParameter Params = new AudioRendererParameter + { + SampleRate = context.RequestData.ReadInt32(), + SampleCount = context.RequestData.ReadInt32(), + MixBufferCount = context.RequestData.ReadInt32(), + SubMixCount = context.RequestData.ReadInt32(), + VoiceCount = context.RequestData.ReadInt32(), + SinkCount = context.RequestData.ReadInt32(), + EffectCount = context.RequestData.ReadInt32(), + PerformanceManagerCount = context.RequestData.ReadInt32(), + VoiceDropEnable = context.RequestData.ReadInt32(), + SplitterCount = context.RequestData.ReadInt32(), + SplitterDestinationDataCount = context.RequestData.ReadInt32(), + Unknown2C = context.RequestData.ReadInt32(), + Revision = context.RequestData.ReadInt32() + }; + + return Params; + } + + [Command(2)] + // GetAudioDeviceService(nn::applet::AppletResourceUserId) -> object + public ResultCode GetAudioDeviceService(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + + MakeObject(context, new IAudioDevice(context.Device.System)); + + return ResultCode.Success; + } + + [Command(4)] // 4.0.0+ + // GetAudioDeviceServiceWithRevisionInfo(u32 revision_info, nn::applet::AppletResourceUserId) -> object + public ResultCode GetAudioDeviceServiceWithRevisionInfo(ServiceCtx context) + { + int revisionInfo = context.RequestData.ReadInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceAudio, new { appletResourceUserId, revisionInfo }); + + return GetAudioDeviceService(context); + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManagerForApplet.cs b/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManagerForApplet.cs new file mode 100644 index 0000000000..ca5768ccaf --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManagerForApplet.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Audio +{ + [Service("audren:a")] + class IAudioRendererManagerForApplet : IpcService + { + public IAudioRendererManagerForApplet(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManagerForDebugger.cs b/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManagerForDebugger.cs new file mode 100644 index 0000000000..a970ae45cc --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManagerForDebugger.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Audio +{ + [Service("audren:d")] + class IAudioRendererManagerForDebugger : IpcService + { + public IAudioRendererManagerForDebugger(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/IAudioSnoopManager.cs b/Ryujinx.HLE/HOS/Services/Audio/IAudioSnoopManager.cs new file mode 100644 index 0000000000..59e3ad09b1 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/IAudioSnoopManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Audio +{ + [Service("auddev")] // 6.0.0+ + class IAudioSnoopManager : IpcService + { + public IAudioSnoopManager(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManager.cs b/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManager.cs new file mode 100644 index 0000000000..0143500811 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Audio +{ + [Service("audrec:u")] + class IFinalOutputRecorderManager : IpcService + { + public IFinalOutputRecorderManager(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManagerForApplet.cs b/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManagerForApplet.cs new file mode 100644 index 0000000000..d8fd270d50 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManagerForApplet.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Audio +{ + [Service("audrec:a")] + class IFinalOutputRecorderManagerForApplet : IpcService + { + public IFinalOutputRecorderManagerForApplet(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManagerForDebugger.cs b/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManagerForDebugger.cs new file mode 100644 index 0000000000..a8ec51ee50 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManagerForDebugger.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Audio +{ + [Service("audrec:d")] + class IFinalOutputRecorderManagerForDebugger : IpcService + { + public IFinalOutputRecorderManagerForDebugger(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/IHardwareOpusDecoderManager.cs b/Ryujinx.HLE/HOS/Services/Audio/IHardwareOpusDecoderManager.cs new file mode 100644 index 0000000000..ed40cdadb9 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/IHardwareOpusDecoderManager.cs @@ -0,0 +1,65 @@ +using Ryujinx.HLE.HOS.Services.Audio.HardwareOpusDecoderManager; + +namespace Ryujinx.HLE.HOS.Services.Audio +{ + [Service("hwopus")] + class IHardwareOpusDecoderManager : IpcService + { + public IHardwareOpusDecoderManager(ServiceCtx context) { } + + [Command(0)] + // Initialize(bytes<8, 4>, u32, handle) -> object + public ResultCode Initialize(ServiceCtx context) + { + int sampleRate = context.RequestData.ReadInt32(); + int channelsCount = context.RequestData.ReadInt32(); + + MakeObject(context, new IHardwareOpusDecoder(sampleRate, channelsCount)); + + return ResultCode.Success; + } + + [Command(1)] + // GetWorkBufferSize(bytes<8, 4>) -> u32 + public ResultCode GetWorkBufferSize(ServiceCtx context) + { + // Note: The sample rate is ignored because it is fixed to 48KHz. + int sampleRate = context.RequestData.ReadInt32(); + int channelsCount = context.RequestData.ReadInt32(); + + context.ResponseData.Write(GetOpusDecoderSize(channelsCount)); + + return ResultCode.Success; + } + + private static int GetOpusDecoderSize(int channelsCount) + { + const int silkDecoderSize = 0x2198; + + if (channelsCount < 1 || channelsCount > 2) + { + return 0; + } + + int celtDecoderSize = GetCeltDecoderSize(channelsCount); + + int opusDecoderSize = (channelsCount * 0x800 + 0x4807) & -0x800 | 0x50; + + return opusDecoderSize + silkDecoderSize + celtDecoderSize; + } + + private static int GetCeltDecoderSize(int channelsCount) + { + const int decodeBufferSize = 0x2030; + const int celtDecoderSize = 0x58; + const int celtSigSize = 0x4; + const int overlap = 120; + const int eBandsCount = 21; + + return (decodeBufferSize + overlap * 4) * channelsCount + + eBandsCount * 16 + + celtDecoderSize + + celtSigSize; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/ResultCode.cs b/Ryujinx.HLE/HOS/Services/Audio/ResultCode.cs new file mode 100644 index 0000000000..5bba3582fb --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/ResultCode.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.HLE.HOS.Services.Audio +{ + enum ResultCode + { + ModuleId = 153, + ErrorCodeShift = 9, + + Success = 0, + + DeviceNotFound = (1 << ErrorCodeShift) | ModuleId, + UnsupportedRevision = (2 << ErrorCodeShift) | ModuleId, + UnsupportedSampleRate = (3 << ErrorCodeShift) | ModuleId, + OpusInvalidInput = (6 << ErrorCodeShift) | ModuleId + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/Types/OpusPacketHeader.cs b/Ryujinx.HLE/HOS/Services/Audio/Types/OpusPacketHeader.cs new file mode 100644 index 0000000000..0a50a5c885 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/Types/OpusPacketHeader.cs @@ -0,0 +1,25 @@ +using Ryujinx.Common; +using System; +using System.Buffers.Binary; +using System.IO; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Audio.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct OpusPacketHeader + { + public uint length; + public uint finalRange; + + public static OpusPacketHeader FromStream(BinaryReader reader) + { + OpusPacketHeader header = reader.ReadStruct(); + + header.length = BitConverter.IsLittleEndian ? BinaryPrimitives.ReverseEndianness(header.length) : header.length; + header.finalRange = BitConverter.IsLittleEndian ? BinaryPrimitives.ReverseEndianness(header.finalRange) : header.finalRange; + + return header; + } + } +} diff --git a/Ryujinx.Audio/AudioFormat.cs b/Ryujinx.HLE/HOS/Services/Audio/Types/SampleFormat.cs similarity index 52% rename from Ryujinx.Audio/AudioFormat.cs rename to Ryujinx.HLE/HOS/Services/Audio/Types/SampleFormat.cs index 8250d1368e..654436e458 100644 --- a/Ryujinx.Audio/AudioFormat.cs +++ b/Ryujinx.HLE/HOS/Services/Audio/Types/SampleFormat.cs @@ -1,12 +1,12 @@ -namespace Ryujinx.Audio +namespace Ryujinx.HLE.HOS.Services.Audio { - public enum AudioFormat + enum SampleFormat : byte { Invalid = 0, PcmInt8 = 1, PcmInt16 = 2, - PcmImt24 = 3, - PcmImt32 = 4, + PcmInt24 = 3, + PcmInt32 = 4, PcmFloat = 5, Adpcm = 6 } diff --git a/Ryujinx.HLE/HOS/Services/Bcat/IServiceCreator.cs b/Ryujinx.HLE/HOS/Services/Bcat/IServiceCreator.cs new file mode 100644 index 0000000000..ec34f5407e --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Bcat/IServiceCreator.cs @@ -0,0 +1,50 @@ +using Ryujinx.HLE.HOS.Services.Bcat.ServiceCreator; +using Ryujinx.HLE.HOS.Services.Arp; + +namespace Ryujinx.HLE.HOS.Services.Bcat +{ + [Service("bcat:a")] + [Service("bcat:m")] + [Service("bcat:u")] + [Service("bcat:s")] + class IServiceCreator : IpcService + { + public IServiceCreator(ServiceCtx context) { } + + [Command(0)] + // CreateBcatService(u64, pid) -> object + public ResultCode CreateBcatService(ServiceCtx context) + { + // TODO: Call arp:r GetApplicationLaunchProperty with the pid to get the TitleId. + // Add an instance of nn::bcat::detail::service::core::PassphraseManager. + // Add an instance of nn::bcat::detail::service::ServiceMemoryManager. + // Add an instance of nn::bcat::detail::service::core::TaskManager who load "bcat-sys:/" system save data and open "dc/task.bin". + // If the file don't exist, create a new one (size of 0x800) and write 2 empty struct with a size of 0x400. + + MakeObject(context, new IBcatService(ApplicationLaunchProperty.GetByPid(context))); + + // NOTE: If the IBcatService is null this error is returned, Doesn't occur in our case. + // return ResultCode.NullObject; + + return ResultCode.Success; + } + + [Command(1)] + // CreateDeliveryCacheStorageService(u64, pid) -> object + public ResultCode CreateDeliveryCacheStorageService(ServiceCtx context) + { + // TODO: Call arp:r GetApplicationLaunchProperty with the pid to get the TitleId. + // Add an instance of nn::bcat::detail::service::core::ApplicationStorageManager who load "bcat-dc-X:/" system save data, + // return ResultCode.NullSaveData if failed. + // Where X depend of the ApplicationLaunchProperty stored in an array (range 0-3). + // Add an instance of nn::bcat::detail::service::ServiceMemoryManager. + + MakeObject(context, new IDeliveryCacheStorageService(context, ApplicationLaunchProperty.GetByPid(context))); + + // NOTE: If the IDeliveryCacheStorageService is null this error is returned, Doesn't occur in our case. + // return ResultCode.NullObject; + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Bcat/ResultCode.cs b/Ryujinx.HLE/HOS/Services/Bcat/ResultCode.cs new file mode 100644 index 0000000000..bc13d9dd4a --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Bcat/ResultCode.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.HLE.HOS.Services.Bcat +{ + enum ResultCode + { + ModuleId = 122, + ErrorCodeShift = 9, + + Success = 0, + + NullArgument = (2 << ErrorCodeShift) | ModuleId, + NullSaveData = (31 << ErrorCodeShift) | ModuleId, + NullObject = (91 << ErrorCodeShift) | ModuleId + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IBcatService.cs b/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IBcatService.cs new file mode 100644 index 0000000000..1b32756a25 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IBcatService.cs @@ -0,0 +1,9 @@ +using Ryujinx.HLE.HOS.Services.Arp; + +namespace Ryujinx.HLE.HOS.Services.Bcat.ServiceCreator +{ + class IBcatService : IpcService + { + public IBcatService(ApplicationLaunchProperty applicationLaunchProperty) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheStorageService.cs b/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheStorageService.cs new file mode 100644 index 0000000000..cad4437052 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheStorageService.cs @@ -0,0 +1,47 @@ +using Ryujinx.HLE.HOS.Services.Arp; +using System; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Bcat.ServiceCreator +{ + class IDeliveryCacheStorageService : IpcService + { + private const int DeliveryCacheDirectoriesLimit = 100; + private const int DeliveryCacheDirectoryNameLength = 32; + + private string[] _deliveryCacheDirectories = new string[0]; + + public IDeliveryCacheStorageService(ServiceCtx context, ApplicationLaunchProperty applicationLaunchProperty) + { + // TODO: Read directories.meta file from the save data (loaded in IServiceCreator) in _deliveryCacheDirectories. + } + + [Command(10)] + // EnumerateDeliveryCacheDirectory() -> (u32, buffer) + public ResultCode EnumerateDeliveryCacheDirectory(ServiceCtx context) + { + long outputPosition = context.Request.ReceiveBuff[0].Position; + long outputSize = context.Request.ReceiveBuff[0].Size; + + for (int index = 0; index < _deliveryCacheDirectories.Length; index++) + { + if (index == DeliveryCacheDirectoriesLimit - 1) + { + break; + } + + byte[] directoryNameBuffer = Encoding.ASCII.GetBytes(_deliveryCacheDirectories[index]); + + Array.Resize(ref directoryNameBuffer, DeliveryCacheDirectoryNameLength); + + directoryNameBuffer[DeliveryCacheDirectoryNameLength - 1] = 0x00; + + context.Memory.WriteBytes(outputPosition + index * DeliveryCacheDirectoryNameLength, directoryNameBuffer); + } + + context.ResponseData.Write(_deliveryCacheDirectories.Length); + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Bgtc/IStateControlService.cs b/Ryujinx.HLE/HOS/Services/Bgtc/IStateControlService.cs new file mode 100644 index 0000000000..4926d4d829 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Bgtc/IStateControlService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Bgct +{ + [Service("bgtc:sc")] + class IStateControlService : IpcService + { + public IStateControlService(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Bgtc/ITaskService.cs b/Ryujinx.HLE/HOS/Services/Bgtc/ITaskService.cs new file mode 100644 index 0000000000..a032c3809d --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Bgtc/ITaskService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Bgct +{ + [Service("bgtc:t")] + class ITaskService : IpcService + { + public ITaskService(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Bluetooth/BluetoothDriver/BluetoothEventManager.cs b/Ryujinx.HLE/HOS/Services/Bluetooth/BluetoothDriver/BluetoothEventManager.cs new file mode 100644 index 0000000000..81f4a7d292 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Bluetooth/BluetoothDriver/BluetoothEventManager.cs @@ -0,0 +1,25 @@ +using Ryujinx.HLE.HOS.Kernel.Threading; + +namespace Ryujinx.HLE.HOS.Services.Bluetooth.BluetoothDriver +{ + static class BluetoothEventManager + { + public static KEvent InitializeBleDebugEvent; + public static int InitializeBleDebugEventHandle; + + public static KEvent UnknownBleDebugEvent; + public static int UnknownBleDebugEventHandle; + + public static KEvent RegisterBleDebugEvent; + public static int RegisterBleDebugEventHandle; + + public static KEvent InitializeBleEvent; + public static int InitializeBleEventHandle; + + public static KEvent UnknownBleEvent; + public static int UnknownBleEventHandle; + + public static KEvent RegisterBleEvent; + public static int RegisterBleEventHandle; + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Bluetooth/IBluetoothDriver.cs b/Ryujinx.HLE/HOS/Services/Bluetooth/IBluetoothDriver.cs new file mode 100644 index 0000000000..fc20ec3086 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Bluetooth/IBluetoothDriver.cs @@ -0,0 +1,92 @@ +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Bluetooth.BluetoothDriver; +using Ryujinx.HLE.HOS.Services.Settings; +using System; + +namespace Ryujinx.HLE.HOS.Services.Bluetooth +{ + [Service("btdrv")] + class IBluetoothDriver : IpcService + { + private string _unknownLowEnergy; + + public IBluetoothDriver(ServiceCtx context) { } + + [Command(46)] + // InitializeBluetoothLe() -> handle + public ResultCode InitializeBluetoothLe(ServiceCtx context) + { + NxSettings.Settings.TryGetValue("bluetooth_debug!skip_boot", out object debugMode); + + if ((bool)debugMode) + { + if (BluetoothEventManager.InitializeBleDebugEventHandle == 0) + { + BluetoothEventManager.InitializeBleDebugEvent = new KEvent(context.Device.System); + + if (context.Process.HandleTable.GenerateHandle(BluetoothEventManager.InitializeBleDebugEvent.ReadableEvent, out BluetoothEventManager.InitializeBleDebugEventHandle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + if (BluetoothEventManager.UnknownBleDebugEventHandle == 0) + { + BluetoothEventManager.UnknownBleDebugEvent = new KEvent(context.Device.System); + + if (context.Process.HandleTable.GenerateHandle(BluetoothEventManager.UnknownBleDebugEvent.ReadableEvent, out BluetoothEventManager.UnknownBleDebugEventHandle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + if (BluetoothEventManager.RegisterBleDebugEventHandle == 0) + { + BluetoothEventManager.RegisterBleDebugEvent = new KEvent(context.Device.System); + + if (context.Process.HandleTable.GenerateHandle(BluetoothEventManager.RegisterBleDebugEvent.ReadableEvent, out BluetoothEventManager.RegisterBleDebugEventHandle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + } + else + { + _unknownLowEnergy = "low_energy"; + + if (BluetoothEventManager.InitializeBleEventHandle == 0) + { + BluetoothEventManager.InitializeBleEvent = new KEvent(context.Device.System); + + if (context.Process.HandleTable.GenerateHandle(BluetoothEventManager.InitializeBleEvent.ReadableEvent, out BluetoothEventManager.InitializeBleEventHandle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + if (BluetoothEventManager.UnknownBleEventHandle == 0) + { + BluetoothEventManager.UnknownBleEvent = new KEvent(context.Device.System); + + if (context.Process.HandleTable.GenerateHandle(BluetoothEventManager.UnknownBleEvent.ReadableEvent, out BluetoothEventManager.UnknownBleEventHandle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + if (BluetoothEventManager.RegisterBleEventHandle == 0) + { + BluetoothEventManager.RegisterBleEvent = new KEvent(context.Device.System); + + if (context.Process.HandleTable.GenerateHandle(BluetoothEventManager.RegisterBleEvent.ReadableEvent, out BluetoothEventManager.RegisterBleEventHandle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + } + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Bluetooth/IBluetoothUser.cs b/Ryujinx.HLE/HOS/Services/Bluetooth/IBluetoothUser.cs new file mode 100644 index 0000000000..c5693c5724 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Bluetooth/IBluetoothUser.cs @@ -0,0 +1,30 @@ +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Services.Bluetooth.BluetoothDriver; +using Ryujinx.HLE.HOS.Services.Settings; + +namespace Ryujinx.HLE.HOS.Services.Bluetooth +{ + [Service("bt")] + class IBluetoothUser : IpcService + { + public IBluetoothUser(ServiceCtx context) { } + + [Command(9)] + // RegisterBleEvent(pid) -> handle + public ResultCode RegisterBleEvent(ServiceCtx context) + { + NxSettings.Settings.TryGetValue("bluetooth_debug!skip_boot", out object debugMode); + + if ((bool)debugMode) + { + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(BluetoothEventManager.RegisterBleDebugEventHandle); + } + else + { + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(BluetoothEventManager.RegisterBleEventHandle); + } + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/BluetoothManager/BtmUser/IBtmUserCore.cs b/Ryujinx.HLE/HOS/Services/BluetoothManager/BtmUser/IBtmUserCore.cs new file mode 100644 index 0000000000..47fdb88f7b --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/BluetoothManager/BtmUser/IBtmUserCore.cs @@ -0,0 +1,128 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Threading; + +namespace Ryujinx.HLE.HOS.Services.BluetoothManager.BtmUser +{ + class IBtmUserCore : IpcService + { + public KEvent _bleScanEvent; + public int _bleScanEventHandle; + + public KEvent _bleConnectionEvent; + public int _bleConnectionEventHandle; + + public KEvent _bleServiceDiscoveryEvent; + public int _bleServiceDiscoveryEventHandle; + + public KEvent _bleMtuConfigEvent; + public int _bleMtuConfigEventHandle; + + public IBtmUserCore() { } + + [Command(0)] // 5.0.0+ + // AcquireBleScanEvent() -> (byte<1>, handle) + public ResultCode AcquireBleScanEvent(ServiceCtx context) + { + KernelResult result = KernelResult.Success; + + if (_bleScanEventHandle == 0) + { + _bleScanEvent = new KEvent(context.Device.System); + + result = context.Process.HandleTable.GenerateHandle(_bleScanEvent.ReadableEvent, out _bleScanEventHandle); + + if (result != KernelResult.Success) + { + // NOTE: We use a Logging instead of an exception because the call return a boolean if succeed or not. + Logger.PrintError(LogClass.ServiceBsd, "Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_bleScanEventHandle); + + context.ResponseData.Write(result == KernelResult.Success ? 1 : 0); + + return ResultCode.Success; + } + + [Command(17)] // 5.0.0+ + // AcquireBleConnectionEvent() -> (byte<1>, handle) + public ResultCode AcquireBleConnectionEvent(ServiceCtx context) + { + KernelResult result = KernelResult.Success; + + if (_bleConnectionEventHandle == 0) + { + _bleConnectionEvent = new KEvent(context.Device.System); + + result = context.Process.HandleTable.GenerateHandle(_bleConnectionEvent.ReadableEvent, out _bleConnectionEventHandle); + + if (result != KernelResult.Success) + { + // NOTE: We use a Logging instead of an exception because the call return a boolean if succeed or not. + Logger.PrintError(LogClass.ServiceBsd, "Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_bleConnectionEventHandle); + + context.ResponseData.Write(result == KernelResult.Success ? 1 : 0); + + return ResultCode.Success; + } + + [Command(26)] // 5.0.0+ + // AcquireBleServiceDiscoveryEvent() -> (byte<1>, handle) + public ResultCode AcquireBleServiceDiscoveryEvent(ServiceCtx context) + { + KernelResult result = KernelResult.Success; + + if (_bleServiceDiscoveryEventHandle == 0) + { + _bleServiceDiscoveryEvent = new KEvent(context.Device.System); + + result = context.Process.HandleTable.GenerateHandle(_bleServiceDiscoveryEvent.ReadableEvent, out _bleServiceDiscoveryEventHandle); + + if (result != KernelResult.Success) + { + // NOTE: We use a Logging instead of an exception because the call return a boolean if succeed or not. + Logger.PrintError(LogClass.ServiceBsd, "Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_bleServiceDiscoveryEventHandle); + + context.ResponseData.Write(result == KernelResult.Success ? 1 : 0); + + return ResultCode.Success; + } + + [Command(33)] // 5.0.0+ + // AcquireBleMtuConfigEvent() -> (byte<1>, handle) + public ResultCode AcquireBleMtuConfigEvent(ServiceCtx context) + { + KernelResult result = KernelResult.Success; + + if (_bleMtuConfigEventHandle == 0) + { + _bleMtuConfigEvent = new KEvent(context.Device.System); + + result = context.Process.HandleTable.GenerateHandle(_bleMtuConfigEvent.ReadableEvent, out _bleMtuConfigEventHandle); + + if (result != KernelResult.Success) + { + // NOTE: We use a Logging instead of an exception because the call return a boolean if succeed or not. + Logger.PrintError(LogClass.ServiceBsd, "Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_bleMtuConfigEventHandle); + + context.ResponseData.Write(result == KernelResult.Success ? 1 : 0); + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/BluetoothManager/IBtm.cs b/Ryujinx.HLE/HOS/Services/BluetoothManager/IBtm.cs new file mode 100644 index 0000000000..48a273a099 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/BluetoothManager/IBtm.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.BluetoothManager +{ + [Service("btm")] + class IBtm : IpcService + { + public IBtm(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/BluetoothManager/IBtmDebug.cs b/Ryujinx.HLE/HOS/Services/BluetoothManager/IBtmDebug.cs new file mode 100644 index 0000000000..259698af57 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/BluetoothManager/IBtmDebug.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.BluetoothManager +{ + [Service("btm:dbg")] + class IBtmDebug : IpcService + { + public IBtmDebug(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/BluetoothManager/IBtmSystem.cs b/Ryujinx.HLE/HOS/Services/BluetoothManager/IBtmSystem.cs new file mode 100644 index 0000000000..c4210b7826 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/BluetoothManager/IBtmSystem.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.BluetoothManager +{ + [Service("btm:sys")] + class IBtmSystem : IpcService + { + public IBtmSystem(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/BluetoothManager/IBtmUser.cs b/Ryujinx.HLE/HOS/Services/BluetoothManager/IBtmUser.cs new file mode 100644 index 0000000000..b704b51ca2 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/BluetoothManager/IBtmUser.cs @@ -0,0 +1,19 @@ +using Ryujinx.HLE.HOS.Services.BluetoothManager.BtmUser; + +namespace Ryujinx.HLE.HOS.Services.BluetoothManager +{ + [Service("btm:u")] // 5.0.0+ + class IBtmUser : IpcService + { + public IBtmUser(ServiceCtx context) { } + + [Command(0)] // 5.0.0+ + // GetCore() -> object + public ResultCode GetCore(ServiceCtx context) + { + MakeObject(context, new IBtmUserCore()); + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/BluetoothManager/ResultCode.cs b/Ryujinx.HLE/HOS/Services/BluetoothManager/ResultCode.cs new file mode 100644 index 0000000000..0ad2c48553 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/BluetoothManager/ResultCode.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Services.BluetoothManager +{ + enum ResultCode + { + ModuleId = 143, + ErrorCodeShift = 9, + + Success = 0 + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Caps/IAlbumAccessorService.cs b/Ryujinx.HLE/HOS/Services/Caps/IAlbumAccessorService.cs new file mode 100644 index 0000000000..4071b9cc06 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Caps/IAlbumAccessorService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Caps +{ + [Service("caps:a")] + class IAlbumAccessorService : IpcService + { + public IAlbumAccessorService(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Caps/IAlbumApplicationService.cs b/Ryujinx.HLE/HOS/Services/Caps/IAlbumApplicationService.cs new file mode 100644 index 0000000000..199d6aa350 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Caps/IAlbumApplicationService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Caps +{ + [Service("caps:u")] + class IAlbumApplicationService : IpcService + { + public IAlbumApplicationService(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Caps/IAlbumControlService.cs b/Ryujinx.HLE/HOS/Services/Caps/IAlbumControlService.cs new file mode 100644 index 0000000000..de880153b3 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Caps/IAlbumControlService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Caps +{ + [Service("caps:c")] + class IAlbumControlService : IpcService + { + public IAlbumControlService(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Caps/IScreenShotApplicationService.cs b/Ryujinx.HLE/HOS/Services/Caps/IScreenShotApplicationService.cs new file mode 100644 index 0000000000..209bfd3d77 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Caps/IScreenShotApplicationService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Caps +{ + [Service("caps:su")] // 6.0.0+ + class IScreenShotApplicationService : IpcService + { + public IScreenShotApplicationService(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Caps/IScreenShotControlService.cs b/Ryujinx.HLE/HOS/Services/Caps/IScreenShotControlService.cs new file mode 100644 index 0000000000..337fa9eec1 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Caps/IScreenShotControlService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Caps +{ + [Service("caps:sc")] + class IScreenShotControlService : IpcService + { + public IScreenShotControlService(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Caps/IScreenshotService.cs b/Ryujinx.HLE/HOS/Services/Caps/IScreenshotService.cs new file mode 100644 index 0000000000..03703e05d7 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Caps/IScreenshotService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Caps +{ + [Service("caps:ss")] // 2.0.0+ + class IScreenshotService : IpcService + { + public IScreenshotService(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Cec/ICecManager.cs b/Ryujinx.HLE/HOS/Services/Cec/ICecManager.cs new file mode 100644 index 0000000000..71c267868b --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Cec/ICecManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Cec +{ + [Service("cec-mgr")] + class ICecManager : IpcService + { + public ICecManager(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/CommandAttributes.cs b/Ryujinx.HLE/HOS/Services/CommandAttributes.cs new file mode 100644 index 0000000000..2747552b86 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/CommandAttributes.cs @@ -0,0 +1,12 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services +{ + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + public class CommandAttribute : Attribute + { + public readonly int Id; + + public CommandAttribute(int id) => Id = id; + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/DummyService.cs b/Ryujinx.HLE/HOS/Services/DummyService.cs new file mode 100644 index 0000000000..d69441a3a8 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/DummyService.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.HLE.HOS.Services +{ + class DummyService : IpcService + { + public string ServiceName { get; set; } + + public DummyService(string serviceName) + { + ServiceName = serviceName; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Erpt/IContext.cs b/Ryujinx.HLE/HOS/Services/Erpt/IContext.cs new file mode 100644 index 0000000000..9a689172bf --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Erpt/IContext.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Erpt +{ + [Service("erpt:c")] + class IContext : IpcService + { + public IContext(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Erpt/ISession.cs b/Ryujinx.HLE/HOS/Services/Erpt/ISession.cs new file mode 100644 index 0000000000..6397afae8d --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Erpt/ISession.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Erpt +{ + [Service("erpt:r")] + class ISession : IpcService + { + public ISession(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Es/IETicketService.cs b/Ryujinx.HLE/HOS/Services/Es/IETicketService.cs new file mode 100644 index 0000000000..34be7bdd87 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Es/IETicketService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Es +{ + [Service("es")] + class IETicketService : IpcService + { + public IETicketService(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Eupld/IControl.cs b/Ryujinx.HLE/HOS/Services/Eupld/IControl.cs new file mode 100644 index 0000000000..dd8705e66d --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Eupld/IControl.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Eupld +{ + [Service("eupld:c")] + class IControl : IpcService + { + public IControl(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Eupld/IRequest.cs b/Ryujinx.HLE/HOS/Services/Eupld/IRequest.cs new file mode 100644 index 0000000000..8509787892 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Eupld/IRequest.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Eupld +{ + [Service("eupld:r")] + class IRequest : IpcService + { + public IRequest(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Fatal/IPrivateService.cs b/Ryujinx.HLE/HOS/Services/Fatal/IPrivateService.cs new file mode 100644 index 0000000000..eb2c955345 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Fatal/IPrivateService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Fatal +{ + [Service("fatal:p")] + class IPrivateService : IpcService + { + public IPrivateService(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Fatal/IService.cs b/Ryujinx.HLE/HOS/Services/Fatal/IService.cs new file mode 100644 index 0000000000..692d2b0b7d --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Fatal/IService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Fatal +{ + [Service("fatal:u")] + class IService : IpcService + { + public IService(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Friend/IServiceCreator.cs b/Ryujinx.HLE/HOS/Services/Friend/IServiceCreator.cs new file mode 100644 index 0000000000..cec3c422ad --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Friend/IServiceCreator.cs @@ -0,0 +1,55 @@ +using Ryujinx.Common; +using Ryujinx.HLE.HOS.Services.Friend.ServiceCreator; +using Ryujinx.HLE.Utilities; + +namespace Ryujinx.HLE.HOS.Services.Friend +{ + [Service("friend:a", FriendServicePermissionLevel.Admin)] + [Service("friend:m", FriendServicePermissionLevel.Manager)] + [Service("friend:s", FriendServicePermissionLevel.System)] + [Service("friend:u", FriendServicePermissionLevel.User)] + [Service("friend:v", FriendServicePermissionLevel.Overlay)] + class IServiceCreator : IpcService + { + private FriendServicePermissionLevel _permissionLevel; + + public IServiceCreator(ServiceCtx context, FriendServicePermissionLevel permissionLevel) + { + _permissionLevel = permissionLevel; + } + + [Command(0)] + // CreateFriendService() -> object + public ResultCode CreateFriendService(ServiceCtx context) + { + MakeObject(context, new IFriendService(_permissionLevel)); + + return ResultCode.Success; + } + + [Command(1)] // 2.0.0+ + // CreateNotificationService(nn::account::Uid) -> object + public ResultCode CreateNotificationService(ServiceCtx context) + { + UInt128 userId = context.RequestData.ReadStruct(); + + if (userId.IsNull) + { + return ResultCode.InvalidArgument; + } + + MakeObject(context, new INotificationService(context, userId, _permissionLevel)); + + return ResultCode.Success; + } + + [Command(2)] // 4.0.0+ + // CreateDaemonSuspendSessionService() -> object + public ResultCode CreateDaemonSuspendSessionService(ServiceCtx context) + { + MakeObject(context, new IDaemonSuspendSessionService(_permissionLevel)); + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Friend/ResultCode.cs b/Ryujinx.HLE/HOS/Services/Friend/ResultCode.cs new file mode 100644 index 0000000000..2edc20cbea --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Friend/ResultCode.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.HLE.HOS.Services.Friend +{ + enum ResultCode + { + ModuleId = 121, + ErrorCodeShift = 9, + + Success = 0, + + InvalidArgument = (2 << ErrorCodeShift) | ModuleId, + NotificationQueueEmpty = (15 << ErrorCodeShift) | ModuleId + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/Friend.cs b/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/Friend.cs new file mode 100644 index 0000000000..4947a5ce95 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/Friend.cs @@ -0,0 +1,29 @@ +using Ryujinx.HLE.Utilities; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.FriendService +{ + [StructLayout(LayoutKind.Sequential, Pack = 0x8, Size = 0x200, CharSet = CharSet.Ansi)] + struct Friend + { + public UInt128 UserId; + public long NetworkUserId; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x21)] + public string Nickname; + + public UserPresence presence; + + [MarshalAs(UnmanagedType.I1)] + public bool IsFavourite; + + [MarshalAs(UnmanagedType.I1)] + public bool IsNew; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x6)] + char[] Unknown; + + [MarshalAs(UnmanagedType.I1)] + public bool IsValid; + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/FriendFilter.cs b/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/FriendFilter.cs new file mode 100644 index 0000000000..261bf7bf0c --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/FriendFilter.cs @@ -0,0 +1,24 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.FriendService +{ + [StructLayout(LayoutKind.Sequential)] + struct FriendFilter + { + public PresenceStatusFilter PresenceStatus; + + [MarshalAs(UnmanagedType.I1)] + public bool IsFavoriteOnly; + + [MarshalAs(UnmanagedType.I1)] + public bool IsSameAppPresenceOnly; + + [MarshalAs(UnmanagedType.I1)] + public bool IsSameAppPlayedOnly; + + [MarshalAs(UnmanagedType.I1)] + public bool IsArbitraryAppPlayedOnly; + + public long PresenceGroupId; + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/PresenceStatus.cs b/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/PresenceStatus.cs new file mode 100644 index 0000000000..df2e65257f --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/PresenceStatus.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.FriendService +{ + enum PresenceStatus : uint + { + Offline, + Online, + OnlinePlay + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/PresenceStatusFilter.cs b/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/PresenceStatusFilter.cs new file mode 100644 index 0000000000..24da7fd33f --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/PresenceStatusFilter.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.FriendService +{ + enum PresenceStatusFilter : uint + { + None, + Online, + OnlinePlay, + OnlineOrOnlinePlay + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/UserPresence.cs b/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/UserPresence.cs new file mode 100644 index 0000000000..5fe8bfd727 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/UserPresence.cs @@ -0,0 +1,27 @@ +using Ryujinx.HLE.Utilities; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.FriendService +{ + [StructLayout(LayoutKind.Sequential, Pack = 0x8, CharSet = CharSet.Ansi)] + struct UserPresence + { + public UInt128 UserId; + public long LastTimeOnlineTimestamp; + public PresenceStatus Status; + + [MarshalAs(UnmanagedType.I1)] + public bool SamePresenceGroupApplication; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x3)] + public char[] Unknown; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0xC0)] + public char[] AppKeyValueStorage; + + public override string ToString() + { + return $"UserPresence {{ UserId: {UserId}, LastTimeOnlineTimestamp: {LastTimeOnlineTimestamp}, Status: {Status}, AppKeyValueStorage: {AppKeyValueStorage} }}"; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IDaemonSuspendSessionService.cs b/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IDaemonSuspendSessionService.cs new file mode 100644 index 0000000000..42b34312c1 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IDaemonSuspendSessionService.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator +{ + class IDaemonSuspendSessionService : IpcService + { + private FriendServicePermissionLevel PermissionLevel; + + public IDaemonSuspendSessionService(FriendServicePermissionLevel permissionLevel) + { + PermissionLevel = permissionLevel; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IFriendService.cs b/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IFriendService.cs new file mode 100644 index 0000000000..7492c5a72d --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IFriendService.cs @@ -0,0 +1,170 @@ +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Services.Account.Acc; +using Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.FriendService; +using Ryujinx.HLE.Utilities; +using System.IO; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator +{ + class IFriendService : IpcService + { + private FriendServicePermissionLevel _permissionLevel; + + public IFriendService(FriendServicePermissionLevel permissionLevel) + { + _permissionLevel = permissionLevel; + } + + [Command(10100)] + // nn::friends::GetFriendListIds(int offset, nn::account::Uid userUUID, nn::friends::detail::ipc::SizedFriendFilter friendFilter, ulong pidPlaceHolder, pid) + // -> int outCount, array + public ResultCode GetFriendListIds(ServiceCtx context) + { + int offset = context.RequestData.ReadInt32(); + + // Padding + context.RequestData.ReadInt32(); + + UInt128 uuid = context.RequestData.ReadStruct(); + FriendFilter filter = context.RequestData.ReadStruct(); + + // Pid placeholder + context.RequestData.ReadInt64(); + + if (uuid.IsNull) + { + return ResultCode.InvalidArgument; + } + + // There are no friends online, so we return 0 because the nn::account::NetworkServiceAccountId array is empty. + context.ResponseData.Write(0); + + Logger.PrintStub(LogClass.ServiceFriend, new + { + UserId = uuid.ToString(), + offset, + filter.PresenceStatus, + filter.IsFavoriteOnly, + filter.IsSameAppPresenceOnly, + filter.IsSameAppPlayedOnly, + filter.IsArbitraryAppPlayedOnly, + filter.PresenceGroupId, + }); + + return ResultCode.Success; + } + + [Command(10101)] + // nn::friends::GetFriendList(int offset, nn::account::Uid userUUID, nn::friends::detail::ipc::SizedFriendFilter friendFilter, ulong pidPlaceHolder, pid) + // -> int outCount, array + public ResultCode GetFriendList(ServiceCtx context) + { + int offset = context.RequestData.ReadInt32(); + + // Padding + context.RequestData.ReadInt32(); + + UInt128 uuid = context.RequestData.ReadStruct(); + FriendFilter filter = context.RequestData.ReadStruct(); + + // Pid placeholder + context.RequestData.ReadInt64(); + + if (uuid.IsNull) + { + return ResultCode.InvalidArgument; + } + + // There are no friends online, so we return 0 because the nn::account::NetworkServiceAccountId array is empty. + context.ResponseData.Write(0); + + Logger.PrintStub(LogClass.ServiceFriend, new { + UserId = uuid.ToString(), + offset, + filter.PresenceStatus, + filter.IsFavoriteOnly, + filter.IsSameAppPresenceOnly, + filter.IsSameAppPlayedOnly, + filter.IsArbitraryAppPlayedOnly, + filter.PresenceGroupId, + }); + + return ResultCode.Success; + } + + [Command(10600)] + // nn::friends::DeclareOpenOnlinePlaySession(nn::account::Uid) + public ResultCode DeclareOpenOnlinePlaySession(ServiceCtx context) + { + UInt128 uuid = context.RequestData.ReadStruct(); + + if (uuid.IsNull) + { + return ResultCode.InvalidArgument; + } + + if (context.Device.System.State.Account.TryGetUser(uuid, out UserProfile profile)) + { + profile.OnlinePlayState = AccountState.Open; + } + + Logger.PrintStub(LogClass.ServiceFriend, new { UserId = uuid.ToString(), profile.OnlinePlayState }); + + return ResultCode.Success; + } + + [Command(10601)] + // nn::friends::DeclareCloseOnlinePlaySession(nn::account::Uid) + public ResultCode DeclareCloseOnlinePlaySession(ServiceCtx context) + { + UInt128 uuid = context.RequestData.ReadStruct(); + + if (uuid.IsNull) + { + return ResultCode.InvalidArgument; + } + + if (context.Device.System.State.Account.TryGetUser(uuid, out UserProfile profile)) + { + profile.OnlinePlayState = AccountState.Closed; + } + + Logger.PrintStub(LogClass.ServiceFriend, new { UserId = uuid.ToString(), profile.OnlinePlayState }); + + return ResultCode.Success; + } + + [Command(10610)] + // nn::friends::UpdateUserPresence(nn::account::Uid, u64, pid, buffer) + public ResultCode UpdateUserPresence(ServiceCtx context) + { + UInt128 uuid = context.RequestData.ReadStruct(); + + // Pid placeholder + context.RequestData.ReadInt64(); + + long position = context.Request.PtrBuff[0].Position; + long size = context.Request.PtrBuff[0].Size; + + byte[] bufferContent = context.Memory.ReadBytes(position, size); + + if (uuid.IsNull) + { + return ResultCode.InvalidArgument; + } + + int elementCount = bufferContent.Length / Marshal.SizeOf(); + + using (BinaryReader bufferReader = new BinaryReader(new MemoryStream(bufferContent))) + { + UserPresence[] userPresenceInputArray = bufferReader.ReadStructArray(elementCount); + + Logger.PrintStub(LogClass.ServiceFriend, new { UserId = uuid.ToString(), userPresenceInputArray }); + } + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/INotificationService.cs b/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/INotificationService.cs new file mode 100644 index 0000000000..1ff37442c1 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/INotificationService.cs @@ -0,0 +1,175 @@ +using Ryujinx.Common; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.NotificationService; +using Ryujinx.HLE.Utilities; +using System; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator +{ + class INotificationService : IpcService, IDisposable + { + private readonly UInt128 _userId; + private readonly FriendServicePermissionLevel _permissionLevel; + + private readonly object _lock = new object(); + + private KEvent _notificationEvent; + private int _notificationEventHandle = 0; + + private LinkedList _notifications; + + private bool _hasNewFriendRequest; + private bool _hasFriendListUpdate; + + public INotificationService(ServiceCtx context, UInt128 userId, FriendServicePermissionLevel permissionLevel) + { + _userId = userId; + _permissionLevel = permissionLevel; + _notifications = new LinkedList(); + _notificationEvent = new KEvent(context.Device.System); + + _hasNewFriendRequest = false; + _hasFriendListUpdate = false; + + NotificationEventHandler.Instance.RegisterNotificationService(this); + } + + [Command(0)] //2.0.0+ + // nn::friends::detail::ipc::INotificationService::GetEvent() -> handle + public ResultCode GetEvent(ServiceCtx context) + { + if (_notificationEventHandle == 0) + { + if (context.Process.HandleTable.GenerateHandle(_notificationEvent.ReadableEvent, out _notificationEventHandle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_notificationEventHandle); + + return ResultCode.Success; + } + + [Command(1)] //2.0.0+ + // nn::friends::detail::ipc::INotificationService::Clear() + public ResultCode Clear(ServiceCtx context) + { + lock (_lock) + { + _hasNewFriendRequest = false; + _hasFriendListUpdate = false; + + _notifications.Clear(); + } + + return ResultCode.Success; + } + + [Command(2)] // 2.0.0+ + // nn::friends::detail::ipc::INotificationService::Pop() -> nn::friends::detail::ipc::SizedNotificationInfo + public ResultCode Pop(ServiceCtx context) + { + lock (_lock) + { + if (_notifications.Count >= 1) + { + NotificationInfo notificationInfo = _notifications.First.Value; + _notifications.RemoveFirst(); + + if (notificationInfo.Type == NotificationEventType.FriendListUpdate) + { + _hasFriendListUpdate = false; + } + else if (notificationInfo.Type == NotificationEventType.NewFriendRequest) + { + _hasNewFriendRequest = false; + } + + context.ResponseData.WriteStruct(notificationInfo); + + return ResultCode.Success; + } + } + + return ResultCode.NotificationQueueEmpty; + } + + public void SignalFriendListUpdate(UInt128 targetId) + { + lock (_lock) + { + if (_userId == targetId) + { + if (!_hasFriendListUpdate) + { + NotificationInfo friendListNotification = new NotificationInfo(); + + if (_notifications.Count != 0) + { + friendListNotification = _notifications.First.Value; + _notifications.RemoveFirst(); + } + + friendListNotification.Type = NotificationEventType.FriendListUpdate; + _hasFriendListUpdate = true; + + if (_hasNewFriendRequest) + { + NotificationInfo newFriendRequestNotification = new NotificationInfo(); + + if (_notifications.Count != 0) + { + newFriendRequestNotification = _notifications.First.Value; + _notifications.RemoveFirst(); + } + + newFriendRequestNotification.Type = NotificationEventType.NewFriendRequest; + _notifications.AddFirst(newFriendRequestNotification); + } + + // We defer this to make sure we are on top of the queue. + _notifications.AddFirst(friendListNotification); + } + + _notificationEvent.ReadableEvent.Signal(); + } + } + } + + public void SignalNewFriendRequest(UInt128 targetId) + { + lock (_lock) + { + if ((_permissionLevel & FriendServicePermissionLevel.OverlayMask) != 0 && _userId == targetId) + { + if (!_hasNewFriendRequest) + { + if (_notifications.Count == 100) + { + SignalFriendListUpdate(targetId); + } + + NotificationInfo newFriendRequestNotification = new NotificationInfo + { + Type = NotificationEventType.NewFriendRequest + }; + + _notifications.AddLast(newFriendRequestNotification); + _hasNewFriendRequest = true; + } + + _notificationEvent.ReadableEvent.Signal(); + } + } + } + + public void Dispose() + { + NotificationEventHandler.Instance.UnregisterNotificationService(this); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/NotificationService/NotificationEventHandler.cs b/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/NotificationService/NotificationEventHandler.cs new file mode 100644 index 0000000000..19b15416a0 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/NotificationService/NotificationEventHandler.cs @@ -0,0 +1,83 @@ +using Ryujinx.HLE.Utilities; + +namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.NotificationService +{ + public sealed class NotificationEventHandler + { + private static NotificationEventHandler instance; + private static object instanceLock = new object(); + + private INotificationService[] _registry; + + public static NotificationEventHandler Instance + { + get + { + lock (instanceLock) + { + if (instance == null) + { + instance = new NotificationEventHandler(); + } + + return instance; + } + } + } + + NotificationEventHandler() + { + _registry = new INotificationService[0x20]; + } + + internal void RegisterNotificationService(INotificationService service) + { + // NOTE: in case there isn't space anymore in the registry array, Nintendo doesn't return any errors. + for (int i = 0; i < _registry.Length; i++) + { + if (_registry[i] == null) + { + _registry[i] = service; + break; + } + } + } + + internal void UnregisterNotificationService(INotificationService service) + { + // NOTE: in case there isn't the entry in the registry array, Nintendo doesn't return any errors. + for (int i = 0; i < _registry.Length; i++) + { + if (_registry[i] == service) + { + _registry[i] = null; + break; + } + } + } + + // TODO: Use this when we will have enough things to go online. + public void SignalFriendListUpdate(UInt128 targetId) + { + for (int i = 0; i < _registry.Length; i++) + { + if (_registry[i] != null) + { + _registry[i].SignalFriendListUpdate(targetId); + } + } + } + + // TODO: Use this when we will have enough things to go online. + public void SignalNewFriendRequest(UInt128 targetId) + { + for (int i = 0; i < _registry.Length; i++) + { + if (_registry[i] != null) + { + _registry[i].SignalNewFriendRequest(targetId); + } + } + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/NotificationService/Types/NotificationEventType.cs b/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/NotificationService/Types/NotificationEventType.cs new file mode 100644 index 0000000000..5136ae8a63 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/NotificationService/Types/NotificationEventType.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.NotificationService +{ + enum NotificationEventType : uint + { + Invalid = 0x0, + FriendListUpdate = 0x1, + NewFriendRequest = 0x65 + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/NotificationService/Types/NotificationInfo.cs b/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/NotificationService/Types/NotificationInfo.cs new file mode 100644 index 0000000000..1bd6f01189 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/NotificationService/Types/NotificationInfo.cs @@ -0,0 +1,15 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.NotificationService +{ + [StructLayout(LayoutKind.Sequential, Pack = 0x8, Size = 0x10)] + struct NotificationInfo + { + public NotificationEventType Type; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x4)] + public char[] Padding; + + public long NetworkUserIdPlaceholder; + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/Types/FriendServicePermissionLevel.cs b/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/Types/FriendServicePermissionLevel.cs new file mode 100644 index 0000000000..9c81136581 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/Types/FriendServicePermissionLevel.cs @@ -0,0 +1,19 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator +{ + [Flags] + enum FriendServicePermissionLevel + { + UserMask = 1, + OverlayMask = 2, + ManagerMask = 4, + SystemMask = 8, + + Admin = -1, + User = UserMask, + Overlay = UserMask | OverlayMask, + Manager = UserMask | OverlayMask | ManagerMask, + System = UserMask | SystemMask + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/FileSystemProxyHelper.cs b/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/FileSystemProxyHelper.cs new file mode 100644 index 0000000000..1dd5fb86e7 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/FileSystemProxyHelper.cs @@ -0,0 +1,126 @@ +using LibHac; +using LibHac.Fs; +using LibHac.FsSystem; +using LibHac.FsSystem.NcaUtils; +using LibHac.Spl; +using System.IO; + +namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy +{ + static class FileSystemProxyHelper + { + public static ResultCode OpenNsp(ServiceCtx context, string pfsPath, out IFileSystem openedFileSystem) + { + openedFileSystem = null; + + try + { + LocalStorage storage = new LocalStorage(pfsPath, FileAccess.Read, FileMode.Open); + PartitionFileSystem nsp = new PartitionFileSystem(storage); + + ImportTitleKeysFromNsp(nsp, context.Device.System.KeySet); + + openedFileSystem = new IFileSystem(nsp); + } + catch (HorizonResultException ex) + { + return (ResultCode)ex.ResultValue.Value; + } + + return ResultCode.Success; + } + + public static ResultCode OpenNcaFs(ServiceCtx context, string ncaPath, LibHac.Fs.IStorage ncaStorage, out IFileSystem openedFileSystem) + { + openedFileSystem = null; + + try + { + Nca nca = new Nca(context.Device.System.KeySet, ncaStorage); + + if (!nca.SectionExists(NcaSectionType.Data)) + { + return ResultCode.PartitionNotFound; + } + + LibHac.Fs.IFileSystem fileSystem = nca.OpenFileSystem(NcaSectionType.Data, context.Device.System.FsIntegrityCheckLevel); + + openedFileSystem = new IFileSystem(fileSystem); + } + catch (HorizonResultException ex) + { + return (ResultCode)ex.ResultValue.Value; + } + + return ResultCode.Success; + } + + public static ResultCode OpenFileSystemFromInternalFile(ServiceCtx context, string fullPath, out IFileSystem openedFileSystem) + { + openedFileSystem = null; + + DirectoryInfo archivePath = new DirectoryInfo(fullPath).Parent; + + while (string.IsNullOrWhiteSpace(archivePath.Extension)) + { + archivePath = archivePath.Parent; + } + + if (archivePath.Extension == ".nsp" && File.Exists(archivePath.FullName)) + { + FileStream pfsFile = new FileStream( + archivePath.FullName.TrimEnd(Path.DirectorySeparatorChar), + FileMode.Open, + FileAccess.Read); + + try + { + PartitionFileSystem nsp = new PartitionFileSystem(pfsFile.AsStorage()); + + ImportTitleKeysFromNsp(nsp, context.Device.System.KeySet); + + string filename = fullPath.Replace(archivePath.FullName, string.Empty).TrimStart('\\'); + + Result result = nsp.OpenFile(out LibHac.Fs.IFile ncaFile, filename, OpenMode.Read); + if (result.IsFailure()) + { + return (ResultCode)result.Value; + } + + return OpenNcaFs(context, fullPath, ncaFile.AsStorage(), out openedFileSystem); + } + catch (HorizonResultException ex) + { + return (ResultCode)ex.ResultValue.Value; + } + } + + return ResultCode.PathDoesNotExist; + } + + public static void ImportTitleKeysFromNsp(LibHac.Fs.IFileSystem nsp, Keyset keySet) + { + foreach (DirectoryEntryEx ticketEntry in nsp.EnumerateEntries("/", "*.tik")) + { + Result result = nsp.OpenFile(out LibHac.Fs.IFile ticketFile, ticketEntry.FullPath, OpenMode.Read); + + if (result.IsSuccess()) + { + Ticket ticket = new Ticket(ticketFile.AsStream()); + + keySet.ExternalKeySet.Add(new RightsId(ticket.RightsId), new AccessKey(ticket.GetTitleKey(keySet))); + } + } + } + + public static Result ReadFsPath(out FsPath path, ServiceCtx context, int index = 0) + { + long position = context.Request.SendBuff[index].Position; + long size = context.Request.SendBuff[index].Size; + + byte[] pathBytes = context.Memory.ReadBytes(position, size); + + return FsPath.FromSpan(out path, pathBytes); + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IDirectory.cs b/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IDirectory.cs new file mode 100644 index 0000000000..c042ed8e96 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IDirectory.cs @@ -0,0 +1,46 @@ +using LibHac; +using LibHac.Fs; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy +{ + class IDirectory : IpcService + { + private LibHac.Fs.IDirectory _baseDirectory; + + public IDirectory(LibHac.Fs.IDirectory directory) + { + _baseDirectory = directory; + } + + [Command(0)] + // Read() -> (u64 count, buffer entries) + public ResultCode Read(ServiceCtx context) + { + long bufferPosition = context.Request.ReceiveBuff[0].Position; + long bufferLen = context.Request.ReceiveBuff[0].Size; + + byte[] entriesBytes = new byte[bufferLen]; + Span entries = MemoryMarshal.Cast(entriesBytes); + + Result result = _baseDirectory.Read(out long entriesRead, entries); + + context.Memory.WriteBytes(bufferPosition, entriesBytes); + context.ResponseData.Write(entriesRead); + + return (ResultCode)result.Value; + } + + [Command(1)] + // GetEntryCount() -> u64 + public ResultCode GetEntryCount(ServiceCtx context) + { + Result result = _baseDirectory.GetEntryCount(out long entryCount); + + context.ResponseData.Write(entryCount); + + return (ResultCode)result.Value; + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IFile.cs b/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IFile.cs new file mode 100644 index 0000000000..f09624f87f --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IFile.cs @@ -0,0 +1,96 @@ +using LibHac; +using LibHac.Fs; +using System; + +namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy +{ + class IFile : IpcService, IDisposable + { + private LibHac.Fs.IFile _baseFile; + + public IFile(LibHac.Fs.IFile baseFile) + { + _baseFile = baseFile; + } + + [Command(0)] + // Read(u32 readOption, u64 offset, u64 size) -> (u64 out_size, buffer out_buf) + public ResultCode Read(ServiceCtx context) + { + long position = context.Request.ReceiveBuff[0].Position; + + ReadOption readOption = (ReadOption)context.RequestData.ReadInt32(); + context.RequestData.BaseStream.Position += 4; + + long offset = context.RequestData.ReadInt64(); + long size = context.RequestData.ReadInt64(); + + byte[] data = new byte[size]; + + Result result = _baseFile.Read(out long bytesRead, offset, data, readOption); + + context.Memory.WriteBytes(position, data); + + context.ResponseData.Write(bytesRead); + + return (ResultCode)result.Value; + } + + [Command(1)] + // Write(u32 writeOption, u64 offset, u64 size, buffer) + public ResultCode Write(ServiceCtx context) + { + long position = context.Request.SendBuff[0].Position; + + WriteOption writeOption = (WriteOption)context.RequestData.ReadInt32(); + context.RequestData.BaseStream.Position += 4; + + long offset = context.RequestData.ReadInt64(); + long size = context.RequestData.ReadInt64(); + + byte[] data = context.Memory.ReadBytes(position, size); + + return (ResultCode)_baseFile.Write(offset, data, writeOption).Value; + } + + [Command(2)] + // Flush() + public ResultCode Flush(ServiceCtx context) + { + return (ResultCode)_baseFile.Flush().Value; + } + + [Command(3)] + // SetSize(u64 size) + public ResultCode SetSize(ServiceCtx context) + { + long size = context.RequestData.ReadInt64(); + + return (ResultCode)_baseFile.SetSize(size).Value; + } + + [Command(4)] + // GetSize() -> u64 fileSize + public ResultCode GetSize(ServiceCtx context) + { + Result result = _baseFile.GetSize(out long size); + + context.ResponseData.Write(size); + + return (ResultCode)result.Value; + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _baseFile?.Dispose(); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IFileSystem.cs b/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IFileSystem.cs new file mode 100644 index 0000000000..ed7ae0c178 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IFileSystem.cs @@ -0,0 +1,204 @@ +using LibHac; +using LibHac.Fs; + +using static Ryujinx.HLE.Utilities.StringUtils; + +namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy +{ + class IFileSystem : IpcService + { + private LibHac.Fs.IFileSystem _fileSystem; + + public IFileSystem(LibHac.Fs.IFileSystem provider) + { + _fileSystem = provider; + } + + [Command(0)] + // CreateFile(u32 createOption, u64 size, buffer, 0x19, 0x301> path) + public ResultCode CreateFile(ServiceCtx context) + { + string name = ReadUtf8String(context); + + CreateFileOptions createOption = (CreateFileOptions)context.RequestData.ReadInt32(); + context.RequestData.BaseStream.Position += 4; + + long size = context.RequestData.ReadInt64(); + + return (ResultCode)_fileSystem.CreateFile(name, size, createOption).Value; + } + + [Command(1)] + // DeleteFile(buffer, 0x19, 0x301> path) + public ResultCode DeleteFile(ServiceCtx context) + { + string name = ReadUtf8String(context); + + return (ResultCode)_fileSystem.DeleteFile(name).Value; + } + + [Command(2)] + // CreateDirectory(buffer, 0x19, 0x301> path) + public ResultCode CreateDirectory(ServiceCtx context) + { + string name = ReadUtf8String(context); + + return (ResultCode)_fileSystem.CreateDirectory(name).Value; + } + + [Command(3)] + // DeleteDirectory(buffer, 0x19, 0x301> path) + public ResultCode DeleteDirectory(ServiceCtx context) + { + string name = ReadUtf8String(context); + + return (ResultCode)_fileSystem.DeleteDirectory(name).Value; + } + + [Command(4)] + // DeleteDirectoryRecursively(buffer, 0x19, 0x301> path) + public ResultCode DeleteDirectoryRecursively(ServiceCtx context) + { + string name = ReadUtf8String(context); + + return (ResultCode)_fileSystem.DeleteDirectoryRecursively(name).Value; + } + + [Command(5)] + // RenameFile(buffer, 0x19, 0x301> oldPath, buffer, 0x19, 0x301> newPath) + public ResultCode RenameFile(ServiceCtx context) + { + string oldName = ReadUtf8String(context, 0); + string newName = ReadUtf8String(context, 1); + + return (ResultCode)_fileSystem.RenameFile(oldName, newName).Value; + } + + [Command(6)] + // RenameDirectory(buffer, 0x19, 0x301> oldPath, buffer, 0x19, 0x301> newPath) + public ResultCode RenameDirectory(ServiceCtx context) + { + string oldName = ReadUtf8String(context, 0); + string newName = ReadUtf8String(context, 1); + + return (ResultCode)_fileSystem.RenameDirectory(oldName, newName).Value; + } + + [Command(7)] + // GetEntryType(buffer, 0x19, 0x301> path) -> nn::fssrv::sf::DirectoryEntryType + public ResultCode GetEntryType(ServiceCtx context) + { + string name = ReadUtf8String(context); + + Result result = _fileSystem.GetEntryType(out DirectoryEntryType entryType, name); + + context.ResponseData.Write((int)entryType); + + return (ResultCode)result.Value; + } + + [Command(8)] + // OpenFile(u32 mode, buffer, 0x19, 0x301> path) -> object file + public ResultCode OpenFile(ServiceCtx context) + { + OpenMode mode = (OpenMode)context.RequestData.ReadInt32(); + + string name = ReadUtf8String(context); + + Result result = _fileSystem.OpenFile(out LibHac.Fs.IFile file, name, mode); + + if (result.IsSuccess()) + { + IFile fileInterface = new IFile(file); + + MakeObject(context, fileInterface); + } + + return (ResultCode)result.Value; + } + + [Command(9)] + // OpenDirectory(u32 filter_flags, buffer, 0x19, 0x301> path) -> object directory + public ResultCode OpenDirectory(ServiceCtx context) + { + OpenDirectoryMode mode = (OpenDirectoryMode)context.RequestData.ReadInt32(); + + string name = ReadUtf8String(context); + + Result result = _fileSystem.OpenDirectory(out LibHac.Fs.IDirectory dir, name, mode); + + if (result.IsSuccess()) + { + IDirectory dirInterface = new IDirectory(dir); + + MakeObject(context, dirInterface); + } + + return (ResultCode)result.Value; + } + + [Command(10)] + // Commit() + public ResultCode Commit(ServiceCtx context) + { + return (ResultCode)_fileSystem.Commit().Value; + } + + [Command(11)] + // GetFreeSpaceSize(buffer, 0x19, 0x301> path) -> u64 totalFreeSpace + public ResultCode GetFreeSpaceSize(ServiceCtx context) + { + string name = ReadUtf8String(context); + + Result result = _fileSystem.GetFreeSpaceSize(out long size, name); + + context.ResponseData.Write(size); + + return (ResultCode)result.Value; + } + + [Command(12)] + // GetTotalSpaceSize(buffer, 0x19, 0x301> path) -> u64 totalSize + public ResultCode GetTotalSpaceSize(ServiceCtx context) + { + string name = ReadUtf8String(context); + + Result result = _fileSystem.GetTotalSpaceSize(out long size, name); + + context.ResponseData.Write(size); + + return (ResultCode)result.Value; + } + + [Command(13)] + // CleanDirectoryRecursively(buffer, 0x19, 0x301> path) + public ResultCode CleanDirectoryRecursively(ServiceCtx context) + { + string name = ReadUtf8String(context); + + return (ResultCode)_fileSystem.CleanDirectoryRecursively(name).Value; + } + + [Command(14)] + // GetFileTimeStampRaw(buffer, 0x19, 0x301> path) -> bytes<0x20> timestamp + public ResultCode GetFileTimeStampRaw(ServiceCtx context) + { + string name = ReadUtf8String(context); + + Result result = _fileSystem.GetFileTimeStampRaw(out FileTimeStampRaw timestamp, name); + + context.ResponseData.Write(timestamp.Created); + context.ResponseData.Write(timestamp.Modified); + context.ResponseData.Write(timestamp.Accessed); + + byte[] data = new byte[8]; + + // is valid? + data[0] = 1; + + context.ResponseData.Write(data); + + return (ResultCode)result.Value; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IStorage.cs b/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IStorage.cs new file mode 100644 index 0000000000..b2db08dd38 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IStorage.cs @@ -0,0 +1,69 @@ +using LibHac; +using Ryujinx.HLE.HOS.Ipc; +using System; + +namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy +{ + class IStorage : IpcService, IDisposable + { + private LibHac.Fs.IStorage _baseStorage; + + public IStorage(LibHac.Fs.IStorage baseStorage) + { + _baseStorage = baseStorage; + } + + [Command(0)] + // Read(u64 offset, u64 length) -> buffer buffer + public ResultCode Read(ServiceCtx context) + { + long offset = context.RequestData.ReadInt64(); + long size = context.RequestData.ReadInt64(); + + if (context.Request.ReceiveBuff.Count > 0) + { + IpcBuffDesc buffDesc = context.Request.ReceiveBuff[0]; + + // Use smaller length to avoid overflows. + if (size > buffDesc.Size) + { + size = buffDesc.Size; + } + + byte[] data = new byte[size]; + + Result result = _baseStorage.Read(offset, data); + + context.Memory.WriteBytes(buffDesc.Position, data); + + return (ResultCode)result.Value; + } + + return ResultCode.Success; + } + + [Command(4)] + // GetSize() -> u64 size + public ResultCode GetSize(ServiceCtx context) + { + Result result = _baseStorage.GetSize(out long size); + + context.ResponseData.Write(size); + + return (ResultCode)result.Value; + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _baseStorage?.Dispose(); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Fs/IDeviceOperator.cs b/Ryujinx.HLE/HOS/Services/Fs/IDeviceOperator.cs new file mode 100644 index 0000000000..426b50ed2a --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Fs/IDeviceOperator.cs @@ -0,0 +1,37 @@ +using LibHac; +using LibHac.FsService; + +namespace Ryujinx.HLE.HOS.Services.Fs +{ + class IDeviceOperator : IpcService + { + private LibHac.FsService.IDeviceOperator _baseOperator; + + public IDeviceOperator(LibHac.FsService.IDeviceOperator baseOperator) + { + _baseOperator = baseOperator; + } + + [Command(200)] + // IsGameCardInserted() -> b8 is_inserted + public ResultCode IsGameCardInserted(ServiceCtx context) + { + Result result = _baseOperator.IsGameCardInserted(out bool isInserted); + + context.ResponseData.Write(isInserted); + + return (ResultCode)result.Value; + } + + [Command(202)] + // GetGameCardHandle() -> u32 gamecard_handle + public ResultCode GetGameCardHandle(ServiceCtx context) + { + Result result = _baseOperator.GetGameCardHandle(out GameCardHandle handle); + + context.ResponseData.Write(handle.Value); + + return (ResultCode)result.Value; + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxy.cs b/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxy.cs new file mode 100644 index 0000000000..4e96788644 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxy.cs @@ -0,0 +1,486 @@ +using LibHac; +using LibHac.Fs; +using LibHac.FsService; +using LibHac.FsSystem; +using LibHac.FsSystem.NcaUtils; +using LibHac.Ncm; +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy; +using System.IO; + +using static Ryujinx.HLE.Utilities.StringUtils; +using StorageId = Ryujinx.HLE.FileSystem.StorageId; + +namespace Ryujinx.HLE.HOS.Services.Fs +{ + [Service("fsp-srv")] + class IFileSystemProxy : IpcService + { + private LibHac.FsService.IFileSystemProxy _baseFileSystemProxy; + + public IFileSystemProxy(ServiceCtx context) + { + _baseFileSystemProxy = context.Device.System.FsServer.CreateFileSystemProxyService(); + } + + [Command(1)] + // Initialize(u64, pid) + public ResultCode Initialize(ServiceCtx context) + { + return ResultCode.Success; + } + + [Command(8)] + // OpenFileSystemWithId(nn::fssrv::sf::FileSystemType filesystem_type, nn::ApplicationId tid, buffer, 0x19, 0x301> path) + // -> object contentFs + public ResultCode OpenFileSystemWithId(ServiceCtx context) + { + FileSystemType fileSystemType = (FileSystemType)context.RequestData.ReadInt32(); + long titleId = context.RequestData.ReadInt64(); + string switchPath = ReadUtf8String(context); + string fullPath = context.Device.FileSystem.SwitchPathToSystemPath(switchPath); + + if (!File.Exists(fullPath)) + { + if (fullPath.Contains(".")) + { + ResultCode result = FileSystemProxyHelper.OpenFileSystemFromInternalFile(context, fullPath, out FileSystemProxy.IFileSystem fileSystem); + + if (result == ResultCode.Success) + { + MakeObject(context, fileSystem); + } + + return result; + } + + return ResultCode.PathDoesNotExist; + } + + FileStream fileStream = new FileStream(fullPath, FileMode.Open, FileAccess.Read); + string extension = Path.GetExtension(fullPath); + + if (extension == ".nca") + { + ResultCode result = FileSystemProxyHelper.OpenNcaFs(context, fullPath, fileStream.AsStorage(), out FileSystemProxy.IFileSystem fileSystem); + + if (result == ResultCode.Success) + { + MakeObject(context, fileSystem); + } + + return result; + } + else if (extension == ".nsp") + { + ResultCode result = FileSystemProxyHelper.OpenNsp(context, fullPath, out FileSystemProxy.IFileSystem fileSystem); + + if (result == ResultCode.Success) + { + MakeObject(context, fileSystem); + } + + return result; + } + + return ResultCode.InvalidInput; + } + + [Command(11)] + // OpenBisFileSystem(nn::fssrv::sf::Partition partitionID, buffer, 0x19, 0x301>) -> object Bis + public ResultCode OpenBisFileSystem(ServiceCtx context) + { + BisPartitionId bisPartitionId = (BisPartitionId)context.RequestData.ReadInt32(); + + Result rc = FileSystemProxyHelper.ReadFsPath(out FsPath path, context); + if (rc.IsFailure()) return (ResultCode)rc.Value; + + rc = _baseFileSystemProxy.OpenBisFileSystem(out LibHac.Fs.IFileSystem fileSystem, ref path, bisPartitionId); + if (rc.IsFailure()) return (ResultCode)rc.Value; + + MakeObject(context, new FileSystemProxy.IFileSystem(fileSystem)); + + return ResultCode.Success; + } + + [Command(18)] + // OpenSdCardFileSystem() -> object + public ResultCode OpenSdCardFileSystem(ServiceCtx context) + { + Result rc = _baseFileSystemProxy.OpenSdCardFileSystem(out LibHac.Fs.IFileSystem fileSystem); + if (rc.IsFailure()) return (ResultCode)rc.Value; + + MakeObject(context, new FileSystemProxy.IFileSystem(fileSystem)); + + return ResultCode.Success; + } + + [Command(21)] + public ResultCode DeleteSaveDataFileSystem(ServiceCtx context) + { + ulong saveDataId = context.RequestData.ReadUInt64(); + + Result result = _baseFileSystemProxy.DeleteSaveDataFileSystem(saveDataId); + + return (ResultCode)result.Value; + } + + [Command(22)] + public ResultCode CreateSaveDataFileSystem(ServiceCtx context) + { + SaveDataAttribute attribute = context.RequestData.ReadStruct(); + SaveDataCreateInfo createInfo = context.RequestData.ReadStruct(); + SaveMetaCreateInfo metaCreateInfo = context.RequestData.ReadStruct(); + + // TODO: There's currently no program registry for FS to reference. + // Workaround that by setting the application ID and owner ID if they're not already set + if (attribute.TitleId == TitleId.Zero) + { + attribute.TitleId = new TitleId(context.Process.TitleId); + } + + if (createInfo.OwnerId == TitleId.Zero) + { + createInfo.OwnerId = new TitleId(context.Process.TitleId); + } + + Logger.PrintInfo(LogClass.ServiceFs, $"Creating save with title ID {attribute.TitleId.Value:x16}"); + + Result result = _baseFileSystemProxy.CreateSaveDataFileSystem(ref attribute, ref createInfo, ref metaCreateInfo); + + return (ResultCode)result.Value; + } + + [Command(23)] + public ResultCode CreateSaveDataFileSystemBySystemSaveDataId(ServiceCtx context) + { + SaveDataAttribute attribute = context.RequestData.ReadStruct(); + SaveDataCreateInfo createInfo = context.RequestData.ReadStruct(); + + Result result = _baseFileSystemProxy.CreateSaveDataFileSystemBySystemSaveDataId(ref attribute, ref createInfo); + + return (ResultCode)result.Value; + } + + [Command(25)] + public ResultCode DeleteSaveDataFileSystemBySaveDataSpaceId(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + ulong saveDataId = context.RequestData.ReadUInt64(); + + Result result = _baseFileSystemProxy.DeleteSaveDataFileSystemBySaveDataSpaceId(spaceId, saveDataId); + + return (ResultCode)result.Value; + } + + [Command(28)] + public ResultCode DeleteSaveDataFileSystemBySaveDataAttribute(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + SaveDataAttribute attribute = context.RequestData.ReadStruct(); + + Result result = _baseFileSystemProxy.DeleteSaveDataFileSystemBySaveDataAttribute(spaceId, ref attribute); + + return (ResultCode)result.Value; + } + + [Command(30)] + // OpenGameCardStorage(u32, u32) -> object + public ResultCode OpenGameCardStorage(ServiceCtx context) + { + GameCardHandle handle = new GameCardHandle(context.RequestData.ReadInt32()); + GameCardPartitionRaw partitionId = (GameCardPartitionRaw)context.RequestData.ReadInt32(); + + Result result = _baseFileSystemProxy.OpenGameCardStorage(out LibHac.Fs.IStorage storage, handle, partitionId); + + if (result.IsSuccess()) + { + MakeObject(context, new FileSystemProxy.IStorage(storage)); + } + + return (ResultCode)result.Value; + } + + [Command(35)] + public ResultCode CreateSaveDataFileSystemWithHashSalt(ServiceCtx context) + { + SaveDataAttribute attribute = context.RequestData.ReadStruct(); + SaveDataCreateInfo createInfo = context.RequestData.ReadStruct(); + SaveMetaCreateInfo metaCreateInfo = context.RequestData.ReadStruct(); + HashSalt hashSalt = context.RequestData.ReadStruct(); + + // TODO: There's currently no program registry for FS to reference. + // Workaround that by setting the application ID and owner ID if they're not already set + if (attribute.TitleId == TitleId.Zero) + { + attribute.TitleId = new TitleId(context.Process.TitleId); + } + + if (createInfo.OwnerId == TitleId.Zero) + { + createInfo.OwnerId = new TitleId(context.Process.TitleId); + } + + Result result = _baseFileSystemProxy.CreateSaveDataFileSystemWithHashSalt(ref attribute, ref createInfo, ref metaCreateInfo, ref hashSalt); + + return (ResultCode)result.Value; + } + + [Command(51)] + // OpenSaveDataFileSystem(u8 save_data_space_id, nn::fssrv::sf::SaveStruct saveStruct) -> object saveDataFs + public ResultCode OpenSaveDataFileSystem(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + SaveDataAttribute attribute = context.RequestData.ReadStruct(); + + // TODO: There's currently no program registry for FS to reference. + // Workaround that by setting the application ID if it's not already set + if (attribute.TitleId == TitleId.Zero) + { + attribute.TitleId = new TitleId(context.Process.TitleId); + } + + Result result = _baseFileSystemProxy.OpenSaveDataFileSystem(out LibHac.Fs.IFileSystem fileSystem, spaceId, ref attribute); + + if (result.IsSuccess()) + { + MakeObject(context, new FileSystemProxy.IFileSystem(fileSystem)); + } + + return (ResultCode)result.Value; + } + + [Command(52)] + // OpenSaveDataFileSystemBySystemSaveDataId(u8 save_data_space_id, nn::fssrv::sf::SaveStruct saveStruct) -> object systemSaveDataFs + public ResultCode OpenSaveDataFileSystemBySystemSaveDataId(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + SaveDataAttribute attribute = context.RequestData.ReadStruct(); + + Result result = _baseFileSystemProxy.OpenSaveDataFileSystemBySystemSaveDataId(out LibHac.Fs.IFileSystem fileSystem, spaceId, ref attribute); + + if (result.IsSuccess()) + { + MakeObject(context, new FileSystemProxy.IFileSystem(fileSystem)); + } + + return (ResultCode)result.Value; + } + + [Command(53)] + // OpenReadOnlySaveDataFileSystem(u8 save_data_space_id, nn::fssrv::sf::SaveStruct save_struct) -> object + public ResultCode OpenReadOnlySaveDataFileSystem(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + SaveDataAttribute attribute = context.RequestData.ReadStruct(); + + // TODO: There's currently no program registry for FS to reference. + // Workaround that by setting the application ID if it's not already set + if (attribute.TitleId == TitleId.Zero) + { + attribute.TitleId = new TitleId(context.Process.TitleId); + } + + Result result = _baseFileSystemProxy.OpenReadOnlySaveDataFileSystem(out LibHac.Fs.IFileSystem fileSystem, spaceId, ref attribute); + + if (result.IsSuccess()) + { + MakeObject(context, new FileSystemProxy.IFileSystem(fileSystem)); + } + + return (ResultCode)result.Value; + } + + [Command(60)] + public ResultCode OpenSaveDataInfoReader(ServiceCtx context) + { + Result result = _baseFileSystemProxy.OpenSaveDataInfoReader(out LibHac.FsService.ISaveDataInfoReader infoReader); + + if (result.IsSuccess()) + { + MakeObject(context, new ISaveDataInfoReader(infoReader)); + } + + return (ResultCode)result.Value; + } + + [Command(61)] + public ResultCode OpenSaveDataInfoReaderBySaveDataSpaceId(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadByte(); + + Result result = _baseFileSystemProxy.OpenSaveDataInfoReaderBySaveDataSpaceId(out LibHac.FsService.ISaveDataInfoReader infoReader, spaceId); + + if (result.IsSuccess()) + { + MakeObject(context, new ISaveDataInfoReader(infoReader)); + } + + return (ResultCode)result.Value; + } + + [Command(67)] + public ResultCode FindSaveDataWithFilter(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + SaveDataFilter filter = context.RequestData.ReadStruct(); + + long bufferPosition = context.Request.ReceiveBuff[0].Position; + long bufferLen = context.Request.ReceiveBuff[0].Size; + + byte[] infoBuffer = new byte[bufferLen]; + + Result result = _baseFileSystemProxy.FindSaveDataWithFilter(out long count, infoBuffer, spaceId, ref filter); + + context.Memory.WriteBytes(bufferPosition, infoBuffer); + context.ResponseData.Write(count); + + return (ResultCode)result.Value; + } + + [Command(68)] + public ResultCode OpenSaveDataInfoReaderWithFilter(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + SaveDataFilter filter = context.RequestData.ReadStruct(); + + Result result = _baseFileSystemProxy.OpenSaveDataInfoReaderWithFilter(out LibHac.FsService.ISaveDataInfoReader infoReader, spaceId, ref filter); + + if (result.IsSuccess()) + { + MakeObject(context, new ISaveDataInfoReader(infoReader)); + } + + return (ResultCode)result.Value; + } + + [Command(200)] + // OpenDataStorageByCurrentProcess() -> object dataStorage + public ResultCode OpenDataStorageByCurrentProcess(ServiceCtx context) + { + MakeObject(context, new FileSystemProxy.IStorage(context.Device.FileSystem.RomFs.AsStorage())); + + return 0; + } + + [Command(202)] + // OpenDataStorageByDataId(u8 storageId, nn::ApplicationId tid) -> object dataStorage + public ResultCode OpenDataStorageByDataId(ServiceCtx context) + { + StorageId storageId = (StorageId)context.RequestData.ReadByte(); + byte[] padding = context.RequestData.ReadBytes(7); + long titleId = context.RequestData.ReadInt64(); + + NcaContentType contentType = NcaContentType.Data; + + StorageId installedStorage = + context.Device.System.ContentManager.GetInstalledStorage(titleId, contentType, storageId); + + if (installedStorage == StorageId.None) + { + contentType = NcaContentType.PublicData; + + installedStorage = + context.Device.System.ContentManager.GetInstalledStorage(titleId, contentType, storageId); + } + + if (installedStorage != StorageId.None) + { + string contentPath = context.Device.System.ContentManager.GetInstalledContentPath(titleId, storageId, contentType); + string installPath = context.Device.FileSystem.SwitchPathToSystemPath(contentPath); + + if (!string.IsNullOrWhiteSpace(installPath)) + { + string ncaPath = installPath; + + if (File.Exists(ncaPath)) + { + try + { + LibHac.Fs.IStorage ncaStorage = new LocalStorage(ncaPath, FileAccess.Read, FileMode.Open); + Nca nca = new Nca(context.Device.System.KeySet, ncaStorage); + LibHac.Fs.IStorage romfsStorage = nca.OpenStorage(NcaSectionType.Data, context.Device.System.FsIntegrityCheckLevel); + + MakeObject(context, new FileSystemProxy.IStorage(romfsStorage)); + } + catch (HorizonResultException ex) + { + return (ResultCode)ex.ResultValue.Value; + } + + return ResultCode.Success; + } + else + { + throw new FileNotFoundException($"No Nca found in Path `{ncaPath}`."); + } + } + else + { + throw new DirectoryNotFoundException($"Path for title id {titleId:x16} on Storage {storageId} was not found in Path {installPath}."); + } + } + + throw new FileNotFoundException($"System archive with titleid {titleId:x16} was not found on Storage {storageId}. Found in {installedStorage}."); + } + + [Command(203)] + // OpenPatchDataStorageByCurrentProcess() -> object + public ResultCode OpenPatchDataStorageByCurrentProcess(ServiceCtx context) + { + MakeObject(context, new FileSystemProxy.IStorage(context.Device.FileSystem.RomFs.AsStorage())); + + return ResultCode.Success; + } + + [Command(400)] + // OpenDataStorageByCurrentProcess() -> object dataStorage + public ResultCode OpenDeviceOperator(ServiceCtx context) + { + Result result = _baseFileSystemProxy.OpenDeviceOperator(out LibHac.FsService.IDeviceOperator deviceOperator); + + if (result.IsSuccess()) + { + MakeObject(context, new IDeviceOperator(deviceOperator)); + } + + return (ResultCode)result.Value; + } + + [Command(1005)] + // GetGlobalAccessLogMode() -> u32 logMode + public ResultCode GetGlobalAccessLogMode(ServiceCtx context) + { + int mode = context.Device.System.GlobalAccessLogMode; + + context.ResponseData.Write(mode); + + return ResultCode.Success; + } + + [Command(1006)] + // OutputAccessLogToSdCard(buffer log_text) + public ResultCode OutputAccessLogToSdCard(ServiceCtx context) + { + string message = ReadUtf8StringSend(context); + + // FS ends each line with a newline. Remove it because Ryujinx logging adds its own newline + Logger.PrintAccessLog(LogClass.ServiceFs, message.TrimEnd('\n')); + + return ResultCode.Success; + } + + [Command(1011)] + public ResultCode GetProgramIndexForAccessLog(ServiceCtx context) + { + int programIndex = 0; + int programCount = 1; + + context.ResponseData.Write(programIndex); + context.ResponseData.Write(programCount); + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxyForLoader.cs b/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxyForLoader.cs new file mode 100644 index 0000000000..a40821b931 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxyForLoader.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Fs +{ + [Service("fsp-ldr")] + class IFileSystemProxyForLoader : IpcService + { + public IFileSystemProxyForLoader(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Fs/IProgramRegistry.cs b/Ryujinx.HLE/HOS/Services/Fs/IProgramRegistry.cs new file mode 100644 index 0000000000..e11eadf53f --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Fs/IProgramRegistry.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Fs +{ + [Service("fsp-pr")] + class IProgramRegistry : IpcService + { + public IProgramRegistry(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Fs/ISaveDataInfoReader.cs b/Ryujinx.HLE/HOS/Services/Fs/ISaveDataInfoReader.cs new file mode 100644 index 0000000000..3d5ae8e2cd --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Fs/ISaveDataInfoReader.cs @@ -0,0 +1,31 @@ +using LibHac; + +namespace Ryujinx.HLE.HOS.Services.Fs +{ + class ISaveDataInfoReader : IpcService + { + private LibHac.FsService.ISaveDataInfoReader _baseReader; + + public ISaveDataInfoReader(LibHac.FsService.ISaveDataInfoReader baseReader) + { + _baseReader = baseReader; + } + + [Command(0)] + // ReadSaveDataInfo() -> (u64, buffer) + public ResultCode ReadSaveDataInfo(ServiceCtx context) + { + long bufferPosition = context.Request.ReceiveBuff[0].Position; + long bufferLen = context.Request.ReceiveBuff[0].Size; + + byte[] infoBuffer = new byte[bufferLen]; + + Result result = _baseReader.ReadSaveDataInfo(out long readCount, infoBuffer); + + context.Memory.WriteBytes(bufferPosition, infoBuffer); + context.ResponseData.Write(readCount); + + return (ResultCode)result.Value; + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Fs/ResultCode.cs b/Ryujinx.HLE/HOS/Services/Fs/ResultCode.cs new file mode 100644 index 0000000000..8f87142b19 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Fs/ResultCode.cs @@ -0,0 +1,16 @@ +namespace Ryujinx.HLE.HOS.Services.Fs +{ + enum ResultCode + { + ModuleId = 2, + ErrorCodeShift = 9, + + Success = 0, + + PathDoesNotExist = (1 << ErrorCodeShift) | ModuleId, + PathAlreadyExists = (2 << ErrorCodeShift) | ModuleId, + PathAlreadyInUse = (7 << ErrorCodeShift) | ModuleId, + PartitionNotFound = (1001 << ErrorCodeShift) | ModuleId, + InvalidInput = (6001 << ErrorCodeShift) | ModuleId + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Fs/Types/FileSystemType.cs b/Ryujinx.HLE/HOS/Services/Fs/Types/FileSystemType.cs new file mode 100644 index 0000000000..f12c1661d8 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Fs/Types/FileSystemType.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.HLE.HOS.Services.Fs +{ + enum FileSystemType + { + Logo = 2, + ContentControl = 3, + ContentManual = 4, + ContentMeta = 5, + ContentData = 6, + ApplicationPackage = 7 + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Grc/IGrcService.cs b/Ryujinx.HLE/HOS/Services/Grc/IGrcService.cs new file mode 100644 index 0000000000..90646b4068 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Grc/IGrcService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Grc +{ + [Service("grc:c")] // 4.0.0+ + class IGrcService : IpcService + { + public IGrcService(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Grc/IRemoteVideoTransfer.cs b/Ryujinx.HLE/HOS/Services/Grc/IRemoteVideoTransfer.cs new file mode 100644 index 0000000000..edb1d64e14 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Grc/IRemoteVideoTransfer.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Grc +{ + [Service("grc:d")] // 6.0.0+ + class IRemoteVideoTransfer : IpcService + { + public IRemoteVideoTransfer(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Hid/HidServer/HidUtils.cs b/Ryujinx.HLE/HOS/Services/Hid/HidServer/HidUtils.cs new file mode 100644 index 0000000000..c89ea3067b --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Hid/HidServer/HidUtils.cs @@ -0,0 +1,46 @@ +using Ryujinx.HLE.Input; +using System; + +namespace Ryujinx.HLE.HOS.Services.Hid.HidServer +{ + static class HidUtils + { + public static ControllerId GetIndexFromNpadIdType(HidNpadIdType npadIdType) + { + switch (npadIdType) + { + case HidNpadIdType.Player1: return ControllerId.ControllerPlayer1; + case HidNpadIdType.Player2: return ControllerId.ControllerPlayer2; + case HidNpadIdType.Player3: return ControllerId.ControllerPlayer3; + case HidNpadIdType.Player4: return ControllerId.ControllerPlayer4; + case HidNpadIdType.Player5: return ControllerId.ControllerPlayer5; + case HidNpadIdType.Player6: return ControllerId.ControllerPlayer6; + case HidNpadIdType.Player7: return ControllerId.ControllerPlayer7; + case HidNpadIdType.Player8: return ControllerId.ControllerPlayer8; + case HidNpadIdType.Handheld: return ControllerId.ControllerHandheld; + case HidNpadIdType.Unknown: return ControllerId.ControllerUnknown; + + default: throw new ArgumentOutOfRangeException(nameof(npadIdType)); + } + } + + public static HidNpadIdType GetNpadIdTypeFromIndex(ControllerId index) + { + switch (index) + { + case ControllerId.ControllerPlayer1: return HidNpadIdType.Player1; + case ControllerId.ControllerPlayer2: return HidNpadIdType.Player2; + case ControllerId.ControllerPlayer3: return HidNpadIdType.Player3; + case ControllerId.ControllerPlayer4: return HidNpadIdType.Player4; + case ControllerId.ControllerPlayer5: return HidNpadIdType.Player5; + case ControllerId.ControllerPlayer6: return HidNpadIdType.Player6; + case ControllerId.ControllerPlayer7: return HidNpadIdType.Player7; + case ControllerId.ControllerPlayer8: return HidNpadIdType.Player8; + case ControllerId.ControllerHandheld: return HidNpadIdType.Handheld; + case ControllerId.ControllerUnknown: return HidNpadIdType.Unknown; + + default: throw new ArgumentOutOfRangeException(nameof(index)); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Hid/HidServer/IActiveVibrationDeviceList.cs b/Ryujinx.HLE/HOS/Services/Hid/HidServer/IActiveVibrationDeviceList.cs new file mode 100644 index 0000000000..4c2050f1c2 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Hid/HidServer/IActiveVibrationDeviceList.cs @@ -0,0 +1,16 @@ +namespace Ryujinx.HLE.HOS.Services.Hid.HidServer +{ + class IActiveApplicationDeviceList : IpcService + { + public IActiveApplicationDeviceList() { } + + [Command(0)] + // ActivateVibrationDevice(nn::hid::VibrationDeviceHandle) + public ResultCode ActivateVibrationDevice(ServiceCtx context) + { + int vibrationDeviceHandle = context.RequestData.ReadInt32(); + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Hid/HidServer/IAppletResource.cs b/Ryujinx.HLE/HOS/Services/Hid/HidServer/IAppletResource.cs new file mode 100644 index 0000000000..2c3a6500a3 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Hid/HidServer/IAppletResource.cs @@ -0,0 +1,31 @@ +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Memory; +using System; + +namespace Ryujinx.HLE.HOS.Services.Hid.HidServer +{ + class IAppletResource : IpcService + { + private KSharedMemory _hidSharedMem; + + public IAppletResource(KSharedMemory hidSharedMem) + { + _hidSharedMem = hidSharedMem; + } + + [Command(0)] + // GetSharedMemoryHandle() -> handle + public ResultCode GetSharedMemoryHandle(ServiceCtx context) + { + if (context.Process.HandleTable.GenerateHandle(_hidSharedMem, out int handle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle); + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Hid/IHidDebugServer.cs b/Ryujinx.HLE/HOS/Services/Hid/IHidDebugServer.cs new file mode 100644 index 0000000000..adaaa0123e --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Hid/IHidDebugServer.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + [Service("hid:dbg")] + class IHidDebugServer : IpcService + { + public IHidDebugServer(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs b/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs new file mode 100644 index 0000000000..1af9baf872 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs @@ -0,0 +1,1461 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Hid.HidServer; +using Ryujinx.HLE.Input; +using System; + +namespace Ryujinx.HLE.HOS.Services.Hid +{ + [Service("hid")] + class IHidServer : IpcService + { + private KEvent _npadStyleSetUpdateEvent; + private KEvent _xpadIdEvent; + private KEvent _palmaOperationCompleteEvent; + + private int _xpadIdEventHandle; + + private bool _sixAxisSensorFusionEnabled; + private bool _unintendedHomeButtonInputProtectionEnabled; + private bool _vibrationPermitted; + private bool _usbFullKeyControllerEnabled; + + private HidNpadJoyHoldType _npadJoyHoldType; + private HidNpadStyle _npadStyleSet; + private HidNpadJoyAssignmentMode _npadJoyAssignmentMode; + private HidNpadHandheldActivationMode _npadHandheldActivationMode; + private HidGyroscopeZeroDriftMode _gyroscopeZeroDriftMode; + + private long _npadCommunicationMode; + private uint _accelerometerPlayMode; + private long _vibrationGcErmCommand; + private float _sevenSixAxisSensorFusionStrength; + + private HidSensorFusionParameters _sensorFusionParams; + private HidAccelerometerParameters _accelerometerParams; + private HidVibrationValue _vibrationValue; + + public IHidServer(ServiceCtx context) + { + _npadStyleSetUpdateEvent = new KEvent(context.Device.System); + _xpadIdEvent = new KEvent(context.Device.System); + _palmaOperationCompleteEvent = new KEvent(context.Device.System); + + _npadJoyHoldType = HidNpadJoyHoldType.Vertical; + _npadStyleSet = HidNpadStyle.FullKey | HidNpadStyle.Dual | HidNpadStyle.Left | HidNpadStyle.Right | HidNpadStyle.Handheld; + _npadJoyAssignmentMode = HidNpadJoyAssignmentMode.Dual; + _npadHandheldActivationMode = HidNpadHandheldActivationMode.Dual; + _gyroscopeZeroDriftMode = HidGyroscopeZeroDriftMode.Standard; + + _sensorFusionParams = new HidSensorFusionParameters(); + _accelerometerParams = new HidAccelerometerParameters(); + _vibrationValue = new HidVibrationValue(); + + // TODO: signal event at right place + _xpadIdEvent.ReadableEvent.Signal(); + } + + [Command(0)] + // CreateAppletResource(nn::applet::AppletResourceUserId) -> object + public ResultCode CreateAppletResource(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + + MakeObject(context, new IAppletResource(context.Device.System.HidSharedMem)); + + return ResultCode.Success; + } + + [Command(1)] + // ActivateDebugPad(nn::applet::AppletResourceUserId) + public ResultCode ActivateDebugPad(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId }); + + return ResultCode.Success; + } + + [Command(11)] + // ActivateTouchScreen(nn::applet::AppletResourceUserId) + public ResultCode ActivateTouchScreen(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId }); + + return ResultCode.Success; + } + + [Command(21)] + // ActivateMouse(nn::applet::AppletResourceUserId) + public ResultCode ActivateMouse(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId }); + + return ResultCode.Success; + } + + [Command(31)] + // ActivateKeyboard(nn::applet::AppletResourceUserId) + public ResultCode ActivateKeyboard(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId }); + + return ResultCode.Success; + } + + [Command(40)] + // AcquireXpadIdEventHandle(ulong XpadId) -> nn::sf::NativeHandle + public ResultCode AcquireXpadIdEventHandle(ServiceCtx context) + { + long xpadId = context.RequestData.ReadInt64(); + + if (context.Process.HandleTable.GenerateHandle(_xpadIdEvent.ReadableEvent, out _xpadIdEventHandle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_xpadIdEventHandle); + + Logger.PrintStub(LogClass.ServiceHid, new { xpadId }); + + return ResultCode.Success; + } + + [Command(41)] + // ReleaseXpadIdEventHandle(ulong XpadId) + public ResultCode ReleaseXpadIdEventHandle(ServiceCtx context) + { + long xpadId = context.RequestData.ReadInt64(); + + context.Process.HandleTable.CloseHandle(_xpadIdEventHandle); + + Logger.PrintStub(LogClass.ServiceHid, new { xpadId }); + + return ResultCode.Success; + } + + [Command(51)] + // ActivateXpad(nn::hid::BasicXpadId, nn::applet::AppletResourceUserId) + public ResultCode ActivateXpad(ServiceCtx context) + { + int basicXpadId = context.RequestData.ReadInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, basicXpadId }); + + return ResultCode.Success; + } + + [Command(55)] + // GetXpadIds() -> long IdsCount, buffer, type: 0xa> + public ResultCode GetXpadIds(ServiceCtx context) + { + // There is any Xpad, so we return 0 and write nothing inside the type-0xa buffer. + context.ResponseData.Write(0L); + + Logger.PrintStub(LogClass.ServiceHid); + + return ResultCode.Success; + } + + [Command(56)] + // ActivateJoyXpad(nn::hid::JoyXpadId) + public ResultCode ActivateJoyXpad(ServiceCtx context) + { + int joyXpadId = context.RequestData.ReadInt32(); + + Logger.PrintStub(LogClass.ServiceHid, new { joyXpadId }); + + return ResultCode.Success; + } + + [Command(58)] + // GetJoyXpadLifoHandle(nn::hid::JoyXpadId) -> nn::sf::NativeHandle + public ResultCode GetJoyXpadLifoHandle(ServiceCtx context) + { + int joyXpadId = context.RequestData.ReadInt32(); + + int handle = 0; + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle); + + Logger.PrintStub(LogClass.ServiceHid, new { joyXpadId }); + + return ResultCode.Success; + } + + [Command(59)] + // GetJoyXpadIds() -> long IdsCount, buffer, type: 0xa> + public ResultCode GetJoyXpadIds(ServiceCtx context) + { + // There is any JoyXpad, so we return 0 and write nothing inside the type-0xa buffer. + context.ResponseData.Write(0L); + + Logger.PrintStub(LogClass.ServiceHid); + + return ResultCode.Success; + } + + [Command(60)] + // ActivateSixAxisSensor(nn::hid::BasicXpadId) + public ResultCode ActivateSixAxisSensor(ServiceCtx context) + { + int basicXpadId = context.RequestData.ReadInt32(); + + Logger.PrintStub(LogClass.ServiceHid, new { basicXpadId }); + + return ResultCode.Success; + } + + [Command(61)] + // DeactivateSixAxisSensor(nn::hid::BasicXpadId) + public ResultCode DeactivateSixAxisSensor(ServiceCtx context) + { + int basicXpadId = context.RequestData.ReadInt32(); + + Logger.PrintStub(LogClass.ServiceHid, new { basicXpadId }); + + return ResultCode.Success; + } + + [Command(62)] + // GetSixAxisSensorLifoHandle(nn::hid::BasicXpadId) -> nn::sf::NativeHandle + public ResultCode GetSixAxisSensorLifoHandle(ServiceCtx context) + { + int basicXpadId = context.RequestData.ReadInt32(); + + int handle = 0; + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle); + + Logger.PrintStub(LogClass.ServiceHid, new { basicXpadId }); + + return ResultCode.Success; + } + + [Command(63)] + // ActivateJoySixAxisSensor(nn::hid::JoyXpadId) + public ResultCode ActivateJoySixAxisSensor(ServiceCtx context) + { + int joyXpadId = context.RequestData.ReadInt32(); + + Logger.PrintStub(LogClass.ServiceHid, new { joyXpadId }); + + return ResultCode.Success; + } + + [Command(64)] + // DeactivateJoySixAxisSensor(nn::hid::JoyXpadId) + public ResultCode DeactivateJoySixAxisSensor(ServiceCtx context) + { + int joyXpadId = context.RequestData.ReadInt32(); + + Logger.PrintStub(LogClass.ServiceHid, new { joyXpadId }); + + return ResultCode.Success; + } + + [Command(65)] + // GetJoySixAxisSensorLifoHandle(nn::hid::JoyXpadId) -> nn::sf::NativeHandle + public ResultCode GetJoySixAxisSensorLifoHandle(ServiceCtx context) + { + int joyXpadId = context.RequestData.ReadInt32(); + + int handle = 0; + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle); + + Logger.PrintStub(LogClass.ServiceHid, new { joyXpadId }); + + return ResultCode.Success; + } + + [Command(66)] + // StartSixAxisSensor(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) + public ResultCode StartSixAxisSensor(ServiceCtx context) + { + int sixAxisSensorHandle = context.RequestData.ReadInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle }); + + return ResultCode.Success; + } + + [Command(67)] + // StopSixAxisSensor(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) + public ResultCode StopSixAxisSensor(ServiceCtx context) + { + int sixAxisSensorHandle = context.RequestData.ReadInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle }); + + return ResultCode.Success; + } + + [Command(68)] + // IsSixAxisSensorFusionEnabled(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) -> bool IsEnabled + public ResultCode IsSixAxisSensorFusionEnabled(ServiceCtx context) + { + int sixAxisSensorHandle = context.RequestData.ReadInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + context.ResponseData.Write(_sixAxisSensorFusionEnabled); + + Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _sixAxisSensorFusionEnabled }); + + return ResultCode.Success; + } + + [Command(69)] + // EnableSixAxisSensorFusion(bool Enabled, nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) + public ResultCode EnableSixAxisSensorFusion(ServiceCtx context) + { + _sixAxisSensorFusionEnabled = context.RequestData.ReadBoolean(); + int sixAxisSensorHandle = context.RequestData.ReadInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _sixAxisSensorFusionEnabled }); + + return ResultCode.Success; + } + + [Command(70)] + // SetSixAxisSensorFusionParameters(nn::hid::SixAxisSensorHandle, float RevisePower, float ReviseRange, nn::applet::AppletResourceUserId) + public ResultCode SetSixAxisSensorFusionParameters(ServiceCtx context) + { + int sixAxisSensorHandle = context.RequestData.ReadInt32(); + + _sensorFusionParams = new HidSensorFusionParameters + { + RevisePower = context.RequestData.ReadInt32(), + ReviseRange = context.RequestData.ReadInt32() + }; + + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _sensorFusionParams.RevisePower, _sensorFusionParams.ReviseRange }); + + return ResultCode.Success; + } + + [Command(71)] + // GetSixAxisSensorFusionParameters(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) -> float RevisePower, float ReviseRange) + public ResultCode GetSixAxisSensorFusionParameters(ServiceCtx context) + { + int sixAxisSensorHandle = context.RequestData.ReadInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + context.ResponseData.Write(_sensorFusionParams.RevisePower); + context.ResponseData.Write(_sensorFusionParams.ReviseRange); + + Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _sensorFusionParams.RevisePower, _sensorFusionParams.ReviseRange }); + + return ResultCode.Success; + } + + [Command(72)] + // ResetSixAxisSensorFusionParameters(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) + public ResultCode ResetSixAxisSensorFusionParameters(ServiceCtx context) + { + int sixAxisSensorHandle = context.RequestData.ReadInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + _sensorFusionParams.RevisePower = 0; + _sensorFusionParams.ReviseRange = 0; + + Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _sensorFusionParams.RevisePower, _sensorFusionParams.ReviseRange }); + + return ResultCode.Success; + } + + [Command(73)] + // SetAccelerometerParameters(nn::hid::SixAxisSensorHandle, float X, float Y, nn::applet::AppletResourceUserId) + public ResultCode SetAccelerometerParameters(ServiceCtx context) + { + int sixAxisSensorHandle = context.RequestData.ReadInt32(); + + _accelerometerParams = new HidAccelerometerParameters + { + X = context.RequestData.ReadInt32(), + Y = context.RequestData.ReadInt32() + }; + + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _accelerometerParams.X, _accelerometerParams.Y }); + + return ResultCode.Success; + } + + [Command(74)] + // GetAccelerometerParameters(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) -> float X, float Y + public ResultCode GetAccelerometerParameters(ServiceCtx context) + { + int sixAxisSensorHandle = context.RequestData.ReadInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + context.ResponseData.Write(_accelerometerParams.X); + context.ResponseData.Write(_accelerometerParams.Y); + + Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _accelerometerParams.X, _accelerometerParams.Y }); + + return ResultCode.Success; + } + + [Command(75)] + // ResetAccelerometerParameters(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) + public ResultCode ResetAccelerometerParameters(ServiceCtx context) + { + int sixAxisSensorHandle = context.RequestData.ReadInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + _accelerometerParams.X = 0; + _accelerometerParams.Y = 0; + + Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _accelerometerParams.X, _accelerometerParams.Y }); + + return ResultCode.Success; + } + + [Command(76)] + // SetAccelerometerPlayMode(nn::hid::SixAxisSensorHandle, uint PlayMode, nn::applet::AppletResourceUserId) + public ResultCode SetAccelerometerPlayMode(ServiceCtx context) + { + int sixAxisSensorHandle = context.RequestData.ReadInt32(); + _accelerometerPlayMode = context.RequestData.ReadUInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _accelerometerPlayMode }); + + return ResultCode.Success; + } + + [Command(77)] + // GetAccelerometerPlayMode(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) -> uint PlayMode + public ResultCode GetAccelerometerPlayMode(ServiceCtx context) + { + int sixAxisSensorHandle = context.RequestData.ReadInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + context.ResponseData.Write(_accelerometerPlayMode); + + Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _accelerometerPlayMode }); + + return ResultCode.Success; + } + + [Command(78)] + // ResetAccelerometerPlayMode(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) + public ResultCode ResetAccelerometerPlayMode(ServiceCtx context) + { + int sixAxisSensorHandle = context.RequestData.ReadInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + _accelerometerPlayMode = 0; + + Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _accelerometerPlayMode }); + + return ResultCode.Success; + } + + [Command(79)] + // SetGyroscopeZeroDriftMode(nn::hid::SixAxisSensorHandle, uint GyroscopeZeroDriftMode, nn::applet::AppletResourceUserId) + public ResultCode SetGyroscopeZeroDriftMode(ServiceCtx context) + { + int sixAxisSensorHandle = context.RequestData.ReadInt32(); + _gyroscopeZeroDriftMode = (HidGyroscopeZeroDriftMode)context.RequestData.ReadInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _gyroscopeZeroDriftMode }); + + return ResultCode.Success; + } + + [Command(80)] + // GetGyroscopeZeroDriftMode(nn::applet::AppletResourceUserId, nn::hid::SixAxisSensorHandle) -> int GyroscopeZeroDriftMode + public ResultCode GetGyroscopeZeroDriftMode(ServiceCtx context) + { + int sixAxisSensorHandle = context.RequestData.ReadInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + context.ResponseData.Write((int)_gyroscopeZeroDriftMode); + + Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _gyroscopeZeroDriftMode }); + + return ResultCode.Success; + } + + [Command(81)] + // ResetGyroscopeZeroDriftMode(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) + public ResultCode ResetGyroscopeZeroDriftMode(ServiceCtx context) + { + int sixAxisSensorHandle = context.RequestData.ReadInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + _gyroscopeZeroDriftMode = HidGyroscopeZeroDriftMode.Standard; + + Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _gyroscopeZeroDriftMode }); + + return ResultCode.Success; + } + + [Command(82)] + // IsSixAxisSensorAtRest(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) -> bool IsAsRest + public ResultCode IsSixAxisSensorAtRest(ServiceCtx context) + { + int sixAxisSensorHandle = context.RequestData.ReadInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + bool isAtRest = true; + + context.ResponseData.Write(isAtRest); + + Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, isAtRest }); + + return ResultCode.Success; + } + + [Command(91)] + // ActivateGesture(nn::applet::AppletResourceUserId, int Unknown0) + public ResultCode ActivateGesture(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + int unknown0 = context.RequestData.ReadInt32(); + + Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, unknown0 }); + + return ResultCode.Success; + } + + [Command(100)] + // SetSupportedNpadStyleSet(nn::applet::AppletResourceUserId, nn::hid::NpadStyleTag) + public ResultCode SetSupportedNpadStyleSet(ServiceCtx context) + { + _npadStyleSet = (HidNpadStyle)context.RequestData.ReadInt32(); + + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, _npadStyleSet }); + + _npadStyleSetUpdateEvent.ReadableEvent.Signal(); + + return ResultCode.Success; + } + + [Command(101)] + // GetSupportedNpadStyleSet(nn::applet::AppletResourceUserId) -> uint nn::hid::NpadStyleTag + public ResultCode GetSupportedNpadStyleSet(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + + context.ResponseData.Write((int)_npadStyleSet); + + Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, _npadStyleSet }); + + return ResultCode.Success; + } + + [Command(102)] + // SetSupportedNpadIdType(nn::applet::AppletResourceUserId, array) + public ResultCode SetSupportedNpadIdType(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + ControllerId npadIdType = (ControllerId)context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, npadIdType }); + + return ResultCode.Success; + } + + [Command(103)] + // ActivateNpad(nn::applet::AppletResourceUserId) + public ResultCode ActivateNpad(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId }); + + return ResultCode.Success; + } + + [Command(104)] + // DeactivateNpad(nn::applet::AppletResourceUserId) + public ResultCode DeactivateNpad(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId }); + + return ResultCode.Success; + } + + [Command(106)] + // AcquireNpadStyleSetUpdateEventHandle(nn::applet::AppletResourceUserId, uint, ulong) -> nn::sf::NativeHandle + public ResultCode AcquireNpadStyleSetUpdateEventHandle(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + int npadId = context.RequestData.ReadInt32(); + long npadStyleSet = context.RequestData.ReadInt64(); + + if (context.Process.HandleTable.GenerateHandle(_npadStyleSetUpdateEvent.ReadableEvent, out int handle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle); + + Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, npadId, npadStyleSet }); + + return ResultCode.Success; + } + + [Command(107)] + // DisconnectNpad(nn::applet::AppletResourceUserId, uint NpadIdType) + public ResultCode DisconnectNpad(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + int npadIdType = context.RequestData.ReadInt32(); + + Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, npadIdType }); + + return ResultCode.Success; + } + + [Command(108)] + // GetPlayerLedPattern(uint NpadId) -> ulong LedPattern + public ResultCode GetPlayerLedPattern(ServiceCtx context) + { + int npadId = context.RequestData.ReadInt32(); + + long ledPattern = 0; + + context.ResponseData.Write(ledPattern); + + Logger.PrintStub(LogClass.ServiceHid, new { npadId, ledPattern }); + + return ResultCode.Success; + } + + [Command(109)] // 5.0.0+ + // ActivateNpadWithRevision(nn::applet::AppletResourceUserId, int Unknown) + public ResultCode ActivateNpadWithRevision(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + int unknown = context.RequestData.ReadInt32(); + + Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, unknown }); + + return ResultCode.Success; + } + + [Command(120)] + // SetNpadJoyHoldType(nn::applet::AppletResourceUserId, long NpadJoyHoldType) + public ResultCode SetNpadJoyHoldType(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + _npadJoyHoldType = (HidNpadJoyHoldType)context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, _npadJoyHoldType }); + + return ResultCode.Success; + } + + [Command(121)] + // GetNpadJoyHoldType(nn::applet::AppletResourceUserId) -> long NpadJoyHoldType + public ResultCode GetNpadJoyHoldType(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + + context.ResponseData.Write((long)_npadJoyHoldType); + + Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, _npadJoyHoldType }); + + return ResultCode.Success; + } + + [Command(122)] + // SetNpadJoyAssignmentModeSingleByDefault(uint HidControllerId, nn::applet::AppletResourceUserId) + public ResultCode SetNpadJoyAssignmentModeSingleByDefault(ServiceCtx context) + { + ControllerId hidControllerId = (ControllerId)context.RequestData.ReadInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + _npadJoyAssignmentMode = HidNpadJoyAssignmentMode.Single; + + Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, hidControllerId, _npadJoyAssignmentMode }); + + return ResultCode.Success; + } + + [Command(123)] + // SetNpadJoyAssignmentModeSingle(uint HidControllerId, nn::applet::AppletResourceUserId, long HidNpadJoyDeviceType) + public ResultCode SetNpadJoyAssignmentModeSingle(ServiceCtx context) + { + ControllerId hidControllerId = (ControllerId)context.RequestData.ReadInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); + HidNpadJoyDeviceType hidNpadJoyDeviceType = (HidNpadJoyDeviceType)context.RequestData.ReadInt64(); + + _npadJoyAssignmentMode = HidNpadJoyAssignmentMode.Single; + + Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, hidControllerId, hidNpadJoyDeviceType, _npadJoyAssignmentMode }); + + return ResultCode.Success; + } + + [Command(124)] + // SetNpadJoyAssignmentModeDual(uint HidControllerId, nn::applet::AppletResourceUserId) + public ResultCode SetNpadJoyAssignmentModeDual(ServiceCtx context) + { + ControllerId hidControllerId = (ControllerId)context.RequestData.ReadInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + _npadJoyAssignmentMode = HidNpadJoyAssignmentMode.Dual; + + Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, hidControllerId, _npadJoyAssignmentMode }); + + return ResultCode.Success; + } + + [Command(125)] + // MergeSingleJoyAsDualJoy(uint SingleJoyId0, uint SingleJoyId1, nn::applet::AppletResourceUserId) + public ResultCode MergeSingleJoyAsDualJoy(ServiceCtx context) + { + long singleJoyId0 = context.RequestData.ReadInt32(); + long singleJoyId1 = context.RequestData.ReadInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, singleJoyId0, singleJoyId1 }); + + return ResultCode.Success; + } + + [Command(126)] + // StartLrAssignmentMode(nn::applet::AppletResourceUserId) + public ResultCode StartLrAssignmentMode(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId }); + + return ResultCode.Success; + } + + [Command(127)] + // StopLrAssignmentMode(nn::applet::AppletResourceUserId) + public ResultCode StopLrAssignmentMode(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId }); + + return ResultCode.Success; + } + + [Command(128)] + // SetNpadHandheldActivationMode(nn::applet::AppletResourceUserId, long HidNpadHandheldActivationMode) + public ResultCode SetNpadHandheldActivationMode(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + _npadHandheldActivationMode = (HidNpadHandheldActivationMode)context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, _npadHandheldActivationMode }); + + return ResultCode.Success; + } + + [Command(129)] + // GetNpadHandheldActivationMode(nn::applet::AppletResourceUserId) -> long HidNpadHandheldActivationMode + public ResultCode GetNpadHandheldActivationMode(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + + context.ResponseData.Write((long)_npadHandheldActivationMode); + + Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, _npadHandheldActivationMode }); + + return ResultCode.Success; + } + + [Command(130)] + // SwapNpadAssignment(uint OldNpadAssignment, uint NewNpadAssignment, nn::applet::AppletResourceUserId) + public ResultCode SwapNpadAssignment(ServiceCtx context) + { + int oldNpadAssignment = context.RequestData.ReadInt32(); + int newNpadAssignment = context.RequestData.ReadInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, oldNpadAssignment, newNpadAssignment }); + + return ResultCode.Success; + } + + [Command(131)] + // IsUnintendedHomeButtonInputProtectionEnabled(uint Unknown0, nn::applet::AppletResourceUserId) -> bool IsEnabled + public ResultCode IsUnintendedHomeButtonInputProtectionEnabled(ServiceCtx context) + { + uint unknown0 = context.RequestData.ReadUInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + context.ResponseData.Write(_unintendedHomeButtonInputProtectionEnabled); + + Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, unknown0, _unintendedHomeButtonInputProtectionEnabled }); + + return ResultCode.Success; + } + + [Command(132)] + // EnableUnintendedHomeButtonInputProtection(bool Enable, uint Unknown0, nn::applet::AppletResourceUserId) + public ResultCode EnableUnintendedHomeButtonInputProtection(ServiceCtx context) + { + _unintendedHomeButtonInputProtectionEnabled = context.RequestData.ReadBoolean(); + uint unknown0 = context.RequestData.ReadUInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, unknown0, _unintendedHomeButtonInputProtectionEnabled }); + + return ResultCode.Success; + } + + [Command(133)] // 5.0.0+ + // SetNpadJoyAssignmentModeSingleWithDestination(uint HidControllerId, long HidNpadJoyDeviceType, nn::applet::AppletResourceUserId) -> bool Unknown0, uint Unknown1 + public ResultCode SetNpadJoyAssignmentModeSingleWithDestination(ServiceCtx context) + { + ControllerId hidControllerId = (ControllerId)context.RequestData.ReadInt32(); + HidNpadJoyDeviceType hidNpadJoyDeviceType = (HidNpadJoyDeviceType)context.RequestData.ReadInt64(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + _npadJoyAssignmentMode = HidNpadJoyAssignmentMode.Single; + + context.ResponseData.Write(0); //Unknown0 + context.ResponseData.Write(0); //Unknown1 + + Logger.PrintStub(LogClass.ServiceHid, new { + appletResourceUserId, + hidControllerId, + hidNpadJoyDeviceType, + _npadJoyAssignmentMode, + Unknown0 = 0, + Unknown1 = 0 + }); + + return ResultCode.Success; + } + + [Command(200)] + // GetVibrationDeviceInfo(nn::hid::VibrationDeviceHandle) -> nn::hid::VibrationDeviceInfo + public ResultCode GetVibrationDeviceInfo(ServiceCtx context) + { + int vibrationDeviceHandle = context.RequestData.ReadInt32(); + + HidVibrationDeviceValue deviceInfo = new HidVibrationDeviceValue + { + DeviceType = HidVibrationDeviceType.None, + Position = HidVibrationDevicePosition.None + }; + + context.ResponseData.Write((int)deviceInfo.DeviceType); + context.ResponseData.Write((int)deviceInfo.Position); + + Logger.PrintStub(LogClass.ServiceHid, new { vibrationDeviceHandle, deviceInfo.DeviceType, deviceInfo.Position }); + + return ResultCode.Success; + } + + [Command(201)] + // SendVibrationValue(nn::hid::VibrationDeviceHandle, nn::hid::VibrationValue, nn::applet::AppletResourceUserId) + public ResultCode SendVibrationValue(ServiceCtx context) + { + int vibrationDeviceHandle = context.RequestData.ReadInt32(); + + _vibrationValue = new HidVibrationValue + { + AmplitudeLow = context.RequestData.ReadSingle(), + FrequencyLow = context.RequestData.ReadSingle(), + AmplitudeHigh = context.RequestData.ReadSingle(), + FrequencyHigh = context.RequestData.ReadSingle() + }; + + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, new { + appletResourceUserId, + vibrationDeviceHandle, + _vibrationValue.AmplitudeLow, + _vibrationValue.FrequencyLow, + _vibrationValue.AmplitudeHigh, + _vibrationValue.FrequencyHigh + }); + + return ResultCode.Success; + } + + [Command(202)] + // GetActualVibrationValue(nn::hid::VibrationDeviceHandle, nn::applet::AppletResourceUserId) -> nn::hid::VibrationValue + public ResultCode GetActualVibrationValue(ServiceCtx context) + { + int vibrationDeviceHandle = context.RequestData.ReadInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + context.ResponseData.Write(_vibrationValue.AmplitudeLow); + context.ResponseData.Write(_vibrationValue.FrequencyLow); + context.ResponseData.Write(_vibrationValue.AmplitudeHigh); + context.ResponseData.Write(_vibrationValue.FrequencyHigh); + + Logger.PrintStub(LogClass.ServiceHid, new { + appletResourceUserId, + vibrationDeviceHandle, + _vibrationValue.AmplitudeLow, + _vibrationValue.FrequencyLow, + _vibrationValue.AmplitudeHigh, + _vibrationValue.FrequencyHigh + }); + + return ResultCode.Success; + } + + [Command(203)] + // CreateActiveVibrationDeviceList() -> object + public ResultCode CreateActiveVibrationDeviceList(ServiceCtx context) + { + MakeObject(context, new IActiveApplicationDeviceList()); + + return ResultCode.Success; + } + + [Command(204)] + // PermitVibration(bool Enable) + public ResultCode PermitVibration(ServiceCtx context) + { + _vibrationPermitted = context.RequestData.ReadBoolean(); + + Logger.PrintStub(LogClass.ServiceHid, new { _vibrationPermitted }); + + return ResultCode.Success; + } + + [Command(205)] + // IsVibrationPermitted() -> bool IsEnabled + public ResultCode IsVibrationPermitted(ServiceCtx context) + { + context.ResponseData.Write(_vibrationPermitted); + + Logger.PrintStub(LogClass.ServiceHid, new { _vibrationPermitted }); + + return ResultCode.Success; + } + + [Command(206)] + // SendVibrationValues(nn::applet::AppletResourceUserId, buffer, type: 9>, buffer, type: 9>) + public ResultCode SendVibrationValues(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + + byte[] vibrationDeviceHandleBuffer = context.Memory.ReadBytes( + context.Request.PtrBuff[0].Position, + context.Request.PtrBuff[0].Size); + + byte[] vibrationValueBuffer = context.Memory.ReadBytes( + context.Request.PtrBuff[1].Position, + context.Request.PtrBuff[1].Size); + + // TODO: Read all handles and values from buffer. + + Logger.PrintStub(LogClass.ServiceHid, new { + appletResourceUserId, + VibrationDeviceHandleBufferLength = vibrationDeviceHandleBuffer.Length, + VibrationValueBufferLength = vibrationValueBuffer.Length + }); + + return ResultCode.Success; + } + + [Command(207)] // 4.0.0+ + // SendVibrationGcErmCommand(nn::hid::VibrationDeviceHandle, nn::hid::VibrationGcErmCommand, nn::applet::AppletResourceUserId) + public ResultCode SendVibrationGcErmCommand(ServiceCtx context) + { + int vibrationDeviceHandle = context.RequestData.ReadInt32(); + long vibrationGcErmCommand = context.RequestData.ReadInt64(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, vibrationDeviceHandle, vibrationGcErmCommand }); + + return ResultCode.Success; + } + + [Command(208)] // 4.0.0+ + // GetActualVibrationGcErmCommand(nn::hid::VibrationDeviceHandle, nn::applet::AppletResourceUserId) -> nn::hid::VibrationGcErmCommand + public ResultCode GetActualVibrationGcErmCommand(ServiceCtx context) + { + int vibrationDeviceHandle = context.RequestData.ReadInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + context.ResponseData.Write(_vibrationGcErmCommand); + + Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, vibrationDeviceHandle, _vibrationGcErmCommand }); + + return ResultCode.Success; + } + + [Command(209)] // 4.0.0+ + // BeginPermitVibrationSession(nn::applet::AppletResourceUserId) + public ResultCode BeginPermitVibrationSession(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId }); + + return ResultCode.Success; + } + + [Command(210)] // 4.0.0+ + // EndPermitVibrationSession() + public ResultCode EndPermitVibrationSession(ServiceCtx context) + { + Logger.PrintStub(LogClass.ServiceHid); + + return ResultCode.Success; + } + + [Command(300)] + // ActivateConsoleSixAxisSensor(nn::applet::AppletResourceUserId) + public ResultCode ActivateConsoleSixAxisSensor(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId }); + + return ResultCode.Success; + } + + [Command(301)] + // StartConsoleSixAxisSensor(nn::hid::ConsoleSixAxisSensorHandle, nn::applet::AppletResourceUserId) + public ResultCode StartConsoleSixAxisSensor(ServiceCtx context) + { + int consoleSixAxisSensorHandle = context.RequestData.ReadInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, consoleSixAxisSensorHandle }); + + return ResultCode.Success; + } + + [Command(302)] + // StopConsoleSixAxisSensor(nn::hid::ConsoleSixAxisSensorHandle, nn::applet::AppletResourceUserId) + public ResultCode StopConsoleSixAxisSensor(ServiceCtx context) + { + int consoleSixAxisSensorHandle = context.RequestData.ReadInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, consoleSixAxisSensorHandle }); + + return ResultCode.Success; + } + + [Command(303)] // 5.0.0+ + // ActivateSevenSixAxisSensor(nn::applet::AppletResourceUserId) + public ResultCode ActivateSevenSixAxisSensor(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId }); + + return ResultCode.Success; + } + + [Command(304)] // 5.0.0+ + // StartSevenSixAxisSensor(nn::applet::AppletResourceUserId) + public ResultCode StartSevenSixAxisSensor(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId }); + + return ResultCode.Success; + } + + [Command(305)] // 5.0.0+ + // StopSevenSixAxisSensor(nn::applet::AppletResourceUserId) + public ResultCode StopSevenSixAxisSensor(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId }); + + return ResultCode.Success; + } + + [Command(306)] // 5.0.0+ + // InitializeSevenSixAxisSensor(array, ulong Counter0, array, ulong Counter1, nn::applet::AppletResourceUserId) + public ResultCode InitializeSevenSixAxisSensor(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + long counter0 = context.RequestData.ReadInt64(); + long counter1 = context.RequestData.ReadInt64(); + + // TODO: Determine if array is a buffer or not... + + Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, counter0, counter1 }); + + return ResultCode.Success; + } + + [Command(307)] // 5.0.0+ + // FinalizeSevenSixAxisSensor(nn::applet::AppletResourceUserId) + public ResultCode FinalizeSevenSixAxisSensor(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId }); + + return ResultCode.Success; + } + + [Command(308)] // 5.0.0+ + // SetSevenSixAxisSensorFusionStrength(float Strength, nn::applet::AppletResourceUserId) + public ResultCode SetSevenSixAxisSensorFusionStrength(ServiceCtx context) + { + _sevenSixAxisSensorFusionStrength = context.RequestData.ReadSingle(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, _sevenSixAxisSensorFusionStrength }); + + return ResultCode.Success; + } + + [Command(309)] // 5.0.0+ + // GetSevenSixAxisSensorFusionStrength(nn::applet::AppletResourceUserId) -> float Strength + public ResultCode GetSevenSixAxisSensorFusionStrength(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + + context.ResponseData.Write(_sevenSixAxisSensorFusionStrength); + + Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, _sevenSixAxisSensorFusionStrength }); + + return ResultCode.Success; + } + + [Command(400)] + // IsUsbFullKeyControllerEnabled() -> bool IsEnabled + public ResultCode IsUsbFullKeyControllerEnabled(ServiceCtx context) + { + context.ResponseData.Write(_usbFullKeyControllerEnabled); + + Logger.PrintStub(LogClass.ServiceHid, new { _usbFullKeyControllerEnabled }); + + return ResultCode.Success; + } + + [Command(401)] + // EnableUsbFullKeyController(bool Enable) + public ResultCode EnableUsbFullKeyController(ServiceCtx context) + { + _usbFullKeyControllerEnabled = context.RequestData.ReadBoolean(); + + Logger.PrintStub(LogClass.ServiceHid, new { _usbFullKeyControllerEnabled }); + + return ResultCode.Success; + } + + [Command(402)] + // IsUsbFullKeyControllerConnected(uint Unknown0) -> bool Connected + public ResultCode IsUsbFullKeyControllerConnected(ServiceCtx context) + { + int unknown0 = context.RequestData.ReadInt32(); + + context.ResponseData.Write(true); //FullKeyController is always connected ? + + Logger.PrintStub(LogClass.ServiceHid, new { unknown0, Connected = true }); + + return ResultCode.Success; + } + + [Command(403)] // 4.0.0+ + // HasBattery(uint NpadId) -> bool HasBattery + public ResultCode HasBattery(ServiceCtx context) + { + int npadId = context.RequestData.ReadInt32(); + + context.ResponseData.Write(true); //Npad always got a battery ? + + Logger.PrintStub(LogClass.ServiceHid, new { npadId, HasBattery = true }); + + return ResultCode.Success; + } + + [Command(404)] // 4.0.0+ + // HasLeftRightBattery(uint NpadId) -> bool HasLeftBattery, bool HasRightBattery + public ResultCode HasLeftRightBattery(ServiceCtx context) + { + int npadId = context.RequestData.ReadInt32(); + + context.ResponseData.Write(true); //Npad always got a left battery ? + context.ResponseData.Write(true); //Npad always got a right battery ? + + Logger.PrintStub(LogClass.ServiceHid, new { npadId, HasLeftBattery = true, HasRightBattery = true }); + + return ResultCode.Success; + } + + [Command(405)] // 4.0.0+ + // GetNpadInterfaceType(uint NpadId) -> uchar InterfaceType + public ResultCode GetNpadInterfaceType(ServiceCtx context) + { + int npadId = context.RequestData.ReadInt32(); + + context.ResponseData.Write((byte)0); + + Logger.PrintStub(LogClass.ServiceHid, new { npadId, NpadInterfaceType = 0 }); + + return ResultCode.Success; + } + + [Command(406)] // 4.0.0+ + // GetNpadLeftRightInterfaceType(uint NpadId) -> uchar LeftInterfaceType, uchar RightInterfaceType + public ResultCode GetNpadLeftRightInterfaceType(ServiceCtx context) + { + int npadId = context.RequestData.ReadInt32(); + + context.ResponseData.Write((byte)0); + context.ResponseData.Write((byte)0); + + Logger.PrintStub(LogClass.ServiceHid, new { npadId, LeftInterfaceType = 0, RightInterfaceType = 0 }); + + return ResultCode.Success; + } + + [Command(500)] // 5.0.0+ + // GetPalmaConnectionHandle(uint Unknown0, nn::applet::AppletResourceUserId) -> nn::hid::PalmaConnectionHandle + public ResultCode GetPalmaConnectionHandle(ServiceCtx context) + { + int unknown0 = context.RequestData.ReadInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + int palmaConnectionHandle = 0; + + context.ResponseData.Write(palmaConnectionHandle); + + Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId , unknown0, palmaConnectionHandle }); + + return ResultCode.Success; + } + + [Command(501)] // 5.0.0+ + // InitializePalma(nn::hid::PalmaConnectionHandle) + public ResultCode InitializePalma(ServiceCtx context) + { + int palmaConnectionHandle = context.RequestData.ReadInt32(); + + Logger.PrintStub(LogClass.ServiceHid, new { palmaConnectionHandle }); + + _palmaOperationCompleteEvent.ReadableEvent.Signal(); + + return ResultCode.Success; + } + + [Command(502)] // 5.0.0+ + // AcquirePalmaOperationCompleteEvent(nn::hid::PalmaConnectionHandle) -> nn::sf::NativeHandle + public ResultCode AcquirePalmaOperationCompleteEvent(ServiceCtx context) + { + int palmaConnectionHandle = context.RequestData.ReadInt32(); + + if (context.Process.HandleTable.GenerateHandle(_palmaOperationCompleteEvent.ReadableEvent, out int handle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle); + + Logger.PrintStub(LogClass.ServiceHid, new { palmaConnectionHandle }); + + return ResultCode.Success; + } + + [Command(503)] // 5.0.0+ + // GetPalmaOperationInfo(nn::hid::PalmaConnectionHandle) -> long Unknown0, buffer + public ResultCode GetPalmaOperationInfo(ServiceCtx context) + { + int palmaConnectionHandle = context.RequestData.ReadInt32(); + + long unknown0 = 0; //Counter? + + context.ResponseData.Write(unknown0); + + Logger.PrintStub(LogClass.ServiceHid, new { palmaConnectionHandle, unknown0 }); + + return ResultCode.Success; + } + + [Command(504)] // 5.0.0+ + // PlayPalmaActivity(nn::hid::PalmaConnectionHandle, ulong Unknown0) + public ResultCode PlayPalmaActivity(ServiceCtx context) + { + int palmaConnectionHandle = context.RequestData.ReadInt32(); + long unknown0 = context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, new { palmaConnectionHandle, unknown0 }); + + _palmaOperationCompleteEvent.ReadableEvent.Signal(); + + return ResultCode.Success; + } + + [Command(505)] // 5.0.0+ + // SetPalmaFrModeType(nn::hid::PalmaConnectionHandle, ulong FrModeType) + public ResultCode SetPalmaFrModeType(ServiceCtx context) + { + int palmaConnectionHandle = context.RequestData.ReadInt32(); + long frModeType = context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, new { palmaConnectionHandle, frModeType }); + + _palmaOperationCompleteEvent.ReadableEvent.Signal(); + + return ResultCode.Success; + } + + [Command(506)] // 5.0.0+ + // ReadPalmaStep(nn::hid::PalmaConnectionHandle) + public ResultCode ReadPalmaStep(ServiceCtx context) + { + int palmaConnectionHandle = context.RequestData.ReadInt32(); + + Logger.PrintStub(LogClass.ServiceHid, new { palmaConnectionHandle }); + + return ResultCode.Success; + } + + [Command(507)] // 5.0.0+ + // EnablePalmaStep(nn::hid::PalmaConnectionHandle, bool Enable) + public ResultCode EnablePalmaStep(ServiceCtx context) + { + int palmaConnectionHandle = context.RequestData.ReadInt32(); + bool enabledPalmaStep = context.RequestData.ReadBoolean(); + + Logger.PrintStub(LogClass.ServiceHid, new { palmaConnectionHandle, enabledPalmaStep }); + + _palmaOperationCompleteEvent.ReadableEvent.Signal(); + + return ResultCode.Success; + } + + [Command(508)] // 5.0.0+ + // ResetPalmaStep(nn::hid::PalmaConnectionHandle) + public ResultCode ResetPalmaStep(ServiceCtx context) + { + int palmaConnectionHandle = context.RequestData.ReadInt32(); + + Logger.PrintStub(LogClass.ServiceHid, new { palmaConnectionHandle }); + + _palmaOperationCompleteEvent.ReadableEvent.Signal(); + + return ResultCode.Success; + } + + [Command(509)] // 5.0.0+ + // ReadPalmaApplicationSection(nn::hid::PalmaConnectionHandle, ulong Unknown0, ulong Unknown1) + public ResultCode ReadPalmaApplicationSection(ServiceCtx context) + { + int palmaConnectionHandle = context.RequestData.ReadInt32(); + long unknown0 = context.RequestData.ReadInt64(); + long unknown1 = context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, new { palmaConnectionHandle, unknown0, unknown1 }); + + return ResultCode.Success; + } + + [Command(510)] // 5.0.0+ + // WritePalmaApplicationSection(nn::hid::PalmaConnectionHandle, ulong Unknown0, ulong Unknown1, nn::hid::PalmaApplicationSectionAccessBuffer) + public ResultCode WritePalmaApplicationSection(ServiceCtx context) + { + int palmaConnectionHandle = context.RequestData.ReadInt32(); + long unknown0 = context.RequestData.ReadInt64(); + long unknown1 = context.RequestData.ReadInt64(); + // nn::hid::PalmaApplicationSectionAccessBuffer cast is unknown + + Logger.PrintStub(LogClass.ServiceHid, new { palmaConnectionHandle, unknown0, unknown1 }); + + _palmaOperationCompleteEvent.ReadableEvent.Signal(); + + return ResultCode.Success; + } + + [Command(511)] // 5.0.0+ + // ReadPalmaUniqueCode(nn::hid::PalmaConnectionHandle) + public ResultCode ReadPalmaUniqueCode(ServiceCtx context) + { + int palmaConnectionHandle = context.RequestData.ReadInt32(); + + Logger.PrintStub(LogClass.ServiceHid, new { palmaConnectionHandle }); + + return ResultCode.Success; + } + + [Command(512)] // 5.0.0+ + // SetPalmaUniqueCodeInvalid(nn::hid::PalmaConnectionHandle) + public ResultCode SetPalmaUniqueCodeInvalid(ServiceCtx context) + { + int palmaConnectionHandle = context.RequestData.ReadInt32(); + + Logger.PrintStub(LogClass.ServiceHid, new { palmaConnectionHandle }); + + return ResultCode.Success; + } + + [Command(522)] // 5.1.0+ + // SetIsPalmaAllConnectable(nn::applet::AppletResourceUserId, bool, pid) + public ResultCode SetIsPalmaAllConnectable(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + long unknownBool = context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, unknownBool }); + + return ResultCode.Success; + } + + [Command(525)] // 5.1.0+ + // SetPalmaBoostMode(bool) + public ResultCode SetPalmaBoostMode(ServiceCtx context) + { + // NOTE: Stubbed in system module. + + return ResultCode.Success; + } + + [Command(1000)] + // SetNpadCommunicationMode(long CommunicationMode, nn::applet::AppletResourceUserId) + public ResultCode SetNpadCommunicationMode(ServiceCtx context) + { + _npadCommunicationMode = context.RequestData.ReadInt64(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, _npadCommunicationMode }); + + return ResultCode.Success; + } + + [Command(1001)] + // GetNpadCommunicationMode() -> long CommunicationMode + public ResultCode GetNpadCommunicationMode(ServiceCtx context) + { + context.ResponseData.Write(_npadCommunicationMode); + + Logger.PrintStub(LogClass.ServiceHid, new { _npadCommunicationMode }); + + return ResultCode.Success; + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Hid/IHidSystemServer.cs b/Ryujinx.HLE/HOS/Services/Hid/IHidSystemServer.cs new file mode 100644 index 0000000000..019e995440 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Hid/IHidSystemServer.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + [Service("hid:sys")] + class IHidSystemServer : IpcService + { + public IHidSystemServer(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Hid/IHidbusServer.cs b/Ryujinx.HLE/HOS/Services/Hid/IHidbusServer.cs new file mode 100644 index 0000000000..bfd1d4dcdf --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Hid/IHidbusServer.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + [Service("hidbus")] + class IHidbusServer : IpcService + { + public IHidbusServer(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Hid/ISystemServer.cs b/Ryujinx.HLE/HOS/Services/Hid/ISystemServer.cs new file mode 100644 index 0000000000..7135334420 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Hid/ISystemServer.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + [Service("xcd:sys")] + class ISystemServer : IpcService + { + public ISystemServer(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Hid/Irs/IIrSensorServer.cs b/Ryujinx.HLE/HOS/Services/Hid/Irs/IIrSensorServer.cs new file mode 100644 index 0000000000..cd571f11e4 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Hid/Irs/IIrSensorServer.cs @@ -0,0 +1,91 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Services.Hid.HidServer; +using Ryujinx.HLE.Input; +using System; + +namespace Ryujinx.HLE.HOS.Services.Hid.Irs +{ + [Service("irs")] + class IIrSensorServer : IpcService + { + private int _irsensorSharedMemoryHandle = 0; + + public IIrSensorServer(ServiceCtx context) { } + + [Command(302)] + // ActivateIrsensor(nn::applet::AppletResourceUserId, pid) + public ResultCode ActivateIrsensor(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceIrs, new { appletResourceUserId }); + + return ResultCode.Success; + } + + [Command(303)] + // DeactivateIrsensor(nn::applet::AppletResourceUserId, pid) + public ResultCode DeactivateIrsensor(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceIrs, new { appletResourceUserId }); + + return ResultCode.Success; + } + + [Command(304)] + // GetIrsensorSharedMemoryHandle(nn::applet::AppletResourceUserId, pid) -> handle + public ResultCode GetIrsensorSharedMemoryHandle(ServiceCtx context) + { + if (_irsensorSharedMemoryHandle == 0) + { + if (context.Process.HandleTable.GenerateHandle(context.Device.System.IirsSharedMem, out _irsensorSharedMemoryHandle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_irsensorSharedMemoryHandle); + + return ResultCode.Success; + } + + [Command(311)] + // GetNpadIrCameraHandle(u32) -> nn::irsensor::IrCameraHandle + public ResultCode GetNpadIrCameraHandle(ServiceCtx context) + { + HidNpadIdType npadIdType = (HidNpadIdType)context.RequestData.ReadUInt32(); + + if (npadIdType > HidNpadIdType.Player8 && + npadIdType != HidNpadIdType.Unknown && + npadIdType != HidNpadIdType.Handheld) + { + return ResultCode.NpadIdOutOfRange; + } + + ControllerId irCameraHandle = HidUtils.GetIndexFromNpadIdType(npadIdType); + + context.ResponseData.Write((int)irCameraHandle); + + // NOTE: If the irCameraHandle pointer is null this error is returned, Doesn't occur in our case. + // return ResultCode.HandlePointerIsNull; + + return ResultCode.Success; + } + + [Command(319)] // 4.0.0+ + // ActivateIrsensorWithFunctionLevel(nn::applet::AppletResourceUserId, nn::irsensor::PackedFunctionLevel, pid) + public ResultCode ActivateIrsensorWithFunctionLevel(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + long packedFunctionLevel = context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceIrs, new { appletResourceUserId, packedFunctionLevel }); + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Hid/Irs/IIrSensorSystemServer.cs b/Ryujinx.HLE/HOS/Services/Hid/Irs/IIrSensorSystemServer.cs new file mode 100644 index 0000000000..99fcd5415d --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Hid/Irs/IIrSensorSystemServer.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Hid.Irs +{ + [Service("irs:sys")] + class IIrSensorSystemServer : IpcService + { + public IIrSensorSystemServer(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Hid/Irs/ResultCode.cs b/Ryujinx.HLE/HOS/Services/Hid/Irs/ResultCode.cs new file mode 100644 index 0000000000..016f6402da --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Hid/Irs/ResultCode.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.HLE.HOS.Services.Hid.Irs +{ + public enum ResultCode + { + ModuleId = 205, + ErrorCodeShift = 9, + + Success = 0, + + HandlePointerIsNull = (212 << ErrorCodeShift) | ModuleId, + NpadIdOutOfRange = (709 << ErrorCodeShift) | ModuleId + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/HidNpadHandheldActivationMode.cs b/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/HidNpadHandheldActivationMode.cs new file mode 100644 index 0000000000..0aa8334d82 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/HidNpadHandheldActivationMode.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public enum HidNpadHandheldActivationMode + { + Dual, + Single, + None + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/HidNpadIdType.cs b/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/HidNpadIdType.cs new file mode 100644 index 0000000000..9a3989de66 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/HidNpadIdType.cs @@ -0,0 +1,16 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public enum HidNpadIdType + { + Player1 = 0, + Player2 = 1, + Player3 = 2, + Player4 = 3, + Player5 = 4, + Player6 = 5, + Player7 = 6, + Player8 = 7, + Unknown = 16, + Handheld = 32 + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/HidNpadJoyAssignmentMode.cs b/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/HidNpadJoyAssignmentMode.cs new file mode 100644 index 0000000000..a2e22661de --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/HidNpadJoyAssignmentMode.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public enum HidNpadJoyAssignmentMode + { + Dual, + Single + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/HidNpadJoyDeviceType.cs b/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/HidNpadJoyDeviceType.cs new file mode 100644 index 0000000000..d0b34def2e --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/HidNpadJoyDeviceType.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public enum HidNpadJoyDeviceType + { + Left, + Right + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/HidNpadJoyHoldType.cs b/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/HidNpadJoyHoldType.cs new file mode 100644 index 0000000000..3bd3aa914d --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/HidNpadJoyHoldType.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public enum HidNpadJoyHoldType + { + Vertical, + Horizontal + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/HidNpadStyle.cs b/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/HidNpadStyle.cs new file mode 100644 index 0000000000..93717acf16 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/HidNpadStyle.cs @@ -0,0 +1,16 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Hid +{ + [Flags] + public enum HidNpadStyle + { + None, + FullKey = 1 << 0, + Handheld = 1 << 1, + Dual = 1 << 2, + Left = 1 << 3, + Right = 1 << 4, + Invalid = 1 << 5 + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Hid/Types/SixAxis/HidAccelerometerParameters.cs b/Ryujinx.HLE/HOS/Services/Hid/Types/SixAxis/HidAccelerometerParameters.cs new file mode 100644 index 0000000000..fe7e4cc938 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Hid/Types/SixAxis/HidAccelerometerParameters.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public struct HidAccelerometerParameters + { + public float X; + public float Y; + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Hid/Types/SixAxis/HidGyroscopeZeroDriftMode.cs b/Ryujinx.HLE/HOS/Services/Hid/Types/SixAxis/HidGyroscopeZeroDriftMode.cs new file mode 100644 index 0000000000..cd3aa3180c --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Hid/Types/SixAxis/HidGyroscopeZeroDriftMode.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public enum HidGyroscopeZeroDriftMode + { + Loose, + Standard, + Tight + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Hid/Types/SixAxis/HidSensorFusionParameters.cs b/Ryujinx.HLE/HOS/Services/Hid/Types/SixAxis/HidSensorFusionParameters.cs new file mode 100644 index 0000000000..cadf5ec021 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Hid/Types/SixAxis/HidSensorFusionParameters.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public struct HidSensorFusionParameters + { + public float RevisePower; + public float ReviseRange; + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Hid/Types/Vibration/HidVibrationDevicePosition.cs b/Ryujinx.HLE/HOS/Services/Hid/Types/Vibration/HidVibrationDevicePosition.cs new file mode 100644 index 0000000000..0ab84af3d6 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Hid/Types/Vibration/HidVibrationDevicePosition.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public enum HidVibrationDevicePosition + { + None, + Left, + Right + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Hid/Types/Vibration/HidVibrationDeviceType.cs b/Ryujinx.HLE/HOS/Services/Hid/Types/Vibration/HidVibrationDeviceType.cs new file mode 100644 index 0000000000..cf9e64985c --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Hid/Types/Vibration/HidVibrationDeviceType.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public enum HidVibrationDeviceType + { + None, + LinearResonantActuator + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Hid/Types/Vibration/HidVibrationDeviceValue.cs b/Ryujinx.HLE/HOS/Services/Hid/Types/Vibration/HidVibrationDeviceValue.cs new file mode 100644 index 0000000000..7905ecfdc0 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Hid/Types/Vibration/HidVibrationDeviceValue.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public struct HidVibrationDeviceValue + { + public HidVibrationDeviceType DeviceType; + public HidVibrationDevicePosition Position; + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Hid/Types/Vibration/HidVibrationValue.cs b/Ryujinx.HLE/HOS/Services/Hid/Types/Vibration/HidVibrationValue.cs new file mode 100644 index 0000000000..7211396e42 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Hid/Types/Vibration/HidVibrationValue.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public struct HidVibrationValue + { + public float AmplitudeLow; + public float FrequencyLow; + public float AmplitudeHigh; + public float FrequencyHigh; + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/IIpcService.cs b/Ryujinx.HLE/HOS/Services/IIpcService.cs new file mode 100644 index 0000000000..f17124916a --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/IIpcService.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using System.Reflection; + +namespace Ryujinx.HLE.HOS.Services +{ + interface IIpcService + { + IReadOnlyDictionary Commands { get; } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Ins/IReceiverManager.cs b/Ryujinx.HLE/HOS/Services/Ins/IReceiverManager.cs new file mode 100644 index 0000000000..34d4bdfd78 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Ins/IReceiverManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Ins +{ + [Service("ins:r")] + class IReceiverManager : IpcService + { + public IReceiverManager(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Ins/ISenderManager.cs b/Ryujinx.HLE/HOS/Services/Ins/ISenderManager.cs new file mode 100644 index 0000000000..38a95ee7b1 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Ins/ISenderManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Ins +{ + [Service("ins:s")] + class ISenderManager : IpcService + { + public ISenderManager(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/IpcService.cs b/Ryujinx.HLE/HOS/Services/IpcService.cs new file mode 100644 index 0000000000..e953868358 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/IpcService.cs @@ -0,0 +1,226 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.Exceptions; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Ipc; +using System; +using System.Collections.Generic; +using System.IO; +using Ryujinx.Profiler; +using System.Reflection; +using System.Linq; + +namespace Ryujinx.HLE.HOS.Services +{ + abstract class IpcService : IIpcService + { + public IReadOnlyDictionary Commands { get; } + + private IdDictionary _domainObjects; + + private int _selfId; + + private bool _isDomain; + + public IpcService() + { + Commands = Assembly.GetExecutingAssembly().GetTypes() + .Where(type => type == GetType()) + .SelectMany(type => type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public)) + .SelectMany(methodInfo => methodInfo.GetCustomAttributes(typeof(CommandAttribute)) + .Select(command => (((CommandAttribute)command).Id, methodInfo))) + .ToDictionary(command => command.Id, command => command.methodInfo); + + _domainObjects = new IdDictionary(); + + _selfId = -1; + } + + public int ConvertToDomain() + { + if (_selfId == -1) + { + _selfId = _domainObjects.Add(this); + } + + _isDomain = true; + + return _selfId; + } + + public void ConvertToSession() + { + _isDomain = false; + } + + public void CallMethod(ServiceCtx context) + { + IIpcService service = this; + + if (_isDomain) + { + int domainWord0 = context.RequestData.ReadInt32(); + int domainObjId = context.RequestData.ReadInt32(); + + int domainCmd = (domainWord0 >> 0) & 0xff; + int inputObjCount = (domainWord0 >> 8) & 0xff; + int dataPayloadSize = (domainWord0 >> 16) & 0xffff; + + context.RequestData.BaseStream.Seek(0x10 + dataPayloadSize, SeekOrigin.Begin); + + for (int index = 0; index < inputObjCount; index++) + { + context.Request.ObjectIds.Add(context.RequestData.ReadInt32()); + } + + context.RequestData.BaseStream.Seek(0x10, SeekOrigin.Begin); + + if (domainCmd == 1) + { + service = GetObject(domainObjId); + + context.ResponseData.Write(0L); + context.ResponseData.Write(0L); + } + else if (domainCmd == 2) + { + Delete(domainObjId); + + context.ResponseData.Write(0L); + + return; + } + else + { + throw new NotImplementedException($"Domain command: {domainCmd}"); + } + } + + long sfciMagic = context.RequestData.ReadInt64(); + int commandId = (int)context.RequestData.ReadInt64(); + + bool serviceExists = service.Commands.TryGetValue(commandId, out MethodInfo processRequest); + + if (ServiceConfiguration.IgnoreMissingServices || serviceExists) + { + ResultCode result = ResultCode.Success; + + context.ResponseData.BaseStream.Seek(_isDomain ? 0x20 : 0x10, SeekOrigin.Begin); + + if (serviceExists) + { + Logger.PrintDebug(LogClass.KernelIpc, $"{service.GetType().Name}: {processRequest.Name}"); + + ProfileConfig profile = Profiles.ServiceCall; + + profile.SessionGroup = service.GetType().Name; + profile.SessionItem = processRequest.Name; + + Profile.Begin(profile); + + result = (ResultCode)processRequest.Invoke(service, new object[] { context }); + + Profile.End(profile); + } + else + { + string serviceName; + + DummyService dummyService = service as DummyService; + + serviceName = (dummyService == null) ? service.GetType().FullName : dummyService.ServiceName; + + Logger.PrintWarning(LogClass.KernelIpc, $"Missing service {serviceName}: {commandId} ignored"); + } + + if (_isDomain) + { + foreach (int id in context.Response.ObjectIds) + { + context.ResponseData.Write(id); + } + + context.ResponseData.BaseStream.Seek(0, SeekOrigin.Begin); + + context.ResponseData.Write(context.Response.ObjectIds.Count); + } + + context.ResponseData.BaseStream.Seek(_isDomain ? 0x10 : 0, SeekOrigin.Begin); + + context.ResponseData.Write(IpcMagic.Sfco); + context.ResponseData.Write((long)result); + } + else + { + string dbgMessage = $"{service.GetType().FullName}: {commandId}"; + + throw new ServiceNotImplementedException(context, dbgMessage); + } + } + + protected static void MakeObject(ServiceCtx context, IpcService obj) + { + IpcService service = context.Session.Service; + + if (service._isDomain) + { + context.Response.ObjectIds.Add(service.Add(obj)); + } + else + { + KSession session = new KSession(context.Device.System); + + session.ClientSession.Service = obj; + + if (context.Process.HandleTable.GenerateHandle(session.ClientSession, out int handle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeMove(handle); + } + } + + protected static T GetObject(ServiceCtx context, int index) where T : IpcService + { + IpcService service = context.Session.Service; + + if (!service._isDomain) + { + int handle = context.Request.HandleDesc.ToMove[index]; + + KClientSession session = context.Process.HandleTable.GetObject(handle); + + return session?.Service is T ? (T)session.Service : null; + } + + int objId = context.Request.ObjectIds[index]; + + IIpcService obj = service.GetObject(objId); + + return obj is T ? (T)obj : null; + } + + private int Add(IIpcService obj) + { + return _domainObjects.Add(obj); + } + + private bool Delete(int id) + { + object obj = _domainObjects.Delete(id); + + if (obj is IDisposable disposableObj) + { + disposableObj.Dispose(); + } + + return obj != null; + } + + private IIpcService GetObject(int id) + { + return _domainObjects.GetData(id); + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Lbl/ILblController.cs b/Ryujinx.HLE/HOS/Services/Lbl/ILblController.cs new file mode 100644 index 0000000000..de84095ee9 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Lbl/ILblController.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Lbl +{ + [Service("lbl")] + class ILblController : IpcService + { + public ILblController(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Ldn/IMonitorServiceCreator.cs b/Ryujinx.HLE/HOS/Services/Ldn/IMonitorServiceCreator.cs new file mode 100644 index 0000000000..09dfa78faa --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Ldn/IMonitorServiceCreator.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Ldn +{ + [Service("ldn:m")] + class IMonitorServiceCreator : IpcService + { + public IMonitorServiceCreator(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Ldn/ISystemServiceCreator.cs b/Ryujinx.HLE/HOS/Services/Ldn/ISystemServiceCreator.cs new file mode 100644 index 0000000000..b4dac449e7 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Ldn/ISystemServiceCreator.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Ldn +{ + [Service("ldn:s")] + class ISystemServiceCreator : IpcService + { + public ISystemServiceCreator(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Ldn/IUserServiceCreator.cs b/Ryujinx.HLE/HOS/Services/Ldn/IUserServiceCreator.cs new file mode 100644 index 0000000000..3fc9ce1c7b --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Ldn/IUserServiceCreator.cs @@ -0,0 +1,19 @@ +using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator; + +namespace Ryujinx.HLE.HOS.Services.Ldn +{ + [Service("ldn:u")] + class IUserServiceCreator : IpcService + { + public IUserServiceCreator(ServiceCtx context) { } + + [Command(0)] + // CreateUserLocalCommunicationService() -> object + public ResultCode CreateUserLocalCommunicationService(ServiceCtx context) + { + MakeObject(context, new IUserLocalCommunicationService(context)); + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Ldn/Lp2p/IServiceCreator.cs b/Ryujinx.HLE/HOS/Services/Ldn/Lp2p/IServiceCreator.cs new file mode 100644 index 0000000000..9c9ee3be2e --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Ldn/Lp2p/IServiceCreator.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Ldn.Lp2p +{ + [Service("lp2p:app")] // 9.0.0+ + [Service("lp2p:sys")] // 9.0.0+ + class IServiceCreator : IpcService + { + public IServiceCreator(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Ldn/NetworkInterface.cs b/Ryujinx.HLE/HOS/Services/Ldn/NetworkInterface.cs new file mode 100644 index 0000000000..f06c664464 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Ldn/NetworkInterface.cs @@ -0,0 +1,59 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Ldn.Types; +using System.Net; + +namespace Ryujinx.HLE.HOS.Services.Ldn +{ + internal class NetworkInterface + { + public ResultCode NifmState { get; set; } + public KEvent StateChangeEvent { get; private set; } + + private NetworkState _state; + + public NetworkInterface(Horizon system) + { + // TODO(Ac_K): Determine where the internal state is set. + NifmState = ResultCode.Success; + StateChangeEvent = new KEvent(system); + + _state = NetworkState.None; + } + + public ResultCode Initialize(int unknown, int version, IPAddress ipv4Address, IPAddress subnetMaskAddress) + { + // TODO(Ac_K): Call nn::nifm::InitializeSystem(). + // If the call failed, it returns the result code. + // If the call succeed, it signal and clear an event then start a new thread named nn.ldn.NetworkInterfaceMonitor. + + Logger.PrintStub(LogClass.ServiceLdn, new { version }); + + // NOTE: Since we don't support ldn for now, we can return this following result code to make it disabled. + return ResultCode.DeviceDisabled; + } + + public ResultCode GetState(out NetworkState state) + { + // Return ResultCode.InvalidArgument if _state is null, doesn't occur in our case. + + state = _state; + + return ResultCode.Success; + } + + public ResultCode Finalize() + { + // TODO(Ac_K): Finalize nifm service then kill the thread named nn.ldn.NetworkInterfaceMonitor. + + _state = NetworkState.None; + + StateChangeEvent.WritableEvent.Signal(); + StateChangeEvent.WritableEvent.Clear(); + + Logger.PrintStub(LogClass.ServiceLdn); + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Ldn/ResultCode.cs b/Ryujinx.HLE/HOS/Services/Ldn/ResultCode.cs new file mode 100644 index 0000000000..87674f7c39 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Ldn/ResultCode.cs @@ -0,0 +1,16 @@ +namespace Ryujinx.HLE.HOS.Services.Ldn +{ + enum ResultCode + { + ModuleId = 203, + ErrorCodeShift = 9, + + Success = 0, + + DeviceDisabled = (22 << ErrorCodeShift) | ModuleId, + InvalidState = (32 << ErrorCodeShift) | ModuleId, + Unknown1 = (48 << ErrorCodeShift) | ModuleId, + InvalidArgument = (96 << ErrorCodeShift) | ModuleId, + InvalidObject = (97 << ErrorCodeShift) | ModuleId, + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Ldn/Types/NetworkState.cs b/Ryujinx.HLE/HOS/Services/Ldn/Types/NetworkState.cs new file mode 100644 index 0000000000..6ac2048338 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Ldn/Types/NetworkState.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.HLE.HOS.Services.Ldn.Types +{ + enum NetworkState + { + None, + Initialized, + AccessPoint, + AccessPointCreated, + Station, + StationConnected, + Error + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/IUserLocalCommunicationService.cs b/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/IUserLocalCommunicationService.cs new file mode 100644 index 0000000000..b1ae2d6eee --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/IUserLocalCommunicationService.cs @@ -0,0 +1,88 @@ +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Services.Ldn.Types; +using System; +using System.Net; + +namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator +{ + class IUserLocalCommunicationService : IpcService + { + // TODO(Ac_K): Determine what the hardcoded unknown value is. + private const int UnknownValue = 90; + + private NetworkInterface _networkInterface; + + private int _stateChangeEventHandle = 0; + + public IUserLocalCommunicationService(ServiceCtx context) + { + _networkInterface = new NetworkInterface(context.Device.System); + } + + [Command(0)] + // GetState() -> s32 state + public ResultCode GetState(ServiceCtx context) + { + if (_networkInterface.NifmState != ResultCode.Success) + { + context.ResponseData.Write((int)NetworkState.Error); + + return ResultCode.Success; + } + + ResultCode result = _networkInterface.GetState(out NetworkState state); + + if (result == ResultCode.Success) + { + context.ResponseData.Write((int)state); + } + + return result; + } + + [Command(100)] + // AttachStateChangeEvent() -> handle + public ResultCode AttachStateChangeEvent(ServiceCtx context) + { + if (_stateChangeEventHandle == 0) + { + if (context.Process.HandleTable.GenerateHandle(_networkInterface.StateChangeEvent.ReadableEvent, out _stateChangeEventHandle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_stateChangeEventHandle); + + // Return ResultCode.InvalidArgument if handle is null, doesn't occur in our case since we already throw an Exception. + + return ResultCode.Success; + } + + [Command(400)] + // InitializeOld(u64, pid) + public ResultCode InitializeOld(ServiceCtx context) + { + return _networkInterface.Initialize(UnknownValue, 0, null, null); + } + + [Command(401)] + // Finalize() + public ResultCode Finalize(ServiceCtx context) + { + return _networkInterface.Finalize(); + } + + [Command(402)] // 7.0.0+ + // Initialize(u64 ip_addresses, u64, pid) + public ResultCode Initialize(ServiceCtx context) + { + // TODO(Ac_K): Determine what addresses are. + IPAddress unknownAddress1 = new IPAddress(context.RequestData.ReadUInt32()); + IPAddress unknownAddress2 = new IPAddress(context.RequestData.ReadUInt32()); + + return _networkInterface.Initialize(UnknownValue, version: 1, unknownAddress1, unknownAddress2); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Lm/ILogService.cs b/Ryujinx.HLE/HOS/Services/Lm/ILogService.cs new file mode 100644 index 0000000000..9fc467663a --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Lm/ILogService.cs @@ -0,0 +1,19 @@ +using Ryujinx.HLE.HOS.Services.Lm.LogService; + +namespace Ryujinx.HLE.HOS.Services.Lm +{ + [Service("lm")] + class ILogService : IpcService + { + public ILogService(ServiceCtx context) { } + + [Command(0)] + // Initialize(u64, pid) -> object + public ResultCode Initialize(ServiceCtx context) + { + MakeObject(context, new ILogger()); + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Lm/LogService/ILogger.cs b/Ryujinx.HLE/HOS/Services/Lm/LogService/ILogger.cs new file mode 100644 index 0000000000..a269312ebe --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Lm/LogService/ILogger.cs @@ -0,0 +1,106 @@ +using Ryujinx.Common.Logging; +using System.IO; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Lm.LogService +{ + class ILogger : IpcService + { + public ILogger() { } + + private static int ReadEncodedInt(BinaryReader reader) + { + int result = 0; + int position = 0; + + byte encoded; + + do + { + encoded = reader.ReadByte(); + + result += (encoded & 0x7F) << (7 * position); + + position++; + + } while ((encoded & 0x80) != 0); + + return result; + } + + [Command(0)] + // Log(buffer) + public ResultCode Log(ServiceCtx context) + { + (long bufPos, long bufSize) = context.Request.GetBufferType0x21(); + byte[] logBuffer = context.Memory.ReadBytes(bufPos, bufSize); + + using (MemoryStream ms = new MemoryStream(logBuffer)) + { + BinaryReader reader = new BinaryReader(ms); + + long pid = reader.ReadInt64(); + long threadContext = reader.ReadInt64(); + short flags = reader.ReadInt16(); + byte level = reader.ReadByte(); + byte verbosity = reader.ReadByte(); + int payloadLength = reader.ReadInt32(); + + StringBuilder sb = new StringBuilder(); + + sb.AppendLine("Guest log:"); + + sb.AppendLine($" Log level: {(LmLogLevel)level}"); + + while (ms.Position < ms.Length) + { + int type = ReadEncodedInt(reader); + int size = ReadEncodedInt(reader); + + LmLogField field = (LmLogField)type; + + string fieldStr = string.Empty; + + if (field == LmLogField.Start) + { + reader.ReadBytes(size); + + continue; + } + else if (field == LmLogField.Stop) + { + break; + } + else if (field == LmLogField.Line) + { + fieldStr = $"{field}: {reader.ReadInt32()}"; + } + else if (field == LmLogField.DropCount) + { + fieldStr = $"{field}: {reader.ReadInt64()}"; + } + else if (field == LmLogField.Time) + { + fieldStr = $"{field}: {reader.ReadInt64()}s"; + } + else if (field < LmLogField.Count) + { + fieldStr = $"{field}: '{Encoding.UTF8.GetString(reader.ReadBytes(size)).TrimEnd()}'"; + } + else + { + fieldStr = $"Field{field}: '{Encoding.UTF8.GetString(reader.ReadBytes(size)).TrimEnd()}'"; + } + + sb.AppendLine(" " + fieldStr); + } + + string text = sb.ToString(); + + Logger.PrintGuest(LogClass.ServiceLm, text); + } + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Lm/LogService/Types/LmLogField.cs b/Ryujinx.HLE/HOS/Services/Lm/LogService/Types/LmLogField.cs new file mode 100644 index 0000000000..3f93e16761 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Lm/LogService/Types/LmLogField.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.HLE.HOS.Services.Lm.LogService +{ + enum LmLogField + { + Start = 0, + Stop = 1, + Message = 2, + Line = 3, + Filename = 4, + Function = 5, + Module = 6, + Thread = 7, + DropCount = 8, + Time = 9, + ProgramName = 10, + Count + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Lm/LmLogLevel.cs b/Ryujinx.HLE/HOS/Services/Lm/LogService/Types/LmLogLevel.cs similarity index 69% rename from Ryujinx.HLE/OsHle/Services/Lm/LmLogLevel.cs rename to Ryujinx.HLE/HOS/Services/Lm/LogService/Types/LmLogLevel.cs index d051a595a9..ee1a8396ab 100644 --- a/Ryujinx.HLE/OsHle/Services/Lm/LmLogLevel.cs +++ b/Ryujinx.HLE/HOS/Services/Lm/LogService/Types/LmLogLevel.cs @@ -1,4 +1,4 @@ -namespace Ryujinx.HLE.OsHle.Services.Lm +namespace Ryujinx.HLE.HOS.Services.Lm.LogService { enum LmLogLevel { diff --git a/Ryujinx.HLE/HOS/Services/Loader/IDebugMonitorInterface.cs b/Ryujinx.HLE/HOS/Services/Loader/IDebugMonitorInterface.cs new file mode 100644 index 0000000000..82b24a35cd --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Loader/IDebugMonitorInterface.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Loader +{ + [Service("ldr:dmnt")] + class IDebugMonitorInterface : IpcService + { + public IDebugMonitorInterface(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Loader/IProcessManagerInterface.cs b/Ryujinx.HLE/HOS/Services/Loader/IProcessManagerInterface.cs new file mode 100644 index 0000000000..2ecde2ade4 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Loader/IProcessManagerInterface.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Loader +{ + [Service("ldr:pm")] + class IProcessManagerInterface : IpcService + { + public IProcessManagerInterface(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Loader/IShellInterface.cs b/Ryujinx.HLE/HOS/Services/Loader/IShellInterface.cs new file mode 100644 index 0000000000..362f82f030 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Loader/IShellInterface.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Loader +{ + [Service("ldr:shel")] + class IShellInterface : IpcService + { + public IShellInterface(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Loader/ResultCode.cs b/Ryujinx.HLE/HOS/Services/Loader/ResultCode.cs new file mode 100644 index 0000000000..35fd4dcb54 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Loader/ResultCode.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Services.Loader +{ + enum ResultCode + { + ModuleId = 9, + ErrorCodeShift = 9, + + Success = 0, + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Mig/IService.cs b/Ryujinx.HLE/HOS/Services/Mig/IService.cs new file mode 100644 index 0000000000..2f6eb99eeb --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Mig/IService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Mig +{ + [Service("mig:usr")] // 4.0.0+ + class IService : IpcService + { + public IService(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Mm/IRequest.cs b/Ryujinx.HLE/HOS/Services/Mm/IRequest.cs new file mode 100644 index 0000000000..cdd4729550 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Mm/IRequest.cs @@ -0,0 +1,101 @@ +using Ryujinx.Common.Logging; + +namespace Ryujinx.HLE.HOS.Services.Mm +{ + [Service("mm:u")] + class IRequest : IpcService + { + public IRequest(ServiceCtx context) { } + + [Command(0)] + // InitializeOld(u32, u32, u32) + public ResultCode InitializeOld(ServiceCtx context) + { + int unknown0 = context.RequestData.ReadInt32(); + int unknown1 = context.RequestData.ReadInt32(); + int unknown2 = context.RequestData.ReadInt32(); + + Logger.PrintStub(LogClass.ServiceMm, new { unknown0, unknown1, unknown2 }); + + return ResultCode.Success; + } + + [Command(1)] + // FinalizeOld(u32) + public ResultCode FinalizeOld(ServiceCtx context) + { + Logger.PrintStub(LogClass.ServiceMm); + + return ResultCode.Success; + } + + [Command(2)] + // SetAndWaitOld(u32, u32, u32) + public ResultCode SetAndWaitOld(ServiceCtx context) + { + int unknown0 = context.RequestData.ReadInt32(); + int unknown1 = context.RequestData.ReadInt32(); + int unknown2 = context.RequestData.ReadInt32(); + + Logger.PrintStub(LogClass.ServiceMm, new { unknown0, unknown1, unknown2 }); + return ResultCode.Success; + } + + [Command(3)] + // GetOld(u32) -> u32 + public ResultCode GetOld(ServiceCtx context) + { + int unknown0 = context.RequestData.ReadInt32(); + + Logger.PrintStub(LogClass.ServiceMm, new { unknown0 }); + + context.ResponseData.Write(0); + + return ResultCode.Success; + } + + [Command(4)] + // Initialize() + public ResultCode Initialize(ServiceCtx context) + { + Logger.PrintStub(LogClass.ServiceMm); + + return ResultCode.Success; + } + + [Command(5)] + // Finalize(u32) + public ResultCode Finalize(ServiceCtx context) + { + Logger.PrintStub(LogClass.ServiceMm); + + return ResultCode.Success; + } + + [Command(6)] + // SetAndWait(u32, u32, u32) + public ResultCode SetAndWait(ServiceCtx context) + { + int unknown0 = context.RequestData.ReadInt32(); + int unknown1 = context.RequestData.ReadInt32(); + int unknown2 = context.RequestData.ReadInt32(); + + Logger.PrintStub(LogClass.ServiceMm, new { unknown0, unknown1, unknown2 }); + + return ResultCode.Success; + } + + [Command(7)] + // Get(u32) -> u32 + public ResultCode Get(ServiceCtx context) + { + int unknown0 = context.RequestData.ReadInt32(); + + Logger.PrintStub(LogClass.ServiceMm, new { unknown0 }); + + context.ResponseData.Write(0); + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Ncm/IContentManager.cs b/Ryujinx.HLE/HOS/Services/Ncm/IContentManager.cs new file mode 100644 index 0000000000..7f05d9bedd --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Ncm/IContentManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Ncm +{ + [Service("ncm")] + class IContentManager : IpcService + { + public IContentManager(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Ncm/Lr/ILocationResolverManager.cs b/Ryujinx.HLE/HOS/Services/Ncm/Lr/ILocationResolverManager.cs new file mode 100644 index 0000000000..44b8272b13 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Ncm/Lr/ILocationResolverManager.cs @@ -0,0 +1,22 @@ +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.HOS.Services.Ncm.Lr.LocationResolverManager; + +namespace Ryujinx.HLE.HOS.Services.Ncm.Lr +{ + [Service("lr")] + class ILocationResolverManager : IpcService + { + public ILocationResolverManager(ServiceCtx context) { } + + [Command(0)] + // OpenLocationResolver() + public ResultCode OpenLocationResolver(ServiceCtx context) + { + StorageId storageId = (StorageId)context.RequestData.ReadByte(); + + MakeObject(context, new ILocationResolver(storageId)); + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Ncm/Lr/LocationResolverManager/ILocationResolver.cs b/Ryujinx.HLE/HOS/Services/Ncm/Lr/LocationResolverManager/ILocationResolver.cs new file mode 100644 index 0000000000..88ba45e0aa --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Ncm/Lr/LocationResolverManager/ILocationResolver.cs @@ -0,0 +1,254 @@ +using LibHac.FsSystem.NcaUtils; +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.FileSystem.Content; +using System.Text; + +using static Ryujinx.HLE.Utilities.StringUtils; + +namespace Ryujinx.HLE.HOS.Services.Ncm.Lr.LocationResolverManager +{ + class ILocationResolver : IpcService + { + private StorageId _storageId; + + public ILocationResolver(StorageId storageId) + { + _storageId = storageId; + } + + [Command(0)] + // ResolveProgramPath() + public ResultCode ResolveProgramPath(ServiceCtx context) + { + long titleId = context.RequestData.ReadInt64(); + + if (ResolvePath(context, titleId, NcaContentType.Program)) + { + return ResultCode.Success; + } + else + { + return ResultCode.ProgramLocationEntryNotFound; + } + } + + [Command(1)] + // RedirectProgramPath() + public ResultCode RedirectProgramPath(ServiceCtx context) + { + long titleId = context.RequestData.ReadInt64(); + + RedirectPath(context, titleId, 0, NcaContentType.Program); + + return ResultCode.Success; + } + + [Command(2)] + // ResolveApplicationControlPath() + public ResultCode ResolveApplicationControlPath(ServiceCtx context) + { + long titleId = context.RequestData.ReadInt64(); + + if (ResolvePath(context, titleId, NcaContentType.Control)) + { + return ResultCode.Success; + } + else + { + return ResultCode.AccessDenied; + } + } + + [Command(3)] + // ResolveApplicationHtmlDocumentPath() + public ResultCode ResolveApplicationHtmlDocumentPath(ServiceCtx context) + { + long titleId = context.RequestData.ReadInt64(); + + if (ResolvePath(context, titleId, NcaContentType.Manual)) + { + return ResultCode.Success; + } + else + { + return ResultCode.AccessDenied; + } + } + + [Command(4)] + // ResolveDataPath() + public ResultCode ResolveDataPath(ServiceCtx context) + { + long titleId = context.RequestData.ReadInt64(); + + if (ResolvePath(context, titleId, NcaContentType.Data) || ResolvePath(context, titleId, NcaContentType.PublicData)) + { + return ResultCode.Success; + } + else + { + return ResultCode.AccessDenied; + } + } + + [Command(5)] + // RedirectApplicationControlPath() + public ResultCode RedirectApplicationControlPath(ServiceCtx context) + { + long titleId = context.RequestData.ReadInt64(); + + RedirectPath(context, titleId, 1, NcaContentType.Control); + + return ResultCode.Success; + } + + [Command(6)] + // RedirectApplicationHtmlDocumentPath() + public ResultCode RedirectApplicationHtmlDocumentPath(ServiceCtx context) + { + long titleId = context.RequestData.ReadInt64(); + + RedirectPath(context, titleId, 1, NcaContentType.Manual); + + return ResultCode.Success; + } + + [Command(7)] + // ResolveApplicationLegalInformationPath() + public ResultCode ResolveApplicationLegalInformationPath(ServiceCtx context) + { + long titleId = context.RequestData.ReadInt64(); + + if (ResolvePath(context, titleId, NcaContentType.Manual)) + { + return ResultCode.Success; + } + else + { + return ResultCode.AccessDenied; + } + } + + [Command(8)] + // RedirectApplicationLegalInformationPath() + public ResultCode RedirectApplicationLegalInformationPath(ServiceCtx context) + { + long titleId = context.RequestData.ReadInt64(); + + RedirectPath(context, titleId, 1, NcaContentType.Manual); + + return ResultCode.Success; + } + + [Command(9)] + // Refresh() + public ResultCode Refresh(ServiceCtx context) + { + context.Device.System.ContentManager.RefreshEntries(_storageId, 1); + + return ResultCode.Success; + } + + [Command(10)] + // SetProgramNcaPath2() + public ResultCode SetProgramNcaPath2(ServiceCtx context) + { + long titleId = context.RequestData.ReadInt64(); + + RedirectPath(context, titleId, 1, NcaContentType.Program); + + return ResultCode.Success; + } + + [Command(11)] + // ClearLocationResolver2() + public ResultCode ClearLocationResolver2(ServiceCtx context) + { + context.Device.System.ContentManager.RefreshEntries(_storageId, 1); + + return ResultCode.Success; + } + + [Command(12)] + // DeleteProgramNcaPath() + public ResultCode DeleteProgramNcaPath(ServiceCtx context) + { + long titleId = context.RequestData.ReadInt64(); + + DeleteContentPath(context, titleId, NcaContentType.Program); + + return ResultCode.Success; + } + + [Command(13)] + // DeleteControlNcaPath() + public ResultCode DeleteControlNcaPath(ServiceCtx context) + { + long titleId = context.RequestData.ReadInt64(); + + DeleteContentPath(context, titleId, NcaContentType.Control); + + return ResultCode.Success; + } + + [Command(14)] + // DeleteDocHtmlNcaPath() + public ResultCode DeleteDocHtmlNcaPath(ServiceCtx context) + { + long titleId = context.RequestData.ReadInt64(); + + DeleteContentPath(context, titleId, NcaContentType.Manual); + + return ResultCode.Success; + } + + [Command(15)] + // DeleteInfoHtmlNcaPath() + public ResultCode DeleteInfoHtmlNcaPath(ServiceCtx context) + { + long titleId = context.RequestData.ReadInt64(); + + DeleteContentPath(context, titleId, NcaContentType.Manual); + + return ResultCode.Success; + } + + private void RedirectPath(ServiceCtx context, long titleId, int flag, NcaContentType contentType) + { + string contentPath = ReadUtf8String(context); + LocationEntry newLocation = new LocationEntry(contentPath, flag, titleId, contentType); + + context.Device.System.ContentManager.RedirectLocation(newLocation, _storageId); + } + + private bool ResolvePath(ServiceCtx context, long titleId, NcaContentType contentType) + { + ContentManager contentManager = context.Device.System.ContentManager; + string contentPath = contentManager.GetInstalledContentPath(titleId, _storageId, NcaContentType.Program); + + if (!string.IsNullOrWhiteSpace(contentPath)) + { + long position = context.Request.RecvListBuff[0].Position; + long size = context.Request.RecvListBuff[0].Size; + + byte[] contentPathBuffer = Encoding.UTF8.GetBytes(contentPath); + + context.Memory.WriteBytes(position, contentPathBuffer); + } + else + { + return false; + } + + return true; + } + + private void DeleteContentPath(ServiceCtx context, long titleId, NcaContentType contentType) + { + ContentManager contentManager = context.Device.System.ContentManager; + string contentPath = contentManager.GetInstalledContentPath(titleId, _storageId, NcaContentType.Manual); + + contentManager.ClearEntry(titleId, NcaContentType.Manual, _storageId); + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Ncm/Lr/ResultCode.cs b/Ryujinx.HLE/HOS/Services/Ncm/Lr/ResultCode.cs new file mode 100644 index 0000000000..5ff7e46621 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Ncm/Lr/ResultCode.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.HLE.HOS.Services.Ncm.Lr +{ + enum ResultCode + { + ModuleId = 8, + ErrorCodeShift = 9, + + Success = 0, + + ProgramLocationEntryNotFound = (2 << ErrorCodeShift) | ModuleId, + AccessDenied = (5 << ErrorCodeShift) | ModuleId + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/News/IServiceCreator.cs b/Ryujinx.HLE/HOS/Services/News/IServiceCreator.cs new file mode 100644 index 0000000000..7ea89b20d0 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/News/IServiceCreator.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.HLE.HOS.Services.News +{ + [Service("news:a")] + [Service("news:c")] + [Service("news:m")] + [Service("news:p")] + [Service("news:v")] + class IServiceCreator : IpcService + { + public IServiceCreator(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Nfc/IAmManager.cs b/Ryujinx.HLE/HOS/Services/Nfc/IAmManager.cs new file mode 100644 index 0000000000..33932568df --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nfc/IAmManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Nfc +{ + [Service("nfc:am")] + class IAmManager : IpcService + { + public IAmManager(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Nfc/ISystemManager.cs b/Ryujinx.HLE/HOS/Services/Nfc/ISystemManager.cs new file mode 100644 index 0000000000..0bab0b79a4 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nfc/ISystemManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Nfc +{ + [Service("nfc:sys")] + class ISystemManager : IpcService + { + public ISystemManager(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Nfc/IUserManager.cs b/Ryujinx.HLE/HOS/Services/Nfc/IUserManager.cs new file mode 100644 index 0000000000..048adf8ce7 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nfc/IUserManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Nfc +{ + [Service("nfc:user")] + class IUserManager : IpcService + { + public IUserManager(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Nfc/Mifare/IUserManager.cs b/Ryujinx.HLE/HOS/Services/Nfc/Mifare/IUserManager.cs new file mode 100644 index 0000000000..cc3cd3aa90 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nfc/Mifare/IUserManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare +{ + [Service("nfc:mf:u")] + class IUserManager : IpcService + { + public IUserManager(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Nfc/Nfp/IDebugManager.cs b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/IDebugManager.cs new file mode 100644 index 0000000000..c5da8da9a1 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/IDebugManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp +{ + [Service("nfp:dbg")] + class IAmManager : IpcService + { + public IAmManager(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Nfc/Nfp/ISystemManager.cs b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/ISystemManager.cs new file mode 100644 index 0000000000..78ea4896c5 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/ISystemManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp +{ + [Service("nfp:sys")] + class ISystemManager : IpcService + { + public ISystemManager(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Nfc/Nfp/IUserManager.cs b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/IUserManager.cs new file mode 100644 index 0000000000..f743968666 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/IUserManager.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp +{ + [Service("nfp:user")] + class IUserManager : IpcService + { + public IUserManager(ServiceCtx context) { } + + [Command(0)] + // CreateUserInterface() -> object + public ResultCode GetUserInterface(ServiceCtx context) + { + MakeObject(context, new IUser()); + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Nfc/Nfp/ResultCode.cs b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/ResultCode.cs new file mode 100644 index 0000000000..b42a28a91b --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/ResultCode.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp +{ + public enum ResultCode + { + ModuleId = 115, + ErrorCodeShift = 9, + + Success = 0, + + DeviceNotFound = (64 << ErrorCodeShift) | ModuleId, + DevicesBufferIsNull = (65 << ErrorCodeShift) | ModuleId + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/IUser.cs b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/IUser.cs new file mode 100644 index 0000000000..d26b4eb943 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/IUser.cs @@ -0,0 +1,338 @@ +using Ryujinx.HLE.Exceptions; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Hid; +using Ryujinx.HLE.HOS.Services.Hid.HidServer; +using Ryujinx.HLE.HOS.Services.Nfc.Nfp.UserManager; +using System; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp +{ + class IUser : IpcService + { + private State _state = State.NonInitialized; + + private KEvent _availabilityChangeEvent; + private int _availabilityChangeEventHandle = 0; + + private List _devices = new List(); + + public IUser() { } + + [Command(0)] + // Initialize(u64, u64, pid, buffer) + public ResultCode Initialize(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + long mcuVersionData = context.RequestData.ReadInt64(); + + long inputPosition = context.Request.SendBuff[0].Position; + long inputSize = context.Request.SendBuff[0].Size; + + byte[] unknownBuffer = context.Memory.ReadBytes(inputPosition, inputSize); + + // NOTE: appletResourceUserId, mcuVersionData and the buffer are stored inside an internal struct. + // The buffer seems to contains entries with a size of 0x40 bytes each. + // Sadly, this internal struct doesn't seems to be used in retail. + + // TODO: Add an instance of nn::nfc::server::Manager when it will be implemented. + // Add an instance of nn::nfc::server::SaveData when it will be implemented. + + // TODO: When we will be able to add multiple controllers add one entry by controller here. + Device device1 = new Device + { + NpadIdType = HidNpadIdType.Player1, + Handle = HidUtils.GetIndexFromNpadIdType(HidNpadIdType.Player1), + State = DeviceState.Initialized + }; + + _devices.Add(device1); + + _state = State.Initialized; + + return ResultCode.Success; + } + + [Command(1)] + // Finalize() + public ResultCode Finalize(ServiceCtx context) + { + // TODO: Call StopDetection() and Unmount() when they will be implemented. + // Remove the instance of nn::nfc::server::Manager when it will be implemented. + // Remove the instance of nn::nfc::server::SaveData when it will be implemented. + + _devices.Clear(); + + _state = State.NonInitialized; + + return ResultCode.Success; + } + + [Command(2)] + // ListDevices() -> (u32, buffer) + public ResultCode ListDevices(ServiceCtx context) + { + if (context.Request.RecvListBuff.Count == 0) + { + return ResultCode.DevicesBufferIsNull; + } + + long outputPosition = context.Request.RecvListBuff[0].Position; + long outputSize = context.Request.RecvListBuff[0].Size; + + if (_devices.Count == 0) + { + return ResultCode.DeviceNotFound; + } + + for (int i = 0; i < _devices.Count; i++) + { + context.Memory.WriteUInt32(outputPosition + (i * sizeof(long)), (uint)_devices[i].Handle); + } + + context.ResponseData.Write(_devices.Count); + + return ResultCode.Success; + } + + [Command(3)] + // StartDetection(bytes<8, 4>) + public ResultCode StartDetection(ServiceCtx context) + { + throw new ServiceNotImplementedException(context); + } + + [Command(4)] + // StopDetection(bytes<8, 4>) + public ResultCode StopDetection(ServiceCtx context) + { + throw new ServiceNotImplementedException(context); + } + + [Command(5)] + // Mount(bytes<8, 4>, u32, u32) + public ResultCode Mount(ServiceCtx context) + { + throw new ServiceNotImplementedException(context); + } + + [Command(6)] + // Unmount(bytes<8, 4>) + public ResultCode Unmount(ServiceCtx context) + { + throw new ServiceNotImplementedException(context); + } + + [Command(7)] + // OpenApplicationArea(bytes<8, 4>, u32) + public ResultCode OpenApplicationArea(ServiceCtx context) + { + throw new ServiceNotImplementedException(context); + } + + [Command(8)] + // GetApplicationArea(bytes<8, 4>) -> (u32, buffer) + public ResultCode GetApplicationArea(ServiceCtx context) + { + throw new ServiceNotImplementedException(context); + } + + [Command(9)] + // SetApplicationArea(bytes<8, 4>, buffer) + public ResultCode SetApplicationArea(ServiceCtx context) + { + throw new ServiceNotImplementedException(context); + } + + [Command(10)] + // Flush(bytes<8, 4>) + public ResultCode Flush(ServiceCtx context) + { + throw new ServiceNotImplementedException(context); + } + + [Command(11)] + // Restore(bytes<8, 4>) + public ResultCode Restore(ServiceCtx context) + { + throw new ServiceNotImplementedException(context); + } + + [Command(12)] + // CreateApplicationArea(bytes<8, 4>, u32, buffer) + public ResultCode CreateApplicationArea(ServiceCtx context) + { + throw new ServiceNotImplementedException(context); + } + + [Command(13)] + // GetTagInfo(bytes<8, 4>) -> buffer, 0x1a> + public ResultCode GetTagInfo(ServiceCtx context) + { + throw new ServiceNotImplementedException(context); + } + + [Command(14)] + // GetRegisterInfo(bytes<8, 4>) -> buffer, 0x1a> + public ResultCode GetRegisterInfo(ServiceCtx context) + { + throw new ServiceNotImplementedException(context); + } + + [Command(15)] + // GetCommonInfo(bytes<8, 4>) -> buffer, 0x1a> + public ResultCode GetCommonInfo(ServiceCtx context) + { + throw new ServiceNotImplementedException(context); + } + + [Command(16)] + // GetModelInfo(bytes<8, 4>) -> buffer, 0x1a> + public ResultCode GetModelInfo(ServiceCtx context) + { + throw new ServiceNotImplementedException(context); + } + + [Command(17)] + // AttachActivateEvent(bytes<8, 4>) -> handle + public ResultCode AttachActivateEvent(ServiceCtx context) + { + uint deviceHandle = context.RequestData.ReadUInt32(); + + for (int i = 0; i < _devices.Count; i++) + { + if ((uint)_devices[i].Handle == deviceHandle) + { + if (_devices[i].ActivateEventHandle == 0) + { + _devices[i].ActivateEvent = new KEvent(context.Device.System); + + if (context.Process.HandleTable.GenerateHandle(_devices[i].ActivateEvent.ReadableEvent, out _devices[i].ActivateEventHandle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_devices[i].ActivateEventHandle); + + return ResultCode.Success; + } + } + + return ResultCode.DeviceNotFound; + } + + [Command(18)] + // AttachDeactivateEvent(bytes<8, 4>) -> handle + public ResultCode AttachDeactivateEvent(ServiceCtx context) + { + uint deviceHandle = context.RequestData.ReadUInt32(); + + for (int i = 0; i < _devices.Count; i++) + { + if ((uint)_devices[i].Handle == deviceHandle) + { + if (_devices[i].DeactivateEventHandle == 0) + { + _devices[i].DeactivateEvent = new KEvent(context.Device.System); + + if (context.Process.HandleTable.GenerateHandle(_devices[i].DeactivateEvent.ReadableEvent, out _devices[i].DeactivateEventHandle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_devices[i].DeactivateEventHandle); + + return ResultCode.Success; + } + } + + return ResultCode.DeviceNotFound; + } + + [Command(19)] + // GetState() -> u32 + public ResultCode GetState(ServiceCtx context) + { + context.ResponseData.Write((int)_state); + + return ResultCode.Success; + } + + [Command(20)] + // GetDeviceState(bytes<8, 4>) -> u32 + public ResultCode GetDeviceState(ServiceCtx context) + { + uint deviceHandle = context.RequestData.ReadUInt32(); + + for (int i = 0; i < _devices.Count; i++) + { + if ((uint)_devices[i].Handle == deviceHandle) + { + context.ResponseData.Write((uint)_devices[i].State); + + return ResultCode.Success; + } + } + + context.ResponseData.Write((uint)DeviceState.Unavailable); + + return ResultCode.DeviceNotFound; + } + + [Command(21)] + // GetNpadId(bytes<8, 4>) -> u32 + public ResultCode GetNpadId(ServiceCtx context) + { + uint deviceHandle = context.RequestData.ReadUInt32(); + + for (int i = 0; i < _devices.Count; i++) + { + if ((uint)_devices[i].Handle == deviceHandle) + { + context.ResponseData.Write((uint)HidUtils.GetNpadIdTypeFromIndex(_devices[i].Handle)); + + return ResultCode.Success; + } + } + + return ResultCode.DeviceNotFound; + } + + [Command(22)] + // GetApplicationAreaSize(bytes<8, 4>) -> u32 + public ResultCode GetApplicationAreaSize(ServiceCtx context) + { + throw new ServiceNotImplementedException(context); + } + + [Command(23)] // 3.0.0+ + // AttachAvailabilityChangeEvent() -> handle + public ResultCode AttachAvailabilityChangeEvent(ServiceCtx context) + { + if (_availabilityChangeEventHandle == 0) + { + _availabilityChangeEvent = new KEvent(context.Device.System); + + if (context.Process.HandleTable.GenerateHandle(_availabilityChangeEvent.ReadableEvent, out _availabilityChangeEventHandle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_availabilityChangeEventHandle); + + return ResultCode.Success; + } + + [Command(24)] // 3.0.0+ + // RecreateApplicationArea(bytes<8, 4>, u32, buffer) + public ResultCode RecreateApplicationArea(ServiceCtx context) + { + throw new ServiceNotImplementedException(context); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/Device.cs b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/Device.cs new file mode 100644 index 0000000000..40e7c8808b --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/Device.cs @@ -0,0 +1,20 @@ +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Hid; +using Ryujinx.HLE.Input; + +namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.UserManager +{ + class Device + { + public KEvent ActivateEvent; + public int ActivateEventHandle; + + public KEvent DeactivateEvent; + public int DeactivateEventHandle; + + public DeviceState State = DeviceState.Unavailable; + + public ControllerId Handle; + public HidNpadIdType NpadIdType; + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/DeviceState.cs b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/DeviceState.cs new file mode 100644 index 0000000000..7e37349411 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/DeviceState.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.UserManager +{ + enum DeviceState + { + Initialized = 0, + SearchingForTag = 1, + TagFound = 2, + TagRemoved = 3, + TagMounted = 4, + Unavailable = 5, + Finalized = 6 + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/State.cs b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/State.cs new file mode 100644 index 0000000000..8d141f0b78 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/State.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.UserManager +{ + enum State + { + NonInitialized = 0, + Initialized = 1 + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Ngct/IUnknown1.cs b/Ryujinx.HLE/HOS/Services/Ngct/IUnknown1.cs new file mode 100644 index 0000000000..2baec58561 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Ngct/IUnknown1.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Ngct +{ + [Service("ngct:s")] // 9.0.0+ + [Service("ngct:u")] // 9.0.0+ + class IUnknown1 : IpcService + { + public IUnknown1(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Nifm/IStaticService.cs b/Ryujinx.HLE/HOS/Services/Nifm/IStaticService.cs new file mode 100644 index 0000000000..0cf6a43c42 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nifm/IStaticService.cs @@ -0,0 +1,30 @@ +using Ryujinx.HLE.HOS.Services.Nifm.StaticService; + +namespace Ryujinx.HLE.HOS.Services.Nifm +{ + [Service("nifm:a")] // Max sessions: 2 + [Service("nifm:s")] // Max sessions: 16 + [Service("nifm:u")] // Max sessions: 5 + class IStaticService : IpcService + { + public IStaticService(ServiceCtx context) { } + + [Command(4)] + // CreateGeneralServiceOld() -> object + public ResultCode CreateGeneralServiceOld(ServiceCtx context) + { + MakeObject(context, new IGeneralService()); + + return ResultCode.Success; + } + + [Command(5)] // 3.0.0+ + // CreateGeneralService(u64, pid) -> object + public ResultCode CreateGeneralService(ServiceCtx context) + { + MakeObject(context, new IGeneralService()); + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Nifm/ResultCode.cs b/Ryujinx.HLE/HOS/Services/Nifm/ResultCode.cs new file mode 100644 index 0000000000..c661ee81f2 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nifm/ResultCode.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.HLE.HOS.Services.Nifm +{ + enum ResultCode + { + ModuleId = 110, + ErrorCodeShift = 9, + + Success = 0, + + NoInternetConnection = (300 << ErrorCodeShift) | ModuleId, + ObjectIsNull = (350 << ErrorCodeShift) | ModuleId + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Nifm/StaticService/GeneralService/GeneralServiceManager.cs b/Ryujinx.HLE/HOS/Services/Nifm/StaticService/GeneralService/GeneralServiceManager.cs new file mode 100644 index 0000000000..bbb218bbea --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nifm/StaticService/GeneralService/GeneralServiceManager.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService.GeneralService +{ + static class GeneralServiceManager + { + private static List _generalServices = new List(); + + public static int Count + { + get => _generalServices.Count; + } + + public static void Add(GeneralServiceDetail generalServiceDetail) + { + _generalServices.Add(generalServiceDetail); + } + + public static void Remove(int index) + { + _generalServices.RemoveAt(index); + } + + public static GeneralServiceDetail Get(int clientId) + { + return _generalServices.First(item => item.ClientId == clientId); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Nifm/StaticService/GeneralService/Types/GeneralServiceDetail.cs b/Ryujinx.HLE/HOS/Services/Nifm/StaticService/GeneralService/Types/GeneralServiceDetail.cs new file mode 100644 index 0000000000..3cf55345ca --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nifm/StaticService/GeneralService/Types/GeneralServiceDetail.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService.GeneralService +{ + class GeneralServiceDetail + { + public int ClientId; + public bool IsAnyInternetRequestAccepted; + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Nifm/StaticService/IGeneralService.cs b/Ryujinx.HLE/HOS/Services/Nifm/StaticService/IGeneralService.cs new file mode 100644 index 0000000000..c1642c3f57 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nifm/StaticService/IGeneralService.cs @@ -0,0 +1,116 @@ +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Services.Nifm.StaticService.GeneralService; +using Ryujinx.HLE.HOS.Services.Nifm.StaticService.Types; +using System; +using System.Linq; +using System.Net; +using System.Net.NetworkInformation; +using System.Net.Sockets; + +namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService +{ + class IGeneralService : IpcService, IDisposable + { + private GeneralServiceDetail _generalServiceDetail; + + public IGeneralService() + { + _generalServiceDetail = new GeneralServiceDetail + { + ClientId = GeneralServiceManager.Count, + IsAnyInternetRequestAccepted = true // NOTE: Why not accept any internet request? + }; + + GeneralServiceManager.Add(_generalServiceDetail); + } + + [Command(1)] + // GetClientId() -> buffer + public ResultCode GetClientId(ServiceCtx context) + { + long position = context.Request.RecvListBuff[0].Position; + long size = context.Request.RecvListBuff[0].Size; + + context.Memory.WriteInt32(position, _generalServiceDetail.ClientId); + + return ResultCode.Success; + } + + [Command(4)] + // CreateRequest(u32 version) -> object + public ResultCode CreateRequest(ServiceCtx context) + { + uint version = context.RequestData.ReadUInt32(); + + MakeObject(context, new IRequest(context.Device.System, version)); + + // Doesn't occur in our case. + // return ResultCode.ObjectIsNull; + + Logger.PrintStub(LogClass.ServiceNifm, new { version }); + + return ResultCode.Success; + } + + [Command(12)] + // GetCurrentIpAddress() -> nn::nifm::IpV4Address + public ResultCode GetCurrentIpAddress(ServiceCtx context) + { + if (!NetworkInterface.GetIsNetworkAvailable()) + { + return ResultCode.NoInternetConnection; + } + + IPHostEntry host = Dns.GetHostEntry(Dns.GetHostName()); + + IPAddress address = host.AddressList.FirstOrDefault(a => a.AddressFamily == AddressFamily.InterNetwork); + + context.ResponseData.Write(BitConverter.ToUInt32(address.GetAddressBytes())); + + Logger.PrintInfo(LogClass.ServiceNifm, $"Console's local IP is \"{address}\"."); + + return ResultCode.Success; + } + + [Command(18)] + // GetInternetConnectionStatus() -> nn::nifm::detail::sf::InternetConnectionStatus + public ResultCode GetInternetConnectionStatus(ServiceCtx context) + { + if (!NetworkInterface.GetIsNetworkAvailable()) + { + return ResultCode.NoInternetConnection; + } + + InternetConnectionStatus internetConnectionStatus = new InternetConnectionStatus + { + Type = InternetConnectionType.WiFi, + WifiStrength = 3, + State = InternetConnectionState.Connected, + }; + + context.ResponseData.WriteStruct(internetConnectionStatus); + + return ResultCode.Success; + } + + [Command(21)] + // IsAnyInternetRequestAccepted(buffer) -> bool + public ResultCode IsAnyInternetRequestAccepted(ServiceCtx context) + { + long position = context.Request.PtrBuff[0].Position; + long size = context.Request.PtrBuff[0].Size; + + int clientId = context.Memory.ReadInt32(position); + + context.ResponseData.Write(GeneralServiceManager.Get(clientId).IsAnyInternetRequestAccepted); + + return ResultCode.Success; + } + + public void Dispose() + { + GeneralServiceManager.Remove(_generalServiceDetail.ClientId); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Nifm/StaticService/IRequest.cs b/Ryujinx.HLE/HOS/Services/Nifm/StaticService/IRequest.cs new file mode 100644 index 0000000000..c878c2d6a8 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nifm/StaticService/IRequest.cs @@ -0,0 +1,90 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Threading; +using System; + +namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService +{ + class IRequest : IpcService + { + private KEvent _event0; + private KEvent _event1; + + private uint _version; + + public IRequest(Horizon system, uint version) + { + _event0 = new KEvent(system); + _event1 = new KEvent(system); + + _version = version; + } + + [Command(0)] + // GetRequestState() -> u32 + public ResultCode GetRequestState(ServiceCtx context) + { + context.ResponseData.Write(1); + + Logger.PrintStub(LogClass.ServiceNifm); + + return ResultCode.Success; + } + + [Command(1)] + // GetResult() + public ResultCode GetResult(ServiceCtx context) + { + Logger.PrintStub(LogClass.ServiceNifm); + + return ResultCode.Success; + } + + [Command(2)] + // GetSystemEventReadableHandles() -> (handle, handle) + public ResultCode GetSystemEventReadableHandles(ServiceCtx context) + { + if (context.Process.HandleTable.GenerateHandle(_event0.ReadableEvent, out int handle0) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + if (context.Process.HandleTable.GenerateHandle(_event1.ReadableEvent, out int handle1) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle0, handle1); + + return ResultCode.Success; + } + + [Command(3)] + // Cancel() + public ResultCode Cancel(ServiceCtx context) + { + Logger.PrintStub(LogClass.ServiceNifm); + + return ResultCode.Success; + } + + [Command(4)] + // Submit() + public ResultCode Submit(ServiceCtx context) + { + Logger.PrintStub(LogClass.ServiceNifm); + + return ResultCode.Success; + } + + [Command(11)] + // SetConnectionConfirmationOption(i8) + public ResultCode SetConnectionConfirmationOption(ServiceCtx context) + { + Logger.PrintStub(LogClass.ServiceNifm); + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/InternetConnectionState.cs b/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/InternetConnectionState.cs new file mode 100644 index 0000000000..dfb8f76cac --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/InternetConnectionState.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService.Types +{ + enum InternetConnectionState : byte + { + ConnectingType0 = 0, + ConnectingType1 = 1, + ConnectingType2 = 2, + ConnectingType3 = 3, + Connected = 4, + } +} diff --git a/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/InternetConnectionStatus.cs b/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/InternetConnectionStatus.cs new file mode 100644 index 0000000000..ff944eca21 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/InternetConnectionStatus.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct InternetConnectionStatus + { + public InternetConnectionType Type; + public byte WifiStrength; + public InternetConnectionState State; + } +} diff --git a/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/InternetConnectionType.cs b/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/InternetConnectionType.cs new file mode 100644 index 0000000000..af2bcfa181 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/InternetConnectionType.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService.Types +{ + enum InternetConnectionType : byte + { + Invalid = 0, + WiFi = 1, + Ethernet = 2, + } +} diff --git a/Ryujinx.HLE/HOS/Services/Nim/INetworkInstallManager.cs b/Ryujinx.HLE/HOS/Services/Nim/INetworkInstallManager.cs new file mode 100644 index 0000000000..ad79ca0de1 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nim/INetworkInstallManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Nim +{ + [Service("nim")] + class INetworkInstallManager : IpcService + { + public INetworkInstallManager(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Nim/IShopServiceAccessServerInterface.cs b/Ryujinx.HLE/HOS/Services/Nim/IShopServiceAccessServerInterface.cs new file mode 100644 index 0000000000..9be84393fc --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nim/IShopServiceAccessServerInterface.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Nim +{ + [Service("nim:eca")] // 5.0.0+ + class IShopServiceAccessServerInterface : IpcService + { + public IShopServiceAccessServerInterface(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Nim/IShopServiceAccessSystemInterface.cs b/Ryujinx.HLE/HOS/Services/Nim/IShopServiceAccessSystemInterface.cs new file mode 100644 index 0000000000..bf201b988f --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nim/IShopServiceAccessSystemInterface.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Nim +{ + [Service("nim:ecas")] // 7.0.0+ + class IShopServiceAccessSystemInterface : IpcService + { + public IShopServiceAccessSystemInterface(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Nim/IShopServiceManager.cs b/Ryujinx.HLE/HOS/Services/Nim/IShopServiceManager.cs new file mode 100644 index 0000000000..2420615a60 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nim/IShopServiceManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Nim +{ + [Service("nim:shp")] + class IShopServiceManager : IpcService + { + public IShopServiceManager(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Nim/Ntc/IStaticService.cs b/Ryujinx.HLE/HOS/Services/Nim/Ntc/IStaticService.cs new file mode 100644 index 0000000000..f5a3bc7b45 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nim/Ntc/IStaticService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Nim.Ntc +{ + [Service("ntc")] + class IStaticService : IpcService + { + public IStaticService(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Notification/INotificationServicesForApplication.cs b/Ryujinx.HLE/HOS/Services/Notification/INotificationServicesForApplication.cs new file mode 100644 index 0000000000..c4a35b291e --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Notification/INotificationServicesForApplication.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Notification +{ + [Service("notif:a")] // 9.0.0+ + class INotificationServicesForApplication : IpcService + { + public INotificationServicesForApplication(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Notification/INotificationServicesForSystem.cs b/Ryujinx.HLE/HOS/Services/Notification/INotificationServicesForSystem.cs new file mode 100644 index 0000000000..0939dff621 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Notification/INotificationServicesForSystem.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Notification +{ + [Service("notif:s")] // 9.0.0+ + class INotificationServicesForSystem : IpcService + { + public INotificationServicesForSystem(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Npns/INpnsSystem.cs b/Ryujinx.HLE/HOS/Services/Npns/INpnsSystem.cs new file mode 100644 index 0000000000..fd8ccfb585 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Npns/INpnsSystem.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Npns +{ + [Service("npns:s")] + class INpnsSystem : IpcService + { + public INpnsSystem(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Npns/INpnsUser.cs b/Ryujinx.HLE/HOS/Services/Npns/INpnsUser.cs new file mode 100644 index 0000000000..68e769385a --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Npns/INpnsUser.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Npns +{ + [Service("npns:u")] + class INpnsUser : IpcService + { + public INpnsUser(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Ns/IAddOnContentManager.cs b/Ryujinx.HLE/HOS/Services/Ns/IAddOnContentManager.cs new file mode 100644 index 0000000000..13d5693477 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Ns/IAddOnContentManager.cs @@ -0,0 +1,34 @@ +using Ryujinx.Common.Logging; + +namespace Ryujinx.HLE.HOS.Services.Ns +{ + [Service("aoc:u")] + class IAddOnContentManager : IpcService + { + public IAddOnContentManager(ServiceCtx context) { } + + [Command(2)] + // CountAddOnContent(u64, pid) -> u32 + public static ResultCode CountAddOnContent(ServiceCtx context) + { + context.ResponseData.Write(0); + + Logger.PrintStub(LogClass.ServiceNs); + + return ResultCode.Success; + } + + [Command(3)] + // ListAddOnContent(u32, u32, u64, pid) -> (u32, buffer) + public static ResultCode ListAddOnContent(ServiceCtx context) + { + Logger.PrintStub(LogClass.ServiceNs); + + // TODO: This is supposed to write a u32 array aswell. + // It's unknown what it contains. + context.ResponseData.Write(0); + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Ns/IApplicationManagerInterface.cs b/Ryujinx.HLE/HOS/Services/Ns/IApplicationManagerInterface.cs new file mode 100644 index 0000000000..e185233be5 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Ns/IApplicationManagerInterface.cs @@ -0,0 +1,24 @@ +namespace Ryujinx.HLE.HOS.Services.Ns +{ + [Service("ns:am")] + class IApplicationManagerInterface : IpcService + { + public IApplicationManagerInterface(ServiceCtx context) { } + + [Command(400)] + // GetApplicationControlData(u8, u64) -> (unknown<4>, buffer) + public ResultCode GetApplicationControlData(ServiceCtx context) + { + byte source = (byte)context.RequestData.ReadInt64(); + ulong titleId = (byte)context.RequestData.ReadUInt64(); + + long position = context.Request.ReceiveBuff[0].Position; + + byte[] nacpData = context.Device.System.ControlData.ByteSpan.ToArray(); + + context.Memory.WriteBytes(position, nacpData); + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Ns/IDevelopInterface.cs b/Ryujinx.HLE/HOS/Services/Ns/IDevelopInterface.cs new file mode 100644 index 0000000000..c74ebd691d --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Ns/IDevelopInterface.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Ns +{ + [Service("ns:dev")] + class IDevelopInterface : IpcService + { + public IDevelopInterface(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Ns/IServiceGetterInterface.cs b/Ryujinx.HLE/HOS/Services/Ns/IServiceGetterInterface.cs new file mode 100644 index 0000000000..7133166723 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Ns/IServiceGetterInterface.cs @@ -0,0 +1,21 @@ +namespace Ryujinx.HLE.HOS.Services.Ns +{ + [Service("ns:am2")] + [Service("ns:ec")] + [Service("ns:rid")] + [Service("ns:rt")] + [Service("ns:web")] + class IServiceGetterInterface : IpcService + { + public IServiceGetterInterface(ServiceCtx context) { } + + [Command(7996)] + // GetApplicationManagerInterface() -> object + public ResultCode GetApplicationManagerInterface(ServiceCtx context) + { + MakeObject(context, new IApplicationManagerInterface(context)); + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Ns/ISystemUpdateInterface.cs b/Ryujinx.HLE/HOS/Services/Ns/ISystemUpdateInterface.cs new file mode 100644 index 0000000000..84ed3d0f01 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Ns/ISystemUpdateInterface.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Ns +{ + [Service("ns:su")] + class ISystemUpdateInterface : IpcService + { + public ISystemUpdateInterface(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Ns/IVulnerabilityManagerInterface.cs b/Ryujinx.HLE/HOS/Services/Ns/IVulnerabilityManagerInterface.cs new file mode 100644 index 0000000000..0b6409920a --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Ns/IVulnerabilityManagerInterface.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Ns +{ + [Service("ns:vm")] + class IVulnerabilityManagerInterface : IpcService + { + public IVulnerabilityManagerInterface(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Nv/INvDrvDebugFSServices.cs b/Ryujinx.HLE/HOS/Services/Nv/INvDrvDebugFSServices.cs new file mode 100644 index 0000000000..dffe8783d5 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nv/INvDrvDebugFSServices.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Nv +{ + [Service("nvdrvdbg")] + class INvDrvDebugFSServices : IpcService + { + public INvDrvDebugFSServices(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs b/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs new file mode 100644 index 0000000000..a227d8924c --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs @@ -0,0 +1,523 @@ +using ARMeilleure.Memory; +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.Exceptions; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Memory; +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap; +using Ryujinx.HLE.HOS.Services.Nv.Types; +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace Ryujinx.HLE.HOS.Services.Nv +{ + [Service("nvdrv")] + [Service("nvdrv:a")] + [Service("nvdrv:s")] + [Service("nvdrv:t")] + class INvDrvServices : IpcService + { + private static Dictionary _deviceFileRegistry = + new Dictionary() + { + { "/dev/nvmap", typeof(NvMapDeviceFile) }, + { "/dev/nvhost-ctrl", typeof(NvHostCtrlDeviceFile) }, + { "/dev/nvhost-ctrl-gpu", typeof(NvHostCtrlGpuDeviceFile) }, + { "/dev/nvhost-as-gpu", typeof(NvHostAsGpuDeviceFile) }, + { "/dev/nvhost-gpu", typeof(NvHostGpuDeviceFile) }, + //{ "/dev/nvhost-msenc", typeof(NvHostChannelDeviceFile) }, + { "/dev/nvhost-nvdec", typeof(NvHostChannelDeviceFile) }, + //{ "/dev/nvhost-nvjpg", typeof(NvHostChannelDeviceFile) }, + { "/dev/nvhost-vic", typeof(NvHostChannelDeviceFile) }, + //{ "/dev/nvhost-display", typeof(NvHostChannelDeviceFile) }, + }; + + private static IdDictionary _deviceFileIdRegistry = new IdDictionary(); + + private KProcess _owner; + + public INvDrvServices(ServiceCtx context) + { + _owner = null; + } + + private int Open(ServiceCtx context, string path) + { + if (context.Process == _owner) + { + if (_deviceFileRegistry.TryGetValue(path, out Type deviceFileClass)) + { + ConstructorInfo constructor = deviceFileClass.GetConstructor(new Type[] { typeof(ServiceCtx) }); + + NvDeviceFile deviceFile = (NvDeviceFile)constructor.Invoke(new object[] { context }); + + return _deviceFileIdRegistry.Add(deviceFile); + } + else + { + Logger.PrintWarning(LogClass.ServiceNv, $"Cannot find file device \"{path}\"!"); + } + } + + return -1; + } + + private NvResult GetIoctlArgument(ServiceCtx context, NvIoctl ioctlCommand, out Span arguments) + { + (long inputDataPosition, long inputDataSize) = context.Request.GetBufferType0x21(0); + (long outputDataPosition, long outputDataSize) = context.Request.GetBufferType0x22(0); + + NvIoctl.Direction ioctlDirection = ioctlCommand.DirectionValue; + uint ioctlSize = ioctlCommand.Size; + + bool isRead = (ioctlDirection & NvIoctl.Direction.Read) != 0; + bool isWrite = (ioctlDirection & NvIoctl.Direction.Write) != 0; + + if ((isWrite && ioctlSize > outputDataSize) || (isRead && ioctlSize > inputDataSize)) + { + arguments = null; + + Logger.PrintWarning(LogClass.ServiceNv, "Ioctl size inconsistency found!"); + + return NvResult.InvalidSize; + } + + if (isRead && isWrite) + { + if (outputDataSize < inputDataSize) + { + arguments = null; + + Logger.PrintWarning(LogClass.ServiceNv, "Ioctl size inconsistency found!"); + + return NvResult.InvalidSize; + } + + byte[] outputData = new byte[outputDataSize]; + + byte[] temp = context.Memory.ReadBytes(inputDataPosition, inputDataSize); + + Buffer.BlockCopy(temp, 0, outputData, 0, temp.Length); + + arguments = new Span(outputData); + } + else if (isWrite) + { + byte[] outputData = new byte[outputDataSize]; + + arguments = new Span(outputData); + } + else + { + arguments = new Span(context.Memory.ReadBytes(inputDataPosition, inputDataSize)); + } + + return NvResult.Success; + } + + private NvResult GetDeviceFileFromFd(int fd, out NvDeviceFile deviceFile) + { + deviceFile = null; + + if (fd < 0) + { + return NvResult.InvalidParameter; + } + + deviceFile = _deviceFileIdRegistry.GetData(fd); + + if (deviceFile == null) + { + Logger.PrintWarning(LogClass.ServiceNv, $"Invalid file descriptor {fd}"); + + return NvResult.NotImplemented; + } + + if (deviceFile.Owner.Pid != _owner.Pid) + { + return NvResult.AccessDenied; + } + + return NvResult.Success; + } + + private NvResult EnsureInitialized() + { + if (_owner == null) + { + Logger.PrintWarning(LogClass.ServiceNv, "INvDrvServices is not initialized!"); + + return NvResult.NotInitialized; + } + + return NvResult.Success; + } + + private static NvResult ConvertInternalErrorCode(NvInternalResult errorCode) + { + switch (errorCode) + { + case NvInternalResult.Success: + return NvResult.Success; + case NvInternalResult.Unknown0x72: + return NvResult.AlreadyAllocated; + case NvInternalResult.TimedOut: + case NvInternalResult.TryAgain: + case NvInternalResult.Interrupted: + return NvResult.Timeout; + case NvInternalResult.InvalidAddress: + return NvResult.InvalidAddress; + case NvInternalResult.NotSupported: + case NvInternalResult.Unknown0x18: + return NvResult.NotSupported; + case NvInternalResult.InvalidState: + return NvResult.InvalidState; + case NvInternalResult.ReadOnlyAttribute: + return NvResult.ReadOnlyAttribute; + case NvInternalResult.NoSpaceLeft: + case NvInternalResult.FileTooBig: + return NvResult.InvalidSize; + case NvInternalResult.FileTableOverflow: + case NvInternalResult.BadFileNumber: + return NvResult.FileOperationFailed; + case NvInternalResult.InvalidInput: + return NvResult.InvalidValue; + case NvInternalResult.NotADirectory: + return NvResult.DirectoryOperationFailed; + case NvInternalResult.Busy: + return NvResult.Busy; + case NvInternalResult.BadAddress: + return NvResult.InvalidAddress; + case NvInternalResult.AccessDenied: + case NvInternalResult.OperationNotPermitted: + return NvResult.AccessDenied; + case NvInternalResult.OutOfMemory: + return NvResult.InsufficientMemory; + case NvInternalResult.DeviceNotFound: + return NvResult.ModuleNotPresent; + case NvInternalResult.IoError: + return NvResult.ResourceError; + default: + return NvResult.IoctlFailed; + } + } + + [Command(0)] + // Open(buffer path) -> (s32 fd, u32 error_code) + public ResultCode Open(ServiceCtx context) + { + NvResult errorCode = EnsureInitialized(); + int fd = -1; + + if (errorCode == NvResult.Success) + { + long pathPtr = context.Request.SendBuff[0].Position; + + string path = MemoryHelper.ReadAsciiString(context.Memory, pathPtr); + + fd = Open(context, path); + + if (fd == -1) + { + errorCode = NvResult.FileOperationFailed; + } + } + + context.ResponseData.Write(fd); + context.ResponseData.Write((uint)errorCode); + + return ResultCode.Success; + } + + [Command(1)] + // Ioctl(s32 fd, u32 ioctl_cmd, buffer in_args) -> (u32 error_code, buffer out_args) + public ResultCode Ioctl(ServiceCtx context) + { + NvResult errorCode = EnsureInitialized(); + + if (errorCode == NvResult.Success) + { + int fd = context.RequestData.ReadInt32(); + NvIoctl ioctlCommand = context.RequestData.ReadStruct(); + + errorCode = GetIoctlArgument(context, ioctlCommand, out Span arguments); + + if (errorCode == NvResult.Success) + { + errorCode = GetDeviceFileFromFd(fd, out NvDeviceFile deviceFile); + + if (errorCode == NvResult.Success) + { + NvInternalResult internalResult = deviceFile.Ioctl(ioctlCommand, arguments); + + if (internalResult == NvInternalResult.NotImplemented) + { + throw new NvIoctlNotImplementedException(context, deviceFile, ioctlCommand); + } + + errorCode = ConvertInternalErrorCode(internalResult); + + if (errorCode == NvResult.Success && (ioctlCommand.DirectionValue & NvIoctl.Direction.Write) != 0) + { + context.Memory.WriteBytes(context.Request.GetBufferType0x22(0).Position, arguments.ToArray()); + } + } + } + } + + context.ResponseData.Write((uint)errorCode); + + return ResultCode.Success; + } + + [Command(2)] + // Close(s32 fd) -> u32 error_code + public ResultCode Close(ServiceCtx context) + { + NvResult errorCode = EnsureInitialized(); + + if (errorCode == NvResult.Success) + { + int fd = context.RequestData.ReadInt32(); + + errorCode = GetDeviceFileFromFd(fd, out NvDeviceFile deviceFile); + + if (errorCode == NvResult.Success) + { + deviceFile.Close(); + + _deviceFileIdRegistry.Delete(fd); + } + } + + context.ResponseData.Write((uint)errorCode); + + return ResultCode.Success; + } + + [Command(3)] + // Initialize(u32 transfer_memory_size, handle current_process, handle transfer_memory) -> u32 error_code + public ResultCode Initialize(ServiceCtx context) + { + long transferMemSize = context.RequestData.ReadInt64(); + int transferMemHandle = context.Request.HandleDesc.ToCopy[0]; + + _owner = context.Process; + + context.ResponseData.Write((uint)NvResult.Success); + + return ResultCode.Success; + } + + [Command(4)] + // QueryEvent(s32 fd, u32 event_id) -> (u32, handle) + public ResultCode QueryEvent(ServiceCtx context) + { + NvResult errorCode = EnsureInitialized(); + + if (errorCode == NvResult.Success) + { + int fd = context.RequestData.ReadInt32(); + uint eventId = context.RequestData.ReadUInt32(); + + errorCode = GetDeviceFileFromFd(fd, out NvDeviceFile deviceFile); + + if (errorCode == NvResult.Success) + { + NvInternalResult internalResult = deviceFile.QueryEvent(out int eventHandle, eventId); + + if (internalResult == NvInternalResult.NotImplemented) + { + throw new NvQueryEventNotImplementedException(context, deviceFile, eventId); + } + + errorCode = ConvertInternalErrorCode(internalResult); + + if (errorCode == NvResult.Success) + { + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(eventHandle); + } + } + } + + context.ResponseData.Write((uint)errorCode); + + return ResultCode.Success; + } + + [Command(5)] + // MapSharedMemory(s32 fd, u32 argument, handle) -> u32 error_code + public ResultCode MapSharedMemory(ServiceCtx context) + { + NvResult errorCode = EnsureInitialized(); + + if (errorCode == NvResult.Success) + { + int fd = context.RequestData.ReadInt32(); + uint argument = context.RequestData.ReadUInt32(); + int sharedMemoryHandle = context.Request.HandleDesc.ToCopy[0]; + + errorCode = GetDeviceFileFromFd(fd, out NvDeviceFile deviceFile); + + if (errorCode == NvResult.Success) + { + KSharedMemory sharedMemory = context.Process.HandleTable.GetObject(sharedMemoryHandle); + + errorCode = ConvertInternalErrorCode(deviceFile.MapSharedMemory(sharedMemory, argument)); + } + } + + context.ResponseData.Write((uint)errorCode); + + return ResultCode.Success; + } + + [Command(6)] + // GetStatus() -> (unknown<0x20>, u32 error_code) + public ResultCode GetStatus(ServiceCtx context) + { + throw new ServiceNotImplementedException(context); + } + + [Command(7)] + // ForceSetClientPid(u64) -> u32 error_code + public ResultCode ForceSetClientPid(ServiceCtx context) + { + throw new ServiceNotImplementedException(context); + } + + [Command(8)] + // SetClientPID(u64, pid) -> u32 error_code + public ResultCode SetClientPid(ServiceCtx context) + { + long pid = context.RequestData.ReadInt64(); + + context.ResponseData.Write(0); + + return ResultCode.Success; + } + + [Command(9)] + // DumpGraphicsMemoryInfo() + public ResultCode DumpGraphicsMemoryInfo(ServiceCtx context) + { + Logger.PrintStub(LogClass.ServiceNv); + + return ResultCode.Success; + } + + [Command(10)] // 3.0.0+ + // InitializeDevtools(u32, handle) -> u32 error_code; + public ResultCode InitializeDevtools(ServiceCtx context) + { + throw new ServiceNotImplementedException(context); + } + + [Command(11)] // 3.0.0+ + // Ioctl2(s32 fd, u32 ioctl_cmd, buffer in_args, buffer inline_in_buffer) -> (u32 error_code, buffer out_args) + public ResultCode Ioctl2(ServiceCtx context) + { + NvResult errorCode = EnsureInitialized(); + + if (errorCode == NvResult.Success) + { + int fd = context.RequestData.ReadInt32(); + NvIoctl ioctlCommand = context.RequestData.ReadStruct(); + + (long inlineInBufferPosition, long inlineInBufferSize) = context.Request.GetBufferType0x21(1); + + errorCode = GetIoctlArgument(context, ioctlCommand, out Span arguments); + + Span inlineInBuffer = new Span(context.Memory.ReadBytes(inlineInBufferPosition, inlineInBufferSize)); + + if (errorCode == NvResult.Success) + { + errorCode = GetDeviceFileFromFd(fd, out NvDeviceFile deviceFile); + + if (errorCode == NvResult.Success) + { + NvInternalResult internalResult = deviceFile.Ioctl2(ioctlCommand, arguments, inlineInBuffer); + + if (internalResult == NvInternalResult.NotImplemented) + { + throw new NvIoctlNotImplementedException(context, deviceFile, ioctlCommand); + } + + errorCode = ConvertInternalErrorCode(internalResult); + + if (errorCode == NvResult.Success && (ioctlCommand.DirectionValue & NvIoctl.Direction.Write) != 0) + { + context.Memory.WriteBytes(context.Request.GetBufferType0x22(0).Position, arguments.ToArray()); + } + } + } + } + + context.ResponseData.Write((uint)errorCode); + + return ResultCode.Success; + } + + [Command(12)] // 3.0.0+ + // Ioctl3(s32 fd, u32 ioctl_cmd, buffer in_args) -> (u32 error_code, buffer out_args, buffer inline_out_buffer) + public ResultCode Ioctl3(ServiceCtx context) + { + NvResult errorCode = EnsureInitialized(); + + if (errorCode == NvResult.Success) + { + int fd = context.RequestData.ReadInt32(); + NvIoctl ioctlCommand = context.RequestData.ReadStruct(); + + (long inlineOutBufferPosition, long inlineOutBufferSize) = context.Request.GetBufferType0x22(1); + + errorCode = GetIoctlArgument(context, ioctlCommand, out Span arguments); + + Span inlineOutBuffer = new Span(context.Memory.ReadBytes(inlineOutBufferPosition, inlineOutBufferSize)); + + if (errorCode == NvResult.Success) + { + errorCode = GetDeviceFileFromFd(fd, out NvDeviceFile deviceFile); + + if (errorCode == NvResult.Success) + { + NvInternalResult internalResult = deviceFile.Ioctl3(ioctlCommand, arguments, inlineOutBuffer); + + if (internalResult == NvInternalResult.NotImplemented) + { + throw new NvIoctlNotImplementedException(context, deviceFile, ioctlCommand); + } + + errorCode = ConvertInternalErrorCode(internalResult); + + if (errorCode == NvResult.Success && (ioctlCommand.DirectionValue & NvIoctl.Direction.Write) != 0) + { + context.Memory.WriteBytes(context.Request.GetBufferType0x22(0).Position, arguments.ToArray()); + context.Memory.WriteBytes(inlineOutBufferPosition, inlineOutBuffer.ToArray()); + } + } + } + } + + context.ResponseData.Write((uint)errorCode); + + return ResultCode.Success; + } + + [Command(13)] // 3.0.0+ + // FinishInitialize(unknown<8>) + public ResultCode FinishInitialize(ServiceCtx context) + { + Logger.PrintStub(LogClass.ServiceNv); + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Nv/INvGemControl.cs b/Ryujinx.HLE/HOS/Services/Nv/INvGemControl.cs new file mode 100644 index 0000000000..7bf99ed1a4 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nv/INvGemControl.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Nv +{ + [Service("nvgem:c")] + class INvGemControl : IpcService + { + public INvGemControl(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Nv/INvGemCoreDump.cs b/Ryujinx.HLE/HOS/Services/Nv/INvGemCoreDump.cs new file mode 100644 index 0000000000..ff3774da1a --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nv/INvGemCoreDump.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Nv +{ + [Service("nvgem:cd")] + class INvGemCoreDump : IpcService + { + public INvGemCoreDump(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvDeviceFile.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvDeviceFile.cs new file mode 100644 index 0000000000..b6e6b8f8fa --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvDeviceFile.cs @@ -0,0 +1,82 @@ +using Ryujinx.HLE.HOS.Kernel.Memory; +using Ryujinx.HLE.HOS.Kernel.Process; +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices +{ + abstract class NvDeviceFile + { + public readonly ServiceCtx Context; + public readonly KProcess Owner; + + public NvDeviceFile(ServiceCtx context) + { + Context = context; + Owner = context.Process; + } + + public virtual NvInternalResult QueryEvent(out int eventHandle, uint eventId) + { + eventHandle = 0; + + return NvInternalResult.NotImplemented; + } + + public virtual NvInternalResult MapSharedMemory(KSharedMemory sharedMemory, uint argument) + { + return NvInternalResult.NotImplemented; + } + + public virtual NvInternalResult Ioctl(NvIoctl command, Span arguments) + { + return NvInternalResult.NotImplemented; + } + + public virtual NvInternalResult Ioctl2(NvIoctl command, Span arguments, Span inlineInBuffer) + { + return NvInternalResult.NotImplemented; + } + + public virtual NvInternalResult Ioctl3(NvIoctl command, Span arguments, Span inlineOutBuffer) + { + return NvInternalResult.NotImplemented; + } + + protected delegate NvInternalResult IoctlProcessor(ref T arguments); + protected delegate NvInternalResult IoctlProcessorSpan(Span arguments); + protected delegate NvInternalResult IoctlProcessorInline(ref T arguments, ref T1 inlineData); + protected delegate NvInternalResult IoctlProcessorInlineSpan(ref T arguments, Span inlineData); + + protected static NvInternalResult CallIoctlMethod(IoctlProcessor callback, Span arguments) where T : struct + { + Debug.Assert(arguments.Length == Unsafe.SizeOf()); + + return callback(ref MemoryMarshal.Cast(arguments)[0]); + } + + protected static NvInternalResult CallIoctlMethod(IoctlProcessorInline callback, Span arguments, Span inlineBuffer) where T : struct where T1 : struct + { + Debug.Assert(arguments.Length == Unsafe.SizeOf()); + Debug.Assert(inlineBuffer.Length == Unsafe.SizeOf()); + + return callback(ref MemoryMarshal.Cast(arguments)[0], ref MemoryMarshal.Cast(inlineBuffer)[0]); + } + + protected static NvInternalResult CallIoctlMethod(IoctlProcessorSpan callback, Span arguments) where T : struct + { + return callback(MemoryMarshal.Cast(arguments)); + } + + protected static NvInternalResult CallIoctlMethod(IoctlProcessorInlineSpan callback, Span arguments, Span inlineBuffer) where T : struct where T1 : struct + { + Debug.Assert(arguments.Length == Unsafe.SizeOf()); + + return callback(ref MemoryMarshal.Cast(arguments)[0], MemoryMarshal.Cast(inlineBuffer)); + } + + public abstract void Close(); + } +} diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/NvHostAsGpuDeviceFile.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/NvHostAsGpuDeviceFile.cs new file mode 100644 index 0000000000..6653c0f0eb --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/NvHostAsGpuDeviceFile.cs @@ -0,0 +1,329 @@ +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.Gpu.Memory; +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap; +using System; +using System.Collections.Concurrent; +using System.Diagnostics; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu +{ + class NvHostAsGpuDeviceFile : NvDeviceFile + { + private static ConcurrentDictionary _addressSpaceContextRegistry = new ConcurrentDictionary(); + + public NvHostAsGpuDeviceFile(ServiceCtx context) : base(context) { } + + public override NvInternalResult Ioctl(NvIoctl command, Span arguments) + { + NvInternalResult result = NvInternalResult.NotImplemented; + + if (command.Type == NvIoctl.NvGpuAsMagic) + { + switch (command.Number) + { + case 0x01: + result = CallIoctlMethod(BindChannel, arguments); + break; + case 0x02: + result = CallIoctlMethod(AllocSpace, arguments); + break; + case 0x03: + result = CallIoctlMethod(FreeSpace, arguments); + break; + case 0x05: + result = CallIoctlMethod(UnmapBuffer, arguments); + break; + case 0x06: + result = CallIoctlMethod(MapBufferEx, arguments); + break; + case 0x08: + result = CallIoctlMethod(GetVaRegions, arguments); + break; + case 0x09: + result = CallIoctlMethod(InitializeEx, arguments); + break; + case 0x14: + result = CallIoctlMethod(Remap, arguments); + break; + } + } + + return result; + } + + public override NvInternalResult Ioctl3(NvIoctl command, Span arguments, Span inlineOutBuffer) + { + NvInternalResult result = NvInternalResult.NotImplemented; + + if (command.Type == NvIoctl.NvGpuAsMagic) + { + switch (command.Number) + { + case 0x08: + // This is the same as the one in ioctl as inlineOutBuffer is empty. + result = CallIoctlMethod(GetVaRegions, arguments); + break; + } + } + + return result; + } + + private NvInternalResult BindChannel(ref BindChannelArguments arguments) + { + Logger.PrintStub(LogClass.ServiceNv); + + return NvInternalResult.Success; + } + + private NvInternalResult AllocSpace(ref AllocSpaceArguments arguments) + { + AddressSpaceContext addressSpaceContext = GetAddressSpaceContext(Context); + + ulong size = (ulong)arguments.Pages * (ulong)arguments.PageSize; + + NvInternalResult result = NvInternalResult.Success; + + lock (addressSpaceContext) + { + // Note: When the fixed offset flag is not set, + // the Offset field holds the alignment size instead. + if ((arguments.Flags & AddressSpaceFlags.FixedOffset) != 0) + { + arguments.Offset = (long)addressSpaceContext.Gmm.ReserveFixed((ulong)arguments.Offset, size); + } + else + { + arguments.Offset = (long)addressSpaceContext.Gmm.Reserve((ulong)size, (ulong)arguments.Offset); + } + + if (arguments.Offset < 0) + { + arguments.Offset = 0; + + Logger.PrintWarning(LogClass.ServiceNv, $"Failed to allocate size {size:x16}!"); + + result = NvInternalResult.OutOfMemory; + } + else + { + addressSpaceContext.AddReservation(arguments.Offset, (long)size); + } + } + + return result; + } + + private NvInternalResult FreeSpace(ref FreeSpaceArguments arguments) + { + AddressSpaceContext addressSpaceContext = GetAddressSpaceContext(Context); + + NvInternalResult result = NvInternalResult.Success; + + lock (addressSpaceContext) + { + ulong size = (ulong)arguments.Pages * (ulong)arguments.PageSize; + + if (addressSpaceContext.RemoveReservation(arguments.Offset)) + { + addressSpaceContext.Gmm.Free((ulong)arguments.Offset, size); + } + else + { + Logger.PrintWarning(LogClass.ServiceNv, + $"Failed to free offset 0x{arguments.Offset:x16} size 0x{size:x16}!"); + + result = NvInternalResult.InvalidInput; + } + } + + return result; + } + + private NvInternalResult UnmapBuffer(ref UnmapBufferArguments arguments) + { + AddressSpaceContext addressSpaceContext = GetAddressSpaceContext(Context); + + lock (addressSpaceContext) + { + if (addressSpaceContext.RemoveMap(arguments.Offset, out long size)) + { + if (size != 0) + { + addressSpaceContext.Gmm.Free((ulong)arguments.Offset, (ulong)size); + } + } + else + { + Logger.PrintWarning(LogClass.ServiceNv, $"Invalid buffer offset {arguments.Offset:x16}!"); + } + } + + return NvInternalResult.Success; + } + + private NvInternalResult MapBufferEx(ref MapBufferExArguments arguments) + { + const string mapErrorMsg = "Failed to map fixed buffer with offset 0x{0:x16}, size 0x{1:x16} and alignment 0x{2:x16}!"; + + AddressSpaceContext addressSpaceContext = GetAddressSpaceContext(Context); + + NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(Owner, arguments.NvMapHandle, true); + + if (map == null) + { + Logger.PrintWarning(LogClass.ServiceNv, $"Invalid NvMap handle 0x{arguments.NvMapHandle:x8}!"); + + return NvInternalResult.InvalidInput; + } + + ulong pageSize = (ulong)arguments.PageSize; + + if (pageSize == 0) + { + pageSize = (ulong)map.Align; + } + + long physicalAddress; + + if ((arguments.Flags & AddressSpaceFlags.RemapSubRange) != 0) + { + lock (addressSpaceContext) + { + if (addressSpaceContext.TryGetMapPhysicalAddress(arguments.Offset, out physicalAddress)) + { + long virtualAddress = arguments.Offset + arguments.BufferOffset; + + physicalAddress += arguments.BufferOffset; + + if ((long)addressSpaceContext.Gmm.Map((ulong)physicalAddress, (ulong)virtualAddress, (ulong)arguments.MappingSize) < 0) + { + string message = string.Format(mapErrorMsg, virtualAddress, arguments.MappingSize, pageSize); + + Logger.PrintWarning(LogClass.ServiceNv, message); + + return NvInternalResult.InvalidInput; + } + + return NvInternalResult.Success; + } + else + { + Logger.PrintWarning(LogClass.ServiceNv, $"Address 0x{arguments.Offset:x16} not mapped!"); + + return NvInternalResult.InvalidInput; + } + } + } + + physicalAddress = map.Address + arguments.BufferOffset; + + long size = arguments.MappingSize; + + if (size == 0) + { + size = (uint)map.Size; + } + + NvInternalResult result = NvInternalResult.Success; + + lock (addressSpaceContext) + { + // Note: When the fixed offset flag is not set, + // the Offset field holds the alignment size instead. + bool virtualAddressAllocated = (arguments.Flags & AddressSpaceFlags.FixedOffset) == 0; + + if (!virtualAddressAllocated) + { + if (addressSpaceContext.ValidateFixedBuffer(arguments.Offset, size, pageSize)) + { + arguments.Offset = (long)addressSpaceContext.Gmm.Map((ulong)physicalAddress, (ulong)arguments.Offset, (ulong)size); + } + else + { + string message = string.Format(mapErrorMsg, arguments.Offset, size, pageSize); + + Logger.PrintWarning(LogClass.ServiceNv, message); + + result = NvInternalResult.InvalidInput; + } + } + else + { + arguments.Offset = (long)addressSpaceContext.Gmm.MapAllocate((ulong)physicalAddress, (ulong)size, pageSize); + } + + if (arguments.Offset < 0) + { + arguments.Offset = 0; + + Logger.PrintWarning(LogClass.ServiceNv, $"Failed to map size 0x{size:x16}!"); + + result = NvInternalResult.InvalidInput; + } + else + { + addressSpaceContext.AddMap(arguments.Offset, size, physicalAddress, virtualAddressAllocated); + } + } + + return result; + } + + private NvInternalResult GetVaRegions(ref GetVaRegionsArguments arguments) + { + Logger.PrintStub(LogClass.ServiceNv); + + return NvInternalResult.Success; + } + + private NvInternalResult InitializeEx(ref InitializeExArguments arguments) + { + Logger.PrintStub(LogClass.ServiceNv); + + return NvInternalResult.Success; + } + + private NvInternalResult Remap(Span arguments) + { + for (int index = 0; index < arguments.Length; index++) + { + MemoryManager gmm = GetAddressSpaceContext(Context).Gmm; + + NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(Owner, arguments[index].NvMapHandle, true); + + if (map == null) + { + Logger.PrintWarning(LogClass.ServiceNv, $"Invalid NvMap handle 0x{arguments[index].NvMapHandle:x8}!"); + + return NvInternalResult.InvalidInput; + } + + long result = (long)gmm.Map( + ((ulong)arguments[index].MapOffset << 16) + (ulong)map.Address, + (ulong)arguments[index].GpuOffset << 16, + (ulong)arguments[index].Pages << 16); + + if (result < 0) + { + Logger.PrintWarning(LogClass.ServiceNv, + $"Page 0x{arguments[index].GpuOffset:x16} size 0x{arguments[index].Pages:x16} not allocated!"); + + return NvInternalResult.InvalidInput; + } + } + + return NvInternalResult.Success; + } + + public override void Close() { } + + public static AddressSpaceContext GetAddressSpaceContext(ServiceCtx context) + { + return _addressSpaceContextRegistry.GetOrAdd(context.Process, (key) => new AddressSpaceContext(context)); + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AddressSpaceContext.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AddressSpaceContext.cs new file mode 100644 index 0000000000..d39be2977c --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AddressSpaceContext.cs @@ -0,0 +1,202 @@ +using Ryujinx.Graphics.Gpu.Memory; +using Ryujinx.HLE.HOS.Kernel.Process; +using System; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types +{ + class AddressSpaceContext + { + private class Range + { + public ulong Start { get; private set; } + public ulong End { get; private set; } + + public Range(long position, long size) + { + Start = (ulong)position; + End = (ulong)size + Start; + } + } + + private class MappedMemory : Range + { + public long PhysicalAddress { get; private set; } + public bool VaAllocated { get; private set; } + + public MappedMemory( + long position, + long size, + long physicalAddress, + bool vaAllocated) : base(position, size) + { + PhysicalAddress = physicalAddress; + VaAllocated = vaAllocated; + } + } + + private SortedList _maps; + private SortedList _reservations; + + public MemoryManager Gmm { get; } + + public AddressSpaceContext(ServiceCtx context) + { + Gmm = context.Device.Gpu.MemoryManager; + + _maps = new SortedList(); + _reservations = new SortedList(); + } + + public bool ValidateFixedBuffer(long position, long size, ulong alignment) + { + long mapEnd = position + size; + + // Check if size is valid (0 is also not allowed). + if ((ulong)mapEnd <= (ulong)position) + { + return false; + } + + // Check if address is aligned. + if ((position & (long)(alignment - 1)) != 0) + { + return false; + } + + // Check if region is reserved. + if (BinarySearch(_reservations, position) == null) + { + return false; + } + + // Check for overlap with already mapped buffers. + Range map = BinarySearchLt(_maps, mapEnd); + + if (map != null && map.End > (ulong)position) + { + return false; + } + + return true; + } + + public void AddMap( + long position, + long size, + long physicalAddress, + bool vaAllocated) + { + _maps.Add(position, new MappedMemory(position, size, physicalAddress, vaAllocated)); + } + + public bool RemoveMap(long position, out long size) + { + size = 0; + + if (_maps.Remove(position, out Range value)) + { + MappedMemory map = (MappedMemory)value; + + if (map.VaAllocated) + { + size = (long)(map.End - map.Start); + } + + return true; + } + + return false; + } + + public bool TryGetMapPhysicalAddress(long position, out long physicalAddress) + { + Range map = BinarySearch(_maps, position); + + if (map != null) + { + physicalAddress = ((MappedMemory)map).PhysicalAddress; + + return true; + } + + physicalAddress = 0; + + return false; + } + + public void AddReservation(long position, long size) + { + _reservations.Add(position, new Range(position, size)); + } + + public bool RemoveReservation(long position) + { + return _reservations.Remove(position); + } + + private Range BinarySearch(SortedList lst, long position) + { + int left = 0; + int right = lst.Count - 1; + + while (left <= right) + { + int size = right - left; + + int middle = left + (size >> 1); + + Range rg = lst.Values[middle]; + + if ((ulong)position >= rg.Start && (ulong)position < rg.End) + { + return rg; + } + + if ((ulong)position < rg.Start) + { + right = middle - 1; + } + else + { + left = middle + 1; + } + } + + return null; + } + + private Range BinarySearchLt(SortedList lst, long position) + { + Range ltRg = null; + + int left = 0; + int right = lst.Count - 1; + + while (left <= right) + { + int size = right - left; + + int middle = left + (size >> 1); + + Range rg = lst.Values[middle]; + + if ((ulong)position < rg.Start) + { + right = middle - 1; + } + else + { + left = middle + 1; + + if ((ulong)position > rg.Start) + { + ltRg = rg; + } + } + } + + return ltRg; + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AddressSpaceFlags.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AddressSpaceFlags.cs new file mode 100644 index 0000000000..611cf78bfc --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AddressSpaceFlags.cs @@ -0,0 +1,11 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types +{ + [Flags] + enum AddressSpaceFlags : uint + { + FixedOffset = 1, + RemapSubRange = 0x100, + } +} diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AllocSpaceArguments.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AllocSpaceArguments.cs new file mode 100644 index 0000000000..73f746e2dc --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AllocSpaceArguments.cs @@ -0,0 +1,14 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct AllocSpaceArguments + { + public uint Pages; + public uint PageSize; + public AddressSpaceFlags Flags; + public uint Padding; + public long Offset; + } +} diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/BindChannelArguments.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/BindChannelArguments.cs new file mode 100644 index 0000000000..9c6568a3fe --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/BindChannelArguments.cs @@ -0,0 +1,10 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct BindChannelArguments + { + public int Fd; + } +} diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/FreeSpaceArguments.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/FreeSpaceArguments.cs new file mode 100644 index 0000000000..a853974b4c --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/FreeSpaceArguments.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct FreeSpaceArguments + { + public long Offset; + public uint Pages; + public uint PageSize; + } +} diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/GetVaRegionsArguments.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/GetVaRegionsArguments.cs new file mode 100644 index 0000000000..b3a9cf26cd --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/GetVaRegionsArguments.cs @@ -0,0 +1,23 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct VaRegion + { + public ulong Offset; + public uint PageSize; + public uint Padding; + public ulong Pages; + } + + [StructLayout(LayoutKind.Sequential)] + struct GetVaRegionsArguments + { + public ulong Unused; + public uint BufferSize; + public uint Padding; + public VaRegion Region0; + public VaRegion Region1; + } +} diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/InitializeExArguments.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/InitializeExArguments.cs new file mode 100644 index 0000000000..882bda591f --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/InitializeExArguments.cs @@ -0,0 +1,16 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct InitializeExArguments + { + public uint Flags; + public int AsFd; + public uint BigPageSize; + public uint Reserved; + public ulong Unknown0; + public ulong Unknown1; + public ulong Unknown2; + } +} diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/MapBufferExArguments.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/MapBufferExArguments.cs new file mode 100644 index 0000000000..02e058df63 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/MapBufferExArguments.cs @@ -0,0 +1,16 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct MapBufferExArguments + { + public AddressSpaceFlags Flags; + public int Kind; + public int NvMapHandle; + public int PageSize; + public long BufferOffset; + public long MappingSize; + public long Offset; + } +} diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/RemapArguments.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/RemapArguments.cs new file mode 100644 index 0000000000..bc149d424b --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/RemapArguments.cs @@ -0,0 +1,15 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct RemapArguments + { + public ushort Flags; + public ushort Kind; + public int NvMapHandle; + public uint MapOffset; + public uint GpuOffset; + public uint Pages; + } +} diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/UnmapBufferArguments.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/UnmapBufferArguments.cs new file mode 100644 index 0000000000..1ef880afea --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/UnmapBufferArguments.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types +{ + struct UnmapBufferArguments + { + public long Offset; + } +} diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/NvHostChannelDeviceFile.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/NvHostChannelDeviceFile.cs new file mode 100644 index 0000000000..212d69e09c --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/NvHostChannelDeviceFile.cs @@ -0,0 +1,346 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.Gpu; +using Ryujinx.Graphics.Gpu.Memory; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.Types; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel +{ + class NvHostChannelDeviceFile : NvDeviceFile + { + private uint _timeout; + private uint _submitTimeout; + private uint _timeslice; + + private GpuContext _gpu; + + private ARMeilleure.Memory.MemoryManager _memory; + + public NvHostChannelDeviceFile(ServiceCtx context) : base(context) + { + _gpu = context.Device.Gpu; + _memory = context.Memory; + _timeout = 3000; + _submitTimeout = 0; + _timeslice = 0; + } + + public override NvInternalResult Ioctl(NvIoctl command, Span arguments) + { + NvInternalResult result = NvInternalResult.NotImplemented; + + if (command.Type == NvIoctl.NvHostCustomMagic) + { + switch (command.Number) + { + case 0x01: + result = Submit(arguments); + break; + case 0x02: + result = CallIoctlMethod(GetSyncpoint, arguments); + break; + case 0x03: + result = CallIoctlMethod(GetWaitBase, arguments); + break; + case 0x07: + result = CallIoctlMethod(SetSubmitTimeout, arguments); + break; + case 0x09: + result = MapCommandBuffer(arguments); + break; + case 0x0a: + result = UnmapCommandBuffer(arguments); + break; + } + } + else if (command.Type == NvIoctl.NvHostMagic) + { + switch (command.Number) + { + case 0x01: + result = CallIoctlMethod(SetNvMapFd, arguments); + break; + case 0x03: + result = CallIoctlMethod(SetTimeout, arguments); + break; + case 0x08: + result = SubmitGpfifo(arguments); + break; + case 0x09: + result = CallIoctlMethod(AllocObjCtx, arguments); + break; + case 0x0b: + result = CallIoctlMethod(ZcullBind, arguments); + break; + case 0x0c: + result = CallIoctlMethod(SetErrorNotifier, arguments); + break; + case 0x0d: + result = CallIoctlMethod(SetPriority, arguments); + break; + case 0x18: + result = CallIoctlMethod(AllocGpfifoEx, arguments); + break; + case 0x1a: + result = CallIoctlMethod(AllocGpfifoEx2, arguments); + break; + case 0x1d: + result = CallIoctlMethod(SetTimeslice, arguments); + break; + } + } + else if (command.Type == NvIoctl.NvGpuMagic) + { + switch (command.Number) + { + case 0x14: + result = CallIoctlMethod(SetUserData, arguments); + break; + } + } + + return result; + } + + private NvInternalResult Submit(Span arguments) + { + int headerSize = Unsafe.SizeOf(); + SubmitArguments submitHeader = MemoryMarshal.Cast(arguments)[0]; + Span commandBufferEntries = MemoryMarshal.Cast(arguments.Slice(headerSize)).Slice(0, submitHeader.CmdBufsCount); + MemoryManager gmm = NvHostAsGpuDeviceFile.GetAddressSpaceContext(Context).Gmm; + + foreach (CommandBuffer commandBufferEntry in commandBufferEntries) + { + NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(Owner, commandBufferEntry.MemoryId); + + int[] commandBufferData = new int[commandBufferEntry.WordsCount]; + + for (int offset = 0; offset < commandBufferData.Length; offset++) + { + commandBufferData[offset] = _memory.ReadInt32(map.Address + commandBufferEntry.Offset + offset * 4); + } + + // TODO: Submit command to engines. + } + + return NvInternalResult.Success; + } + + private NvInternalResult GetSyncpoint(ref GetParameterArguments arguments) + { + arguments.Value = 0; + + Logger.PrintStub(LogClass.ServiceNv); + + return NvInternalResult.Success; + } + + private NvInternalResult GetWaitBase(ref GetParameterArguments arguments) + { + arguments.Value = 0; + + Logger.PrintStub(LogClass.ServiceNv); + + return NvInternalResult.Success; + } + + private NvInternalResult SetSubmitTimeout(ref uint submitTimeout) + { + _submitTimeout = submitTimeout; + + Logger.PrintStub(LogClass.ServiceNv); + + return NvInternalResult.Success; + } + + private NvInternalResult MapCommandBuffer(Span arguments) + { + int headerSize = Unsafe.SizeOf(); + MapCommandBufferArguments commandBufferHeader = MemoryMarshal.Cast(arguments)[0]; + Span commandBufferEntries = MemoryMarshal.Cast(arguments.Slice(headerSize)).Slice(0, commandBufferHeader.NumEntries); + MemoryManager gmm = NvHostAsGpuDeviceFile.GetAddressSpaceContext(Context).Gmm; + + foreach (ref CommandBufferHandle commandBufferEntry in commandBufferEntries) + { + NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(Owner, commandBufferEntry.MapHandle); + + if (map == null) + { + Logger.PrintWarning(LogClass.ServiceNv, $"Invalid handle 0x{commandBufferEntry.MapHandle:x8}!"); + + return NvInternalResult.InvalidInput; + } + + lock (map) + { + if (map.DmaMapAddress == 0) + { + map.DmaMapAddress = (long)gmm.MapLow((ulong)map.Address, (uint)map.Size); + } + + commandBufferEntry.MapAddress = (int)map.DmaMapAddress; + } + } + + return NvInternalResult.Success; + } + + private NvInternalResult UnmapCommandBuffer(Span arguments) + { + int headerSize = Unsafe.SizeOf(); + MapCommandBufferArguments commandBufferHeader = MemoryMarshal.Cast(arguments)[0]; + Span commandBufferEntries = MemoryMarshal.Cast(arguments.Slice(headerSize)).Slice(0, commandBufferHeader.NumEntries); + MemoryManager gmm = NvHostAsGpuDeviceFile.GetAddressSpaceContext(Context).Gmm; + + foreach (ref CommandBufferHandle commandBufferEntry in commandBufferEntries) + { + NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(Owner, commandBufferEntry.MapHandle); + + if (map == null) + { + Logger.PrintWarning(LogClass.ServiceNv, $"Invalid handle 0x{commandBufferEntry.MapHandle:x8}!"); + + return NvInternalResult.InvalidInput; + } + + lock (map) + { + if (map.DmaMapAddress != 0) + { + gmm.Free((ulong)map.DmaMapAddress, (uint)map.Size); + + map.DmaMapAddress = 0; + } + } + } + + return NvInternalResult.Success; + } + + private NvInternalResult SetNvMapFd(ref int nvMapFd) + { + Logger.PrintStub(LogClass.ServiceNv); + + return NvInternalResult.Success; + } + + private NvInternalResult SetTimeout(ref uint timeout) + { + _timeout = timeout; + + Logger.PrintStub(LogClass.ServiceNv); + + return NvInternalResult.Success; + } + + private NvInternalResult SubmitGpfifo(Span arguments) + { + int headerSize = Unsafe.SizeOf(); + SubmitGpfifoArguments gpfifoSubmissionHeader = MemoryMarshal.Cast(arguments)[0]; + Span gpfifoEntries = MemoryMarshal.Cast(arguments.Slice(headerSize)).Slice(0, gpfifoSubmissionHeader.NumEntries); + + return SubmitGpfifo(ref gpfifoSubmissionHeader, gpfifoEntries); + } + + private NvInternalResult AllocObjCtx(ref AllocObjCtxArguments arguments) + { + Logger.PrintStub(LogClass.ServiceNv); + + return NvInternalResult.Success; + } + + private NvInternalResult ZcullBind(ref ZcullBindArguments arguments) + { + Logger.PrintStub(LogClass.ServiceNv); + + return NvInternalResult.Success; + } + + private NvInternalResult SetErrorNotifier(ref SetErrorNotifierArguments arguments) + { + Logger.PrintStub(LogClass.ServiceNv); + + return NvInternalResult.Success; + } + + private NvInternalResult SetPriority(ref NvChannelPriority priority) + { + switch (priority) + { + case NvChannelPriority.Low: + _timeslice = 1300; // Timeslice low priority in micro-seconds + break; + case NvChannelPriority.Medium: + _timeslice = 2600; // Timeslice medium priority in micro-seconds + break; + case NvChannelPriority.High: + _timeslice = 5200; // Timeslice high priority in micro-seconds + break; + default: + return NvInternalResult.InvalidInput; + } + + Logger.PrintStub(LogClass.ServiceNv); + + // TODO: disable and preempt channel when GPU scheduler will be implemented. + + return NvInternalResult.Success; + } + + private NvInternalResult AllocGpfifoEx(ref AllocGpfifoExArguments arguments) + { + Logger.PrintStub(LogClass.ServiceNv); + + return NvInternalResult.Success; + } + + private NvInternalResult AllocGpfifoEx2(ref AllocGpfifoExArguments arguments) + { + Logger.PrintStub(LogClass.ServiceNv); + + return NvInternalResult.Success; + } + + private NvInternalResult SetTimeslice(ref uint timeslice) + { + if (timeslice < 1000 || timeslice > 50000) + { + return NvInternalResult.InvalidInput; + } + + _timeslice = timeslice; // in micro-seconds + + Logger.PrintStub(LogClass.ServiceNv); + + // TODO: disable and preempt channel when GPU scheduler will be implemented. + + return NvInternalResult.Success; + } + + private NvInternalResult SetUserData(ref ulong userData) + { + Logger.PrintStub(LogClass.ServiceNv); + + return NvInternalResult.Success; + } + + protected NvInternalResult SubmitGpfifo(ref SubmitGpfifoArguments header, Span entries) + { + foreach (ulong entry in entries) + { + _gpu.DmaPusher.Push(entry); + } + + header.Fence.Id = 0; + header.Fence.Value = 0; + + return NvInternalResult.Success; + } + + public override void Close() { } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/NvHostGpuDeviceFile.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/NvHostGpuDeviceFile.cs new file mode 100644 index 0000000000..7eaa5cc587 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/NvHostGpuDeviceFile.cs @@ -0,0 +1,78 @@ +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.Types; +using System; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel +{ + internal class NvHostGpuDeviceFile : NvHostChannelDeviceFile + { + private KEvent _smExceptionBptIntReportEvent; + private KEvent _smExceptionBptPauseReportEvent; + private KEvent _errorNotifierEvent; + + public NvHostGpuDeviceFile(ServiceCtx context) : base(context) + { + _smExceptionBptIntReportEvent = new KEvent(context.Device.System); + _smExceptionBptPauseReportEvent = new KEvent(context.Device.System); + _errorNotifierEvent = new KEvent(context.Device.System); + } + + public override NvInternalResult Ioctl2(NvIoctl command, Span arguments, Span inlineInBuffer) + { + NvInternalResult result = NvInternalResult.NotImplemented; + + if (command.Type == NvIoctl.NvHostMagic) + { + switch (command.Number) + { + case 0x1b: + result = CallIoctlMethod(SubmitGpfifoEx, arguments, inlineInBuffer); + break; + } + } + + return result; + } + + public override NvInternalResult QueryEvent(out int eventHandle, uint eventId) + { + // TODO: accurately represent and implement those events. + KEvent targetEvent = null; + + switch (eventId) + { + case 0x1: + targetEvent = _smExceptionBptIntReportEvent; + break; + case 0x2: + targetEvent = _smExceptionBptPauseReportEvent; + break; + case 0x3: + targetEvent = _errorNotifierEvent; + break; + } + + if (targetEvent != null) + { + if (Owner.HandleTable.GenerateHandle(targetEvent.ReadableEvent, out eventHandle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + else + { + eventHandle = 0; + + return NvInternalResult.InvalidInput; + } + + return NvInternalResult.Success; + } + + private NvInternalResult SubmitGpfifoEx(ref SubmitGpfifoArguments arguments, Span inlineData) + { + return SubmitGpfifo(ref arguments, inlineData); + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/AllocGpfifoExArguments.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/AllocGpfifoExArguments.cs new file mode 100644 index 0000000000..8e5a15235a --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/AllocGpfifoExArguments.cs @@ -0,0 +1,17 @@ +using Ryujinx.HLE.HOS.Services.Nv.Types; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct AllocGpfifoExArguments + { + public uint NumEntries; + public uint NumJobs; + public uint Flags; + public NvFence Fence; + public uint Reserved1; + public uint Reserved2; + public uint Reserved3; + } +} diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/AllocObjCtxArguments.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/AllocObjCtxArguments.cs new file mode 100644 index 0000000000..fae91622cd --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/AllocObjCtxArguments.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct AllocObjCtxArguments + { + public uint ClassNumber; + public uint Flags; + public ulong ObjectId; + } +} diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/GetParameterArguments.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/GetParameterArguments.cs new file mode 100644 index 0000000000..425e665f42 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/GetParameterArguments.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct GetParameterArguments + { + public uint Parameter; + public uint Value; + } +} diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/MapCommandBufferArguments.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/MapCommandBufferArguments.cs new file mode 100644 index 0000000000..6a7e3da803 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/MapCommandBufferArguments.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct CommandBufferHandle + { + public int MapHandle; + public int MapAddress; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct MapCommandBufferArguments + { + public int NumEntries; + public int DataAddress; // Ignored by the driver. + public bool AttachHostChDas; + public byte Padding1; + public short Padding2; + } +} diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/NvChannel.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/NvChannel.cs new file mode 100644 index 0000000000..22cfba3d6c --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/NvChannel.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel +{ + class NvChannel + { + public int Timeout; + public int SubmitTimeout; + public int Timeslice; + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/NvChannelPriority.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/NvChannelPriority.cs new file mode 100644 index 0000000000..4112a9fcc6 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/NvChannelPriority.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel +{ + enum NvChannelPriority : uint + { + Low = 50, + Medium = 100, + High = 150 + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SetErrorNotifierArguments.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SetErrorNotifierArguments.cs new file mode 100644 index 0000000000..1aba53ca6f --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SetErrorNotifierArguments.cs @@ -0,0 +1,13 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct SetErrorNotifierArguments + { + public ulong Offset; + public ulong Size; + public uint Mem; + public uint Reserved; + } +} diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SubmitArguments.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SubmitArguments.cs new file mode 100644 index 0000000000..bb2fd1cc07 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SubmitArguments.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct CommandBuffer + { + public int MemoryId; + public int Offset; + public int WordsCount; + } + + [StructLayout(LayoutKind.Sequential)] + struct SubmitArguments + { + public int CmdBufsCount; + public int RelocsCount; + public int SyncptIncrsCount; + public int WaitchecksCount; + } +} diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SubmitGpfifoArguments.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SubmitGpfifoArguments.cs new file mode 100644 index 0000000000..18cdde06b9 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SubmitGpfifoArguments.cs @@ -0,0 +1,14 @@ +using Ryujinx.HLE.HOS.Services.Nv.Types; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct SubmitGpfifoArguments + { + public long Address; + public int NumEntries; + public int Flags; + public NvFence Fence; + } +} diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/ZcullBindArguments.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/ZcullBindArguments.cs new file mode 100644 index 0000000000..19a997f433 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/ZcullBindArguments.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct ZcullBindArguments + { + public ulong GpuVirtualAddress; + public uint Mode; + public uint Reserved; + } +} diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/NvHostCtrlDeviceFile.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/NvHostCtrlDeviceFile.cs new file mode 100644 index 0000000000..e740350edd --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/NvHostCtrlDeviceFile.cs @@ -0,0 +1,401 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl.Types; +using Ryujinx.HLE.HOS.Services.Nv.Types; +using Ryujinx.HLE.HOS.Services.Settings; + +using System; +using System.Text; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl +{ + internal class NvHostCtrlDeviceFile : NvDeviceFile + { + private const int EventsCount = 64; + + private bool _isProductionMode; + private NvHostSyncpt _syncpt; + private NvHostEvent[] _events; + private KEvent _dummyEvent; + + public NvHostCtrlDeviceFile(ServiceCtx context) : base(context) + { + if (NxSettings.Settings.TryGetValue("nv!rmos_set_production_mode", out object productionModeSetting)) + { + _isProductionMode = ((string)productionModeSetting) != "0"; // Default value is "" + } + else + { + _isProductionMode = true; + } + + _syncpt = new NvHostSyncpt(); + _events = new NvHostEvent[EventsCount]; + _dummyEvent = new KEvent(context.Device.System); + } + + public override NvInternalResult Ioctl(NvIoctl command, Span arguments) + { + NvInternalResult result = NvInternalResult.NotImplemented; + + if (command.Type == NvIoctl.NvHostCustomMagic) + { + switch (command.Number) + { + case 0x14: + result = CallIoctlMethod(SyncptRead, arguments); + break; + case 0x15: + result = CallIoctlMethod(SyncptIncr, arguments); + break; + case 0x16: + result = CallIoctlMethod(SyncptWait, arguments); + break; + case 0x19: + result = CallIoctlMethod(SyncptWaitEx, arguments); + break; + case 0x1a: + result = CallIoctlMethod(SyncptReadMax, arguments); + break; + case 0x1b: + // As Marshal cannot handle unaligned arrays, we do everything by hand here. + GetConfigurationArguments configArgument = GetConfigurationArguments.FromSpan(arguments); + result = GetConfig(configArgument); + + if (result == NvInternalResult.Success) + { + configArgument.CopyTo(arguments); + } + break; + case 0x1d: + result = CallIoctlMethod(EventWait, arguments); + break; + case 0x1e: + result = CallIoctlMethod(EventWaitAsync, arguments); + break; + case 0x1f: + result = CallIoctlMethod(EventRegister, arguments); + break; + } + } + + return result; + } + + public override NvInternalResult QueryEvent(out int eventHandle, uint eventId) + { + // TODO: implement SyncPts <=> KEvent logic accurately. For now we return a dummy event. + KEvent targetEvent = _dummyEvent; + + if (targetEvent != null) + { + if (Owner.HandleTable.GenerateHandle(targetEvent.ReadableEvent, out eventHandle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + else + { + eventHandle = 0; + + return NvInternalResult.InvalidInput; + } + + return NvInternalResult.Success; + } + + private NvInternalResult SyncptRead(ref NvFence arguments) + { + return SyncptReadMinOrMax(ref arguments, max: false); + } + + private NvInternalResult SyncptIncr(ref uint id) + { + if (id >= NvHostSyncpt.SyncptsCount) + { + return NvInternalResult.InvalidInput; + } + + _syncpt.Increment((int)id); + + return NvInternalResult.Success; + } + + private NvInternalResult SyncptWait(ref SyncptWaitArguments arguments) + { + return SyncptWait(ref arguments, out _); + } + + private NvInternalResult SyncptWaitEx(ref SyncptWaitExArguments arguments) + { + return SyncptWait(ref arguments.Input, out arguments.Value); + } + + private NvInternalResult SyncptReadMax(ref NvFence arguments) + { + return SyncptReadMinOrMax(ref arguments, max: true); + } + + private NvInternalResult GetConfig(GetConfigurationArguments arguments) + { + if (!_isProductionMode && NxSettings.Settings.TryGetValue($"{arguments.Domain}!{arguments.Parameter}".ToLower(), out object nvSetting)) + { + byte[] settingBuffer = new byte[0x101]; + + if (nvSetting is string stringValue) + { + if (stringValue.Length > 0x100) + { + Logger.PrintError(LogClass.ServiceNv, $"{arguments.Domain}!{arguments.Parameter} String value size is too big!"); + } + else + { + settingBuffer = Encoding.ASCII.GetBytes(stringValue + "\0"); + } + } + else if (nvSetting is int intValue) + { + settingBuffer = BitConverter.GetBytes(intValue); + } + else if (nvSetting is bool boolValue) + { + settingBuffer[0] = boolValue ? (byte)1 : (byte)0; + } + else + { + throw new NotImplementedException(nvSetting.GetType().Name); + } + + Logger.PrintDebug(LogClass.ServiceNv, $"Got setting {arguments.Domain}!{arguments.Parameter}"); + + arguments.Configuration = settingBuffer; + + return NvInternalResult.Success; + } + + // NOTE: This actually return NotAvailableInProduction but this is directly translated as a InvalidInput before returning the ioctl. + //return NvInternalResult.NotAvailableInProduction; + return NvInternalResult.InvalidInput; + } + + private NvInternalResult EventWait(ref EventWaitArguments arguments) + { + return EventWait(ref arguments, async: false); + } + + private NvInternalResult EventWaitAsync(ref EventWaitArguments arguments) + { + return EventWait(ref arguments, async: true); + } + + private NvInternalResult EventRegister(ref uint userEventId) + { + Logger.PrintStub(LogClass.ServiceNv); + + return NvInternalResult.Success; + } + + private NvInternalResult SyncptReadMinOrMax(ref NvFence arguments, bool max) + { + if (arguments.Id >= NvHostSyncpt.SyncptsCount) + { + return NvInternalResult.InvalidInput; + } + + if (max) + { + arguments.Value = (uint)_syncpt.GetMax((int)arguments.Id); + } + else + { + arguments.Value = (uint)_syncpt.GetMin((int)arguments.Id); + } + + return NvInternalResult.Success; + } + + private NvInternalResult SyncptWait(ref SyncptWaitArguments arguments, out int value) + { + if (arguments.Id >= NvHostSyncpt.SyncptsCount) + { + value = 0; + + return NvInternalResult.InvalidInput; + } + + NvInternalResult result; + + if (_syncpt.MinCompare((int)arguments.Id, arguments.Thresh)) + { + result = NvInternalResult.Success; + } + else if (arguments.Timeout == 0) + { + result = NvInternalResult.TryAgain; + } + else + { + Logger.PrintDebug(LogClass.ServiceNv, $"Waiting syncpt with timeout of {arguments.Timeout}ms..."); + + using (ManualResetEvent waitEvent = new ManualResetEvent(false)) + { + _syncpt.AddWaiter(arguments.Thresh, waitEvent); + + // Note: Negative (> INT_MAX) timeouts aren't valid on .NET, + // in this case we just use the maximum timeout possible. + int timeout = arguments.Timeout; + + if (timeout < -1) + { + timeout = int.MaxValue; + } + + if (timeout == -1) + { + waitEvent.WaitOne(); + + result = NvInternalResult.Success; + } + else if (waitEvent.WaitOne(timeout)) + { + result = NvInternalResult.Success; + } + else + { + result = NvInternalResult.TimedOut; + } + } + + Logger.PrintDebug(LogClass.ServiceNv, "Resuming..."); + } + + value = _syncpt.GetMin((int)arguments.Id); + + return result; + } + + private NvInternalResult EventWait(ref EventWaitArguments arguments, bool async) + { + if (arguments.Id >= NvHostSyncpt.SyncptsCount) + { + return NvInternalResult.InvalidInput; + } + + if (_syncpt.MinCompare(arguments.Id, arguments.Thresh)) + { + arguments.Value = _syncpt.GetMin(arguments.Id); + + return NvInternalResult.Success; + } + + if (!async) + { + arguments.Value = 0; + } + + if (arguments.Timeout == 0) + { + return NvInternalResult.TryAgain; + } + + NvHostEvent Event; + + NvInternalResult result; + + int eventIndex; + + if (async) + { + eventIndex = arguments.Value; + + if ((uint)eventIndex >= EventsCount) + { + return NvInternalResult.InvalidInput; + } + + Event = _events[eventIndex]; + } + else + { + Event = GetFreeEvent(arguments.Id, out eventIndex); + } + + if (Event != null && + (Event.State == NvHostEventState.Registered || + Event.State == NvHostEventState.Free)) + { + Event.Id = arguments.Id; + Event.Thresh = arguments.Thresh; + + Event.State = NvHostEventState.Waiting; + + if (!async) + { + arguments.Value = ((arguments.Id & 0xfff) << 16) | 0x10000000; + } + else + { + arguments.Value = arguments.Id << 4; + } + + arguments.Value |= eventIndex; + + result = NvInternalResult.TryAgain; + } + else + { + result = NvInternalResult.InvalidInput; + } + + return result; + } + + private NvHostEvent GetFreeEvent(int id, out int eventIndex) + { + eventIndex = EventsCount; + + int nullIndex = EventsCount; + + for (int index = 0; index < EventsCount; index++) + { + NvHostEvent Event = _events[index]; + + if (Event != null) + { + if (Event.State == NvHostEventState.Registered || + Event.State == NvHostEventState.Free) + { + eventIndex = index; + + if (Event.Id == id) + { + return Event; + } + } + } + else if (nullIndex == EventsCount) + { + nullIndex = index; + } + } + + if (nullIndex < EventsCount) + { + eventIndex = nullIndex; + + return _events[nullIndex] = new NvHostEvent(); + } + + if (eventIndex < EventsCount) + { + return _events[eventIndex]; + } + + return null; + } + + public override void Close() { } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/EventWaitArguments.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/EventWaitArguments.cs new file mode 100644 index 0000000000..3f97da1f7f --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/EventWaitArguments.cs @@ -0,0 +1,13 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct EventWaitArguments + { + public int Id; + public int Thresh; + public int Timeout; + public int Value; + } +} diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/GetConfigurationArguments.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/GetConfigurationArguments.cs new file mode 100644 index 0000000000..3ee318a379 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/GetConfigurationArguments.cs @@ -0,0 +1,34 @@ +using System; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl.Types +{ + class GetConfigurationArguments + { + public string Domain; + public string Parameter; + public byte[] Configuration; + + public static GetConfigurationArguments FromSpan(Span span) + { + string domain = Encoding.ASCII.GetString(span.Slice(0, 0x41)); + string parameter = Encoding.ASCII.GetString(span.Slice(0x41, 0x41)); + + GetConfigurationArguments result = new GetConfigurationArguments + { + Domain = domain.Substring(0, domain.IndexOf('\0')), + Parameter = parameter.Substring(0, parameter.IndexOf('\0')), + Configuration = span.Slice(0x82, 0x101).ToArray() + }; + + return result; + } + + public void CopyTo(Span span) + { + Encoding.ASCII.GetBytes(Domain + '\0').CopyTo(span.Slice(0, 0x41)); + Encoding.ASCII.GetBytes(Parameter + '\0').CopyTo(span.Slice(0x41, 0x41)); + Configuration.CopyTo(span.Slice(0x82, 0x101)); + } + } +} diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvHostCtrl/NvHostEvent.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostEvent.cs similarity index 66% rename from Ryujinx.HLE/OsHle/Services/Nv/NvHostCtrl/NvHostEvent.cs rename to Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostEvent.cs index bb294d7275..c10e256ec2 100644 --- a/Ryujinx.HLE/OsHle/Services/Nv/NvHostCtrl/NvHostEvent.cs +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostEvent.cs @@ -1,4 +1,4 @@ -namespace Ryujinx.HLE.OsHle.Services.Nv.NvHostCtrl +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl { class NvHostEvent { diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvHostCtrl/NvHostEventState.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostEventState.cs similarity index 68% rename from Ryujinx.HLE/OsHle/Services/Nv/NvHostCtrl/NvHostEventState.cs rename to Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostEventState.cs index fa4583b8ce..521ae9addb 100644 --- a/Ryujinx.HLE/OsHle/Services/Nv/NvHostCtrl/NvHostEventState.cs +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostEventState.cs @@ -1,4 +1,4 @@ -namespace Ryujinx.HLE.OsHle.Services.Nv.NvHostCtrl +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl { enum NvHostEventState { diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostSyncPt.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostSyncPt.cs new file mode 100644 index 0000000000..8ef45043ff --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostSyncPt.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl +{ + class NvHostSyncpt + { + public const int SyncptsCount = 192; + + private int[] _counterMin; + private int[] _counterMax; + + private long _eventMask; + + private ConcurrentDictionary _waiters; + + public NvHostSyncpt() + { + _counterMin = new int[SyncptsCount]; + _counterMax = new int[SyncptsCount]; + + _waiters = new ConcurrentDictionary(); + } + + public int GetMin(int id) + { + return _counterMin[id]; + } + + public int GetMax(int id) + { + return _counterMax[id]; + } + + public int Increment(int id) + { + if (((_eventMask >> id) & 1) != 0) + { + Interlocked.Increment(ref _counterMax[id]); + } + + return IncrementMin(id); + } + + public int IncrementMin(int id) + { + int value = Interlocked.Increment(ref _counterMin[id]); + + WakeUpWaiters(id, value); + + return value; + } + + public int IncrementMax(int id) + { + return Interlocked.Increment(ref _counterMax[id]); + } + + public void AddWaiter(int threshold, EventWaitHandle waitEvent) + { + if (!_waiters.TryAdd(waitEvent, threshold)) + { + throw new InvalidOperationException(); + } + } + + public bool RemoveWaiter(EventWaitHandle waitEvent) + { + return _waiters.TryRemove(waitEvent, out _); + } + + private void WakeUpWaiters(int id, int newValue) + { + foreach (KeyValuePair kv in _waiters) + { + if (MinCompare(id, newValue, _counterMax[id], kv.Value)) + { + kv.Key.Set(); + + _waiters.TryRemove(kv.Key, out _); + } + } + } + + public bool MinCompare(int id, int threshold) + { + return MinCompare(id, _counterMin[id], _counterMax[id], threshold); + } + + private bool MinCompare(int id, int min, int max, int threshold) + { + int minDiff = min - threshold; + int maxDiff = max - threshold; + + if (((_eventMask >> id) & 1) != 0) + { + return minDiff >= 0; + } + else + { + return (uint)maxDiff >= (uint)minDiff; + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/SyncptWaitArguments.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/SyncptWaitArguments.cs new file mode 100644 index 0000000000..13ea89be05 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/SyncptWaitArguments.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct SyncptWaitArguments + { + public uint Id; + public int Thresh; + public int Timeout; + } +} diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/SyncptWaitExArguments.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/SyncptWaitExArguments.cs new file mode 100644 index 0000000000..d04748ba30 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/SyncptWaitExArguments.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct SyncptWaitExArguments + { + public SyncptWaitArguments Input; + public int Value; + } +} diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/NvHostCtrlGpuDeviceFile.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/NvHostCtrlGpuDeviceFile.cs new file mode 100644 index 0000000000..ac7092a6a0 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/NvHostCtrlGpuDeviceFile.cs @@ -0,0 +1,239 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu.Types; +using System; +using System.Diagnostics; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu +{ + class NvHostCtrlGpuDeviceFile : NvDeviceFile + { + private static Stopwatch _pTimer = new Stopwatch(); + private static double _ticksToNs = (1.0 / Stopwatch.Frequency) * 1_000_000_000; + + private KEvent _errorEvent; + private KEvent _unknownEvent; + + public NvHostCtrlGpuDeviceFile(ServiceCtx context) : base(context) + { + _errorEvent = new KEvent(context.Device.System); + _unknownEvent = new KEvent(context.Device.System); + } + + static NvHostCtrlGpuDeviceFile() + { + _pTimer.Start(); + } + + public override NvInternalResult Ioctl(NvIoctl command, Span arguments) + { + NvInternalResult result = NvInternalResult.NotImplemented; + + if (command.Type == NvIoctl.NvGpuMagic) + { + switch (command.Number) + { + case 0x01: + result = CallIoctlMethod(ZcullGetCtxSize, arguments); + break; + case 0x02: + result = CallIoctlMethod(ZcullGetInfo, arguments); + break; + case 0x03: + result = CallIoctlMethod(ZbcSetTable, arguments); + break; + case 0x05: + result = CallIoctlMethod(GetCharacteristics, arguments); + break; + case 0x06: + result = CallIoctlMethod(GetTpcMasks, arguments); + break; + case 0x14: + result = CallIoctlMethod(GetActiveSlotMask, arguments); + break; + case 0x1c: + result = CallIoctlMethod(GetGpuTime, arguments); + break; + } + } + + return result; + } + + public override NvInternalResult Ioctl3(NvIoctl command, Span arguments, Span inlineOutBuffer) + { + NvInternalResult result = NvInternalResult.NotImplemented; + + if (command.Type == NvIoctl.NvGpuMagic) + { + switch (command.Number) + { + case 0x05: + result = CallIoctlMethod(GetCharacteristics, arguments, inlineOutBuffer); + break; + case 0x06: + result = CallIoctlMethod(GetTpcMasks, arguments, inlineOutBuffer); + break; + } + } + + return result; + } + + public override NvInternalResult QueryEvent(out int eventHandle, uint eventId) + { + // TODO: accurately represent and implement those events. + KEvent targetEvent = null; + + switch (eventId) + { + case 0x1: + targetEvent = _errorEvent; + break; + case 0x2: + targetEvent = _unknownEvent; + break; + } + + if (targetEvent != null) + { + if (Owner.HandleTable.GenerateHandle(targetEvent.ReadableEvent, out eventHandle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + else + { + eventHandle = 0; + + return NvInternalResult.InvalidInput; + } + + return NvInternalResult.Success; + } + + public override void Close() { } + + private NvInternalResult ZcullGetCtxSize(ref ZcullGetCtxSizeArguments arguments) + { + arguments.Size = 1; + + return NvInternalResult.Success; + } + + private NvInternalResult ZcullGetInfo(ref ZcullGetInfoArguments arguments) + { + arguments.WidthAlignPixels = 0x20; + arguments.HeightAlignPixels = 0x20; + arguments.PixelSquaresByAliquots = 0x400; + arguments.AliquotTotal = 0x800; + arguments.RegionByteMultiplier = 0x20; + arguments.RegionHeaderSize = 0x20; + arguments.SubregionHeaderSize = 0xc0; + arguments.SubregionWidthAlignPixels = 0x20; + arguments.SubregionHeightAlignPixels = 0x40; + arguments.SubregionCount = 0x10; + + return NvInternalResult.Success; + } + + private NvInternalResult ZbcSetTable(ref ZbcSetTableArguments arguments) + { + Logger.PrintStub(LogClass.ServiceNv); + + return NvInternalResult.Success; + } + + private NvInternalResult GetCharacteristics(ref GetCharacteristicsArguments arguments) + { + return GetCharacteristics(ref arguments, ref arguments.Characteristics); + } + + private NvInternalResult GetCharacteristics(ref GetCharacteristicsArguments arguments, ref GpuCharacteristics characteristics) + { + arguments.Header.BufferSize = 0xa0; + + characteristics.Arch = 0x120; + characteristics.Impl = 0xb; + characteristics.Rev = 0xa1; + characteristics.NumGpc = 0x1; + characteristics.L2CacheSize = 0x40000; + characteristics.OnBoardVideoMemorySize = 0x0; + characteristics.NumTpcPerGpc = 0x2; + characteristics.BusType = 0x20; + characteristics.BigPageSize = 0x20000; + characteristics.CompressionPageSize = 0x20000; + characteristics.PdeCoverageBitCount = 0x1b; + characteristics.AvailableBigPageSizes = 0x30000; + characteristics.GpcMask = 0x1; + characteristics.SmArchSmVersion = 0x503; + characteristics.SmArchSpaVersion = 0x503; + characteristics.SmArchWarpCount = 0x80; + characteristics.GpuVaBitCount = 0x28; + characteristics.Reserved = 0x0; + characteristics.Flags = 0x55; + characteristics.TwodClass = 0x902d; + characteristics.ThreedClass = 0xb197; + characteristics.ComputeClass = 0xb1c0; + characteristics.GpfifoClass = 0xb06f; + characteristics.InlineToMemoryClass = 0xa140; + characteristics.DmaCopyClass = 0xb0b5; + characteristics.MaxFbpsCount = 0x1; + characteristics.FbpEnMask = 0x0; + characteristics.MaxLtcPerFbp = 0x2; + characteristics.MaxLtsPerLtc = 0x1; + characteristics.MaxTexPerTpc = 0x0; + characteristics.MaxGpcCount = 0x1; + characteristics.RopL2EnMask0 = 0x21d70; + characteristics.RopL2EnMask1 = 0x0; + characteristics.ChipName = 0x6230326d67; + characteristics.GrCompbitStoreBaseHw = 0x0; + + arguments.Characteristics = characteristics; + + return NvInternalResult.Success; + } + + private NvInternalResult GetTpcMasks(ref GetTpcMasksArguments arguments) + { + return GetTpcMasks(ref arguments, ref arguments.TpcMask); + } + + private NvInternalResult GetTpcMasks(ref GetTpcMasksArguments arguments, ref int tpcMask) + { + if (arguments.MaskBufferSize != 0) + { + tpcMask = 3; + arguments.TpcMask = tpcMask; + } + + return NvInternalResult.Success; + } + + private NvInternalResult GetActiveSlotMask(ref GetActiveSlotMaskArguments arguments) + { + Logger.PrintStub(LogClass.ServiceNv); + + arguments.Slot = 0x07; + arguments.Mask = 0x01; + + return NvInternalResult.Success; + } + + private NvInternalResult GetGpuTime(ref GetGpuTimeArguments arguments) + { + arguments.Timestamp = GetPTimerNanoSeconds(); + + return NvInternalResult.Success; + } + + private static ulong GetPTimerNanoSeconds() + { + double ticks = _pTimer.ElapsedTicks; + + return (ulong)(ticks * _ticksToNs) & 0xff_ffff_ffff_ffff; + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetActiveSlotMaskArguments.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetActiveSlotMaskArguments.cs new file mode 100644 index 0000000000..fd73be9e79 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetActiveSlotMaskArguments.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct GetActiveSlotMaskArguments + { + public int Slot; + public int Mask; + } +} diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvGpuGpu/NvGpuGpuGetCharacteristics.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetCharacteristicsArguments.cs similarity index 74% rename from Ryujinx.HLE/OsHle/Services/Nv/NvGpuGpu/NvGpuGpuGetCharacteristics.cs rename to Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetCharacteristicsArguments.cs index a519fed1fe..5b44109a1e 100644 --- a/Ryujinx.HLE/OsHle/Services/Nv/NvGpuGpu/NvGpuGpuGetCharacteristics.cs +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetCharacteristicsArguments.cs @@ -1,9 +1,10 @@ -namespace Ryujinx.HLE.OsHle.Services.Nv.NvGpuGpu +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu.Types { - struct NvGpuGpuGetCharacteristics + [StructLayout(LayoutKind.Sequential)] + struct GpuCharacteristics { - public long BufferSize; - public long BufferAddress; public int Arch; public int Impl; public int Rev; @@ -40,4 +41,17 @@ namespace Ryujinx.HLE.OsHle.Services.Nv.NvGpuGpu public long ChipName; public long GrCompbitStoreBaseHw; } -} \ No newline at end of file + + struct CharacteristicsHeader + { + public long BufferSize; + public long BufferAddress; + } + + [StructLayout(LayoutKind.Sequential)] + struct GetCharacteristicsArguments + { + public CharacteristicsHeader Header; + public GpuCharacteristics Characteristics; + } +} diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetGpuTimeArguments.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetGpuTimeArguments.cs new file mode 100644 index 0000000000..084ef71fb5 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetGpuTimeArguments.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct GetGpuTimeArguments + { + public ulong Timestamp; + public ulong Reserved; + } +} diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetTpcMasksArguments.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetTpcMasksArguments.cs new file mode 100644 index 0000000000..16ef2d6e12 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetTpcMasksArguments.cs @@ -0,0 +1,15 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu.Types +{ + + [StructLayout(LayoutKind.Sequential)] + struct GetTpcMasksArguments + { + public int MaskBufferSize; + public int Reserved; + public long MaskBufferAddress; + public int TpcMask; + public int Padding; + } +} diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/ZbcSetTableArguments.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/ZbcSetTableArguments.cs new file mode 100644 index 0000000000..ed74cc2631 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/ZbcSetTableArguments.cs @@ -0,0 +1,49 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct ZbcColorArray + { + private uint element0; + private uint element1; + private uint element2; + private uint element3; + + public uint this[int index] + { + get + { + if (index == 0) + { + return element0; + } + else if (index == 1) + { + return element1; + } + else if (index == 2) + { + return element2; + } + else if (index == 2) + { + return element3; + } + + throw new IndexOutOfRangeException(); + } + } + } + + [StructLayout(LayoutKind.Sequential)] + struct ZbcSetTableArguments + { + public ZbcColorArray ColorDs; + public ZbcColorArray ColorL2; + public uint Depth; + public uint Format; + public uint Type; + } +} diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/ZcullGetCtxSizeArguments.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/ZcullGetCtxSizeArguments.cs new file mode 100644 index 0000000000..1e668f867a --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/ZcullGetCtxSizeArguments.cs @@ -0,0 +1,10 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct ZcullGetCtxSizeArguments + { + public int Size; + } +} diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvGpuGpu/NvGpuGpuZcullGetInfo.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/ZcullGetInfoArguments.cs similarity index 68% rename from Ryujinx.HLE/OsHle/Services/Nv/NvGpuGpu/NvGpuGpuZcullGetInfo.cs rename to Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/ZcullGetInfoArguments.cs index 168051ed3c..d0d152a3de 100644 --- a/Ryujinx.HLE/OsHle/Services/Nv/NvGpuGpu/NvGpuGpuZcullGetInfo.cs +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/ZcullGetInfoArguments.cs @@ -1,6 +1,9 @@ -namespace Ryujinx.HLE.OsHle.Services.Nv.NvGpuGpu +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu.Types { - struct NvGpuGpuZcullGetInfo + [StructLayout(LayoutKind.Sequential)] + struct ZcullGetInfoArguments { public int WidthAlignPixels; public int HeightAlignPixels; diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvInternalResult.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvInternalResult.cs new file mode 100644 index 0000000000..9345baeb57 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvInternalResult.cs @@ -0,0 +1,32 @@ +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices +{ + enum NvInternalResult : int + { + Success = 0, + OperationNotPermitted = -1, + NoEntry = -2, + Interrupted = -4, + IoError = -5, + DeviceNotFound = -6, + BadFileNumber = -9, + TryAgain = -11, + OutOfMemory = -12, + AccessDenied = -13, + BadAddress = -14, + Busy = -16, + NotADirectory = -20, + InvalidInput = -22, + FileTableOverflow = -23, + Unknown0x18 = -24, + NotSupported = -25, + FileTooBig = -27, + NoSpaceLeft = -28, + ReadOnlyAttribute = -30, + NotImplemented = -38, + InvalidState = -40, + Restart = -85, + InvalidAddress = -99, + TimedOut = -110, + Unknown0x72 = -114, + } +} diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/NvMapDeviceFile.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/NvMapDeviceFile.cs new file mode 100644 index 0000000000..d03ede9445 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/NvMapDeviceFile.cs @@ -0,0 +1,271 @@ +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.Gpu.Memory; +using Ryujinx.HLE.HOS.Kernel.Process; +using System; +using System.Collections.Concurrent; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap +{ + internal class NvMapDeviceFile : NvDeviceFile + { + private const int FlagNotFreedYet = 1; + + private static ConcurrentDictionary _maps = new ConcurrentDictionary(); + + public NvMapDeviceFile(ServiceCtx context) : base(context) + { + IdDictionary dict = _maps.GetOrAdd(Owner, (key) => new IdDictionary()); + + dict.Add(0, new NvMapHandle()); + } + + public override NvInternalResult Ioctl(NvIoctl command, Span arguments) + { + NvInternalResult result = NvInternalResult.NotImplemented; + + if (command.Type == NvIoctl.NvMapCustomMagic) + { + switch (command.Number) + { + case 0x01: + result = CallIoctlMethod(Create, arguments); + break; + case 0x03: + result = CallIoctlMethod(FromId, arguments); + break; + case 0x04: + result = CallIoctlMethod(Alloc, arguments); + break; + case 0x05: + result = CallIoctlMethod(Free, arguments); + break; + case 0x09: + result = CallIoctlMethod(Param, arguments); + break; + case 0x0e: + result = CallIoctlMethod(GetId, arguments); + break; + case 0x02: + case 0x06: + case 0x07: + case 0x08: + case 0x0a: + case 0x0c: + case 0x0d: + case 0x0f: + case 0x10: + case 0x11: + result = NvInternalResult.NotSupported; + break; + } + } + + return result; + } + + private NvInternalResult Create(ref NvMapCreate arguments) + { + if (arguments.Size == 0) + { + Logger.PrintWarning(LogClass.ServiceNv, $"Invalid size 0x{arguments.Size:x8}!"); + + return NvInternalResult.InvalidInput; + } + + int size = BitUtils.AlignUp(arguments.Size, (int)MemoryManager.PageSize); + + arguments.Handle = CreateHandleFromMap(new NvMapHandle(size)); + + Logger.PrintInfo(LogClass.ServiceNv, $"Created map {arguments.Handle} with size 0x{size:x8}!"); + + return NvInternalResult.Success; + } + + private NvInternalResult FromId(ref NvMapFromId arguments) + { + NvMapHandle map = GetMapFromHandle(Owner, arguments.Id); + + if (map == null) + { + Logger.PrintWarning(LogClass.ServiceNv, $"Invalid handle 0x{arguments.Handle:x8}!"); + + return NvInternalResult.InvalidInput; + } + + map.IncrementRefCount(); + + arguments.Handle = arguments.Id; + + return NvInternalResult.Success; + } + + private NvInternalResult Alloc(ref NvMapAlloc arguments) + { + NvMapHandle map = GetMapFromHandle(Owner, arguments.Handle); + + if (map == null) + { + Logger.PrintWarning(LogClass.ServiceNv, $"Invalid handle 0x{arguments.Handle:x8}!"); + + return NvInternalResult.InvalidInput; + } + + if ((arguments.Align & (arguments.Align - 1)) != 0) + { + Logger.PrintWarning(LogClass.ServiceNv, $"Invalid alignment 0x{arguments.Align:x8}!"); + + return NvInternalResult.InvalidInput; + } + + if ((uint)arguments.Align < MemoryManager.PageSize) + { + arguments.Align = (int)MemoryManager.PageSize; + } + + NvInternalResult result = NvInternalResult.Success; + + if (!map.Allocated) + { + map.Allocated = true; + + map.Align = arguments.Align; + map.Kind = (byte)arguments.Kind; + + int size = BitUtils.AlignUp(map.Size, (int)MemoryManager.PageSize); + + long address = arguments.Address; + + if (address == 0) + { + // When the address is zero, we need to allocate + // our own backing memory for the NvMap. + // TODO: Is this allocation inside the transfer memory? + result = NvInternalResult.OutOfMemory; + } + + if (result == NvInternalResult.Success) + { + map.Size = size; + map.Address = address; + } + } + + return result; + } + + private NvInternalResult Free(ref NvMapFree arguments) + { + NvMapHandle map = GetMapFromHandle(Owner, arguments.Handle); + + if (map == null) + { + Logger.PrintWarning(LogClass.ServiceNv, $"Invalid handle 0x{arguments.Handle:x8}!"); + + return NvInternalResult.InvalidInput; + } + + if (map.DecrementRefCount() <= 0) + { + DeleteMapWithHandle(arguments.Handle); + + Logger.PrintInfo(LogClass.ServiceNv, $"Deleted map {arguments.Handle}!"); + + arguments.Address = map.Address; + arguments.Flags = 0; + } + else + { + arguments.Address = 0; + arguments.Flags = FlagNotFreedYet; + } + + arguments.Size = map.Size; + + return NvInternalResult.Success; + } + + private NvInternalResult Param(ref NvMapParam arguments) + { + NvMapHandle map = GetMapFromHandle(Owner, arguments.Handle); + + if (map == null) + { + Logger.PrintWarning(LogClass.ServiceNv, $"Invalid handle 0x{arguments.Handle:x8}!"); + + return NvInternalResult.InvalidInput; + } + + switch (arguments.Param) + { + case NvMapHandleParam.Size: arguments.Result = map.Size; break; + case NvMapHandleParam.Align: arguments.Result = map.Align; break; + case NvMapHandleParam.Heap: arguments.Result = 0x40000000; break; + case NvMapHandleParam.Kind: arguments.Result = map.Kind; break; + case NvMapHandleParam.Compr: arguments.Result = 0; break; + + // Note: Base is not supported and returns an error. + // Any other value also returns an error. + default: return NvInternalResult.InvalidInput; + } + + return NvInternalResult.Success; + } + + private NvInternalResult GetId(ref NvMapGetId arguments) + { + NvMapHandle map = GetMapFromHandle(Owner, arguments.Handle); + + if (map == null) + { + Logger.PrintWarning(LogClass.ServiceNv, $"Invalid handle 0x{arguments.Handle:x8}!"); + + return NvInternalResult.InvalidInput; + } + + arguments.Id = arguments.Handle; + + return NvInternalResult.Success; + } + + public override void Close() + { + // TODO: refcount NvMapDeviceFile instances and remove when closing + // _maps.TryRemove(GetOwner(), out _); + } + + private int CreateHandleFromMap(NvMapHandle map) + { + IdDictionary dict = _maps.GetOrAdd(Owner, (key) => + { + IdDictionary newDict = new IdDictionary(); + + newDict.Add(0, new NvMapHandle()); + + return newDict; + }); + + return dict.Add(map); + } + + private bool DeleteMapWithHandle(int handle) + { + if (_maps.TryGetValue(Owner, out IdDictionary dict)) + { + return dict.Delete(handle) != null; + } + + return false; + } + + public static NvMapHandle GetMapFromHandle(KProcess process, int handle, bool allowHandleZero = false) + { + if ((allowHandleZero || handle != 0) && _maps.TryGetValue(process, out IdDictionary dict)) + { + return dict.GetData(handle); + } + + return null; + } + } +} diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvMap/NvMapAlloc.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapAlloc.cs similarity index 59% rename from Ryujinx.HLE/OsHle/Services/Nv/NvMap/NvMapAlloc.cs rename to Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapAlloc.cs index 10634b860e..efc0f2aa3b 100644 --- a/Ryujinx.HLE/OsHle/Services/Nv/NvMap/NvMapAlloc.cs +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapAlloc.cs @@ -1,5 +1,8 @@ -namespace Ryujinx.HLE.OsHle.Services.Nv.NvMap +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap { + [StructLayout(LayoutKind.Sequential)] struct NvMapAlloc { public int Handle; diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapCreate.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapCreate.cs new file mode 100644 index 0000000000..b47e462941 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapCreate.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap +{ + [StructLayout(LayoutKind.Sequential)] + struct NvMapCreate + { + public int Size; + public int Handle; + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapFree.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapFree.cs new file mode 100644 index 0000000000..d142b9f301 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapFree.cs @@ -0,0 +1,14 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap +{ + [StructLayout(LayoutKind.Sequential)] + struct NvMapFree + { + public int Handle; + public int Padding; + public long Address; + public int Size; + public int Flags; + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapFromId.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapFromId.cs new file mode 100644 index 0000000000..2e559534d1 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapFromId.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap +{ + [StructLayout(LayoutKind.Sequential)] + struct NvMapFromId + { + public int Id; + public int Handle; + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapGetId.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapGetId.cs new file mode 100644 index 0000000000..fe574eea5d --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapGetId.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap +{ + [StructLayout(LayoutKind.Sequential)] + struct NvMapGetId + { + public int Id; + public int Handle; + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvMap/NvMapHandle.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapHandle.cs similarity index 58% rename from Ryujinx.HLE/OsHle/Services/Nv/NvMap/NvMapHandle.cs rename to Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapHandle.cs index 21fce700c3..3903b77c02 100644 --- a/Ryujinx.HLE/OsHle/Services/Nv/NvMap/NvMapHandle.cs +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapHandle.cs @@ -1,6 +1,6 @@ using System.Threading; -namespace Ryujinx.HLE.OsHle.Services.Nv.NvMap +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap { class NvMapHandle { @@ -11,27 +11,28 @@ namespace Ryujinx.HLE.OsHle.Services.Nv.NvMap public int Kind; public long Address; public bool Allocated; + public long DmaMapAddress; - private long Dupes; + private long _dupes; public NvMapHandle() { - Dupes = 1; + _dupes = 1; } - public NvMapHandle(int Size) : this() + public NvMapHandle(int size) : this() { - this.Size = Size; + Size = size; } public void IncrementRefCount() { - Interlocked.Increment(ref Dupes); + Interlocked.Increment(ref _dupes); } public long DecrementRefCount() { - return Interlocked.Decrement(ref Dupes) + 1; + return Interlocked.Decrement(ref _dupes); } } } \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvMap/NvMapHandleParam.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapHandleParam.cs similarity index 58% rename from Ryujinx.HLE/OsHle/Services/Nv/NvMap/NvMapHandleParam.cs rename to Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapHandleParam.cs index ab1b0577d8..61b73cba2e 100644 --- a/Ryujinx.HLE/OsHle/Services/Nv/NvMap/NvMapHandleParam.cs +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapHandleParam.cs @@ -1,6 +1,6 @@ -namespace Ryujinx.HLE.OsHle.Services.Nv.NvMap +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap { - enum NvMapHandleParam + enum NvMapHandleParam : int { Size = 1, Align = 2, diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapParam.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapParam.cs new file mode 100644 index 0000000000..de5bab770d --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapParam.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap +{ + [StructLayout(LayoutKind.Sequential)] + struct NvMapParam + { + public int Handle; + public NvMapHandleParam Param; + public int Result; + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvIoctl.cs b/Ryujinx.HLE/HOS/Services/Nv/NvIoctl.cs new file mode 100644 index 0000000000..0585869496 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nv/NvIoctl.cs @@ -0,0 +1,45 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv +{ + [StructLayout(LayoutKind.Sequential)] + struct NvIoctl + { + public const int NvHostCustomMagic = 0x00; + public const int NvMapCustomMagic = 0x01; + public const int NvGpuAsMagic = 0x41; + public const int NvGpuMagic = 0x47; + public const int NvHostMagic = 0x48; + + private const int NumberBits = 8; + private const int TypeBits = 8; + private const int SizeBits = 14; + private const int DirectionBits = 2; + + private const int NumberShift = 0; + private const int TypeShift = NumberShift + NumberBits; + private const int SizeShift = TypeShift + TypeBits; + private const int DirectionShift = SizeShift + SizeBits; + + private const int NumberMask = (1 << NumberBits) - 1; + private const int TypeMask = (1 << TypeBits) - 1; + private const int SizeMask = (1 << SizeBits) - 1; + private const int DirectionMask = (1 << DirectionBits) - 1; + + [Flags] + public enum Direction : uint + { + None = 0, + Read = 1, + Write = 2, + } + + public uint RawValue; + + public uint Number => (RawValue >> NumberShift) & NumberMask; + public uint Type => (RawValue >> TypeShift) & TypeMask; + public uint Size => (RawValue >> SizeShift) & SizeMask; + public Direction DirectionValue => (Direction)((RawValue >> DirectionShift) & DirectionMask); + } +} diff --git a/Ryujinx.HLE/HOS/Services/Nv/Types/NvFence.cs b/Ryujinx.HLE/HOS/Services/Nv/Types/NvFence.cs new file mode 100644 index 0000000000..1458f482f1 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nv/Types/NvFence.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0x8)] + internal struct NvFence + { + public uint Id; + public uint Value; + } +} diff --git a/Ryujinx.HLE/HOS/Services/Nv/Types/NvIoctlNotImplementedException.cs b/Ryujinx.HLE/HOS/Services/Nv/Types/NvIoctlNotImplementedException.cs new file mode 100644 index 0000000000..9404c18ce2 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nv/Types/NvIoctlNotImplementedException.cs @@ -0,0 +1,55 @@ +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices; +using System; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Nv.Types +{ + class NvIoctlNotImplementedException : Exception + { + public ServiceCtx Context { get; } + public NvDeviceFile DeviceFile { get; } + public NvIoctl Command { get; } + + public NvIoctlNotImplementedException(ServiceCtx context, NvDeviceFile deviceFile, NvIoctl command) + : this(context, deviceFile, command, "The ioctl is not implemented.") + { } + + public NvIoctlNotImplementedException(ServiceCtx context, NvDeviceFile deviceFile, NvIoctl command, string message) + : base(message) + { + Context = context; + DeviceFile = deviceFile; + Command = command; + } + + public override string Message + { + get + { + return base.Message + + Environment.NewLine + + Environment.NewLine + + BuildMessage(); + } + } + + private string BuildMessage() + { + StringBuilder sb = new StringBuilder(); + + sb.AppendLine($"Device File: {DeviceFile.GetType().Name}"); + sb.AppendLine(); + + sb.AppendLine($"Ioctl (0x{Command.RawValue:x8})"); + sb.AppendLine($"\tNumber: 0x{Command.Number:x8}"); + sb.AppendLine($"\tType: 0x{Command.Type:x8}"); + sb.AppendLine($"\tSize: 0x{Command.Size:x8}"); + sb.AppendLine($"\tDirection: {Command.DirectionValue}"); + + sb.AppendLine("Guest Stack Trace:"); + sb.AppendLine(Context.Thread.GetGuestStackTrace()); + + return sb.ToString(); + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Nv/Types/NvQueryEventNotImplementedException.cs b/Ryujinx.HLE/HOS/Services/Nv/Types/NvQueryEventNotImplementedException.cs new file mode 100644 index 0000000000..b7a72eba00 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nv/Types/NvQueryEventNotImplementedException.cs @@ -0,0 +1,51 @@ +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices; +using System; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Nv.Types +{ + class NvQueryEventNotImplementedException : Exception + { + public ServiceCtx Context { get; } + public NvDeviceFile DeviceFile { get; } + public uint EventId { get; } + + public NvQueryEventNotImplementedException(ServiceCtx context, NvDeviceFile deviceFile, uint eventId) + : this(context, deviceFile, eventId, "This query event is not implemented.") + { } + + public NvQueryEventNotImplementedException(ServiceCtx context, NvDeviceFile deviceFile, uint eventId, string message) + : base(message) + { + Context = context; + DeviceFile = deviceFile; + EventId = eventId; + } + + public override string Message + { + get + { + return base.Message + + Environment.NewLine + + Environment.NewLine + + BuildMessage(); + } + } + + private string BuildMessage() + { + StringBuilder sb = new StringBuilder(); + + sb.AppendLine($"Device File: {DeviceFile.GetType().Name}"); + sb.AppendLine(); + + sb.AppendLine($"Event ID: (0x{EventId:x8})"); + + sb.AppendLine("Guest Stack Trace:"); + sb.AppendLine(Context.Thread.GetGuestStackTrace()); + + return sb.ToString(); + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Nv/Types/NvResult.cs b/Ryujinx.HLE/HOS/Services/Nv/Types/NvResult.cs new file mode 100644 index 0000000000..1c9cae8ca6 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nv/Types/NvResult.cs @@ -0,0 +1,30 @@ +namespace Ryujinx.HLE.HOS.Services.Nv +{ + enum NvResult : uint + { + Success = 0, + NotImplemented = 1, + NotSupported = 2, + NotInitialized = 3, + InvalidParameter = 4, + Timeout = 5, + InsufficientMemory = 6, + ReadOnlyAttribute = 7, + InvalidState = 8, + InvalidAddress = 9, + InvalidSize = 10, + InvalidValue = 11, + AlreadyAllocated = 13, + Busy = 14, + ResourceError = 15, + CountMismatch = 16, + SharedMemoryTooSmall = 0x1000, + FileOperationFailed = 0x30003, + DirectoryOperationFailed = 0x30004, + NotAvailableInProduction = 0x30006, + IoctlFailed = 0x3000F, + AccessDenied = 0x30010, + FileNotFound = 0x30013, + ModuleNotPresent = 0xA000E, + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Olsc/IOlscServiceForSystemService.cs b/Ryujinx.HLE/HOS/Services/Olsc/IOlscServiceForSystemService.cs new file mode 100644 index 0000000000..52f74da929 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Olsc/IOlscServiceForSystemService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Olsc +{ + [Service("olsc:s")] // 4.0.0+ + class IOlscServiceForSystemService : IpcService + { + public IOlscServiceForSystemService(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Ovln/IReceiverService.cs b/Ryujinx.HLE/HOS/Services/Ovln/IReceiverService.cs new file mode 100644 index 0000000000..67b82e4248 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Ovln/IReceiverService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Ovln +{ + [Service("ovln:rcv")] + class IReceiverService : IpcService + { + public IReceiverService(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Ovln/ISenderService.cs b/Ryujinx.HLE/HOS/Services/Ovln/ISenderService.cs new file mode 100644 index 0000000000..70c860e1c4 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Ovln/ISenderService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Ovln +{ + [Service("ovln:snd")] + class ISenderService : IpcService + { + public ISenderService(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Pcie/ILogManager.cs b/Ryujinx.HLE/HOS/Services/Pcie/ILogManager.cs new file mode 100644 index 0000000000..9c6387e15f --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Pcie/ILogManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Pcie +{ + [Service("pcie:log")] + class ILogManager : IpcService + { + public ILogManager(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Pcie/IManager.cs b/Ryujinx.HLE/HOS/Services/Pcie/IManager.cs new file mode 100644 index 0000000000..f189dc8c8a --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Pcie/IManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Pcie +{ + [Service("pcie")] + class IManager : IpcService + { + public IManager(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Pctl/IParentalControlServiceFactory.cs b/Ryujinx.HLE/HOS/Services/Pctl/IParentalControlServiceFactory.cs new file mode 100644 index 0000000000..678279f934 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Pctl/IParentalControlServiceFactory.cs @@ -0,0 +1,31 @@ +using Ryujinx.HLE.HOS.Services.Pctl.ParentalControlServiceFactory; + +namespace Ryujinx.HLE.HOS.Services.Pctl +{ + [Service("pctl")] + [Service("pctl:a")] + [Service("pctl:r")] + [Service("pctl:s")] + class IParentalControlServiceFactory : IpcService + { + public IParentalControlServiceFactory(ServiceCtx context) { } + + [Command(0)] + // CreateService(u64, pid) -> object + public ResultCode CreateService(ServiceCtx context) + { + MakeObject(context, new IParentalControlService()); + + return ResultCode.Success; + } + + [Command(1)] // 4.0.0+ + // CreateServiceWithoutInitialize(u64, pid) -> object + public ResultCode CreateServiceWithoutInitialize(ServiceCtx context) + { + MakeObject(context, new IParentalControlService(false)); + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Pctl/ParentalControlServiceFactory/IParentalControlService.cs b/Ryujinx.HLE/HOS/Services/Pctl/ParentalControlServiceFactory/IParentalControlService.cs new file mode 100644 index 0000000000..0e7c8432bc --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Pctl/ParentalControlServiceFactory/IParentalControlService.cs @@ -0,0 +1,41 @@ +using Ryujinx.Common.Logging; + +namespace Ryujinx.HLE.HOS.Services.Pctl.ParentalControlServiceFactory +{ + class IParentalControlService : IpcService + { + private bool _initialized = false; + + private bool _needInitialize; + + public IParentalControlService(bool needInitialize = true) + { + _needInitialize = needInitialize; + } + + [Command(1)] // 4.0.0+ + // Initialize() + public ResultCode Initialize(ServiceCtx context) + { + if (_needInitialize && !_initialized) + { + _initialized = true; + } + else + { + Logger.PrintWarning(LogClass.ServicePctl, "Service is already initialized!"); + } + + return ResultCode.Success; + } + + [Command(1001)] + // CheckFreeCommunicationPermission() + public ResultCode CheckFreeCommunicationPermission(ServiceCtx context) + { + Logger.PrintStub(LogClass.ServicePctl); + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Pcv/Bpc/IBoardPowerControlManager.cs b/Ryujinx.HLE/HOS/Services/Pcv/Bpc/IBoardPowerControlManager.cs new file mode 100644 index 0000000000..7d0222d551 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Pcv/Bpc/IBoardPowerControlManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Pcv.Bpc +{ + [Service("bpc")] + class IBoardPowerControlManager : IpcService + { + public IBoardPowerControlManager(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Pcv/Bpc/IRtcManager.cs b/Ryujinx.HLE/HOS/Services/Pcv/Bpc/IRtcManager.cs new file mode 100644 index 0000000000..3b775da82f --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Pcv/Bpc/IRtcManager.cs @@ -0,0 +1,34 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Pcv.Bpc +{ + [Service("bpc:r")] // 1.0.0 - 8.1.0 + class IRtcManager : IpcService + { + public IRtcManager(ServiceCtx context) { } + + [Command(0)] + // GetRtcTime() -> u64 + public ResultCode GetRtcTime(ServiceCtx context) + { + ResultCode result = GetExternalRtcValue(out ulong rtcValue); + + if (result == ResultCode.Success) + { + context.ResponseData.Write(rtcValue); + } + + return result; + } + + public static ResultCode GetExternalRtcValue(out ulong rtcValue) + { + // TODO: emulate MAX77620/MAX77812 RTC + DateTime unixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + rtcValue = (ulong)(DateTime.Now.ToUniversalTime() - unixEpoch).TotalSeconds; + + return ResultCode.Success; + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Pcv/Clkrst/IArbitrationManager.cs b/Ryujinx.HLE/HOS/Services/Pcv/Clkrst/IArbitrationManager.cs new file mode 100644 index 0000000000..6f1e5d25d5 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Pcv/Clkrst/IArbitrationManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Pcv.Clkrst +{ + [Service("clkrst:a")] // 8.0.0+ + class IArbitrationManager : IpcService + { + public IArbitrationManager(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Pcv/Clkrst/IClkrstManager.cs b/Ryujinx.HLE/HOS/Services/Pcv/Clkrst/IClkrstManager.cs new file mode 100644 index 0000000000..a82e8a94f8 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Pcv/Clkrst/IClkrstManager.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Pcv.Clkrst +{ + [Service("clkrst")] // 8.0.0+ + [Service("clkrst:i")] // 8.0.0+ + class IClkrstManager : IpcService + { + public IClkrstManager(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Pcv/IPcvService.cs b/Ryujinx.HLE/HOS/Services/Pcv/IPcvService.cs new file mode 100644 index 0000000000..0e74dc3e5a --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Pcv/IPcvService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Pcv +{ + [Service("pcv")] + class IPcvService : IpcService + { + public IPcvService(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Pcv/Rgltr/IRegulatorManager.cs b/Ryujinx.HLE/HOS/Services/Pcv/Rgltr/IRegulatorManager.cs new file mode 100644 index 0000000000..f7834777e7 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Pcv/Rgltr/IRegulatorManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Pcv.Rgltr +{ + [Service("rgltr")] // 8.0.0+ + class IRegulatorManager : IpcService + { + public IRegulatorManager(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Pcv/Rtc/IUnknown1.cs b/Ryujinx.HLE/HOS/Services/Pcv/Rtc/IUnknown1.cs new file mode 100644 index 0000000000..0f73f9501e --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Pcv/Rtc/IUnknown1.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Pcv.Rtc +{ + [Service("rtc")] // 8.0.0+ + class IUnknown1 : IpcService + { + public IUnknown1(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Pm/IBootModeInterface.cs b/Ryujinx.HLE/HOS/Services/Pm/IBootModeInterface.cs new file mode 100644 index 0000000000..45771db6bd --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Pm/IBootModeInterface.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Pm +{ + [Service("pm:bm")] + class IBootModeInterface : IpcService + { + public IBootModeInterface(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Pm/IDebugMonitorInterface.cs b/Ryujinx.HLE/HOS/Services/Pm/IDebugMonitorInterface.cs new file mode 100644 index 0000000000..06c1194367 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Pm/IDebugMonitorInterface.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Pm +{ + [Service("pm:dmnt")] + class IDebugMonitorInterface : IpcService + { + public IDebugMonitorInterface(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Pm/IShellInterface.cs b/Ryujinx.HLE/HOS/Services/Pm/IShellInterface.cs new file mode 100644 index 0000000000..c6eec2e8f0 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Pm/IShellInterface.cs @@ -0,0 +1,21 @@ +namespace Ryujinx.HLE.HOS.Services.Pm +{ + [Service("pm:shell")] + class IShellInterface : IpcService + { + public IShellInterface(ServiceCtx context) { } + + [Command(6)] + // GetApplicationPid() -> u64 + public ResultCode GetApplicationPid(ServiceCtx context) + { + // FIXME: This is wrong but needed to make hb loader works + // TODO: Change this when we will have a way to process via a PM like interface. + long pid = context.Process.Pid; + + context.ResponseData.Write(pid); + + return ResultCode.Success; + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Prepo/IPrepoService.cs b/Ryujinx.HLE/HOS/Services/Prepo/IPrepoService.cs new file mode 100644 index 0000000000..f606361c76 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Prepo/IPrepoService.cs @@ -0,0 +1,195 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.Utilities; +using System; +using System.Buffers.Binary; +using System.IO; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Prepo +{ + [Service("prepo:a")] + [Service("prepo:a2")] + [Service("prepo:u")] + class IPrepoService : IpcService + { + public IPrepoService(ServiceCtx context) { } + + [Command(10100)] // 1.0.0-5.1.0 + // SaveReport(u64, pid, buffer, buffer) + public ResultCode SaveReportOld(ServiceCtx context) + { + // We don't care about the differences since we don't use the play report. + return ProcessReport(context, withUserID: false); + } + + [Command(10101)] // 1.0.0-5.1.0 + // SaveReportWithUserOld(nn::account::Uid, u64, pid, buffer, buffer) + public ResultCode SaveReportWithUserOld(ServiceCtx context) + { + // We don't care about the differences since we don't use the play report. + return ProcessReport(context, withUserID: true); + } + + [Command(10102)] // 6.0.0+ + // SaveReport(u64, pid, buffer, buffer) + public ResultCode SaveReport(ServiceCtx context) + { + // We don't care about the differences since we don't use the play report. + return ProcessReport(context, withUserID: false); + } + + [Command(10103)] // 6.0.0+ + // SaveReportWithUser(nn::account::Uid, u64, pid, buffer, buffer) + public ResultCode SaveReportWithUser(ServiceCtx context) + { + // We don't care about the differences since we don't use the play report. + return ProcessReport(context, withUserID: true); + } + + private ResultCode ProcessReport(ServiceCtx context, bool withUserID) + { + UInt128 userId = withUserID ? new UInt128(context.RequestData.ReadBytes(0x10)) : new UInt128(); + string gameRoom = StringUtils.ReadUtf8String(context); + + if (withUserID) + { + if (userId.IsNull) + { + return ResultCode.InvalidArgument; + } + } + + if (gameRoom == string.Empty) + { + return ResultCode.InvalidState; + } + + long inputPosition = context.Request.SendBuff[0].Position; + long inputSize = context.Request.SendBuff[0].Size; + + if (inputSize == 0) + { + return ResultCode.InvalidBufferSize; + } + + byte[] inputBuffer = context.Memory.ReadBytes(inputPosition, inputSize); + + Logger.PrintInfo(LogClass.ServicePrepo, ReadReportBuffer(inputBuffer, gameRoom, userId)); + + return ResultCode.Success; + } + + public string ReadReportBuffer(byte[] buffer, string room, UInt128 userId) + { + StringBuilder sb = new StringBuilder(); + + sb.AppendLine(); + sb.AppendLine("PlayReport log:"); + + if (!userId.IsNull) + { + sb.AppendLine($" UserId: {userId.ToString()}"); + } + + sb.AppendLine($" Room: {room}"); + + try + { + using (MemoryStream stream = new MemoryStream(buffer)) + using (BinaryReader reader = new BinaryReader(stream)) + { + byte unknown1 = reader.ReadByte(); // Version ? + short unknown2 = reader.ReadInt16(); // Size ? + + bool isValue = false; + + string fieldStr = string.Empty; + + while (stream.Position != stream.Length) + { + byte descriptor = reader.ReadByte(); + + if (!isValue) + { + byte[] key = reader.ReadBytes(descriptor - 0xA0); + + fieldStr = $" Key: {Encoding.ASCII.GetString(key)}"; + + isValue = true; + } + else + { + if (descriptor > 0xD0) // Int value. + { + if (descriptor - 0xD0 == 1) + { + fieldStr += $", Value: {BinaryPrimitives.ReverseEndianness(reader.ReadUInt16())}"; + } + else if (descriptor - 0xD0 == 2) + { + fieldStr += $", Value: {BinaryPrimitives.ReverseEndianness(reader.ReadInt32())}"; + } + else if (descriptor - 0xD0 == 4) + { + fieldStr += $", Value: {BinaryPrimitives.ReverseEndianness(reader.ReadInt64())}"; + } + else + { + // Unknown. + break; + } + } + else if (descriptor > 0xA0 && descriptor < 0xD0) // String value, max size = 0x20 bytes ? + { + int size = descriptor - 0xA0; + string value = string.Empty; + byte[] rawValues = new byte[0]; + + for (int i = 0; i < size; i++) + { + byte chr = reader.ReadByte(); + + if (chr >= 0x20 && chr < 0x7f) + { + value += (char)chr; + } + else + { + Array.Resize(ref rawValues, rawValues.Length + 1); + + rawValues[rawValues.Length - 1] = chr; + } + } + + if (value != string.Empty) + { + fieldStr += $", Value: {value}"; + } + + // TODO(Ac_K): Determine why there are non-alphanumeric values sometimes. + if (rawValues.Length > 0) + { + fieldStr += $", RawValue: 0x{BitConverter.ToString(rawValues).Replace("-", "")}"; + } + } + else // Byte value. + { + fieldStr += $", Value: {descriptor}"; + } + + sb.AppendLine(fieldStr); + + isValue = false; + } + } + } + } + catch (Exception) + { + sb.AppendLine(" Error while parsing the report buffer."); + } + + return sb.ToString(); + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Prepo/ResultCode.cs b/Ryujinx.HLE/HOS/Services/Prepo/ResultCode.cs new file mode 100644 index 0000000000..1e110ea68d --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Prepo/ResultCode.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.HLE.HOS.Services.Prepo +{ + enum ResultCode + { + ModuleId = 129, + ErrorCodeShift = 9, + + Success = 0, + + InvalidArgument = (1 << ErrorCodeShift) | ModuleId, + InvalidState = (5 << ErrorCodeShift) | ModuleId, + InvalidBufferSize = (9 << ErrorCodeShift) | ModuleId, + Unknown1 = (90 << ErrorCodeShift) | ModuleId + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Psc/IPmControl.cs b/Ryujinx.HLE/HOS/Services/Psc/IPmControl.cs new file mode 100644 index 0000000000..3810c28262 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Psc/IPmControl.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Psc +{ + [Service("psc:c")] + class IPmControl : IpcService + { + public IPmControl(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Psc/IPmService.cs b/Ryujinx.HLE/HOS/Services/Psc/IPmService.cs new file mode 100644 index 0000000000..c8dfb32e01 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Psc/IPmService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Psc +{ + [Service("psc:m")] + class IPmService : IpcService + { + public IPmService(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Psc/IPmUnknown.cs b/Ryujinx.HLE/HOS/Services/Psc/IPmUnknown.cs new file mode 100644 index 0000000000..ef48fa41ee --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Psc/IPmUnknown.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Psc +{ + [Service("psc:l")] // 9.0.0+ + class IPmUnknown : IpcService + { + public IPmUnknown(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Ptm/Fan/IManager.cs b/Ryujinx.HLE/HOS/Services/Ptm/Fan/IManager.cs new file mode 100644 index 0000000000..e2fe223569 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Ptm/Fan/IManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Ptm.Fan +{ + [Service("fan")] + class IManager : IpcService + { + public IManager(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Ptm/Fgm/IDebugger.cs b/Ryujinx.HLE/HOS/Services/Ptm/Fgm/IDebugger.cs new file mode 100644 index 0000000000..a93f528301 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Ptm/Fgm/IDebugger.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Ptm.Fgm +{ + [Service("fgm:dbg")] // 9.0.0+ + class IDebugger : IpcService + { + public IDebugger(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Ptm/Fgm/ISession.cs b/Ryujinx.HLE/HOS/Services/Ptm/Fgm/ISession.cs new file mode 100644 index 0000000000..0e3f965b34 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Ptm/Fgm/ISession.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Services.Ptm.Fgm +{ + [Service("fgm")] // 9.0.0+ + [Service("fgm:0")] // 9.0.0+ + [Service("fgm:9")] // 9.0.0+ + class ISession : IpcService + { + public ISession(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Ptm/Pcm/IManager.cs b/Ryujinx.HLE/HOS/Services/Ptm/Pcm/IManager.cs new file mode 100644 index 0000000000..0bec45fa38 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Ptm/Pcm/IManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Ptm.Pcm +{ + [Service("pcm")] + class IManager : IpcService + { + public IManager(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Ptm/Psm/IPsmServer.cs b/Ryujinx.HLE/HOS/Services/Ptm/Psm/IPsmServer.cs new file mode 100644 index 0000000000..9511e79d33 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Ptm/Psm/IPsmServer.cs @@ -0,0 +1,45 @@ +using Ryujinx.Common.Logging; + +namespace Ryujinx.HLE.HOS.Services.Ptm.Psm +{ + [Service("psm")] + class IPsmServer : IpcService + { + public IPsmServer(ServiceCtx context) { } + + [Command(0)] + // GetBatteryChargePercentage() -> u32 + public static ResultCode GetBatteryChargePercentage(ServiceCtx context) + { + int chargePercentage = 100; + + context.ResponseData.Write(chargePercentage); + + Logger.PrintStub(LogClass.ServicePsm, new { chargePercentage }); + + return ResultCode.Success; + } + + [Command(1)] + // GetChargerType() -> u32 + public static ResultCode GetChargerType(ServiceCtx context) + { + ChargerType chargerType = ChargerType.ChargerOrDock; + + context.ResponseData.Write((int)chargerType); + + Logger.PrintStub(LogClass.ServicePsm, new { chargerType }); + + return ResultCode.Success; + } + + [Command(7)] + // OpenSession() -> IPsmSession + public ResultCode OpenSession(ServiceCtx context) + { + MakeObject(context, new IPsmSession(context.Device.System)); + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Ptm/Psm/IPsmSession.cs b/Ryujinx.HLE/HOS/Services/Ptm/Psm/IPsmSession.cs new file mode 100644 index 0000000000..e41d6c376f --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Ptm/Psm/IPsmSession.cs @@ -0,0 +1,88 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Threading; + +namespace Ryujinx.HLE.HOS.Services.Ptm.Psm +{ + class IPsmSession : IpcService + { + private KEvent _stateChangeEvent; + private int _stateChangeEventHandle; + + public IPsmSession(Horizon system) + { + _stateChangeEvent = new KEvent(system); + _stateChangeEventHandle = -1; + } + + [Command(0)] + // BindStateChangeEvent() -> KObject + public ResultCode BindStateChangeEvent(ServiceCtx context) + { + if (_stateChangeEventHandle == -1) + { + KernelResult resultCode = context.Process.HandleTable.GenerateHandle(_stateChangeEvent.ReadableEvent, out int stateChangeEventHandle); + + if (resultCode != KernelResult.Success) + { + return (ResultCode)resultCode; + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_stateChangeEventHandle); + + Logger.PrintStub(LogClass.ServicePsm); + + return ResultCode.Success; + } + + [Command(1)] + // UnbindStateChangeEvent() + public ResultCode UnbindStateChangeEvent(ServiceCtx context) + { + if (_stateChangeEventHandle != -1) + { + context.Process.HandleTable.CloseHandle(_stateChangeEventHandle); + _stateChangeEventHandle = -1; + } + + Logger.PrintStub(LogClass.ServicePsm); + + return ResultCode.Success; + } + + [Command(2)] + // SetChargerTypeChangeEventEnabled(u8) + public ResultCode SetChargerTypeChangeEventEnabled(ServiceCtx context) + { + bool chargerTypeChangeEventEnabled = context.RequestData.ReadBoolean(); + + Logger.PrintStub(LogClass.ServicePsm, new { chargerTypeChangeEventEnabled }); + + return ResultCode.Success; + } + + [Command(3)] + // SetPowerSupplyChangeEventEnabled(u8) + public ResultCode SetPowerSupplyChangeEventEnabled(ServiceCtx context) + { + bool powerSupplyChangeEventEnabled = context.RequestData.ReadBoolean(); + + Logger.PrintStub(LogClass.ServicePsm, new { powerSupplyChangeEventEnabled }); + + return ResultCode.Success; + } + + [Command(4)] + // SetBatteryVoltageStateChangeEventEnabled(u8) + public ResultCode SetBatteryVoltageStateChangeEventEnabled(ServiceCtx context) + { + bool batteryVoltageStateChangeEventEnabled = context.RequestData.ReadBoolean(); + + Logger.PrintStub(LogClass.ServicePsm, new { batteryVoltageStateChangeEventEnabled }); + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Ptm/Psm/Types/ChargerType.cs b/Ryujinx.HLE/HOS/Services/Ptm/Psm/Types/ChargerType.cs new file mode 100644 index 0000000000..3e239711d9 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Ptm/Psm/Types/ChargerType.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Ptm.Psm +{ + enum ChargerType + { + None, + ChargerOrDock, + UsbC + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Ptm/Tc/IManager.cs b/Ryujinx.HLE/HOS/Services/Ptm/Tc/IManager.cs new file mode 100644 index 0000000000..1daa4f5e82 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Ptm/Tc/IManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Ptm.Tc +{ + [Service("tc")] + class IManager : IpcService + { + public IManager(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Ptm/Ts/IMeasurementServer.cs b/Ryujinx.HLE/HOS/Services/Ptm/Ts/IMeasurementServer.cs new file mode 100644 index 0000000000..f3b37d67df --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Ptm/Ts/IMeasurementServer.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Ptm.Ts +{ + [Service("ts")] + class IMeasurementServer : IpcService + { + public IMeasurementServer(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Ro/IRoInterface.cs b/Ryujinx.HLE/HOS/Services/Ro/IRoInterface.cs new file mode 100644 index 0000000000..d2eb38732e --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Ro/IRoInterface.cs @@ -0,0 +1,573 @@ +using ARMeilleure.Memory; +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Memory; +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.HLE.Loaders.Executables; +using Ryujinx.HLE.Utilities; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security.Cryptography; + +namespace Ryujinx.HLE.HOS.Services.Ro +{ + [Service("ldr:ro")] + [Service("ro:1")] // 7.0.0+ + class IRoInterface : IpcService, IDisposable + { + private const int MaxNrr = 0x40; + private const int MaxNro = 0x40; + private const int MaxMapRetries = 0x200; + private const int GuardPagesSize = 0x4000; + + private const uint NrrMagic = 0x3052524E; + private const uint NroMagic = 0x304F524E; + + private List _nrrInfos; + private List _nroInfos; + + private KProcess _owner; + + private static Random _random = new Random(); + + public IRoInterface(ServiceCtx context) + { + _nrrInfos = new List(MaxNrr); + _nroInfos = new List(MaxNro); + _owner = null; + } + + private ResultCode ParseNrr(out NrrInfo nrrInfo, ServiceCtx context, long nrrAddress, long nrrSize) + { + nrrInfo = null; + + if (nrrSize == 0 || nrrAddress + nrrSize <= nrrAddress || (nrrSize & 0xFFF) != 0) + { + return ResultCode.InvalidSize; + } + else if ((nrrAddress & 0xFFF) != 0) + { + return ResultCode.InvalidAddress; + } + + StructReader reader = new StructReader(context.Memory, nrrAddress); + NrrHeader header = reader.Read(); + + if (header.Magic != NrrMagic) + { + return ResultCode.InvalidNrr; + } + else if (header.NrrSize != nrrSize) + { + return ResultCode.InvalidSize; + } + + List hashes = new List(); + + for (int i = 0; i < header.HashCount; i++) + { + hashes.Add(context.Memory.ReadBytes(nrrAddress + header.HashOffset + (i * 0x20), 0x20)); + } + + nrrInfo = new NrrInfo(nrrAddress, header, hashes); + + return ResultCode.Success; + } + + public bool IsNroHashPresent(byte[] nroHash) + { + foreach (NrrInfo info in _nrrInfos) + { + foreach (byte[] hash in info.Hashes) + { + if (hash.SequenceEqual(nroHash)) + { + return true; + } + } + } + + return false; + } + + public bool IsNroLoaded(byte[] nroHash) + { + foreach (NroInfo info in _nroInfos) + { + if (info.Hash.SequenceEqual(nroHash)) + { + return true; + } + } + + return false; + } + + public ResultCode ParseNro(out NroInfo res, ServiceCtx context, ulong nroAddress, ulong nroSize, ulong bssAddress, ulong bssSize) + { + res = null; + + if (_nroInfos.Count >= MaxNro) + { + return ResultCode.TooManyNro; + } + else if (nroSize == 0 || nroAddress + nroSize <= nroAddress || (nroSize & 0xFFF) != 0) + { + return ResultCode.InvalidSize; + } + else if (bssSize != 0 && bssAddress + bssSize <= bssAddress) + { + return ResultCode.InvalidSize; + } + else if ((nroAddress & 0xFFF) != 0) + { + return ResultCode.InvalidAddress; + } + + uint magic = context.Memory.ReadUInt32((long)nroAddress + 0x10); + uint nroFileSize = context.Memory.ReadUInt32((long)nroAddress + 0x18); + + if (magic != NroMagic || nroSize != nroFileSize) + { + return ResultCode.InvalidNro; + } + + byte[] nroData = context.Memory.ReadBytes((long)nroAddress, (long)nroSize); + byte[] nroHash = null; + + MemoryStream stream = new MemoryStream(nroData); + + using (SHA256 hasher = SHA256.Create()) + { + nroHash = hasher.ComputeHash(stream); + } + + if (!IsNroHashPresent(nroHash)) + { + return ResultCode.NotRegistered; + } + + if (IsNroLoaded(nroHash)) + { + return ResultCode.AlreadyLoaded; + } + + stream.Position = 0; + + NxRelocatableObject executable = new NxRelocatableObject(stream, nroAddress, bssAddress); + + // check if everything is page align. + if ((executable.Text.Length & 0xFFF) != 0 || (executable.Ro.Length & 0xFFF) != 0 || + (executable.Data.Length & 0xFFF) != 0 || (executable.BssSize & 0xFFF) != 0) + { + return ResultCode.InvalidNro; + } + + // check if everything is contiguous. + if (executable.RoOffset != executable.TextOffset + executable.Text.Length || + executable.DataOffset != executable.RoOffset + executable.Ro.Length || + nroFileSize != executable.DataOffset + executable.Data.Length) + { + return ResultCode.InvalidNro; + } + + // finally check the bss size match. + if ((ulong)executable.BssSize != bssSize) + { + return ResultCode.InvalidNro; + } + + int totalSize = executable.Text.Length + executable.Ro.Length + executable.Data.Length + executable.BssSize; + + res = new NroInfo( + executable, + nroHash, + nroAddress, + nroSize, + bssAddress, + bssSize, + (ulong)totalSize); + + return ResultCode.Success; + } + + private ResultCode MapNro(KProcess process, NroInfo info, out ulong nroMappedAddress) + { + KMemoryManager memMgr = process.MemoryManager; + + int retryCount = 0; + + nroMappedAddress = 0; + + while (retryCount++ < MaxMapRetries) + { + ResultCode result = MapCodeMemoryInProcess(process, info.NroAddress, info.NroSize, out nroMappedAddress); + + if (result != ResultCode.Success) + { + return result; + } + + if (info.BssSize > 0) + { + KernelResult bssMappingResult = memMgr.MapProcessCodeMemory(nroMappedAddress + info.NroSize, info.BssAddress, info.BssSize); + + if (bssMappingResult == KernelResult.InvalidMemState) + { + memMgr.UnmapProcessCodeMemory(nroMappedAddress + info.NroSize, info.BssAddress, info.BssSize); + memMgr.UnmapProcessCodeMemory(nroMappedAddress, info.NroAddress, info.NroSize); + + continue; + } + else if (bssMappingResult != KernelResult.Success) + { + memMgr.UnmapProcessCodeMemory(nroMappedAddress + info.NroSize, info.BssAddress, info.BssSize); + memMgr.UnmapProcessCodeMemory(nroMappedAddress, info.NroAddress, info.NroSize); + + return (ResultCode)bssMappingResult; + } + } + + if (CanAddGuardRegionsInProcess(process, nroMappedAddress, info.TotalSize)) + { + return ResultCode.Success; + } + } + + return ResultCode.InsufficientAddressSpace; + } + + private bool CanAddGuardRegionsInProcess(KProcess process, ulong baseAddress, ulong size) + { + KMemoryManager memMgr = process.MemoryManager; + + KMemoryInfo memInfo = memMgr.QueryMemory(baseAddress - 1); + + if (memInfo.State == MemoryState.Unmapped && baseAddress - GuardPagesSize >= memInfo.Address) + { + memInfo = memMgr.QueryMemory(baseAddress + size); + + if (memInfo.State == MemoryState.Unmapped) + { + return baseAddress + size + GuardPagesSize <= memInfo.Address + memInfo.Size; + } + } + return false; + } + + private ResultCode MapCodeMemoryInProcess(KProcess process, ulong baseAddress, ulong size, out ulong targetAddress) + { + KMemoryManager memMgr = process.MemoryManager; + + targetAddress = 0; + + int retryCount; + + ulong addressSpacePageLimit = (memMgr.GetAddrSpaceSize() - size) >> 12; + + for (retryCount = 0; retryCount < MaxMapRetries; retryCount++) + { + while (true) + { + ulong randomOffset = (ulong)(uint)_random.Next(0, (int)addressSpacePageLimit) << 12; + + targetAddress = memMgr.GetAddrSpaceBaseAddr() + randomOffset; + + if (memMgr.InsideAddrSpace(targetAddress, size) && !memMgr.InsideHeapRegion(targetAddress, size) && !memMgr.InsideAliasRegion(targetAddress, size)) + { + break; + } + } + + KernelResult result = memMgr.MapProcessCodeMemory(targetAddress, baseAddress, size); + + if (result == KernelResult.InvalidMemState) + { + continue; + } + else if (result != KernelResult.Success) + { + return (ResultCode)result; + } + + if (!CanAddGuardRegionsInProcess(process, targetAddress, size)) + { + continue; + } + + return ResultCode.Success; + } + + if (retryCount == MaxMapRetries) + { + return ResultCode.InsufficientAddressSpace; + } + + return ResultCode.Success; + } + + private KernelResult SetNroMemoryPermissions(KProcess process, IExecutable relocatableObject, ulong baseAddress) + { + ulong textStart = baseAddress + (ulong)relocatableObject.TextOffset; + ulong roStart = baseAddress + (ulong)relocatableObject.RoOffset; + ulong dataStart = baseAddress + (ulong)relocatableObject.DataOffset; + + ulong bssStart = dataStart + (ulong)relocatableObject.Data.Length; + + ulong bssEnd = BitUtils.AlignUp(bssStart + (ulong)relocatableObject.BssSize, KMemoryManager.PageSize); + + process.CpuMemory.WriteBytes((long)textStart, relocatableObject.Text); + process.CpuMemory.WriteBytes((long)roStart, relocatableObject.Ro); + process.CpuMemory.WriteBytes((long)dataStart, relocatableObject.Data); + + MemoryHelper.FillWithZeros(process.CpuMemory, (long)bssStart, (int)(bssEnd - bssStart)); + + KernelResult result; + + result = process.MemoryManager.SetProcessMemoryPermission(textStart, roStart - textStart, MemoryPermission.ReadAndExecute); + + if (result != KernelResult.Success) + { + return result; + } + + result = process.MemoryManager.SetProcessMemoryPermission(roStart, dataStart - roStart, MemoryPermission.Read); + + if (result != KernelResult.Success) + { + return result; + } + + return process.MemoryManager.SetProcessMemoryPermission(dataStart, bssEnd - dataStart, MemoryPermission.ReadAndWrite); + } + + private ResultCode RemoveNrrInfo(long nrrAddress) + { + foreach (NrrInfo info in _nrrInfos) + { + if (info.NrrAddress == nrrAddress) + { + _nrrInfos.Remove(info); + + return ResultCode.Success; + } + } + + return ResultCode.NotLoaded; + } + + private ResultCode RemoveNroInfo(ulong nroMappedAddress) + { + foreach (NroInfo info in _nroInfos) + { + if (info.NroMappedAddress == nroMappedAddress) + { + _nroInfos.Remove(info); + + return UnmapNroFromInfo(info); + } + } + + return ResultCode.NotLoaded; + } + + private ResultCode UnmapNroFromInfo(NroInfo info) + { + ulong textSize = (ulong)info.Executable.Text.Length; + ulong roSize = (ulong)info.Executable.Ro.Length; + ulong dataSize = (ulong)info.Executable.Data.Length; + ulong bssSize = (ulong)info.Executable.BssSize; + + KernelResult result = KernelResult.Success; + + if (info.Executable.BssSize != 0) + { + result = _owner.MemoryManager.UnmapProcessCodeMemory( + info.NroMappedAddress + textSize + roSize + dataSize, + info.Executable.BssAddress, + bssSize); + } + + if (result == KernelResult.Success) + { + result = _owner.MemoryManager.UnmapProcessCodeMemory( + info.NroMappedAddress + textSize + roSize, + info.Executable.SourceAddress + textSize + roSize, + dataSize); + + if (result == KernelResult.Success) + { + result = _owner.MemoryManager.UnmapProcessCodeMemory( + info.NroMappedAddress, + info.Executable.SourceAddress, + textSize + roSize); + } + } + + return (ResultCode)result; + } + + private ResultCode IsInitialized(KProcess process) + { + if (_owner != null && _owner.Pid == process.Pid) + { + return ResultCode.Success; + } + + return ResultCode.InvalidProcess; + } + + [Command(0)] + // LoadNro(u64, u64, u64, u64, u64, pid) -> u64 + public ResultCode LoadNro(ServiceCtx context) + { + ResultCode result = IsInitialized(context.Process); + + // Zero + context.RequestData.ReadUInt64(); + + ulong nroHeapAddress = context.RequestData.ReadUInt64(); + ulong nroSize = context.RequestData.ReadUInt64(); + ulong bssHeapAddress = context.RequestData.ReadUInt64(); + ulong bssSize = context.RequestData.ReadUInt64(); + + ulong nroMappedAddress = 0; + + if (result == ResultCode.Success) + { + NroInfo info; + + result = ParseNro(out info, context, nroHeapAddress, nroSize, bssHeapAddress, bssSize); + + if (result == ResultCode.Success) + { + result = MapNro(context.Process, info, out nroMappedAddress); + + if (result == ResultCode.Success) + { + result = (ResultCode)SetNroMemoryPermissions(context.Process, info.Executable, nroMappedAddress); + + if (result == ResultCode.Success) + { + info.NroMappedAddress = nroMappedAddress; + + _nroInfos.Add(info); + } + } + } + } + + context.ResponseData.Write(nroMappedAddress); + + return result; + } + + [Command(1)] + // UnloadNro(u64, u64, pid) + public ResultCode UnloadNro(ServiceCtx context) + { + ResultCode result = IsInitialized(context.Process); + + // Zero + context.RequestData.ReadUInt64(); + + ulong nroMappedAddress = context.RequestData.ReadUInt64(); + + if (result == ResultCode.Success) + { + if ((nroMappedAddress & 0xFFF) != 0) + { + return ResultCode.InvalidAddress; + } + + result = RemoveNroInfo(nroMappedAddress); + } + + return result; + } + + [Command(2)] + // LoadNrr(u64, u64, u64, pid) + public ResultCode LoadNrr(ServiceCtx context) + { + ResultCode result = IsInitialized(context.Process); + + // pid placeholder, zero + context.RequestData.ReadUInt64(); + + long nrrAddress = context.RequestData.ReadInt64(); + long nrrSize = context.RequestData.ReadInt64(); + + if (result == ResultCode.Success) + { + NrrInfo info; + result = ParseNrr(out info, context, nrrAddress, nrrSize); + + if (result == ResultCode.Success) + { + if (_nrrInfos.Count >= MaxNrr) + { + result = ResultCode.NotLoaded; + } + else + { + _nrrInfos.Add(info); + } + } + } + + return result; + } + + [Command(3)] + // UnloadNrr(u64, u64, pid) + public ResultCode UnloadNrr(ServiceCtx context) + { + ResultCode result = IsInitialized(context.Process); + + // pid placeholder, zero + context.RequestData.ReadUInt64(); + + long nrrHeapAddress = context.RequestData.ReadInt64(); + + if (result == ResultCode.Success) + { + if ((nrrHeapAddress & 0xFFF) != 0) + { + return ResultCode.InvalidAddress; + } + + result = RemoveNrrInfo(nrrHeapAddress); + } + + return result; + } + + [Command(4)] + // Initialize(u64, pid, KObject) + public ResultCode Initialize(ServiceCtx context) + { + if (_owner != null) + { + return ResultCode.InvalidSession; + } + + _owner = context.Process; + + return ResultCode.Success; + } + + public void Dispose() + { + foreach (NroInfo info in _nroInfos) + { + UnmapNroFromInfo(info); + } + + _nroInfos.Clear(); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Ro/ResultCode.cs b/Ryujinx.HLE/HOS/Services/Ro/ResultCode.cs new file mode 100644 index 0000000000..92bb550293 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Ro/ResultCode.cs @@ -0,0 +1,27 @@ +namespace Ryujinx.HLE.HOS.Services.Ro +{ + enum ResultCode + { + ModuleId = 22, + ErrorCodeShift = 9, + + Success = 0, + + InsufficientAddressSpace = (2 << ErrorCodeShift) | ModuleId, + AlreadyLoaded = (3 << ErrorCodeShift) | ModuleId, + InvalidNro = (4 << ErrorCodeShift) | ModuleId, + InvalidNrr = (6 << ErrorCodeShift) | ModuleId, + TooManyNro = (7 << ErrorCodeShift) | ModuleId, + TooManyNrr = (8 << ErrorCodeShift) | ModuleId, + NotAuthorized = (9 << ErrorCodeShift) | ModuleId, + + InvalidNrrType = (10 << ErrorCodeShift) | ModuleId, + + InvalidAddress = (1025 << ErrorCodeShift) | ModuleId, + InvalidSize = (1026 << ErrorCodeShift) | ModuleId, + NotLoaded = (1028 << ErrorCodeShift) | ModuleId, + NotRegistered = (1029 << ErrorCodeShift) | ModuleId, + InvalidSession = (1030 << ErrorCodeShift) | ModuleId, + InvalidProcess = (1031 << ErrorCodeShift) | ModuleId, + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Ro/Types/NroInfo.cs b/Ryujinx.HLE/HOS/Services/Ro/Types/NroInfo.cs new file mode 100644 index 0000000000..a09116272c --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Ro/Types/NroInfo.cs @@ -0,0 +1,35 @@ +using Ryujinx.HLE.Loaders.Executables; + +namespace Ryujinx.HLE.HOS.Services.Ro +{ + class NroInfo + { + public NxRelocatableObject Executable { get; private set; } + + public byte[] Hash { get; private set; } + public ulong NroAddress { get; private set; } + public ulong NroSize { get; private set; } + public ulong BssAddress { get; private set; } + public ulong BssSize { get; private set; } + public ulong TotalSize { get; private set; } + public ulong NroMappedAddress { get; set; } + + public NroInfo( + NxRelocatableObject executable, + byte[] hash, + ulong nroAddress, + ulong nroSize, + ulong bssAddress, + ulong bssSize, + ulong totalSize) + { + Executable = executable; + Hash = hash; + NroAddress = nroAddress; + NroSize = nroSize; + BssAddress = bssAddress; + BssSize = bssSize; + TotalSize = totalSize; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Ro/Types/NrrHeader.cs b/Ryujinx.HLE/HOS/Services/Ro/Types/NrrHeader.cs new file mode 100644 index 0000000000..ec06feb436 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Ro/Types/NrrHeader.cs @@ -0,0 +1,38 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Ro +{ + [StructLayout(LayoutKind.Explicit, Size = 0x350)] + unsafe struct NrrHeader + { + [FieldOffset(0)] + public uint Magic; + + [FieldOffset(0x10)] + public ulong TitleIdMask; + + [FieldOffset(0x18)] + public ulong TitleIdPattern; + + [FieldOffset(0x30)] + public fixed byte Modulus[0x100]; + + [FieldOffset(0x130)] + public fixed byte FixedKeySignature[0x100]; + + [FieldOffset(0x230)] + public fixed byte NrrSignature[0x100]; + + [FieldOffset(0x330)] + public ulong TitleIdMin; + + [FieldOffset(0x338)] + public uint NrrSize; + + [FieldOffset(0x340)] + public uint HashOffset; + + [FieldOffset(0x344)] + public uint HashCount; + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Ro/Types/NrrInfo.cs b/Ryujinx.HLE/HOS/Services/Ro/Types/NrrInfo.cs new file mode 100644 index 0000000000..8e038fcb6b --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Ro/Types/NrrInfo.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Services.Ro +{ + class NrrInfo + { + public NrrHeader Header { get; private set; } + public List Hashes { get; private set; } + public long NrrAddress { get; private set; } + + public NrrInfo(long nrrAddress, NrrHeader header, List hashes) + { + NrrAddress = nrrAddress; + Header = header; + Hashes = hashes; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Sdb/Avm/IAvmService.cs b/Ryujinx.HLE/HOS/Services/Sdb/Avm/IAvmService.cs new file mode 100644 index 0000000000..d65c8bbae4 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sdb/Avm/IAvmService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Am.Tcap +{ + [Service("avm")] // 6.0.0+ + class IAvmService : IpcService + { + public IAvmService(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Sdb/Mii/IImageDatabaseService.cs b/Ryujinx.HLE/HOS/Services/Sdb/Mii/IImageDatabaseService.cs new file mode 100644 index 0000000000..b084714c5a --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sdb/Mii/IImageDatabaseService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Sdb.Mii +{ + [Service("miiimg")] // 5.0.0+ + class IImageDatabaseService : IpcService + { + public IImageDatabaseService(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Sdb/Mii/IStaticService.cs b/Ryujinx.HLE/HOS/Services/Sdb/Mii/IStaticService.cs new file mode 100644 index 0000000000..6c156d9421 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sdb/Mii/IStaticService.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Sdb.Mii +{ + [Service("mii:e")] + [Service("mii:u")] + class IStaticService : IpcService + { + public IStaticService(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Sdb/Pdm/INotifyService.cs b/Ryujinx.HLE/HOS/Services/Sdb/Pdm/INotifyService.cs new file mode 100644 index 0000000000..5247a238db --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sdb/Pdm/INotifyService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm +{ + [Service("pdm:ntfy")] + class INotifyService : IpcService + { + public INotifyService(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Sdb/Pdm/IQueryService.cs b/Ryujinx.HLE/HOS/Services/Sdb/Pdm/IQueryService.cs new file mode 100644 index 0000000000..210dd98bf4 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sdb/Pdm/IQueryService.cs @@ -0,0 +1,24 @@ +using Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService; + +namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm +{ + [Service("pdm:qry")] + class IQueryService : IpcService + { + public IQueryService(ServiceCtx context) { } + + [Command(13)] // 5.0.0+ + // QueryApplicationPlayStatisticsForSystem(buffer title_id_list) -> (buffer entries, s32 entries_count) + public ResultCode QueryApplicationPlayStatisticsForSystem(ServiceCtx context) + { + return QueryPlayStatisticsManager.GetPlayStatistics(context); + } + + [Command(16)] // 6.0.0+ + // QueryApplicationPlayStatisticsByUserAccountIdForSystem(nn::account::Uid, buffer title_id_list) -> (buffer entries, s32 entries_count) + public ResultCode QueryApplicationPlayStatisticsByUserAccountIdForSystem(ServiceCtx context) + { + return QueryPlayStatisticsManager.GetPlayStatistics(context, true); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/QueryPlayStatisticsManager.cs b/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/QueryPlayStatisticsManager.cs new file mode 100644 index 0000000000..925a2593b1 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/QueryPlayStatisticsManager.cs @@ -0,0 +1,83 @@ +using ARMeilleure.Memory; +using Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService.Types; +using Ryujinx.HLE.Utilities; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService +{ + static class QueryPlayStatisticsManager + { + private static Dictionary applicationPlayStatistics = new Dictionary(); + + internal static ResultCode GetPlayStatistics(ServiceCtx context, bool byUserId = false) + { + long inputPosition = context.Request.SendBuff[0].Position; + long inputSize = context.Request.SendBuff[0].Size; + + long outputPosition = context.Request.ReceiveBuff[0].Position; + long outputSize = context.Request.ReceiveBuff[0].Size; + + UInt128 userId = byUserId ? new UInt128(context.RequestData.ReadBytes(0x10)) : new UInt128(); + + if (byUserId) + { + if (!context.Device.System.State.Account.TryGetUser(userId, out _)) + { + return ResultCode.UserNotFound; + } + } + + PlayLogQueryCapability queryCapability = (PlayLogQueryCapability)context.Device.System.ControlData.Value.PlayLogQueryCapability; + + List titleIds = new List(); + + for (int i = 0; i < inputSize / sizeof(ulong); i++) + { + titleIds.Add(BitConverter.ToUInt64(context.Memory.ReadBytes(inputPosition, inputSize), 0)); + } + + if (queryCapability == PlayLogQueryCapability.WhiteList) + { + // Check if input title ids are in the whitelist. + foreach (ulong titleId in titleIds) + { + if (!context.Device.System.ControlData.Value.PlayLogQueryableApplicationId.Contains(titleId)) + { + return (ResultCode)Am.ResultCode.ObjectInvalid; + } + } + } + + MemoryHelper.FillWithZeros(context.Memory, outputPosition, (int)outputSize); + + // Return ResultCode.ServiceUnavailable if data is locked by another process. + var filteredApplicationPlayStatistics = applicationPlayStatistics.AsEnumerable(); + + if (queryCapability == PlayLogQueryCapability.None) + { + filteredApplicationPlayStatistics = filteredApplicationPlayStatistics.Where(kv => kv.Value.TitleId == context.Process.TitleId); + } + else // PlayLogQueryCapability.All + { + filteredApplicationPlayStatistics = filteredApplicationPlayStatistics.Where(kv => titleIds.Contains(kv.Value.TitleId)); + } + + if (byUserId) + { + filteredApplicationPlayStatistics = filteredApplicationPlayStatistics.Where(kv => kv.Key == userId); + } + + for (int i = 0; i < filteredApplicationPlayStatistics.Count(); i++) + { + MemoryHelper.Write(context.Memory, outputPosition + (i * Marshal.SizeOf()), filteredApplicationPlayStatistics.ElementAt(i).Value); + } + + context.ResponseData.Write(filteredApplicationPlayStatistics.Count()); + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/Types/ApplicationPlayStatistics.cs b/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/Types/ApplicationPlayStatistics.cs new file mode 100644 index 0000000000..c28d757e86 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/Types/ApplicationPlayStatistics.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0x18)] + struct ApplicationPlayStatistics + { + public ulong TitleId; + public long TotalPlayTime; // In nanoseconds. + public long TotalLaunchCount; + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/Types/PlayLogQueryCapability.cs b/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/Types/PlayLogQueryCapability.cs new file mode 100644 index 0000000000..9e4b85dead --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/Types/PlayLogQueryCapability.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService.Types +{ + enum PlayLogQueryCapability + { + None, + WhiteList, + All + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Sdb/Pdm/ResultCode.cs b/Ryujinx.HLE/HOS/Services/Sdb/Pdm/ResultCode.cs new file mode 100644 index 0000000000..3ceb8d1a8d --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sdb/Pdm/ResultCode.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm +{ + enum ResultCode + { + ModuleId = 178, + ErrorCodeShift = 9, + + Success = 0, + + UserNotFound = (101 << ErrorCodeShift) | ModuleId, + ServiceUnavailable = (150 << ErrorCodeShift) | ModuleId + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Sdb/Pl/ISharedFontManager.cs b/Ryujinx.HLE/HOS/Services/Sdb/Pl/ISharedFontManager.cs new file mode 100644 index 0000000000..418c15f2c2 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sdb/Pl/ISharedFontManager.cs @@ -0,0 +1,128 @@ +using Ryujinx.HLE.HOS.Font; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Common; +using System; + +namespace Ryujinx.HLE.HOS.Services.Sdb.Pl +{ + [Service("pl:u")] + [Service("pl:s")] // 9.0.0+ + class ISharedFontManager : IpcService + { + public ISharedFontManager(ServiceCtx context) { } + + [Command(0)] + // RequestLoad(u32) + public ResultCode RequestLoad(ServiceCtx context) + { + SharedFontType fontType = (SharedFontType)context.RequestData.ReadInt32(); + + // We don't need to do anything here because we do lazy initialization + // on SharedFontManager (the font is loaded when necessary). + return ResultCode.Success; + } + + [Command(1)] + // GetLoadState(u32) -> u32 + public ResultCode GetLoadState(ServiceCtx context) + { + SharedFontType fontType = (SharedFontType)context.RequestData.ReadInt32(); + + // 1 (true) indicates that the font is already loaded. + // All fonts are already loaded. + context.ResponseData.Write(1); + + return ResultCode.Success; + } + + [Command(2)] + // GetFontSize(u32) -> u32 + public ResultCode GetFontSize(ServiceCtx context) + { + SharedFontType fontType = (SharedFontType)context.RequestData.ReadInt32(); + + context.ResponseData.Write(context.Device.System.Font.GetFontSize(fontType)); + + return ResultCode.Success; + } + + [Command(3)] + // GetSharedMemoryAddressOffset(u32) -> u32 + public ResultCode GetSharedMemoryAddressOffset(ServiceCtx context) + { + SharedFontType fontType = (SharedFontType)context.RequestData.ReadInt32(); + + context.ResponseData.Write(context.Device.System.Font.GetSharedMemoryAddressOffset(fontType)); + + return ResultCode.Success; + } + + [Command(4)] + // GetSharedMemoryNativeHandle() -> handle + public ResultCode GetSharedMemoryNativeHandle(ServiceCtx context) + { + context.Device.System.Font.EnsureInitialized(context.Device.System.ContentManager, false); + + if (context.Process.HandleTable.GenerateHandle(context.Device.System.FontSharedMem, out int handle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle); + + return ResultCode.Success; + } + + [Command(5)] + // GetSharedFontInOrderOfPriority(bytes<8, 1>) -> (u8, u32, buffer, buffer, buffer) + public ResultCode GetSharedFontInOrderOfPriority(ServiceCtx context) + { + long languageCode = context.RequestData.ReadInt64(); + int loadedCount = 0; + + for (SharedFontType type = 0; type < SharedFontType.Count; type++) + { + int offset = (int)type * 4; + + if (!AddFontToOrderOfPriorityList(context, type, offset)) + { + break; + } + + loadedCount++; + } + + context.ResponseData.Write(loadedCount); + context.ResponseData.Write((int)SharedFontType.Count); + + return ResultCode.Success; + } + + private bool AddFontToOrderOfPriorityList(ServiceCtx context, SharedFontType fontType, int offset) + { + long typesPosition = context.Request.ReceiveBuff[0].Position; + long typesSize = context.Request.ReceiveBuff[0].Size; + + long offsetsPosition = context.Request.ReceiveBuff[1].Position; + long offsetsSize = context.Request.ReceiveBuff[1].Size; + + long fontSizeBufferPosition = context.Request.ReceiveBuff[2].Position; + long fontSizeBufferSize = context.Request.ReceiveBuff[2].Size; + + if ((uint)offset + 4 > (uint)typesSize || + (uint)offset + 4 > (uint)offsetsSize || + (uint)offset + 4 > (uint)fontSizeBufferSize) + { + return false; + } + + context.Memory.WriteInt32(typesPosition + offset, (int)fontType); + + context.Memory.WriteInt32(offsetsPosition + offset, context.Device.System.Font.GetSharedMemoryAddressOffset(fontType)); + + context.Memory.WriteInt32(fontSizeBufferPosition + offset, context.Device.System.Font.GetFontSize(fontType)); + + return true; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/ServiceAttributes.cs b/Ryujinx.HLE/HOS/Services/ServiceAttributes.cs new file mode 100644 index 0000000000..9ca775afe8 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/ServiceAttributes.cs @@ -0,0 +1,17 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services +{ + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + public class ServiceAttribute : Attribute + { + public readonly string Name; + public readonly object Parameter; + + public ServiceAttribute(string name, object parameter = null) + { + Name = name; + Parameter = parameter; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/ServiceConfiguration.cs b/Ryujinx.HLE/HOS/Services/ServiceConfiguration.cs new file mode 100644 index 0000000000..d73c76d9cf --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/ServiceConfiguration.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.HLE.HOS.Services +{ + public static class ServiceConfiguration + { + public static bool IgnoreMissingServices { get; set; } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Settings/IFactorySettingsServer.cs b/Ryujinx.HLE/HOS/Services/Settings/IFactorySettingsServer.cs new file mode 100644 index 0000000000..4dd344f8e2 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Settings/IFactorySettingsServer.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Am.Tcap +{ + [Service("set:cal")] + class IFactorySettingsServer : IpcService + { + public IFactorySettingsServer(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Settings/IFirmwareDebugSettingsServer.cs b/Ryujinx.HLE/HOS/Services/Settings/IFirmwareDebugSettingsServer.cs new file mode 100644 index 0000000000..3b7e1af2d9 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Settings/IFirmwareDebugSettingsServer.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Settings +{ + [Service("set:fd")] + class IFirmwareDebugSettingsServer : IpcService + { + public IFirmwareDebugSettingsServer(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Settings/ISettingsServer.cs b/Ryujinx.HLE/HOS/Services/Settings/ISettingsServer.cs new file mode 100644 index 0000000000..2d2512dff3 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Settings/ISettingsServer.cs @@ -0,0 +1,109 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.SystemState; +using System; + +namespace Ryujinx.HLE.HOS.Services.Settings +{ + [Service("set")] + class ISettingsServer : IpcService + { + public ISettingsServer(ServiceCtx context) { } + + [Command(0)] + // GetLanguageCode() -> nn::settings::LanguageCode + public ResultCode GetLanguageCode(ServiceCtx context) + { + context.ResponseData.Write(context.Device.System.State.DesiredLanguageCode); + + return ResultCode.Success; + } + + [Command(1)] + // GetAvailableLanguageCodes() -> (u32, buffer) + public ResultCode GetAvailableLanguageCodes(ServiceCtx context) + { + return GetAvailableLanguagesCodesImpl( + context, + context.Request.RecvListBuff[0].Position, + context.Request.RecvListBuff[0].Size, + 0xF); + } + + [Command(2)] // 4.0.0+ + // MakeLanguageCode(nn::settings::Language language_index) -> nn::settings::LanguageCode + public ResultCode MakeLanguageCode(ServiceCtx context) + { + int languageIndex = context.RequestData.ReadInt32(); + + if ((uint)languageIndex >= (uint)SystemStateMgr.LanguageCodes.Length) + { + return ResultCode.LanguageOutOfRange; + } + + context.ResponseData.Write(SystemStateMgr.GetLanguageCode(languageIndex)); + + return ResultCode.Success; + } + + [Command(3)] + // GetAvailableLanguageCodeCount() -> u32 + public ResultCode GetAvailableLanguageCodeCount(ServiceCtx context) + { + context.ResponseData.Write(Math.Min(SystemStateMgr.LanguageCodes.Length, 0xF)); + + return ResultCode.Success; + } + + [Command(5)] + // GetAvailableLanguageCodes2() -> (u32, buffer) + public ResultCode GetAvailableLanguageCodes2(ServiceCtx context) + { + return GetAvailableLanguagesCodesImpl( + context, + context.Request.ReceiveBuff[0].Position, + context.Request.ReceiveBuff[0].Size, + SystemStateMgr.LanguageCodes.Length); + } + + [Command(6)] + // GetAvailableLanguageCodeCount2() -> u32 + public ResultCode GetAvailableLanguageCodeCount2(ServiceCtx context) + { + context.ResponseData.Write(SystemStateMgr.LanguageCodes.Length); + + return ResultCode.Success; + } + + [Command(8)] // 5.0.0+ + // GetQuestFlag() -> bool + public ResultCode GetQuestFlag(ServiceCtx context) + { + context.ResponseData.Write(false); + + Logger.PrintStub(LogClass.ServiceSet); + + return ResultCode.Success; + } + + public ResultCode GetAvailableLanguagesCodesImpl(ServiceCtx context, long position, long size, int maxSize) + { + int count = (int)(size / 8); + + if (count > maxSize) + { + count = maxSize; + } + + for (int index = 0; index < count; index++) + { + context.Memory.WriteInt64(position, SystemStateMgr.GetLanguageCode(index)); + + position += 8; + } + + context.ResponseData.Write(count); + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Settings/ISystemSettingsServer.cs b/Ryujinx.HLE/HOS/Services/Settings/ISystemSettingsServer.cs new file mode 100644 index 0000000000..73f437e8bf --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Settings/ISystemSettingsServer.cs @@ -0,0 +1,258 @@ +using LibHac; +using LibHac.Fs; +using LibHac.FsSystem; +using LibHac.FsSystem.NcaUtils; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.HOS.SystemState; +using System; +using System.IO; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Settings +{ + [Service("set:sys")] + class ISystemSettingsServer : IpcService + { + public ISystemSettingsServer(ServiceCtx context) { } + + [Command(3)] + // GetFirmwareVersion() -> buffer + public ResultCode GetFirmwareVersion(ServiceCtx context) + { + return GetFirmwareVersion2(context); + } + + [Command(4)] + // GetFirmwareVersion2() -> buffer + public ResultCode GetFirmwareVersion2(ServiceCtx context) + { + long replyPos = context.Request.RecvListBuff[0].Position; + long replySize = context.Request.RecvListBuff[0].Size; + + byte[] firmwareData = GetFirmwareData(context.Device); + + if (firmwareData != null) + { + context.Memory.WriteBytes(replyPos, firmwareData); + + return ResultCode.Success; + } + + const byte majorFwVersion = 0x03; + const byte minorFwVersion = 0x00; + const byte microFwVersion = 0x00; + const byte unknown = 0x00; //Build? + + const int revisionNumber = 0x0A; + + const string platform = "NX"; + const string unknownHex = "7fbde2b0bba4d14107bf836e4643043d9f6c8e47"; + const string version = "3.0.0"; + const string build = "NintendoSDK Firmware for NX 3.0.0-10.0"; + + // http://switchbrew.org/index.php?title=System_Version_Title + using (MemoryStream ms = new MemoryStream(0x100)) + { + BinaryWriter writer = new BinaryWriter(ms); + + writer.Write(majorFwVersion); + writer.Write(minorFwVersion); + writer.Write(microFwVersion); + writer.Write(unknown); + + writer.Write(revisionNumber); + + writer.Write(Encoding.ASCII.GetBytes(platform)); + + ms.Seek(0x28, SeekOrigin.Begin); + + writer.Write(Encoding.ASCII.GetBytes(unknownHex)); + + ms.Seek(0x68, SeekOrigin.Begin); + + writer.Write(Encoding.ASCII.GetBytes(version)); + + ms.Seek(0x80, SeekOrigin.Begin); + + writer.Write(Encoding.ASCII.GetBytes(build)); + + context.Memory.WriteBytes(replyPos, ms.ToArray()); + } + + return ResultCode.Success; + } + + [Command(23)] + // GetColorSetId() -> i32 + public ResultCode GetColorSetId(ServiceCtx context) + { + context.ResponseData.Write((int)context.Device.System.State.ThemeColor); + + return ResultCode.Success; + } + + [Command(24)] + // GetColorSetId() -> i32 + public ResultCode SetColorSetId(ServiceCtx context) + { + int colorSetId = context.RequestData.ReadInt32(); + + context.Device.System.State.ThemeColor = (ColorSet)colorSetId; + + return ResultCode.Success; + } + + [Command(37)] + // GetSettingsItemValueSize(buffer, buffer) -> u64 + public ResultCode GetSettingsItemValueSize(ServiceCtx context) + { + long classPos = context.Request.PtrBuff[0].Position; + long classSize = context.Request.PtrBuff[0].Size; + + long namePos = context.Request.PtrBuff[1].Position; + long nameSize = context.Request.PtrBuff[1].Size; + + byte[] Class = context.Memory.ReadBytes(classPos, classSize); + byte[] name = context.Memory.ReadBytes(namePos, nameSize); + + string askedSetting = Encoding.ASCII.GetString(Class).Trim('\0') + "!" + Encoding.ASCII.GetString(name).Trim('\0'); + + NxSettings.Settings.TryGetValue(askedSetting, out object nxSetting); + + if (nxSetting != null) + { + ulong settingSize; + + if (nxSetting is string stringValue) + { + settingSize = (ulong)stringValue.Length + 1; + } + else if (nxSetting is int) + { + settingSize = sizeof(int); + } + else if (nxSetting is bool) + { + settingSize = 1; + } + else + { + throw new NotImplementedException(nxSetting.GetType().Name); + } + + context.ResponseData.Write(settingSize); + } + + return ResultCode.Success; + } + + [Command(38)] + // GetSettingsItemValue(buffer, buffer) -> (u64, buffer) + public ResultCode GetSettingsItemValue(ServiceCtx context) + { + long classPos = context.Request.PtrBuff[0].Position; + long classSize = context.Request.PtrBuff[0].Size; + + long namePos = context.Request.PtrBuff[1].Position; + long nameSize = context.Request.PtrBuff[1].Size; + + long replyPos = context.Request.ReceiveBuff[0].Position; + long replySize = context.Request.ReceiveBuff[0].Size; + + byte[] Class = context.Memory.ReadBytes(classPos, classSize); + byte[] name = context.Memory.ReadBytes(namePos, nameSize); + + string askedSetting = Encoding.ASCII.GetString(Class).Trim('\0') + "!" + Encoding.ASCII.GetString(name).Trim('\0'); + + NxSettings.Settings.TryGetValue(askedSetting, out object nxSetting); + + if (nxSetting != null) + { + byte[] settingBuffer = new byte[replySize]; + + if (nxSetting is string stringValue) + { + if (stringValue.Length + 1 > replySize) + { + Logger.PrintError(LogClass.ServiceSet, $"{askedSetting} String value size is too big!"); + } + else + { + settingBuffer = Encoding.ASCII.GetBytes(stringValue + "\0"); + } + } + + if (nxSetting is int intValue) + { + settingBuffer = BitConverter.GetBytes(intValue); + } + else if (nxSetting is bool boolValue) + { + settingBuffer[0] = boolValue ? (byte)1 : (byte)0; + } + else + { + throw new NotImplementedException(nxSetting.GetType().Name); + } + + context.Memory.WriteBytes(replyPos, settingBuffer); + + Logger.PrintDebug(LogClass.ServiceSet, $"{askedSetting} set value: {nxSetting} as {nxSetting.GetType()}"); + } + else + { + Logger.PrintError(LogClass.ServiceSet, $"{askedSetting} not found!"); + } + + return ResultCode.Success; + } + + public byte[] GetFirmwareData(Switch device) + { + long titleId = 0x0100000000000809; + string contentPath = device.System.ContentManager.GetInstalledContentPath(titleId, StorageId.NandSystem, NcaContentType.Data); + + if (string.IsNullOrWhiteSpace(contentPath)) + { + return null; + } + + string firmwareTitlePath = device.FileSystem.SwitchPathToSystemPath(contentPath); + + using(IStorage firmwareStorage = new LocalStorage(firmwareTitlePath, FileAccess.Read)) + { + Nca firmwareContent = new Nca(device.System.KeySet, firmwareStorage); + + if (!firmwareContent.CanOpenSection(NcaSectionType.Data)) + { + return null; + } + + IFileSystem firmwareRomFs = firmwareContent.OpenFileSystem(NcaSectionType.Data, device.System.FsIntegrityCheckLevel); + + Result result = firmwareRomFs.OpenFile(out IFile firmwareFile, "/file", OpenMode.Read); + if (result.IsFailure()) + { + return null; + } + + result = firmwareFile.GetSize(out long fileSize); + if (result.IsFailure()) + { + return null; + } + + byte[] data = new byte[fileSize]; + + result = firmwareFile.Read(out _, 0, data); + if (result.IsFailure()) + { + return null; + } + + return data; + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Set/NxSettings.cs b/Ryujinx.HLE/HOS/Services/Settings/NxSettings.cs similarity index 99% rename from Ryujinx.HLE/OsHle/Services/Set/NxSettings.cs rename to Ryujinx.HLE/HOS/Services/Settings/NxSettings.cs index dc4a0b96fb..ca3853e8b8 100644 --- a/Ryujinx.HLE/OsHle/Services/Set/NxSettings.cs +++ b/Ryujinx.HLE/HOS/Services/Settings/NxSettings.cs @@ -1,11 +1,11 @@ using System.Collections.Generic; -namespace Ryujinx.HLE.OsHle.Services.Set +namespace Ryujinx.HLE.HOS.Services.Settings { static class NxSettings { - //Generated automatically from a Switch 3.0 config file (Tid: 0100000000000818). - public static Dictionary Settings = new Dictionary() + // Generated automatically from a Switch 3.0 config file (Tid: 0100000000000818). + public static Dictionary Settings = new Dictionary { { "account!na_required_for_network_service", true }, { "account.daemon!background_awaking_periodicity", 10800 }, @@ -1704,8 +1704,9 @@ namespace Ryujinx.HLE.OsHle.Services.Set { "time!standard_steady_clock_test_offset_minutes", 0 }, { "time!standard_steady_clock_rtc_update_interval_minutes", 5 }, { "time!standard_network_clock_sufficient_accuracy_minutes", 43200 }, + { "time!standard_user_clock_initial_year", 2019 }, { "usb!usb30_force_enabled", false }, - { "wlan_debug!skip_wlan_boot", false }, + { "wlan_debug!skip_wlan_boot", false } }; } -} +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Settings/ResultCode.cs b/Ryujinx.HLE/HOS/Services/Settings/ResultCode.cs new file mode 100644 index 0000000000..6d4d578fdf --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Settings/ResultCode.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.HLE.HOS.Services.Settings +{ + enum ResultCode + { + ModuleId = 105, + ErrorCodeShift = 9, + + Success = 0, + + LanguageOutOfRange = (625 << ErrorCodeShift) | ModuleId + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Sm/IManagerInterface.cs b/Ryujinx.HLE/HOS/Services/Sm/IManagerInterface.cs new file mode 100644 index 0000000000..f867f23a47 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sm/IManagerInterface.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Sm +{ + [Service("sm:m")] + class IManagerInterface : IpcService + { + public IManagerInterface(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs b/Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs new file mode 100644 index 0000000000..bcab5b17d6 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs @@ -0,0 +1,206 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Ipc; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; + +namespace Ryujinx.HLE.HOS.Services.Sm +{ + [Service("sm:")] + class IUserInterface : IpcService + { + private Dictionary _services; + + private ConcurrentDictionary _registeredServices; + + private bool _isInitialized; + + public IUserInterface(ServiceCtx context = null) + { + _registeredServices = new ConcurrentDictionary(); + + _services = Assembly.GetExecutingAssembly().GetTypes() + .SelectMany(type => type.GetCustomAttributes(typeof(ServiceAttribute), true) + .Select(service => (((ServiceAttribute)service).Name, type))) + .ToDictionary(service => service.Name, service => service.type); + } + + public static void InitializePort(Horizon system) + { + KPort port = new KPort(system, 256, false, 0); + + port.ClientPort.SetName("sm:"); + + port.ClientPort.Service = new IUserInterface(); + } + + [Command(0)] + // Initialize(pid, u64 reserved) + public ResultCode Initialize(ServiceCtx context) + { + _isInitialized = true; + + return ResultCode.Success; + } + + [Command(1)] + // GetService(ServiceName name) -> handle + public ResultCode GetService(ServiceCtx context) + { + if (!_isInitialized) + { + return ResultCode.NotInitialized; + } + + string name = ReadName(context); + + if (name == string.Empty) + { + return ResultCode.InvalidName; + } + + KSession session = new KSession(context.Device.System); + + if (_registeredServices.TryGetValue(name, out KPort port)) + { + KernelResult result = port.EnqueueIncomingSession(session.ServerSession); + + if (result != KernelResult.Success) + { + throw new InvalidOperationException($"Session enqueue on port returned error \"{result}\"."); + } + } + else + { + if (_services.TryGetValue(name, out Type type)) + { + ServiceAttribute serviceAttribute = (ServiceAttribute)type.GetCustomAttributes(typeof(ServiceAttribute)).First(service => ((ServiceAttribute)service).Name == name); + + session.ClientSession.Service = serviceAttribute.Parameter != null ? (IpcService)Activator.CreateInstance(type, context, serviceAttribute.Parameter) + : (IpcService)Activator.CreateInstance(type, context); + } + else + { + if (ServiceConfiguration.IgnoreMissingServices) + { + Logger.PrintWarning(LogClass.Service, $"Missing service {name} ignored"); + + session.ClientSession.Service = new DummyService(name); + } + else + { + throw new NotImplementedException(name); + } + } + } + + if (context.Process.HandleTable.GenerateHandle(session.ClientSession, out int handle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeMove(handle); + + return ResultCode.Success; + } + + [Command(2)] + // RegisterService(ServiceName name, u8, u32 maxHandles) -> handle + public ResultCode RegisterService(ServiceCtx context) + { + if (!_isInitialized) + { + return ResultCode.NotInitialized; + } + + long namePosition = context.RequestData.BaseStream.Position; + + string name = ReadName(context); + + context.RequestData.BaseStream.Seek(namePosition + 8, SeekOrigin.Begin); + + bool isLight = (context.RequestData.ReadInt32() & 1) != 0; + + int maxSessions = context.RequestData.ReadInt32(); + + if (name == string.Empty) + { + return ResultCode.InvalidName; + } + + Logger.PrintInfo(LogClass.ServiceSm, $"Register \"{name}\"."); + + KPort port = new KPort(context.Device.System, maxSessions, isLight, 0); + + if (!_registeredServices.TryAdd(name, port)) + { + return ResultCode.AlreadyRegistered; + } + + if (context.Process.HandleTable.GenerateHandle(port.ServerPort, out int handle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeMove(handle); + + return ResultCode.Success; + } + + [Command(3)] + // UnregisterService(ServiceName name) + public ResultCode UnregisterService(ServiceCtx context) + { + if (!_isInitialized) + { + return ResultCode.NotInitialized; + } + + long namePosition = context.RequestData.BaseStream.Position; + + string name = ReadName(context); + + context.RequestData.BaseStream.Seek(namePosition + 8, SeekOrigin.Begin); + + bool isLight = (context.RequestData.ReadInt32() & 1) != 0; + + int maxSessions = context.RequestData.ReadInt32(); + + if (name == string.Empty) + { + return ResultCode.InvalidName; + } + + if (!_registeredServices.TryRemove(name, out _)) + { + return ResultCode.NotRegistered; + } + + return ResultCode.Success; + } + + private static string ReadName(ServiceCtx context) + { + string name = string.Empty; + + for (int index = 0; index < 8 && + context.RequestData.BaseStream.Position < + context.RequestData.BaseStream.Length; index++) + { + byte chr = context.RequestData.ReadByte(); + + if (chr >= 0x20 && chr < 0x7f) + { + name += (char)chr; + } + } + + return name; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Sm/ResultCode.cs b/Ryujinx.HLE/HOS/Services/Sm/ResultCode.cs new file mode 100644 index 0000000000..f72bf01093 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sm/ResultCode.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.HLE.HOS.Services.Sm +{ + enum ResultCode + { + ModuleId = 21, + ErrorCodeShift = 9, + + Success = 0, + + NotInitialized = (2 << ErrorCodeShift) | ModuleId, + AlreadyRegistered = (4 << ErrorCodeShift) | ModuleId, + InvalidName = (6 << ErrorCodeShift) | ModuleId, + NotRegistered = (7 << ErrorCodeShift) | ModuleId + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs new file mode 100644 index 0000000000..b2b3d05260 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs @@ -0,0 +1,1196 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.Utilities; +using System; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd +{ + [Service("bsd:s", true)] + [Service("bsd:u", false)] + class IClient : IpcService + { + private static Dictionary _errorMap = new Dictionary + { + // WSAEINTR + {WsaError.WSAEINTR, LinuxError.EINTR}, + // WSAEWOULDBLOCK + {WsaError.WSAEWOULDBLOCK, LinuxError.EWOULDBLOCK}, + // WSAEINPROGRESS + {WsaError.WSAEINPROGRESS, LinuxError.EINPROGRESS}, + // WSAEALREADY + {WsaError.WSAEALREADY, LinuxError.EALREADY}, + // WSAENOTSOCK + {WsaError.WSAENOTSOCK, LinuxError.ENOTSOCK}, + // WSAEDESTADDRREQ + {WsaError.WSAEDESTADDRREQ, LinuxError.EDESTADDRREQ}, + // WSAEMSGSIZE + {WsaError.WSAEMSGSIZE, LinuxError.EMSGSIZE}, + // WSAEPROTOTYPE + {WsaError.WSAEPROTOTYPE, LinuxError.EPROTOTYPE}, + // WSAENOPROTOOPT + {WsaError.WSAENOPROTOOPT, LinuxError.ENOPROTOOPT}, + // WSAEPROTONOSUPPORT + {WsaError.WSAEPROTONOSUPPORT, LinuxError.EPROTONOSUPPORT}, + // WSAESOCKTNOSUPPORT + {WsaError.WSAESOCKTNOSUPPORT, LinuxError.ESOCKTNOSUPPORT}, + // WSAEOPNOTSUPP + {WsaError.WSAEOPNOTSUPP, LinuxError.EOPNOTSUPP}, + // WSAEPFNOSUPPORT + {WsaError.WSAEPFNOSUPPORT, LinuxError.EPFNOSUPPORT}, + // WSAEAFNOSUPPORT + {WsaError.WSAEAFNOSUPPORT, LinuxError.EAFNOSUPPORT}, + // WSAEADDRINUSE + {WsaError.WSAEADDRINUSE, LinuxError.EADDRINUSE}, + // WSAEADDRNOTAVAIL + {WsaError.WSAEADDRNOTAVAIL, LinuxError.EADDRNOTAVAIL}, + // WSAENETDOWN + {WsaError.WSAENETDOWN, LinuxError.ENETDOWN}, + // WSAENETUNREACH + {WsaError.WSAENETUNREACH, LinuxError.ENETUNREACH}, + // WSAENETRESET + {WsaError.WSAENETRESET, LinuxError.ENETRESET}, + // WSAECONNABORTED + {WsaError.WSAECONNABORTED, LinuxError.ECONNABORTED}, + // WSAECONNRESET + {WsaError.WSAECONNRESET, LinuxError.ECONNRESET}, + // WSAENOBUFS + {WsaError.WSAENOBUFS, LinuxError.ENOBUFS}, + // WSAEISCONN + {WsaError.WSAEISCONN, LinuxError.EISCONN}, + // WSAENOTCONN + {WsaError.WSAENOTCONN, LinuxError.ENOTCONN}, + // WSAESHUTDOWN + {WsaError.WSAESHUTDOWN, LinuxError.ESHUTDOWN}, + // WSAETOOMANYREFS + {WsaError.WSAETOOMANYREFS, LinuxError.ETOOMANYREFS}, + // WSAETIMEDOUT + {WsaError.WSAETIMEDOUT, LinuxError.ETIMEDOUT}, + // WSAECONNREFUSED + {WsaError.WSAECONNREFUSED, LinuxError.ECONNREFUSED}, + // WSAELOOP + {WsaError.WSAELOOP, LinuxError.ELOOP}, + // WSAENAMETOOLONG + {WsaError.WSAENAMETOOLONG, LinuxError.ENAMETOOLONG}, + // WSAEHOSTDOWN + {WsaError.WSAEHOSTDOWN, LinuxError.EHOSTDOWN}, + // WSAEHOSTUNREACH + {WsaError.WSAEHOSTUNREACH, LinuxError.EHOSTUNREACH}, + // WSAENOTEMPTY + {WsaError.WSAENOTEMPTY, LinuxError.ENOTEMPTY}, + // WSAEUSERS + {WsaError.WSAEUSERS, LinuxError.EUSERS}, + // WSAEDQUOT + {WsaError.WSAEDQUOT, LinuxError.EDQUOT}, + // WSAESTALE + {WsaError.WSAESTALE, LinuxError.ESTALE}, + // WSAEREMOTE + {WsaError.WSAEREMOTE, LinuxError.EREMOTE}, + // WSAEINVAL + {WsaError.WSAEINVAL, LinuxError.EINVAL}, + // WSAEFAULT + {WsaError.WSAEFAULT, LinuxError.EFAULT}, + // NOERROR + {0, 0} + }; + + private bool _isPrivileged; + + private List _sockets = new List(); + + public IClient(ServiceCtx context, bool isPrivileged) + { + _isPrivileged = isPrivileged; + } + + private LinuxError ConvertError(WsaError errorCode) + { + if (!_errorMap.TryGetValue(errorCode, out LinuxError errno)) + { + errno = (LinuxError)errorCode; + } + + return errno; + } + + private ResultCode WriteWinSock2Error(ServiceCtx context, WsaError errorCode) + { + return WriteBsdResult(context, -1, ConvertError(errorCode)); + } + + private ResultCode WriteBsdResult(ServiceCtx context, int result, LinuxError errorCode = 0) + { + if (errorCode != LinuxError.SUCCESS) + { + result = -1; + } + + context.ResponseData.Write(result); + context.ResponseData.Write((int)errorCode); + + return ResultCode.Success; + } + + private BsdSocket RetrieveSocket(int socketFd) + { + if (socketFd >= 0 && _sockets.Count > socketFd) + { + return _sockets[socketFd]; + } + + return null; + } + + private LinuxError SetResultErrno(Socket socket, int result) + { + return result == 0 && !socket.Blocking ? LinuxError.EWOULDBLOCK : LinuxError.SUCCESS; + } + + private AddressFamily ConvertFromBsd(int domain) + { + if (domain == 2) + { + return AddressFamily.InterNetwork; + } + + // FIXME: AF_ROUTE ignored, is that really needed? + return AddressFamily.Unknown; + } + + private ResultCode SocketInternal(ServiceCtx context, bool exempt) + { + AddressFamily domain = (AddressFamily)context.RequestData.ReadInt32(); + SocketType type = (SocketType)context.RequestData.ReadInt32(); + ProtocolType protocol = (ProtocolType)context.RequestData.ReadInt32(); + + if (domain == AddressFamily.Unknown) + { + return WriteBsdResult(context, -1, LinuxError.EPROTONOSUPPORT); + } + else if ((type == SocketType.Seqpacket || type == SocketType.Raw) && !_isPrivileged) + { + if (domain != AddressFamily.InterNetwork || type != SocketType.Raw || protocol != ProtocolType.Icmp) + { + return WriteBsdResult(context, -1, LinuxError.ENOENT); + } + } + + BsdSocket newBsdSocket = new BsdSocket + { + Family = (int)domain, + Type = (int)type, + Protocol = (int)protocol, + Handle = new Socket(domain, type, protocol) + }; + + _sockets.Add(newBsdSocket); + + if (exempt) + { + newBsdSocket.Handle.Disconnect(true); + } + + return WriteBsdResult(context, _sockets.Count - 1); + } + + private IPEndPoint ParseSockAddr(ServiceCtx context, long bufferPosition, long bufferSize) + { + int size = context.Memory.ReadByte(bufferPosition); + int family = context.Memory.ReadByte(bufferPosition + 1); + int port = BinaryPrimitives.ReverseEndianness(context.Memory.ReadUInt16(bufferPosition + 2)); + + byte[] rawIp = context.Memory.ReadBytes(bufferPosition + 4, 4); + + return new IPEndPoint(new IPAddress(rawIp), port); + } + + private void WriteSockAddr(ServiceCtx context, long bufferPosition, IPEndPoint endPoint) + { + context.Memory.WriteByte(bufferPosition, 0); + context.Memory.WriteByte(bufferPosition + 1, (byte)endPoint.AddressFamily); + context.Memory.WriteUInt16(bufferPosition + 2, BinaryPrimitives.ReverseEndianness((ushort)endPoint.Port)); + context.Memory.WriteBytes(bufferPosition + 4, endPoint.Address.GetAddressBytes()); + } + + private void WriteSockAddr(ServiceCtx context, long bufferPosition, BsdSocket socket, bool isRemote) + { + IPEndPoint endPoint = (isRemote ? socket.Handle.RemoteEndPoint : socket.Handle.LocalEndPoint) as IPEndPoint; + + WriteSockAddr(context, bufferPosition, endPoint); + } + + [Command(0)] + // Initialize(nn::socket::BsdBufferConfig config, u64 pid, u64 transferMemorySize, KObject, pid) -> u32 bsd_errno + public ResultCode RegisterClient(ServiceCtx context) + { + /* + typedef struct { + u32 version; // Observed 1 on 2.0 LibAppletWeb, 2 on 3.0. + u32 tcp_tx_buf_size; // Size of the TCP transfer (send) buffer (initial or fixed). + u32 tcp_rx_buf_size; // Size of the TCP recieve buffer (initial or fixed). + u32 tcp_tx_buf_max_size; // Maximum size of the TCP transfer (send) buffer. If it is 0, the size of the buffer is fixed to its initial value. + u32 tcp_rx_buf_max_size; // Maximum size of the TCP receive buffer. If it is 0, the size of the buffer is fixed to its initial value. + u32 udp_tx_buf_size; // Size of the UDP transfer (send) buffer (typically 0x2400 bytes). + u32 udp_rx_buf_size; // Size of the UDP receive buffer (typically 0xA500 bytes). + u32 sb_efficiency; // Number of buffers for each socket (standard values range from 1 to 8). + } BsdBufferConfig; + */ + + // bsd_error + context.ResponseData.Write(0); + + Logger.PrintStub(LogClass.ServiceBsd); + + return ResultCode.Success; + } + + [Command(1)] + // StartMonitoring(u64, pid) + public ResultCode StartMonitoring(ServiceCtx context) + { + ulong unknown0 = context.RequestData.ReadUInt64(); + + Logger.PrintStub(LogClass.ServiceBsd, new { unknown0 }); + + return ResultCode.Success; + } + + [Command(2)] + // Socket(u32 domain, u32 type, u32 protocol) -> (i32 ret, u32 bsd_errno) + public ResultCode Socket(ServiceCtx context) + { + return SocketInternal(context, false); + } + + [Command(3)] + // SocketExempt(u32 domain, u32 type, u32 protocol) -> (i32 ret, u32 bsd_errno) + public ResultCode SocketExempt(ServiceCtx context) + { + return SocketInternal(context, true); + } + + [Command(4)] + // Open(u32 flags, array path) -> (i32 ret, u32 bsd_errno) + public ResultCode Open(ServiceCtx context) + { + (long bufferPosition, long bufferSize) = context.Request.GetBufferType0x21(); + + int flags = context.RequestData.ReadInt32(); + + byte[] rawPath = context.Memory.ReadBytes(bufferPosition, bufferSize); + string path = Encoding.ASCII.GetString(rawPath); + + WriteBsdResult(context, -1, LinuxError.EOPNOTSUPP); + + Logger.PrintStub(LogClass.ServiceBsd, new { path, flags }); + + return ResultCode.Success; + } + + [Command(5)] + // Select(u32 nfds, nn::socket::timeout timeout, buffer readfds_in, buffer writefds_in, buffer errorfds_in) -> (i32 ret, u32 bsd_errno, buffer readfds_out, buffer writefds_out, buffer errorfds_out) + public ResultCode Select(ServiceCtx context) + { + WriteBsdResult(context, -1, LinuxError.EOPNOTSUPP); + + Logger.PrintStub(LogClass.ServiceBsd); + + return ResultCode.Success; + } + + [Command(6)] + // Poll(u32 nfds, u32 timeout, buffer fds) -> (i32 ret, u32 bsd_errno, buffer) + public ResultCode Poll(ServiceCtx context) + { + int fdsCount = context.RequestData.ReadInt32(); + int timeout = context.RequestData.ReadInt32(); + + (long bufferPosition, long bufferSize) = context.Request.GetBufferType0x21(); + + + if (timeout < -1 || fdsCount < 0 || (fdsCount * 8) > bufferSize) + { + return WriteBsdResult(context, -1, LinuxError.EINVAL); + } + + PollEvent[] events = new PollEvent[fdsCount]; + + for (int i = 0; i < fdsCount; i++) + { + int socketFd = context.Memory.ReadInt32(bufferPosition + i * 8); + + BsdSocket socket = RetrieveSocket(socketFd); + + if (socket == null) + { + return WriteBsdResult(context, -1, LinuxError.EBADF);} + + PollEvent.EventTypeMask inputEvents = (PollEvent.EventTypeMask)context.Memory.ReadInt16(bufferPosition + i * 8 + 4); + PollEvent.EventTypeMask outputEvents = (PollEvent.EventTypeMask)context.Memory.ReadInt16(bufferPosition + i * 8 + 6); + + events[i] = new PollEvent(socketFd, socket, inputEvents, outputEvents); + } + + List readEvents = new List(); + List writeEvents = new List(); + List errorEvents = new List(); + + foreach (PollEvent Event in events) + { + bool isValidEvent = false; + + if ((Event.InputEvents & PollEvent.EventTypeMask.Input) != 0) + { + readEvents.Add(Event.Socket.Handle); + errorEvents.Add(Event.Socket.Handle); + + isValidEvent = true; + } + + if ((Event.InputEvents & PollEvent.EventTypeMask.UrgentInput) != 0) + { + readEvents.Add(Event.Socket.Handle); + errorEvents.Add(Event.Socket.Handle); + + isValidEvent = true; + } + + if ((Event.InputEvents & PollEvent.EventTypeMask.Output) != 0) + { + writeEvents.Add(Event.Socket.Handle); + errorEvents.Add(Event.Socket.Handle); + + isValidEvent = true; + } + + if ((Event.InputEvents & PollEvent.EventTypeMask.Error) != 0) + { + errorEvents.Add(Event.Socket.Handle); + isValidEvent = true; + } + + if (!isValidEvent) + { + Logger.PrintWarning(LogClass.ServiceBsd, $"Unsupported Poll input event type: {Event.InputEvents}"); + return WriteBsdResult(context, -1, LinuxError.EINVAL); + } + } + + if (fdsCount != 0) + { + try + { + System.Net.Sockets.Socket.Select(readEvents, writeEvents, errorEvents, timeout); + } + catch (SocketException exception) + { + return WriteWinSock2Error(context, (WsaError)exception.ErrorCode); + } + } + else if (timeout == -1) + { + // FIXME: If we get a timeout of -1 and there is no fds to wait on, this should kill the KProces. (need to check that with re) + throw new InvalidOperationException(); + } + else + { + // FIXME: We should make the KThread sleep but we can't do much about it yet. + Thread.Sleep(timeout); + } + + for (int i = 0; i < fdsCount; i++) + { + PollEvent Event = events[i]; + context.Memory.WriteInt32(bufferPosition + i * 8, Event.SocketFd); + context.Memory.WriteInt16(bufferPosition + i * 8 + 4, (short)Event.InputEvents); + + PollEvent.EventTypeMask outputEvents = 0; + + Socket socket = Event.Socket.Handle; + + if (errorEvents.Contains(socket)) + { + outputEvents |= PollEvent.EventTypeMask.Error; + + if (!socket.Connected || !socket.IsBound) + { + outputEvents |= PollEvent.EventTypeMask.Disconnected; + } + } + + if (readEvents.Contains(socket)) + { + if ((Event.InputEvents & PollEvent.EventTypeMask.Input) != 0) + { + outputEvents |= PollEvent.EventTypeMask.Input; + } + } + + if (writeEvents.Contains(socket)) + { + outputEvents |= PollEvent.EventTypeMask.Output; + } + + context.Memory.WriteInt16(bufferPosition + i * 8 + 6, (short)outputEvents); + } + + return WriteBsdResult(context, readEvents.Count + writeEvents.Count + errorEvents.Count, LinuxError.SUCCESS); + } + + [Command(7)] + // Sysctl(buffer, buffer) -> (i32 ret, u32 bsd_errno, u32, buffer) + public ResultCode Sysctl(ServiceCtx context) + { + WriteBsdResult(context, -1, LinuxError.EOPNOTSUPP); + + Logger.PrintStub(LogClass.ServiceBsd); + + return ResultCode.Success; + } + + [Command(8)] + // Recv(u32 socket, u32 flags) -> (i32 ret, u32 bsd_errno, array message) + public ResultCode Recv(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + SocketFlags socketFlags = (SocketFlags)context.RequestData.ReadInt32(); + + (long receivePosition, long receiveLength) = context.Request.GetBufferType0x22(); + + LinuxError errno = LinuxError.EBADF; + BsdSocket socket = RetrieveSocket(socketFd); + int result = -1; + + if (socket != null) + { + if (socketFlags != SocketFlags.None && (socketFlags & SocketFlags.OutOfBand) == 0 + && (socketFlags & SocketFlags.Peek) == 0) + { + Logger.PrintWarning(LogClass.ServiceBsd, $"Unsupported Recv flags: {socketFlags}"); + return WriteBsdResult(context, -1, LinuxError.EOPNOTSUPP); + } + + byte[] receivedBuffer = new byte[receiveLength]; + + try + { + result = socket.Handle.Receive(receivedBuffer, socketFlags); + errno = SetResultErrno(socket.Handle, result); + + context.Memory.WriteBytes(receivePosition, receivedBuffer); + } + catch (SocketException exception) + { + errno = ConvertError((WsaError)exception.ErrorCode); + } + } + + return WriteBsdResult(context, result, errno); + } + + [Command(9)] + // RecvFrom(u32 sock, u32 flags) -> (i32 ret, u32 bsd_errno, u32 addrlen, buffer message, buffer) + public ResultCode RecvFrom(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + SocketFlags socketFlags = (SocketFlags)context.RequestData.ReadInt32(); + + (long receivePosition, long receiveLength) = context.Request.GetBufferType0x22(); + (long sockAddrOutPosition, long sockAddrOutSize) = context.Request.GetBufferType0x22(1); + + LinuxError errno = LinuxError.EBADF; + BsdSocket socket = RetrieveSocket(socketFd); + int result = -1; + + if (socket != null) + { + if (socketFlags != SocketFlags.None && (socketFlags & SocketFlags.OutOfBand) == 0 + && (socketFlags & SocketFlags.Peek) == 0) + { + Logger.PrintWarning(LogClass.ServiceBsd, $"Unsupported Recv flags: {socketFlags}"); + + return WriteBsdResult(context, -1, LinuxError.EOPNOTSUPP); + } + + byte[] receivedBuffer = new byte[receiveLength]; + EndPoint endPoint = new IPEndPoint(IPAddress.Any, 0); + + try + { + result = socket.Handle.ReceiveFrom(receivedBuffer, receivedBuffer.Length, socketFlags, ref endPoint); + errno = SetResultErrno(socket.Handle, result); + + context.Memory.WriteBytes(receivePosition, receivedBuffer); + WriteSockAddr(context, sockAddrOutPosition, (IPEndPoint)endPoint); + } + catch (SocketException exception) + { + errno = ConvertError((WsaError)exception.ErrorCode); + } + } + + return WriteBsdResult(context, result, errno); + } + + [Command(10)] + // Send(u32 socket, u32 flags, buffer) -> (i32 ret, u32 bsd_errno) + public ResultCode Send(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + SocketFlags socketFlags = (SocketFlags)context.RequestData.ReadInt32(); + + (long sendPosition, long sendSize) = context.Request.GetBufferType0x21(); + + LinuxError errno = LinuxError.EBADF; + BsdSocket socket = RetrieveSocket(socketFd); + int result = -1; + + if (socket != null) + { + if (socketFlags != SocketFlags.None && socketFlags != SocketFlags.OutOfBand + && socketFlags != SocketFlags.Peek && socketFlags != SocketFlags.DontRoute) + { + Logger.PrintWarning(LogClass.ServiceBsd, $"Unsupported Send flags: {socketFlags}"); + + return WriteBsdResult(context, -1, LinuxError.EOPNOTSUPP); + } + + byte[] sendBuffer = context.Memory.ReadBytes(sendPosition, sendSize); + + try + { + result = socket.Handle.Send(sendBuffer, socketFlags); + errno = SetResultErrno(socket.Handle, result); + } + catch (SocketException exception) + { + errno = ConvertError((WsaError)exception.ErrorCode); + } + + } + + return WriteBsdResult(context, result, errno); + } + + [Command(11)] + // SendTo(u32 socket, u32 flags, buffer, buffer) -> (i32 ret, u32 bsd_errno) + public ResultCode SendTo(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + SocketFlags socketFlags = (SocketFlags)context.RequestData.ReadInt32(); + + (long sendPosition, long sendSize) = context.Request.GetBufferType0x21(); + (long bufferPosition, long bufferSize) = context.Request.GetBufferType0x21(1); + + LinuxError errno = LinuxError.EBADF; + BsdSocket socket = RetrieveSocket(socketFd); + int result = -1; + + if (socket != null) + { + if (socketFlags != SocketFlags.None && socketFlags != SocketFlags.OutOfBand + && socketFlags != SocketFlags.Peek && socketFlags != SocketFlags.DontRoute) + { + Logger.PrintWarning(LogClass.ServiceBsd, $"Unsupported Send flags: {socketFlags}"); + + return WriteBsdResult(context, -1, LinuxError.EOPNOTSUPP); + } + + byte[] sendBuffer = context.Memory.ReadBytes(sendPosition, sendSize); + EndPoint endPoint = ParseSockAddr(context, bufferPosition, bufferSize); + + try + { + result = socket.Handle.SendTo(sendBuffer, sendBuffer.Length, socketFlags, endPoint); + errno = SetResultErrno(socket.Handle, result); + } + catch (SocketException exception) + { + errno = ConvertError((WsaError)exception.ErrorCode); + } + + } + + return WriteBsdResult(context, result, errno); + } + + [Command(12)] + // Accept(u32 socket) -> (i32 ret, u32 bsd_errno, u32 addrlen, buffer addr) + public ResultCode Accept(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + + (long bufferPos, long bufferSize) = context.Request.GetBufferType0x22(); + + LinuxError errno = LinuxError.EBADF; + BsdSocket socket = RetrieveSocket(socketFd); + + if (socket != null) + { + errno = LinuxError.SUCCESS; + + Socket newSocket = null; + + try + { + newSocket = socket.Handle.Accept(); + } + catch (SocketException exception) + { + errno = ConvertError((WsaError)exception.ErrorCode); + } + + if (newSocket == null && errno == LinuxError.SUCCESS) + { + errno = LinuxError.EWOULDBLOCK; + } + else if (errno == LinuxError.SUCCESS) + { + BsdSocket newBsdSocket = new BsdSocket + { + Family = (int)newSocket.AddressFamily, + Type = (int)newSocket.SocketType, + Protocol = (int)newSocket.ProtocolType, + Handle = newSocket + }; + + _sockets.Add(newBsdSocket); + + WriteSockAddr(context, bufferPos, newBsdSocket, true); + + WriteBsdResult(context, _sockets.Count - 1, errno); + + context.ResponseData.Write(0x10); + + return ResultCode.Success; + } + } + + return WriteBsdResult(context, -1, errno); + } + + [Command(13)] + // Bind(u32 socket, buffer addr) -> (i32 ret, u32 bsd_errno) + public ResultCode Bind(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + + (long bufferPos, long bufferSize) = context.Request.GetBufferType0x21(); + + LinuxError errno = LinuxError.EBADF; + BsdSocket socket = RetrieveSocket(socketFd); + + if (socket != null) + { + errno = LinuxError.SUCCESS; + + try + { + IPEndPoint endPoint = ParseSockAddr(context, bufferPos, bufferSize); + + socket.Handle.Bind(endPoint); + } + catch (SocketException exception) + { + errno = ConvertError((WsaError)exception.ErrorCode); + } + } + + return WriteBsdResult(context, 0, errno); + } + + [Command(14)] + // Connect(u32 socket, buffer) -> (i32 ret, u32 bsd_errno) + public ResultCode Connect(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + + (long bufferPos, long bufferSize) = context.Request.GetBufferType0x21(); + + LinuxError errno = LinuxError.EBADF; + BsdSocket socket = RetrieveSocket(socketFd); + + if (socket != null) + { + errno = LinuxError.SUCCESS; + try + { + IPEndPoint endPoint = ParseSockAddr(context, bufferPos, bufferSize); + + socket.Handle.Connect(endPoint); + } + catch (SocketException exception) + { + errno = ConvertError((WsaError)exception.ErrorCode); + } + } + + return WriteBsdResult(context, 0, errno); + } + + [Command(15)] + // GetPeerName(u32 socket) -> (i32 ret, u32 bsd_errno, u32 addrlen, buffer addr) + public ResultCode GetPeerName(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + + (long bufferPos, long bufferSize) = context.Request.GetBufferType0x22(); + + LinuxError errno = LinuxError.EBADF; + BsdSocket socket = RetrieveSocket(socketFd); + + if (socket != null) + { + errno = LinuxError.SUCCESS; + + WriteSockAddr(context, bufferPos, socket, true); + WriteBsdResult(context, 0, errno); + context.ResponseData.Write(0x10); + } + + return WriteBsdResult(context, 0, errno); + } + + [Command(16)] + // GetSockName(u32 socket) -> (i32 ret, u32 bsd_errno, u32 addrlen, buffer addr) + public ResultCode GetSockName(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + + (long bufferPos, long bufferSize) = context.Request.GetBufferType0x22(); + + LinuxError errno = LinuxError.EBADF; + BsdSocket socket = RetrieveSocket(socketFd); + + if (socket != null) + { + errno = LinuxError.SUCCESS; + + WriteSockAddr(context, bufferPos, socket, false); + WriteBsdResult(context, 0, errno); + context.ResponseData.Write(0x10); + } + + return WriteBsdResult(context, 0, errno); + } + + [Command(17)] + // GetSockOpt(u32 socket, u32 level, u32 option_name) -> (i32 ret, u32 bsd_errno, u32, buffer) + public ResultCode GetSockOpt(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + int level = context.RequestData.ReadInt32(); + int optionName = context.RequestData.ReadInt32(); + + (long bufferPosition, long bufferSize) = context.Request.GetBufferType0x22(); + + LinuxError errno = LinuxError.EBADF; + BsdSocket socket = RetrieveSocket(socketFd); + + if (socket != null) + { + errno = LinuxError.ENOPROTOOPT; + + if (level == 0xFFFF) + { + errno = HandleGetSocketOption(context, socket, (SocketOptionName)optionName, bufferPosition, bufferSize); + } + else + { + Logger.PrintWarning(LogClass.ServiceBsd, $"Unsupported GetSockOpt Level: {(SocketOptionLevel)level}"); + } + } + + return WriteBsdResult(context, 0, errno); + } + + [Command(18)] + // Listen(u32 socket, u32 backlog) -> (i32 ret, u32 bsd_errno) + public ResultCode Listen(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + int backlog = context.RequestData.ReadInt32(); + + LinuxError errno = LinuxError.EBADF; + BsdSocket socket = RetrieveSocket(socketFd); + + if (socket != null) + { + errno = LinuxError.SUCCESS; + + try + { + socket.Handle.Listen(backlog); + } + catch (SocketException exception) + { + errno = ConvertError((WsaError)exception.ErrorCode); + } + } + + return WriteBsdResult(context, 0, errno); + } + + [Command(19)] + // Ioctl(u32 fd, u32 request, u32 bufcount, buffer, buffer, buffer, buffer) -> (i32 ret, u32 bsd_errno, buffer, buffer, buffer, buffer) + public ResultCode Ioctl(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + BsdIoctl cmd = (BsdIoctl)context.RequestData.ReadInt32(); + int bufferCount = context.RequestData.ReadInt32(); + + LinuxError errno = LinuxError.EBADF; + BsdSocket socket = RetrieveSocket(socketFd); + + if (socket != null) + { + switch (cmd) + { + case BsdIoctl.AtMark: + errno = LinuxError.SUCCESS; + + (long bufferPosition, long bufferSize) = context.Request.GetBufferType0x22(); + + // FIXME: OOB not implemented. + context.Memory.WriteInt32(bufferPosition, 0); + break; + + default: + errno = LinuxError.EOPNOTSUPP; + + Logger.PrintWarning(LogClass.ServiceBsd, $"Unsupported Ioctl Cmd: {cmd}"); + break; + } + } + + return WriteBsdResult(context, 0, errno); + } + + [Command(20)] + // Fcntl(u32 socket, u32 cmd, u32 arg) -> (i32 ret, u32 bsd_errno) + public ResultCode Fcntl(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + int cmd = context.RequestData.ReadInt32(); + int arg = context.RequestData.ReadInt32(); + + int result = 0; + LinuxError errno = LinuxError.EBADF; + BsdSocket socket = RetrieveSocket(socketFd); + + if (socket != null) + { + errno = LinuxError.SUCCESS; + + if (cmd == 0x3) + { + result = !socket.Handle.Blocking ? 0x800 : 0; + } + else if (cmd == 0x4 && arg == 0x800) + { + socket.Handle.Blocking = false; + result = 0; + } + else + { + errno = LinuxError.EOPNOTSUPP; + } + } + + return WriteBsdResult(context, result, errno); + } + + private LinuxError HandleGetSocketOption(ServiceCtx context, BsdSocket socket, SocketOptionName optionName, long optionValuePosition, long optionValueSize) + { + try + { + byte[] optionValue = new byte[optionValueSize]; + + switch (optionName) + { + case SocketOptionName.Broadcast: + case SocketOptionName.DontLinger: + case SocketOptionName.Debug: + case SocketOptionName.Error: + case SocketOptionName.KeepAlive: + case SocketOptionName.OutOfBandInline: + case SocketOptionName.ReceiveBuffer: + case SocketOptionName.ReceiveTimeout: + case SocketOptionName.SendBuffer: + case SocketOptionName.SendTimeout: + case SocketOptionName.Type: + case SocketOptionName.Linger: + socket.Handle.GetSocketOption(SocketOptionLevel.Socket, optionName, optionValue); + context.Memory.WriteBytes(optionValuePosition, optionValue); + + return LinuxError.SUCCESS; + + case (SocketOptionName)0x200: + socket.Handle.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, optionValue); + context.Memory.WriteBytes(optionValuePosition, optionValue); + + return LinuxError.SUCCESS; + + default: + Logger.PrintWarning(LogClass.ServiceBsd, $"Unsupported SetSockOpt OptionName: {optionName}"); + + return LinuxError.EOPNOTSUPP; + } + } + catch (SocketException exception) + { + return ConvertError((WsaError)exception.ErrorCode); + } + } + + private LinuxError HandleSetSocketOption(ServiceCtx context, BsdSocket socket, SocketOptionName optionName, long optionValuePosition, long optionValueSize) + { + try + { + switch (optionName) + { + case SocketOptionName.Broadcast: + case SocketOptionName.DontLinger: + case SocketOptionName.Debug: + case SocketOptionName.Error: + case SocketOptionName.KeepAlive: + case SocketOptionName.OutOfBandInline: + case SocketOptionName.ReceiveBuffer: + case SocketOptionName.ReceiveTimeout: + case SocketOptionName.SendBuffer: + case SocketOptionName.SendTimeout: + case SocketOptionName.Type: + case SocketOptionName.ReuseAddress: + socket.Handle.SetSocketOption(SocketOptionLevel.Socket, optionName, context.Memory.ReadInt32(optionValuePosition)); + + return LinuxError.SUCCESS; + + case (SocketOptionName)0x200: + socket.Handle.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, context.Memory.ReadInt32(optionValuePosition)); + + return LinuxError.SUCCESS; + + case SocketOptionName.Linger: + socket.Handle.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Linger, + new LingerOption(context.Memory.ReadInt32(optionValuePosition) != 0, context.Memory.ReadInt32(optionValuePosition + 4))); + + return LinuxError.SUCCESS; + + default: + Logger.PrintWarning(LogClass.ServiceBsd, $"Unsupported SetSockOpt OptionName: {optionName}"); + + return LinuxError.EOPNOTSUPP; + } + } + catch (SocketException exception) + { + return ConvertError((WsaError)exception.ErrorCode); + } + } + + [Command(21)] + // SetSockOpt(u32 socket, u32 level, u32 option_name, buffer option_value) -> (i32 ret, u32 bsd_errno) + public ResultCode SetSockOpt(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + int level = context.RequestData.ReadInt32(); + int optionName = context.RequestData.ReadInt32(); + + (long bufferPos, long bufferSize) = context.Request.GetBufferType0x21(); + + LinuxError errno = LinuxError.EBADF; + BsdSocket socket = RetrieveSocket(socketFd); + + if (socket != null) + { + errno = LinuxError.ENOPROTOOPT; + + if (level == 0xFFFF) + { + errno = HandleSetSocketOption(context, socket, (SocketOptionName)optionName, bufferPos, bufferSize); + } + else + { + Logger.PrintWarning(LogClass.ServiceBsd, $"Unsupported SetSockOpt Level: {(SocketOptionLevel)level}"); + } + } + + return WriteBsdResult(context, 0, errno); + } + + [Command(22)] + // Shutdown(u32 socket, u32 how) -> (i32 ret, u32 bsd_errno) + public ResultCode Shutdown(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + int how = context.RequestData.ReadInt32(); + + LinuxError errno = LinuxError.EBADF; + BsdSocket socket = RetrieveSocket(socketFd); + + if (socket != null) + { + errno = LinuxError.EINVAL; + + if (how >= 0 && how <= 2) + { + errno = LinuxError.SUCCESS; + + try + { + socket.Handle.Shutdown((SocketShutdown)how); + } + catch (SocketException exception) + { + errno = ConvertError((WsaError)exception.ErrorCode); + } + } + } + + return WriteBsdResult(context, 0, errno); + } + + [Command(23)] + // ShutdownAllSockets(u32 how) -> (i32 ret, u32 bsd_errno) + public ResultCode ShutdownAllSockets(ServiceCtx context) + { + int how = context.RequestData.ReadInt32(); + + LinuxError errno = LinuxError.EINVAL; + + if (how >= 0 && how <= 2) + { + errno = LinuxError.SUCCESS; + + foreach (BsdSocket socket in _sockets) + { + if (socket != null) + { + try + { + socket.Handle.Shutdown((SocketShutdown)how); + } + catch (SocketException exception) + { + errno = ConvertError((WsaError)exception.ErrorCode); + break; + } + } + } + } + + return WriteBsdResult(context, 0, errno); + } + + [Command(24)] + // Write(u32 socket, buffer message) -> (i32 ret, u32 bsd_errno) + public ResultCode Write(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + + (long sendPosition, long sendSize) = context.Request.GetBufferType0x21(); + + LinuxError errno = LinuxError.EBADF; + BsdSocket socket = RetrieveSocket(socketFd); + int result = -1; + + if (socket != null) + { + byte[] sendBuffer = context.Memory.ReadBytes(sendPosition, sendSize); + + try + { + result = socket.Handle.Send(sendBuffer); + errno = SetResultErrno(socket.Handle, result); + } + catch (SocketException exception) + { + errno = ConvertError((WsaError)exception.ErrorCode); + } + } + + return WriteBsdResult(context, result, errno); + } + + [Command(25)] + // Read(u32 socket) -> (i32 ret, u32 bsd_errno, buffer message) + public ResultCode Read(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + + (long receivePosition, long receiveLength) = context.Request.GetBufferType0x22(); + + LinuxError errno = LinuxError.EBADF; + BsdSocket socket = RetrieveSocket(socketFd); + int result = -1; + + if (socket != null) + { + byte[] receivedBuffer = new byte[receiveLength]; + + try + { + result = socket.Handle.Receive(receivedBuffer); + errno = SetResultErrno(socket.Handle, result); + } + catch (SocketException exception) + { + errno = ConvertError((WsaError)exception.ErrorCode); + } + } + + return WriteBsdResult(context, result, errno); + } + + [Command(26)] + // Close(u32 socket) -> (i32 ret, u32 bsd_errno) + public ResultCode Close(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + + LinuxError errno = LinuxError.EBADF; + BsdSocket socket = RetrieveSocket(socketFd); + + if (socket != null) + { + socket.Handle.Close(); + + _sockets[socketFd] = null; + + errno = LinuxError.SUCCESS; + } + + return WriteBsdResult(context, 0, errno); + } + + [Command(27)] + // DuplicateSocket(u32 socket, u64 reserved) -> (i32 ret, u32 bsd_errno) + public ResultCode DuplicateSocket(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + ulong reserved = context.RequestData.ReadUInt64(); + + LinuxError errno = LinuxError.ENOENT; + int newSockFd = -1; + + if (_isPrivileged) + { + errno = LinuxError.EBADF; + + BsdSocket oldSocket = RetrieveSocket(socketFd); + + if (oldSocket != null) + { + _sockets.Add(oldSocket); + newSockFd = _sockets.Count - 1; + } + } + + return WriteBsdResult(context, newSockFd, errno); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Sockets/Bsd/ServerInterface.cs b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/ServerInterface.cs new file mode 100644 index 0000000000..798fc0157d --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/ServerInterface.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd +{ + [Service("bsdcfg")] + class ServerInterface : IpcService + { + public ServerInterface(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdIoctl.cs b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdIoctl.cs new file mode 100644 index 0000000000..421a255ccb --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdIoctl.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd +{ + enum BsdIoctl + { + AtMark = 0x40047307 + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Bsd/BsdSocket.cs b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocket.cs similarity index 56% rename from Ryujinx.HLE/OsHle/Services/Bsd/BsdSocket.cs rename to Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocket.cs index 9cde99478c..2d5bf42902 100644 --- a/Ryujinx.HLE/OsHle/Services/Bsd/BsdSocket.cs +++ b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocket.cs @@ -1,7 +1,6 @@ -using System.Net; using System.Net.Sockets; -namespace Ryujinx.HLE.OsHle.Services.Bsd +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd { class BsdSocket { @@ -9,10 +8,6 @@ namespace Ryujinx.HLE.OsHle.Services.Bsd public int Type; public int Protocol; - public IPAddress IpAddress; - - public IPEndPoint RemoteEP; - public Socket Handle; } } \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/PollEvent.cs b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/PollEvent.cs new file mode 100644 index 0000000000..ff47a4c70c --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/PollEvent.cs @@ -0,0 +1,28 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd +{ + class PollEvent + { + public enum EventTypeMask + { + Input = 1, + UrgentInput = 2, + Output = 4, + Error = 8, + Disconnected = 0x10, + Invalid = 0x20 + } + + public int SocketFd { get; private set; } + public BsdSocket Socket { get; private set; } + public EventTypeMask InputEvents { get; private set; } + public EventTypeMask OutputEvents { get; private set; } + + public PollEvent(int socketFd, BsdSocket socket, EventTypeMask inputEvents, EventTypeMask outputEvents) + { + SocketFd = socketFd; + Socket = socket; + InputEvents = inputEvents; + OutputEvents = outputEvents; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Sockets/Ethc/IEthInterface.cs b/Ryujinx.HLE/HOS/Services/Sockets/Ethc/IEthInterface.cs new file mode 100644 index 0000000000..f58776977e --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sockets/Ethc/IEthInterface.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Ethc +{ + [Service("ethc:c")] + class IEthInterface : IpcService + { + public IEthInterface(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Sockets/Ethc/IEthInterfaceGroup.cs b/Ryujinx.HLE/HOS/Services/Sockets/Ethc/IEthInterfaceGroup.cs new file mode 100644 index 0000000000..9832e44831 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sockets/Ethc/IEthInterfaceGroup.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Ethc +{ + [Service("ethc:i")] + class IEthInterfaceGroup : IpcService + { + public IEthInterfaceGroup(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Sockets/Nsd/IManager.cs b/Ryujinx.HLE/HOS/Services/Sockets/Nsd/IManager.cs new file mode 100644 index 0000000000..277bc374f5 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sockets/Nsd/IManager.cs @@ -0,0 +1,268 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.Exceptions; +using Ryujinx.HLE.HOS.Services.Settings; +using Ryujinx.HLE.HOS.Services.Sockets.Nsd.Manager; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Nsd +{ + [Service("nsd:a")] // Max sessions: 5 + [Service("nsd:u")] // Max sessions: 20 + class IManager : IpcService + { + private NsdSettings _nsdSettings; + private FqdnResolver _fqdnResolver; + + private bool _isInitialized = false; + + public IManager(ServiceCtx context) + { + // TODO: Load nsd settings through the savedata 0x80000000000000B0 (nsdsave:/). + + NxSettings.Settings.TryGetValue("nsd!test_mode", out object testMode); + + _nsdSettings = new NsdSettings + { + Initialized = true, + TestMode = (bool)testMode + }; + + _fqdnResolver = new FqdnResolver(_nsdSettings); + + _isInitialized = true; + } + + [Command(10)] + // GetSettingName() -> buffer, 0x16> + public ResultCode GetSettingName(ServiceCtx context) + { + (long outputPosition, long outputSize) = context.Request.GetBufferType0x22(); + + ResultCode result = _fqdnResolver.GetSettingName(context, out string settingName); + + if (result == ResultCode.Success) + { + byte[] settingNameBuffer = Encoding.UTF8.GetBytes(settingName + '\0'); + + context.Memory.WriteBytes(outputPosition, settingNameBuffer); + } + + return result; + } + + [Command(11)] + // GetEnvironmentIdentifier() -> buffer, 0x16> + public ResultCode GetEnvironmentIdentifier(ServiceCtx context) + { + (long outputPosition, long outputSize) = context.Request.GetBufferType0x22(); + + ResultCode result = _fqdnResolver.GetEnvironmentIdentifier(context, out string identifier); + + if (result == ResultCode.Success) + { + byte[] identifierBuffer = Encoding.UTF8.GetBytes(identifier + '\0'); + + context.Memory.WriteBytes(outputPosition, identifierBuffer); + } + + return result; + } + + [Command(12)] + // GetDeviceId() -> bytes<0x10, 1> + public ResultCode GetDeviceId(ServiceCtx context) + { + // NOTE: Stubbed in system module. + + return ResultCode.Success; + } + + [Command(13)] + // DeleteSettings(u32) + public ResultCode DeleteSettings(ServiceCtx context) + { + uint unknown = context.RequestData.ReadUInt32(); + + if (!_isInitialized) + { + return ResultCode.ServiceNotInitialized; + } + + if (unknown > 1) + { + return ResultCode.InvalidArgument; + } + + if (unknown == 1) + { + NxSettings.Settings.TryGetValue("nsd!environment_identifier", out object environmentIdentifier); + + if ((string)environmentIdentifier == _nsdSettings.Environment) + { + // TODO: Call nn::fs::DeleteSystemFile() to delete the savedata file and return ResultCode. + } + else + { + // TODO: Mount the savedata file and return ResultCode. + } + } + else + { + // TODO: Call nn::fs::DeleteSystemFile() to delete the savedata file and return ResultCode. + } + + return ResultCode.Success; + } + + [Command(14)] + // ImportSettings(u32, buffer) -> buffer + public ResultCode ImportSettings(ServiceCtx context) + { + throw new ServiceNotImplementedException(context); + } + + [Command(15)] + // Unknown(bytes<1>) + public ResultCode Unknown(ServiceCtx context) + { + throw new ServiceNotImplementedException(context); + } + + [Command(20)] + // Resolve(buffer, 0x15>) -> buffer, 0x16> + public ResultCode Resolve(ServiceCtx context) + { + (long outputPosition, long outputSize) = context.Request.GetBufferType0x22(); + + ResultCode result = _fqdnResolver.ResolveEx(context, out ResultCode errorCode, out string resolvedAddress); + + byte[] resolvedAddressBuffer = Encoding.UTF8.GetBytes(resolvedAddress + '\0'); + + context.Memory.WriteBytes(outputPosition, resolvedAddressBuffer); + + return result; + } + + [Command(21)] + // ResolveEx(buffer, 0x15>) -> (u32, buffer, 0x16>) + public ResultCode ResolveEx(ServiceCtx context) + { + (long outputPosition, long outputSize) = context.Request.GetBufferType0x22(); + + ResultCode result = _fqdnResolver.ResolveEx(context, out ResultCode errorCode, out string resolvedAddress); + + byte[] resolvedAddressBuffer = Encoding.UTF8.GetBytes(resolvedAddress + '\0'); + + context.Memory.WriteBytes(outputPosition, resolvedAddressBuffer); + + context.ResponseData.Write((int)errorCode); + + return result; + } + + [Command(30)] + // GetNasServiceSetting(buffer, 0x15>) -> buffer, 0x16> + public ResultCode GetNasServiceSetting(ServiceCtx context) + { + throw new ServiceNotImplementedException(context); + } + + [Command(31)] + // GetNasServiceSettingEx(buffer, 0x15>) -> (u32, buffer, 0x16>) + public ResultCode GetNasServiceSettingEx(ServiceCtx context) + { + throw new ServiceNotImplementedException(context); + } + + [Command(40)] + // GetNasRequestFqdn() -> buffer, 0x16> + public ResultCode GetNasRequestFqdn(ServiceCtx context) + { + throw new ServiceNotImplementedException(context); + } + + [Command(41)] + // GetNasRequestFqdnEx() -> (u32, buffer, 0x16>) + public ResultCode GetNasRequestFqdnEx(ServiceCtx context) + { + throw new ServiceNotImplementedException(context); + } + + [Command(42)] + // GetNasApiFqdn() -> buffer, 0x16> + public ResultCode GetNasApiFqdn(ServiceCtx context) + { + throw new ServiceNotImplementedException(context); + } + + [Command(43)] + // GetNasApiFqdnEx() -> (u32, buffer, 0x16>) + public ResultCode GetNasApiFqdnEx(ServiceCtx context) + { + throw new ServiceNotImplementedException(context); + } + + [Command(50)] + // GetCurrentSetting() -> buffer, 0x16> + public ResultCode GetCurrentSetting(ServiceCtx context) + { + throw new ServiceNotImplementedException(context); + } + + [Command(60)] + // ReadSaveDataFromFsForTest() -> buffer, 0x16> + public ResultCode ReadSaveDataFromFsForTest(ServiceCtx context) + { + if (!_isInitialized) + { + return ResultCode.ServiceNotInitialized; + } + + // TODO: Call nn::nsd::detail::fs::ReadSaveDataWithOffset() at offset 0 to write the + // whole savedata inside the buffer. + + Logger.PrintStub(LogClass.ServiceNsd); + + return ResultCode.Success; + } + + [Command(61)] + // WriteSaveDataToFsForTest(buffer, 0x15>) + public ResultCode WriteSaveDataToFsForTest(ServiceCtx context) + { + // NOTE: Stubbed in system module. + + if (_isInitialized) + { + return ResultCode.NotImplemented; + } + else + { + return ResultCode.ServiceNotInitialized; + } + } + + [Command(62)] + // DeleteSaveDataOfFsForTest() + public ResultCode DeleteSaveDataOfFsForTest(ServiceCtx context) + { + // NOTE: Stubbed in system module. + + if (_isInitialized) + { + return ResultCode.NotImplemented; + } + else + { + return ResultCode.ServiceNotInitialized; + } + } + + [Command(63)] + // IsChangeEnvironmentIdentifierDisabled() -> bytes<1> + public ResultCode IsChangeEnvironmentIdentifierDisabled(ServiceCtx context) + { + throw new ServiceNotImplementedException(context); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Sockets/Nsd/Manager/FqdnResolver.cs b/Ryujinx.HLE/HOS/Services/Sockets/Nsd/Manager/FqdnResolver.cs new file mode 100644 index 0000000000..aaab7b3b8a --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sockets/Nsd/Manager/FqdnResolver.cs @@ -0,0 +1,131 @@ +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Nsd.Manager +{ + class FqdnResolver + { + private const string _dummyAddress = "unknown.dummy.nintendo.net"; + + private NsdSettings _nsdSettings; + + public FqdnResolver(NsdSettings nsdSettings) + { + _nsdSettings = nsdSettings; + } + + public ResultCode GetSettingName(ServiceCtx context, out string settingName) + { + if (_nsdSettings.TestMode) + { + settingName = ""; + + return ResultCode.NotImplemented; + } + else + { + settingName = ""; + + if (true) // TODO: Determine field (struct + 0x2C) + { + settingName = _nsdSettings.Environment; + + return ResultCode.Success; + } + + return ResultCode.NullOutputObject; + } + } + + public ResultCode GetEnvironmentIdentifier(ServiceCtx context, out string identifier) + { + if (_nsdSettings.TestMode) + { + identifier = "rre"; + + return ResultCode.NotImplemented; + } + else + { + identifier = _nsdSettings.Environment; + } + + return ResultCode.Success; + } + + public ResultCode Resolve(ServiceCtx context, string address, out string resolvedAddress) + { + if (address != "api.sect.srv.nintendo.net" || address != "conntest.nintendowifi.net") + { + // TODO: Load Environment from the savedata. + address = address.Replace("%", _nsdSettings.Environment); + + resolvedAddress = ""; + + if (_nsdSettings == null) + { + return ResultCode.SettingsNotInitialized; + } + + if (!_nsdSettings.Initialized) + { + return ResultCode.SettingsNotLoaded; + } + + switch (address) + { + case "e97b8a9d672e4ce4845ec6947cd66ef6-sb-api.accounts.nintendo.com": // dp1 environment + resolvedAddress = "e97b8a9d672e4ce4845ec6947cd66ef6-sb.baas.nintendo.com"; + break; + case "api.accounts.nintendo.com": // dp1 environment + resolvedAddress = "e0d67c509fb203858ebcb2fe3f88c2aa.baas.nintendo.com"; + break; + case "e97b8a9d672e4ce4845ec6947cd66ef6-sb.accounts.nintendo.com": // lp1 environment + resolvedAddress = "e97b8a9d672e4ce4845ec6947cd66ef6-sb.baas.nintendo.com"; + break; + case "accounts.nintendo.com": // lp1 environment + resolvedAddress = "e0d67c509fb203858ebcb2fe3f88c2aa.baas.nintendo.com"; + break; + /* + // TODO: Determine fields of the struct. + case "": // + 0xEB8 || + 0x2BE8 + resolvedAddress = ""; // + 0xEB8 + 0x300 || + 0x2BE8 + 0x300 + break; + */ + default: + resolvedAddress = address; + break; + } + } + else + { + resolvedAddress = address; + } + + return ResultCode.Success; + } + + public ResultCode ResolveEx(ServiceCtx context, out ResultCode resultCode, out string resolvedAddress) + { + (long inputPosition, long inputSize) = context.Request.GetBufferType0x21(); + + byte[] addressBuffer = context.Memory.ReadBytes(inputPosition, inputSize); + string address = Encoding.UTF8.GetString(addressBuffer); + + resultCode = Resolve(context, address, out resolvedAddress); + + if (resultCode != ResultCode.Success) + { + resolvedAddress = _dummyAddress; + } + + if (_nsdSettings.TestMode) + { + return ResultCode.Success; + } + else + { + return resultCode; + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Sockets/Nsd/ResultCode.cs b/Ryujinx.HLE/HOS/Services/Sockets/Nsd/ResultCode.cs new file mode 100644 index 0000000000..0e636f9ae8 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sockets/Nsd/ResultCode.cs @@ -0,0 +1,19 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Nsd +{ + enum ResultCode + { + ModuleId = 141, + ErrorCodeShift = 9, + + Success = 0, + + NotImplemented = ( 1 << ErrorCodeShift) | ModuleId, + InvalidObject1 = ( 3 << ErrorCodeShift) | ModuleId, + InvalidObject2 = ( 4 << ErrorCodeShift) | ModuleId, + NullOutputObject = ( 5 << ErrorCodeShift) | ModuleId, + SettingsNotLoaded = ( 6 << ErrorCodeShift) | ModuleId, + InvalidArgument = ( 8 << ErrorCodeShift) | ModuleId, + SettingsNotInitialized = ( 10 << ErrorCodeShift) | ModuleId, + ServiceNotInitialized = (400 << ErrorCodeShift) | ModuleId, + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Sockets/Nsd/Types/NsdSettings.cs b/Ryujinx.HLE/HOS/Services/Sockets/Nsd/Types/NsdSettings.cs new file mode 100644 index 0000000000..2b31cb1d20 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sockets/Nsd/Types/NsdSettings.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Nsd +{ + class NsdSettings + { + public bool Initialized; + public bool TestMode; + public string Environment = "lp1"; // or "dd1" if devkit. + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/IResolver.cs b/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/IResolver.cs new file mode 100644 index 0000000000..1cf2aa1c65 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/IResolver.cs @@ -0,0 +1,387 @@ +using Ryujinx.Common.Logging; +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres +{ + [Service("sfdnsres")] + class IResolver : IpcService + { + public IResolver(ServiceCtx context) { } + + private long SerializeHostEnt(ServiceCtx context, IPHostEntry hostEntry, List addresses = null) + { + long originalBufferPosition = context.Request.ReceiveBuff[0].Position; + long bufferPosition = originalBufferPosition; + long bufferSize = context.Request.ReceiveBuff[0].Size; + + string hostName = hostEntry.HostName + '\0'; + + // h_name + context.Memory.WriteBytes(bufferPosition, Encoding.ASCII.GetBytes(hostName)); + bufferPosition += hostName.Length; + + // h_aliases list size + context.Memory.WriteInt32(bufferPosition, IPAddress.HostToNetworkOrder(hostEntry.Aliases.Length)); + bufferPosition += 4; + + // Actual aliases + foreach (string alias in hostEntry.Aliases) + { + context.Memory.WriteBytes(bufferPosition, Encoding.ASCII.GetBytes(alias + '\0')); + bufferPosition += alias.Length + 1; + } + + // h_addrtype but it's a short (also only support IPv4) + context.Memory.WriteInt16(bufferPosition, IPAddress.HostToNetworkOrder((short)2)); + bufferPosition += 2; + + // h_length but it's a short + context.Memory.WriteInt16(bufferPosition, IPAddress.HostToNetworkOrder((short)4)); + bufferPosition += 2; + + // Ip address count, we can only support ipv4 (blame Nintendo) + context.Memory.WriteInt32(bufferPosition, addresses != null ? IPAddress.HostToNetworkOrder(addresses.Count) : 0); + bufferPosition += 4; + + if (addresses != null) + { + foreach (IPAddress ip in addresses) + { + context.Memory.WriteInt32(bufferPosition, IPAddress.HostToNetworkOrder(BitConverter.ToInt32(ip.GetAddressBytes(), 0))); + bufferPosition += 4; + } + } + + return bufferPosition - originalBufferPosition; + } + + private string GetGaiStringErrorFromErrorCode(GaiError errorCode) + { + if (errorCode > GaiError.Max) + { + errorCode = GaiError.Max; + } + + switch (errorCode) + { + case GaiError.AddressFamily: + return "Address family for hostname not supported"; + case GaiError.Again: + return "Temporary failure in name resolution"; + case GaiError.BadFlags: + return "Invalid value for ai_flags"; + case GaiError.Fail: + return "Non-recoverable failure in name resolution"; + case GaiError.Family: + return "ai_family not supported"; + case GaiError.Memory: + return "Memory allocation failure"; + case GaiError.NoData: + return "No address associated with hostname"; + case GaiError.NoName: + return "hostname nor servname provided, or not known"; + case GaiError.Service: + return "servname not supported for ai_socktype"; + case GaiError.SocketType: + return "ai_socktype not supported"; + case GaiError.System: + return "System error returned in errno"; + case GaiError.BadHints: + return "Invalid value for hints"; + case GaiError.Protocol: + return "Resolved protocol is unknown"; + case GaiError.Overflow: + return "Argument buffer overflow"; + case GaiError.Max: + return "Unknown error"; + default: + return "Success"; + } + } + + private string GetHostStringErrorFromErrorCode(NetDbError errorCode) + { + if (errorCode <= NetDbError.Internal) + { + return "Resolver internal error"; + } + + switch (errorCode) + { + case NetDbError.Success: + return "Resolver Error 0 (no error)"; + case NetDbError.HostNotFound: + return "Unknown host"; + case NetDbError.TryAgain: + return "Host name lookup failure"; + case NetDbError.NoRecovery: + return "Unknown server error"; + case NetDbError.NoData: + return "No address associated with name"; + default: + return "Unknown resolver error"; + } + } + + private List GetIpv4Addresses(IPHostEntry hostEntry) + { + List result = new List(); + foreach (IPAddress ip in hostEntry.AddressList) + { + if (ip.AddressFamily == AddressFamily.InterNetwork) + result.Add(ip); + } + return result; + } + + [Command(0)] + // SetDnsAddressesPrivate(u32, buffer) + public ResultCode SetDnsAddressesPrivate(ServiceCtx context) + { + uint unknown0 = context.RequestData.ReadUInt32(); + long bufferPosition = context.Request.SendBuff[0].Position; + long bufferSize = context.Request.SendBuff[0].Size; + + // TODO: This is stubbed in 2.0.0+, reverse 1.0.0 version for the sake completeness. + Logger.PrintStub(LogClass.ServiceSfdnsres, new { unknown0 }); + + return ResultCode.NotAllocated; + } + + [Command(1)] + // GetDnsAddressPrivate(u32) -> buffer + public ResultCode GetDnsAddressesPrivate(ServiceCtx context) + { + uint unknown0 = context.RequestData.ReadUInt32(); + + // TODO: This is stubbed in 2.0.0+, reverse 1.0.0 version for the sake completeness. + Logger.PrintStub(LogClass.ServiceSfdnsres, new { unknown0 }); + + return ResultCode.NotAllocated; + } + + [Command(2)] + // GetHostByName(u8, u32, u64, pid, buffer) -> (u32, u32, u32, buffer) + public ResultCode GetHostByName(ServiceCtx context) + { + byte[] rawName = context.Memory.ReadBytes(context.Request.SendBuff[0].Position, context.Request.SendBuff[0].Size); + string name = Encoding.ASCII.GetString(rawName).TrimEnd('\0'); + + // TODO: use params + bool enableNsdResolve = context.RequestData.ReadInt32() == 1; + int timeOut = context.RequestData.ReadInt32(); + ulong pidPlaceholder = context.RequestData.ReadUInt64(); + + IPHostEntry hostEntry = null; + + NetDbError netDbErrorCode = NetDbError.Success; + GaiError errno = GaiError.Overflow; + long serializedSize = 0; + + if (name.Length <= 255) + { + try + { + hostEntry = Dns.GetHostEntry(name); + } + catch (SocketException exception) + { + netDbErrorCode = NetDbError.Internal; + + if (exception.ErrorCode == 11001) + { + netDbErrorCode = NetDbError.HostNotFound; + errno = GaiError.NoData; + } + else if (exception.ErrorCode == 11002) + { + netDbErrorCode = NetDbError.TryAgain; + } + else if (exception.ErrorCode == 11003) + { + netDbErrorCode = NetDbError.NoRecovery; + } + else if (exception.ErrorCode == 11004) + { + netDbErrorCode = NetDbError.NoData; + } + else if (exception.ErrorCode == 10060) + { + errno = GaiError.Again; + } + } + } + else + { + netDbErrorCode = NetDbError.HostNotFound; + } + + if (hostEntry != null) + { + errno = GaiError.Success; + + List addresses = GetIpv4Addresses(hostEntry); + + if (addresses.Count == 0) + { + errno = GaiError.NoData; + netDbErrorCode = NetDbError.NoAddress; + } + else + { + serializedSize = SerializeHostEnt(context, hostEntry, addresses); + } + } + + context.ResponseData.Write((int)netDbErrorCode); + context.ResponseData.Write((int)errno); + context.ResponseData.Write(serializedSize); + + return ResultCode.Success; + } + + [Command(3)] + // GetHostByAddr(u32, u32, u32, u64, pid, buffer) -> (u32, u32, u32, buffer) + public ResultCode GetHostByAddress(ServiceCtx context) + { + byte[] rawIp = context.Memory.ReadBytes(context.Request.SendBuff[0].Position, context.Request.SendBuff[0].Size); + + // TODO: use params + uint socketLength = context.RequestData.ReadUInt32(); + uint type = context.RequestData.ReadUInt32(); + int timeOut = context.RequestData.ReadInt32(); + ulong pidPlaceholder = context.RequestData.ReadUInt64(); + + IPHostEntry hostEntry = null; + + NetDbError netDbErrorCode = NetDbError.Success; + GaiError errno = GaiError.AddressFamily; + long serializedSize = 0; + + if (rawIp.Length == 4) + { + try + { + IPAddress address = new IPAddress(rawIp); + + hostEntry = Dns.GetHostEntry(address); + } + catch (SocketException exception) + { + netDbErrorCode = NetDbError.Internal; + if (exception.ErrorCode == 11001) + { + netDbErrorCode = NetDbError.HostNotFound; + errno = GaiError.NoData; + } + else if (exception.ErrorCode == 11002) + { + netDbErrorCode = NetDbError.TryAgain; + } + else if (exception.ErrorCode == 11003) + { + netDbErrorCode = NetDbError.NoRecovery; + } + else if (exception.ErrorCode == 11004) + { + netDbErrorCode = NetDbError.NoData; + } + else if (exception.ErrorCode == 10060) + { + errno = GaiError.Again; + } + } + } + else + { + netDbErrorCode = NetDbError.NoAddress; + } + + if (hostEntry != null) + { + errno = GaiError.Success; + serializedSize = SerializeHostEnt(context, hostEntry, GetIpv4Addresses(hostEntry)); + } + + context.ResponseData.Write((int)netDbErrorCode); + context.ResponseData.Write((int)errno); + context.ResponseData.Write(serializedSize); + + return ResultCode.Success; + } + + [Command(4)] + // GetHostStringError(u32) -> buffer + public ResultCode GetHostStringError(ServiceCtx context) + { + ResultCode resultCode = ResultCode.NotAllocated; + NetDbError errorCode = (NetDbError)context.RequestData.ReadInt32(); + string errorString = GetHostStringErrorFromErrorCode(errorCode); + + if (errorString.Length + 1 <= context.Request.ReceiveBuff[0].Size) + { + resultCode = 0; + context.Memory.WriteBytes(context.Request.ReceiveBuff[0].Position, Encoding.ASCII.GetBytes(errorString + '\0')); + } + + return resultCode; + } + + [Command(5)] + // GetGaiStringError(u32) -> buffer + public ResultCode GetGaiStringError(ServiceCtx context) + { + ResultCode resultCode = ResultCode.NotAllocated; + GaiError errorCode = (GaiError)context.RequestData.ReadInt32(); + string errorString = GetGaiStringErrorFromErrorCode(errorCode); + + if (errorString.Length + 1 <= context.Request.ReceiveBuff[0].Size) + { + resultCode = 0; + context.Memory.WriteBytes(context.Request.ReceiveBuff[0].Position, Encoding.ASCII.GetBytes(errorString + '\0')); + } + + return resultCode; + } + + [Command(8)] + // RequestCancelHandle(u64, pid) -> u32 + public ResultCode RequestCancelHandle(ServiceCtx context) + { + ulong unknown0 = context.RequestData.ReadUInt64(); + + context.ResponseData.Write(0); + + Logger.PrintStub(LogClass.ServiceSfdnsres, new { unknown0 }); + + return ResultCode.Success; + } + + [Command(9)] + // CancelSocketCall(u32, u64, pid) + public ResultCode CancelSocketCall(ServiceCtx context) + { + uint unknown0 = context.RequestData.ReadUInt32(); + ulong unknown1 = context.RequestData.ReadUInt64(); + + Logger.PrintStub(LogClass.ServiceSfdnsres, new { unknown0, unknown1 }); + + return ResultCode.Success; + } + + [Command(11)] + // ClearDnsAddresses(u32) + public ResultCode ClearDnsAddresses(ServiceCtx context) + { + uint unknown0 = context.RequestData.ReadUInt32(); + + Logger.PrintStub(LogClass.ServiceSfdnsres, new { unknown0 }); + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/GaiError.cs b/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/GaiError.cs new file mode 100644 index 0000000000..f9f28b44cf --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/GaiError.cs @@ -0,0 +1,22 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres +{ + enum GaiError + { + Success, + AddressFamily, + Again, + BadFlags, + Fail, + Family, + Memory, + NoData, + NoName, + Service, + SocketType, + System, + BadHints, + Protocol, + Overflow, + Max + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/NetDBError.cs b/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/NetDBError.cs new file mode 100644 index 0000000000..3c04c049a6 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/NetDBError.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres +{ + enum NetDbError + { + Internal = -1, + Success, + HostNotFound, + TryAgain, + NoRecovery, + NoData, + NoAddress = NoData + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Spl/IGeneralInterface.cs b/Ryujinx.HLE/HOS/Services/Spl/IGeneralInterface.cs new file mode 100644 index 0000000000..b4aebc7ea7 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Spl/IGeneralInterface.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.HLE.HOS.Services.Sm +{ + [Service("spl:")] + [Service("spl:es")] + [Service("spl:fs")] + [Service("spl:manu")] + [Service("spl:mig")] + [Service("spl:ssl")] + class IGeneralInterface : IpcService + { + public IGeneralInterface(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Spl/IRandomInterface.cs b/Ryujinx.HLE/HOS/Services/Spl/IRandomInterface.cs new file mode 100644 index 0000000000..75c308af0e --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Spl/IRandomInterface.cs @@ -0,0 +1,42 @@ +using System; +using System.Security.Cryptography; + +namespace Ryujinx.HLE.HOS.Services.Spl +{ + [Service("csrng")] + class IRandomInterface : IpcService, IDisposable + { + private RNGCryptoServiceProvider _rng; + + public IRandomInterface(ServiceCtx context) + { + _rng = new RNGCryptoServiceProvider(); + } + + [Command(0)] + // GetRandomBytes() -> buffer + public ResultCode GetRandomBytes(ServiceCtx context) + { + byte[] randomBytes = new byte[context.Request.ReceiveBuff[0].Size]; + + _rng.GetBytes(randomBytes); + + context.Memory.WriteBytes(context.Request.ReceiveBuff[0].Position, randomBytes); + + return ResultCode.Success; + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _rng.Dispose(); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Srepo/ISrepoService.cs b/Ryujinx.HLE/HOS/Services/Srepo/ISrepoService.cs new file mode 100644 index 0000000000..167dea6772 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Srepo/ISrepoService.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Srepo +{ + [Service("srepo:a")] // 5.0.0+ + [Service("srepo:u")] // 5.0.0+ + class ISrepoService : IpcService + { + public ISrepoService(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Ssl/ISslService.cs b/Ryujinx.HLE/HOS/Services/Ssl/ISslService.cs new file mode 100644 index 0000000000..2f4b93ca21 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Ssl/ISslService.cs @@ -0,0 +1,36 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Services.Ssl.SslService; + +namespace Ryujinx.HLE.HOS.Services.Ssl +{ + [Service("ssl")] + class ISslService : IpcService + { + public ISslService(ServiceCtx context) { } + + [Command(0)] + // CreateContext(nn::ssl::sf::SslVersion, u64, pid) -> object + public ResultCode CreateContext(ServiceCtx context) + { + int sslVersion = context.RequestData.ReadInt32(); + long unknown = context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceSsl, new { sslVersion, unknown }); + + MakeObject(context, new ISslContext()); + + return ResultCode.Success; + } + + [Command(5)] + // SetInterfaceVersion(u32) + public ResultCode SetInterfaceVersion(ServiceCtx context) + { + int version = context.RequestData.ReadInt32(); + + Logger.PrintStub(LogClass.ServiceSsl, new { version }); + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Ssl/SslService/ISslContext.cs b/Ryujinx.HLE/HOS/Services/Ssl/SslService/ISslContext.cs new file mode 100644 index 0000000000..ef78817568 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Ssl/SslService/ISslContext.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.HLE.HOS.Services.Ssl.SslService +{ + class ISslContext : IpcService + { + public ISslContext() { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NvFlinger.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NvFlinger.cs new file mode 100644 index 0000000000..e70666ed9d --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NvFlinger.cs @@ -0,0 +1,414 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap; +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; + +using static Ryujinx.HLE.HOS.Services.SurfaceFlinger.Parcel; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + class NvFlinger : IDisposable + { + private delegate ResultCode ServiceProcessParcel(ServiceCtx context, BinaryReader parcelReader); + + private Dictionary<(string, int), ServiceProcessParcel> _commands; + + private KEvent _binderEvent; + + private IRenderer _renderer; + + private const int BufferQueueCount = 0x40; + private const int BufferQueueMask = BufferQueueCount - 1; + + private BufferEntry[] _bufferQueue; + + private AutoResetEvent _waitBufferFree; + + private bool _disposed; + + public NvFlinger(IRenderer renderer, KEvent binderEvent) + { + _commands = new Dictionary<(string, int), ServiceProcessParcel> + { + { ("android.gui.IGraphicBufferProducer", 0x1), GbpRequestBuffer }, + { ("android.gui.IGraphicBufferProducer", 0x3), GbpDequeueBuffer }, + { ("android.gui.IGraphicBufferProducer", 0x4), GbpDetachBuffer }, + { ("android.gui.IGraphicBufferProducer", 0x7), GbpQueueBuffer }, + { ("android.gui.IGraphicBufferProducer", 0x8), GbpCancelBuffer }, + { ("android.gui.IGraphicBufferProducer", 0x9), GbpQuery }, + { ("android.gui.IGraphicBufferProducer", 0xa), GbpConnect }, + { ("android.gui.IGraphicBufferProducer", 0xb), GbpDisconnect }, + { ("android.gui.IGraphicBufferProducer", 0xe), GbpPreallocBuffer } + }; + + _renderer = renderer; + _binderEvent = binderEvent; + + _bufferQueue = new BufferEntry[0x40]; + + _waitBufferFree = new AutoResetEvent(false); + } + + public ResultCode ProcessParcelRequest(ServiceCtx context, byte[] parcelData, int code) + { + using (MemoryStream ms = new MemoryStream(parcelData)) + { + BinaryReader reader = new BinaryReader(ms); + + ms.Seek(4, SeekOrigin.Current); + + int strSize = reader.ReadInt32(); + + string interfaceName = Encoding.Unicode.GetString(reader.ReadBytes(strSize * 2)); + + long remainder = ms.Position & 0xf; + + if (remainder != 0) + { + ms.Seek(0x10 - remainder, SeekOrigin.Current); + } + + ms.Seek(0x50, SeekOrigin.Begin); + + if (_commands.TryGetValue((interfaceName, code), out ServiceProcessParcel procReq)) + { + Logger.PrintDebug(LogClass.ServiceVi, $"{interfaceName} {procReq.Method.Name}"); + + return procReq(context, reader); + } + else + { + throw new NotImplementedException($"{interfaceName} {code}"); + } + } + } + + private ResultCode GbpRequestBuffer(ServiceCtx context, BinaryReader parcelReader) + { + int slot = parcelReader.ReadInt32(); + + using (MemoryStream ms = new MemoryStream()) + { + BinaryWriter writer = new BinaryWriter(ms); + + BufferEntry entry = _bufferQueue[slot]; + + int bufferCount = 1; //? + long bufferSize = entry.Data.Size; + + writer.Write(bufferCount); + writer.Write(bufferSize); + + entry.Data.Write(writer); + + writer.Write(0); + + return MakeReplyParcel(context, ms.ToArray()); + } + } + + private ResultCode GbpDequeueBuffer(ServiceCtx context, BinaryReader parcelReader) + { + // TODO: Errors. + int format = parcelReader.ReadInt32(); + int width = parcelReader.ReadInt32(); + int height = parcelReader.ReadInt32(); + int getTimestamps = parcelReader.ReadInt32(); + int usage = parcelReader.ReadInt32(); + + int slot = GetFreeSlotBlocking(width, height); + + return MakeReplyParcel(context, slot, 1, 0x24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + } + + private ResultCode GbpQueueBuffer(ServiceCtx context, BinaryReader parcelReader) + { + context.Device.Statistics.RecordGameFrameTime(); + + // TODO: Errors. + int slot = parcelReader.ReadInt32(); + + long Position = parcelReader.BaseStream.Position; + + QueueBufferObject queueBufferObject = ReadFlattenedObject(parcelReader); + + parcelReader.BaseStream.Position = Position; + + _bufferQueue[slot].Transform = queueBufferObject.Transform; + _bufferQueue[slot].Crop = queueBufferObject.Crop; + + _bufferQueue[slot].State = BufferState.Queued; + + SendFrameBuffer(context, slot); + + if (context.Device.EnableDeviceVsync) + { + context.Device.VsyncEvent.WaitOne(); + } + + return MakeReplyParcel(context, 1280, 720, 0, 0, 0); + } + + private ResultCode GbpDetachBuffer(ServiceCtx context, BinaryReader parcelReader) + { + return MakeReplyParcel(context, 0); + } + + private ResultCode GbpCancelBuffer(ServiceCtx context, BinaryReader parcelReader) + { + // TODO: Errors. + int slot = parcelReader.ReadInt32(); + + MultiFence fence = ReadFlattenedObject(parcelReader); + + _bufferQueue[slot].State = BufferState.Free; + + _waitBufferFree.Set(); + + return MakeReplyParcel(context, 0); + } + + private ResultCode GbpQuery(ServiceCtx context, BinaryReader parcelReader) + { + return MakeReplyParcel(context, 0, 0); + } + + private ResultCode GbpConnect(ServiceCtx context, BinaryReader parcelReader) + { + return MakeReplyParcel(context, 1280, 720, 0, 0, 0); + } + + private ResultCode GbpDisconnect(ServiceCtx context, BinaryReader parcelReader) + { + return MakeReplyParcel(context, 0); + } + + private ResultCode GbpPreallocBuffer(ServiceCtx context, BinaryReader parcelReader) + { + int slot = parcelReader.ReadInt32(); + + bool hasInput = parcelReader.ReadInt32() == 1; + + if (hasInput) + { + byte[] graphicBuffer = ReadFlattenedObject(parcelReader); + + _bufferQueue[slot].State = BufferState.Free; + + using (BinaryReader graphicBufferReader = new BinaryReader(new MemoryStream(graphicBuffer))) + { + _bufferQueue[slot].Data = new GbpBuffer(graphicBufferReader); + } + + } + + return MakeReplyParcel(context, 0); + } + + private byte[] ReadFlattenedObject(BinaryReader reader) + { + long flattenedObjectSize = reader.ReadInt64(); + + return reader.ReadBytes((int)flattenedObjectSize); + } + + private unsafe T ReadFlattenedObject(BinaryReader reader) where T: struct + { + byte[] data = ReadFlattenedObject(reader); + + fixed (byte* ptr = data) + { + return Marshal.PtrToStructure((IntPtr)ptr); + } + } + + private ResultCode MakeReplyParcel(ServiceCtx context, params int[] ints) + { + using (MemoryStream ms = new MemoryStream()) + { + BinaryWriter writer = new BinaryWriter(ms); + + foreach (int Int in ints) + { + writer.Write(Int); + } + + return MakeReplyParcel(context, ms.ToArray()); + } + } + + private ResultCode MakeReplyParcel(ServiceCtx context, byte[] data) + { + (long replyPos, long replySize) = context.Request.GetBufferType0x22(); + + byte[] reply = MakeParcel(data, new byte[0]); + + context.Memory.WriteBytes(replyPos, reply); + + return ResultCode.Success; + } + + private Format ConvertColorFormat(ColorFormat colorFormat) + { + switch (colorFormat) + { + case ColorFormat.A8B8G8R8: + return Format.R8G8B8A8Unorm; + case ColorFormat.X8B8G8R8: + return Format.R8G8B8A8Unorm; + case ColorFormat.R5G6B5: + return Format.B5G6R5Unorm; + case ColorFormat.A8R8G8B8: + return Format.B8G8R8A8Unorm; + case ColorFormat.A4B4G4R4: + return Format.R4G4B4A4Unorm; + default: + throw new NotImplementedException($"Color Format \"{colorFormat}\" not implemented!"); + } + } + + // TODO: support multi surface + private void SendFrameBuffer(ServiceCtx context, int slot) + { + int fbWidth = _bufferQueue[slot].Data.Header.Width; + int fbHeight = _bufferQueue[slot].Data.Header.Height; + + int nvMapHandle = _bufferQueue[slot].Data.Buffer.Surfaces[0].NvMapHandle; + + if (nvMapHandle == 0) + { + nvMapHandle = _bufferQueue[slot].Data.Buffer.NvMapId; + } + + int bufferOffset = _bufferQueue[slot].Data.Buffer.Surfaces[0].Offset; + + NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(context.Process, nvMapHandle); + + ulong fbAddr = (ulong)(map.Address + bufferOffset); + + _bufferQueue[slot].State = BufferState.Acquired; + + Format format = ConvertColorFormat(_bufferQueue[slot].Data.Buffer.Surfaces[0].ColorFormat); + + int bytesPerPixel = + format == Format.B5G6R5Unorm || + format == Format.R4G4B4A4Unorm ? 2 : 4; + + int gobBlocksInY = 1 << _bufferQueue[slot].Data.Buffer.Surfaces[0].BlockHeightLog2; + + // Note: Rotation is being ignored. + + Rect cropRect = _bufferQueue[slot].Crop; + + bool flipX = _bufferQueue[slot].Transform.HasFlag(HalTransform.FlipX); + bool flipY = _bufferQueue[slot].Transform.HasFlag(HalTransform.FlipY); + + ImageCrop crop = new ImageCrop( + cropRect.Left, + cropRect.Right, + cropRect.Top, + cropRect.Bottom, + flipX, + flipY); + + context.Device.Gpu.Window.EnqueueFrameThreadSafe( + fbAddr, + fbWidth, + fbHeight, + 0, + false, + gobBlocksInY, + format, + bytesPerPixel, + crop, + ReleaseBuffer, + slot); + } + + private void ReleaseBuffer(object slot) + { + ReleaseBuffer((int)slot); + } + + private void ReleaseBuffer(int slot) + { + _bufferQueue[slot].State = BufferState.Free; + + _binderEvent.ReadableEvent.Signal(); + + _waitBufferFree.Set(); + } + + private int GetFreeSlotBlocking(int width, int height) + { + int slot; + + do + { + if ((slot = GetFreeSlot(width, height)) != -1) + { + break; + } + + if (_disposed) + { + break; + } + + _waitBufferFree.WaitOne(); + } + while (!_disposed); + + return slot; + } + + private int GetFreeSlot(int width, int height) + { + lock (_bufferQueue) + { + for (int slot = 0; slot < _bufferQueue.Length; slot++) + { + if (_bufferQueue[slot].State != BufferState.Free) + { + continue; + } + + GbpBuffer data = _bufferQueue[slot].Data; + + if (data.Header.Width == width && + data.Header.Height == height) + { + _bufferQueue[slot].State = BufferState.Dequeued; + + return slot; + } + } + } + + return -1; + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing && !_disposed) + { + _disposed = true; + + _waitBufferFree.Set(); + _waitBufferFree.Dispose(); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Parcel.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Parcel.cs new file mode 100644 index 0000000000..f5d934232e --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Parcel.cs @@ -0,0 +1,58 @@ +using System; +using System.IO; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + static class Parcel + { + public static byte[] GetParcelData(byte[] parcel) + { + if (parcel == null) + { + throw new ArgumentNullException(nameof(parcel)); + } + + using (MemoryStream ms = new MemoryStream(parcel)) + { + BinaryReader reader = new BinaryReader(ms); + + int dataSize = reader.ReadInt32(); + int dataOffset = reader.ReadInt32(); + int objsSize = reader.ReadInt32(); + int objsOffset = reader.ReadInt32(); + + ms.Seek(dataOffset - 0x10, SeekOrigin.Current); + + return reader.ReadBytes(dataSize); + } + } + + public static byte[] MakeParcel(byte[] data, byte[] objs) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + + if (objs == null) + { + throw new ArgumentNullException(nameof(objs)); + } + + using (MemoryStream ms = new MemoryStream()) + { + BinaryWriter writer = new BinaryWriter(ms); + + writer.Write(data.Length); + writer.Write(0x10); + writer.Write(objs.Length); + writer.Write(data.Length + 0x10); + + writer.Write(data); + writer.Write(objs); + + return ms.ToArray(); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/BufferEntry.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/BufferEntry.cs new file mode 100644 index 0000000000..cc4e07d9be --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/BufferEntry.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + struct BufferEntry + { + public BufferState State; + + public HalTransform Transform; + + public Rect Crop; + + public GbpBuffer Data; + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/BufferState.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/BufferState.cs new file mode 100644 index 0000000000..673da86013 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/BufferState.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + enum BufferState + { + Free, + Dequeued, + Queued, + Acquired + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorBytePerPixel.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorBytePerPixel.cs new file mode 100644 index 0000000000..b47d35b47c --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorBytePerPixel.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + enum ColorBytePerPixel + { + Bpp1 = 1, + Bpp2 = 2, + Bpp4 = 4, + Bpp8 = 8, + Bpp16 = 16, + Bpp24 = 24, + Bpp32 = 32, + Bpp48 = 48, + Bpp64 = 64, + Bpp96 = 96, + Bpp128 = 128 + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorComponent.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorComponent.cs new file mode 100644 index 0000000000..e9669f1299 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorComponent.cs @@ -0,0 +1,42 @@ +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + enum ColorComponent : uint + { + X1 = (0x01 << ColorShift.Component) | ColorBytePerPixel.Bpp1, + X2 = (0x02 << ColorShift.Component) | ColorBytePerPixel.Bpp2, + X4 = (0x03 << ColorShift.Component) | ColorBytePerPixel.Bpp4, + X8 = (0x04 << ColorShift.Component) | ColorBytePerPixel.Bpp8, + Y4X4 = (0x05 << ColorShift.Component) | ColorBytePerPixel.Bpp8, + X3Y3Z2 = (0x06 << ColorShift.Component) | ColorBytePerPixel.Bpp8, + X8Y8 = (0x07 << ColorShift.Component) | ColorBytePerPixel.Bpp16, + X8Y8X8Z8 = (0x08 << ColorShift.Component) | ColorBytePerPixel.Bpp16, + Y8X8Z8X8 = (0x09 << ColorShift.Component) | ColorBytePerPixel.Bpp16, + X16 = (0x0A << ColorShift.Component) | ColorBytePerPixel.Bpp16, + Y2X14 = (0x0B << ColorShift.Component) | ColorBytePerPixel.Bpp16, + Y4X12 = (0x0C << ColorShift.Component) | ColorBytePerPixel.Bpp16, + Y6X10 = (0x0D << ColorShift.Component) | ColorBytePerPixel.Bpp16, + Y8X8 = (0x0E << ColorShift.Component) | ColorBytePerPixel.Bpp16, + X10 = (0x0F << ColorShift.Component) | ColorBytePerPixel.Bpp16, + X12 = (0x10 << ColorShift.Component) | ColorBytePerPixel.Bpp16, + Z5Y5X6 = (0x11 << ColorShift.Component) | ColorBytePerPixel.Bpp16, + X5Y6Z5 = (0x12 << ColorShift.Component) | ColorBytePerPixel.Bpp16, + X6Y5Z5 = (0x13 << ColorShift.Component) | ColorBytePerPixel.Bpp16, + X1Y5Z5W5 = (0x14 << ColorShift.Component) | ColorBytePerPixel.Bpp16, + X4Y4Z4W4 = (0x15 << ColorShift.Component) | ColorBytePerPixel.Bpp16, + X5Y1Z5W5 = (0x16 << ColorShift.Component) | ColorBytePerPixel.Bpp16, + X5Y5Z1W5 = (0x17 << ColorShift.Component) | ColorBytePerPixel.Bpp16, + X5Y5Z5W1 = (0x18 << ColorShift.Component) | ColorBytePerPixel.Bpp16, + X8Y8Z8 = (0x19 << ColorShift.Component) | ColorBytePerPixel.Bpp24, + X24 = (0x1A << ColorShift.Component) | ColorBytePerPixel.Bpp24, + X32 = (0x1C << ColorShift.Component) | ColorBytePerPixel.Bpp32, + X16Y16 = (0x1D << ColorShift.Component) | ColorBytePerPixel.Bpp32, + X11Y11Z10 = (0x1E << ColorShift.Component) | ColorBytePerPixel.Bpp32, + X2Y10Z10W10 = (0x20 << ColorShift.Component) | ColorBytePerPixel.Bpp32, + X8Y8Z8W8 = (0x21 << ColorShift.Component) | ColorBytePerPixel.Bpp32, + Y10X10 = (0x22 << ColorShift.Component) | ColorBytePerPixel.Bpp32, + X10Y10Z10W2 = (0x23 << ColorShift.Component) | ColorBytePerPixel.Bpp32, + Y12X12 = (0x24 << ColorShift.Component) | ColorBytePerPixel.Bpp32, + X20Y20Z20 = (0x26 << ColorShift.Component) | ColorBytePerPixel.Bpp64, + X16Y16Z16W16 = (0x27 << ColorShift.Component) | ColorBytePerPixel.Bpp64, + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorDataType.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorDataType.cs new file mode 100644 index 0000000000..cfa3b018cf --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorDataType.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + enum ColorDataType + { + Integer = 0x0 << ColorShift.DataType, + Float = 0x1 << ColorShift.DataType, + Stencil = 0x2 << ColorShift.DataType + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorFormat.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorFormat.cs new file mode 100644 index 0000000000..227d648ac7 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorFormat.cs @@ -0,0 +1,235 @@ +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + enum ColorFormat : ulong + { + NonColor8 = ColorSpace.NonColor | ColorSwizzle.X000 | ColorComponent.X8 | ColorDataType.Integer, + NonColor16 = ColorSpace.NonColor | ColorSwizzle.X000 | ColorComponent.X16 | ColorDataType.Integer, + NonColor24 = ColorSpace.NonColor | ColorSwizzle.X000 | ColorComponent.X24 | ColorDataType.Integer, + NonColor32 = ColorSpace.NonColor | ColorSwizzle.X000 | ColorComponent.X32 | ColorDataType.Integer, + X4C4 = ColorSpace.NonColor | ColorSwizzle.Y000 | ColorComponent.Y4X4 | ColorDataType.Integer, + A4L4 = ColorSpace.LinearRGBA | ColorSwizzle.YYYX | ColorComponent.Y4X4 | ColorDataType.Integer, + A8L8 = ColorSpace.LinearRGBA | ColorSwizzle.YYYX | ColorComponent.Y8X8 | ColorDataType.Integer, + Float_A16L16 = ColorSpace.LinearRGBA | ColorSwizzle.YYYX | ColorComponent.X16Y16 | ColorDataType.Float, + A1B5G5R5 = ColorSpace.LinearRGBA | ColorSwizzle.WZYX | ColorComponent.X1Y5Z5W5 | ColorDataType.Integer, + A4B4G4R4 = ColorSpace.LinearRGBA | ColorSwizzle.WZYX | ColorComponent.X4Y4Z4W4 | ColorDataType.Integer, + A5B5G5R1 = ColorSpace.LinearRGBA | ColorSwizzle.WZYX | ColorComponent.X5Y5Z5W1 | ColorDataType.Integer, + A2B10G10R10 = ColorSpace.LinearRGBA | ColorSwizzle.WZYX | ColorComponent.X2Y10Z10W10 | ColorDataType.Integer, + A8B8G8R8 = ColorSpace.LinearRGBA | ColorSwizzle.WZYX | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer, + A16B16G16R16 = ColorSpace.LinearRGBA | ColorSwizzle.WZYX | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer, + Float_A16B16G16R16 = ColorSpace.LinearRGBA | ColorSwizzle.WZYX | ColorComponent.X16Y16Z16W16 | ColorDataType.Float, + A1R5G5B5 = ColorSpace.LinearRGBA | ColorSwizzle.YZWX | ColorComponent.X1Y5Z5W5 | ColorDataType.Integer, + A4R4G4B4 = ColorSpace.LinearRGBA | ColorSwizzle.YZWX | ColorComponent.X4Y4Z4W4 | ColorDataType.Integer, + A5R1G5B5 = ColorSpace.LinearRGBA | ColorSwizzle.YZWX | ColorComponent.X5Y1Z5W5 | ColorDataType.Integer, + A2R10G10B10 = ColorSpace.LinearRGBA | ColorSwizzle.YZWX | ColorComponent.X2Y10Z10W10 | ColorDataType.Integer, + A8R8G8B8 = ColorSpace.LinearRGBA | ColorSwizzle.YZWX | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer, + A1 = ColorSpace.LinearRGBA | ColorSwizzle._000X | ColorComponent.X1 | ColorDataType.Integer, + A2 = ColorSpace.LinearRGBA | ColorSwizzle._000X | ColorComponent.X2 | ColorDataType.Integer, + A4 = ColorSpace.LinearRGBA | ColorSwizzle._000X | ColorComponent.X4 | ColorDataType.Integer, + A8 = ColorSpace.LinearRGBA | ColorSwizzle._000X | ColorComponent.X8 | ColorDataType.Integer, + A16 = ColorSpace.LinearRGBA | ColorSwizzle._000X | ColorComponent.X16 | ColorDataType.Integer, + A32 = ColorSpace.LinearRGBA | ColorSwizzle._000X | ColorComponent.X32 | ColorDataType.Integer, + Float_A16 = ColorSpace.LinearRGBA | ColorSwizzle._000X | ColorComponent.X16 | ColorDataType.Float, + L4A4 = ColorSpace.LinearRGBA | ColorSwizzle.XXXY | ColorComponent.Y4X4 | ColorDataType.Integer, + L8A8 = ColorSpace.LinearRGBA | ColorSwizzle.XXXY | ColorComponent.Y8X8 | ColorDataType.Integer, + B4G4R4A4 = ColorSpace.LinearRGBA | ColorSwizzle.ZYXW | ColorComponent.X4Y4Z4W4 | ColorDataType.Integer, + B5G5R1A5 = ColorSpace.LinearRGBA | ColorSwizzle.ZYXW | ColorComponent.X5Y5Z1W5 | ColorDataType.Integer, + B5G5R5A1 = ColorSpace.LinearRGBA | ColorSwizzle.ZYXW | ColorComponent.X5Y5Z5W1 | ColorDataType.Integer, + B8G8R8A8 = ColorSpace.LinearRGBA | ColorSwizzle.ZYXW | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer, + B10G10R10A2 = ColorSpace.LinearRGBA | ColorSwizzle.ZYXW | ColorComponent.X10Y10Z10W2 | ColorDataType.Integer, + R1G5B5A5 = ColorSpace.LinearRGBA | ColorSwizzle.XYZW | ColorComponent.X1Y5Z5W5 | ColorDataType.Integer, + R4G4B4A4 = ColorSpace.LinearRGBA | ColorSwizzle.XYZW | ColorComponent.X4Y4Z4W4 | ColorDataType.Integer, + R5G5B5A1 = ColorSpace.LinearRGBA | ColorSwizzle.XYZW | ColorComponent.X5Y5Z5W1 | ColorDataType.Integer, + R8G8B8A8 = ColorSpace.LinearRGBA | ColorSwizzle.XYZW | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer, + R10G10B10A2 = ColorSpace.LinearRGBA | ColorSwizzle.XYZW | ColorComponent.X10Y10Z10W2 | ColorDataType.Integer, + L1 = ColorSpace.LinearRGBA | ColorSwizzle.XXX1 | ColorComponent.X1 | ColorDataType.Integer, + L2 = ColorSpace.LinearRGBA | ColorSwizzle.XXX1 | ColorComponent.X2 | ColorDataType.Integer, + L4 = ColorSpace.LinearRGBA | ColorSwizzle.XXX1 | ColorComponent.X4 | ColorDataType.Integer, + L8 = ColorSpace.LinearRGBA | ColorSwizzle.XXX1 | ColorComponent.X8 | ColorDataType.Integer, + L16 = ColorSpace.LinearRGBA | ColorSwizzle.XXX1 | ColorComponent.X16 | ColorDataType.Integer, + L32 = ColorSpace.LinearRGBA | ColorSwizzle.XXX1 | ColorComponent.X32 | ColorDataType.Integer, + Float_L16 = ColorSpace.LinearRGBA | ColorSwizzle.XXX1 | ColorComponent.X16 | ColorDataType.Float, + B5G6R5 = ColorSpace.LinearRGBA | ColorSwizzle.ZYX1 | ColorComponent.X5Y6Z5 | ColorDataType.Integer, + B6G5R5 = ColorSpace.LinearRGBA | ColorSwizzle.ZYX1 | ColorComponent.X6Y5Z5 | ColorDataType.Integer, + B5G5R5X1 = ColorSpace.LinearRGBA | ColorSwizzle.ZYX1 | ColorComponent.X5Y5Z5W1 | ColorDataType.Integer, + B8_G8_R8 = ColorSpace.LinearRGBA | ColorSwizzle.ZYX1 | ColorComponent.X8Y8Z8 | ColorDataType.Integer, + B8G8R8X8 = ColorSpace.LinearRGBA | ColorSwizzle.ZYX1 | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer, + Float_B10G11R11 = ColorSpace.LinearRGBA | ColorSwizzle.ZYX1 | ColorComponent.X11Y11Z10 | ColorDataType.Float, + X1B5G5R5 = ColorSpace.LinearRGBA | ColorSwizzle.WZY1 | ColorComponent.X1Y5Z5W5 | ColorDataType.Integer, + X8B8G8R8 = ColorSpace.LinearRGBA | ColorSwizzle.WZY1 | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer, + X16B16G16R16 = ColorSpace.LinearRGBA | ColorSwizzle.WZY1 | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer, + Float_X16B16G16R16 = ColorSpace.LinearRGBA | ColorSwizzle.WZY1 | ColorComponent.X16Y16Z16W16 | ColorDataType.Float, + R3G3B2 = ColorSpace.LinearRGBA | ColorSwizzle.XYZ1 | ColorComponent.X3Y3Z2 | ColorDataType.Integer, + R5G5B6 = ColorSpace.LinearRGBA | ColorSwizzle.XYZ1 | ColorComponent.Z5Y5X6 | ColorDataType.Integer, + R5G6B5 = ColorSpace.LinearRGBA | ColorSwizzle.XYZ1 | ColorComponent.X5Y6Z5 | ColorDataType.Integer, + R5G5B5X1 = ColorSpace.LinearRGBA | ColorSwizzle.XYZ1 | ColorComponent.X5Y5Z5W1 | ColorDataType.Integer, + R8_G8_B8 = ColorSpace.LinearRGBA | ColorSwizzle.XYZ1 | ColorComponent.X8Y8Z8 | ColorDataType.Integer, + R8G8B8X8 = ColorSpace.LinearRGBA | ColorSwizzle.XYZ1 | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer, + X1R5G5B5 = ColorSpace.LinearRGBA | ColorSwizzle.YZW1 | ColorComponent.X1Y5Z5W5 | ColorDataType.Integer, + X8R8G8B8 = ColorSpace.LinearRGBA | ColorSwizzle.YZW1 | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer, + RG8 = ColorSpace.LinearRGBA | ColorSwizzle.XY01 | ColorComponent.Y8X8 | ColorDataType.Integer, + R16G16 = ColorSpace.LinearRGBA | ColorSwizzle.XY01 | ColorComponent.X16Y16 | ColorDataType.Integer, + Float_R16G16 = ColorSpace.LinearRGBA | ColorSwizzle.XY01 | ColorComponent.X16Y16 | ColorDataType.Float, + R8 = ColorSpace.LinearRGBA | ColorSwizzle.X001 | ColorComponent.X8 | ColorDataType.Integer, + R16 = ColorSpace.LinearRGBA | ColorSwizzle.X001 | ColorComponent.X16 | ColorDataType.Integer, + Float_R16 = ColorSpace.LinearRGBA | ColorSwizzle.X001 | ColorComponent.X16 | ColorDataType.Float, + A2B10G10R10_sRGB = ColorSpace.SRGB | ColorSwizzle.WZYX | ColorComponent.X2Y10Z10W10 | ColorDataType.Integer, + A8B8G8R8_sRGB = ColorSpace.SRGB | ColorSwizzle.WZYX | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer, + A16B16G16R16_sRGB = ColorSpace.SRGB | ColorSwizzle.WZYX | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer, + A2R10G10B10_sRGB = ColorSpace.SRGB | ColorSwizzle.YZWX | ColorComponent.X2Y10Z10W10 | ColorDataType.Integer, + B10G10R10A2_sRGB = ColorSpace.SRGB | ColorSwizzle.ZYXW | ColorComponent.X10Y10Z10W2 | ColorDataType.Integer, + R10G10B10A2_sRGB = ColorSpace.SRGB | ColorSwizzle.XYZW | ColorComponent.X10Y10Z10W2 | ColorDataType.Integer, + X8B8G8R8_sRGB = ColorSpace.SRGB | ColorSwizzle.WZY1 | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer, + X16B16G16R16_sRGB = ColorSpace.SRGB | ColorSwizzle.WZY1 | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer, + A2B10G10R10_709 = ColorSpace.RGB709 | ColorSwizzle.WZYX | ColorComponent.X2Y10Z10W10 | ColorDataType.Integer, + A8B8G8R8_709 = ColorSpace.RGB709 | ColorSwizzle.WZYX | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer, + A16B16G16R16_709 = ColorSpace.RGB709 | ColorSwizzle.WZYX | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer, + A2R10G10B10_709 = ColorSpace.RGB709 | ColorSwizzle.YZWX | ColorComponent.X2Y10Z10W10 | ColorDataType.Integer, + B10G10R10A2_709 = ColorSpace.RGB709 | ColorSwizzle.ZYXW | ColorComponent.X10Y10Z10W2 | ColorDataType.Integer, + R10G10B10A2_709 = ColorSpace.RGB709 | ColorSwizzle.XYZW | ColorComponent.X10Y10Z10W2 | ColorDataType.Integer, + X8B8G8R8_709 = ColorSpace.RGB709 | ColorSwizzle.WZY1 | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer, + X16B16G16R16_709 = ColorSpace.RGB709 | ColorSwizzle.WZY1 | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer, + A2B10G10R10_709_Linear = ColorSpace.LinearRGB709 | ColorSwizzle.WZYX | ColorComponent.X2Y10Z10W10 | ColorDataType.Integer, + A8B8G8R8_709_Linear = ColorSpace.LinearRGB709 | ColorSwizzle.WZYX | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer, + A16B16G16R16_709_Linear = ColorSpace.LinearRGB709 | ColorSwizzle.WZYX | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer, + A2R10G10B10_709_Linear = ColorSpace.LinearRGB709 | ColorSwizzle.YZWX | ColorComponent.X2Y10Z10W10 | ColorDataType.Integer, + B10G10R10A2_709_Linear = ColorSpace.LinearRGB709 | ColorSwizzle.ZYXW | ColorComponent.X10Y10Z10W2 | ColorDataType.Integer, + R10G10B10A2_709_Linear = ColorSpace.LinearRGB709 | ColorSwizzle.XYZW | ColorComponent.X10Y10Z10W2 | ColorDataType.Integer, + X8B8G8R8_709_Linear = ColorSpace.LinearRGB709 | ColorSwizzle.WZY1 | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer, + X16B16G16R16_709_Linear = ColorSpace.LinearRGB709 | ColorSwizzle.WZY1 | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer, + Float_A16B16G16R16_scRGB_Linear = ColorSpace.LinearScRGB | ColorSwizzle.WZYX | ColorComponent.X16Y16Z16W16 | ColorDataType.Float, + A2B10G10R10_2020 = ColorSpace.RGB2020 | ColorSwizzle.WZYX | ColorComponent.X2Y10Z10W10 | ColorDataType.Integer, + A8B8G8R8_2020 = ColorSpace.RGB2020 | ColorSwizzle.WZYX | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer, + A16B16G16R16_2020 = ColorSpace.RGB2020 | ColorSwizzle.WZYX | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer, + A2R10G10B10_2020 = ColorSpace.RGB2020 | ColorSwizzle.YZWX | ColorComponent.X2Y10Z10W10 | ColorDataType.Integer, + B10G10R10A2_2020 = ColorSpace.RGB2020 | ColorSwizzle.ZYXW | ColorComponent.X10Y10Z10W2 | ColorDataType.Integer, + R10G10B10A2_2020 = ColorSpace.RGB2020 | ColorSwizzle.XYZW | ColorComponent.X10Y10Z10W2 | ColorDataType.Integer, + X8B8G8R8_2020 = ColorSpace.RGB2020 | ColorSwizzle.WZY1 | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer, + X16B16G16R16_2020 = ColorSpace.RGB2020 | ColorSwizzle.WZY1 | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer, + A2B10G10R10_2020_Linear = ColorSpace.LinearRGB2020 | ColorSwizzle.WZYX | ColorComponent.X2Y10Z10W10 | ColorDataType.Integer, + A8B8G8R8_2020_Linear = ColorSpace.LinearRGB2020 | ColorSwizzle.WZYX | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer, + A16B16G16R16_2020_Linear = ColorSpace.LinearRGB2020 | ColorSwizzle.WZYX | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer, + Float_A16B16G16R16_2020_Linear = ColorSpace.LinearRGB2020 | ColorSwizzle.WZYX | ColorComponent.X16Y16Z16W16 | ColorDataType.Float, + A2R10G10B10_2020_Linear = ColorSpace.LinearRGB2020 | ColorSwizzle.YZWX | ColorComponent.X2Y10Z10W10 | ColorDataType.Integer, + B10G10R10A2_2020_Linear = ColorSpace.LinearRGB2020 | ColorSwizzle.ZYXW | ColorComponent.X10Y10Z10W2 | ColorDataType.Integer, + R10G10B10A2_2020_Linear = ColorSpace.LinearRGB2020 | ColorSwizzle.XYZW | ColorComponent.X10Y10Z10W2 | ColorDataType.Integer, + X8B8G8R8_2020_Linear = ColorSpace.LinearRGB2020 | ColorSwizzle.WZY1 | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer, + X16B16G16R16_2020_Linear = ColorSpace.LinearRGB2020 | ColorSwizzle.WZY1 | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer, + Float_A16B16G16R16_2020_PQ = ColorSpace.RGB2020_PQ | ColorSwizzle.WZYX | ColorComponent.X16Y16Z16W16 | ColorDataType.Float, + A4I4 = ColorSpace.ColorIndex | ColorSwizzle.X00X | ColorComponent.Y4X4 | ColorDataType.Integer, + A8I8 = ColorSpace.ColorIndex | ColorSwizzle.X00X | ColorComponent.Y8X8 | ColorDataType.Integer, + I4A4 = ColorSpace.ColorIndex | ColorSwizzle.X00Y | ColorComponent.Y4X4 | ColorDataType.Integer, + I8A8 = ColorSpace.ColorIndex | ColorSwizzle.X00Y | ColorComponent.Y8X8 | ColorDataType.Integer, + I1 = ColorSpace.ColorIndex | ColorSwizzle.X000 | ColorComponent.X1 | ColorDataType.Integer, + I2 = ColorSpace.ColorIndex | ColorSwizzle.X000 | ColorComponent.X2 | ColorDataType.Integer, + I4 = ColorSpace.ColorIndex | ColorSwizzle.X000 | ColorComponent.X4 | ColorDataType.Integer, + I8 = ColorSpace.ColorIndex | ColorSwizzle.X000 | ColorComponent.X8 | ColorDataType.Integer, + A8Y8U8V8 = ColorSpace.YCbCr601 | ColorSwizzle.YZWX | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer, + A16Y16U16V16 = ColorSpace.YCbCr601 | ColorSwizzle.YZWX | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer, + Y8U8V8A8 = ColorSpace.YCbCr601 | ColorSwizzle.XYZW | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer, + V8_U8 = ColorSpace.YCbCr601 | ColorSwizzle._0YX0 | ColorComponent.X8Y8 | ColorDataType.Integer, + V8U8 = ColorSpace.YCbCr601 | ColorSwizzle._0YX0 | ColorComponent.Y8X8 | ColorDataType.Integer, + V10U10 = ColorSpace.YCbCr601 | ColorSwizzle._0ZY0 | ColorComponent.Y10X10 | ColorDataType.Integer, + V12U12 = ColorSpace.YCbCr601 | ColorSwizzle._0ZY0 | ColorComponent.Y12X12 | ColorDataType.Integer, + V8 = ColorSpace.YCbCr601 | ColorSwizzle._00X0 | ColorComponent.X8 | ColorDataType.Integer, + V10 = ColorSpace.YCbCr601 | ColorSwizzle._00X0 | ColorComponent.X10 | ColorDataType.Integer, + V12 = ColorSpace.YCbCr601 | ColorSwizzle._00X0 | ColorComponent.X12 | ColorDataType.Integer, + U8_V8 = ColorSpace.YCbCr601 | ColorSwizzle._0XY0 | ColorComponent.X8Y8 | ColorDataType.Integer, + U8V8 = ColorSpace.YCbCr601 | ColorSwizzle._0XY0 | ColorComponent.Y8X8 | ColorDataType.Integer, + U10V10 = ColorSpace.YCbCr601 | ColorSwizzle._0XZ0 | ColorComponent.Y10X10 | ColorDataType.Integer, + U12V12 = ColorSpace.YCbCr601 | ColorSwizzle._0XZ0 | ColorComponent.Y12X12 | ColorDataType.Integer, + U8 = ColorSpace.YCbCr601 | ColorSwizzle._0X00 | ColorComponent.X8 | ColorDataType.Integer, + U10 = ColorSpace.YCbCr601 | ColorSwizzle._0X00 | ColorComponent.X10 | ColorDataType.Integer, + U12 = ColorSpace.YCbCr601 | ColorSwizzle._0X00 | ColorComponent.X12 | ColorDataType.Integer, + Y8 = ColorSpace.YCbCr601 | ColorSwizzle.X000 | ColorComponent.X8 | ColorDataType.Integer, + Y10 = ColorSpace.YCbCr601 | ColorSwizzle.X000 | ColorComponent.X10 | ColorDataType.Integer, + Y12 = ColorSpace.YCbCr601 | ColorSwizzle.X000 | ColorComponent.X12 | ColorDataType.Integer, + YVYU = ColorSpace.YCbCr601 | ColorSwizzle.XZY1 | ColorComponent.X8Y8X8Z8 | ColorDataType.Integer, + VYUY = ColorSpace.YCbCr601 | ColorSwizzle.XZY1 | ColorComponent.Y8X8Z8X8 | ColorDataType.Integer, + UYVY = ColorSpace.YCbCr601 | ColorSwizzle.XYZ1 | ColorComponent.Y8X8Z8X8 | ColorDataType.Integer, + YUYV = ColorSpace.YCbCr601 | ColorSwizzle.XYZ1 | ColorComponent.X8Y8X8Z8 | ColorDataType.Integer, + Y8_U8_V8 = ColorSpace.YCbCr601 | ColorSwizzle.XYZ1 | ColorComponent.X8Y8Z8 | ColorDataType.Integer, + V8_U8_RR = ColorSpace.YCbCr601_RR | ColorSwizzle._0YX0 | ColorComponent.X8Y8 | ColorDataType.Integer, + V8U8_RR = ColorSpace.YCbCr601_RR | ColorSwizzle._0YX0 | ColorComponent.Y8X8 | ColorDataType.Integer, + V8_RR = ColorSpace.YCbCr601_RR | ColorSwizzle._00X0 | ColorComponent.X8 | ColorDataType.Integer, + U8_V8_RR = ColorSpace.YCbCr601_RR | ColorSwizzle._0XY0 | ColorComponent.X8Y8 | ColorDataType.Integer, + U8V8_RR = ColorSpace.YCbCr601_RR | ColorSwizzle._0XY0 | ColorComponent.Y8X8 | ColorDataType.Integer, + U8_RR = ColorSpace.YCbCr601_RR | ColorSwizzle._0X00 | ColorComponent.X8 | ColorDataType.Integer, + Y8_RR = ColorSpace.YCbCr601_RR | ColorSwizzle.X000 | ColorComponent.X8 | ColorDataType.Integer, + V8_U8_ER = ColorSpace.YCbCr601_ER | ColorSwizzle._0YX0 | ColorComponent.X8Y8 | ColorDataType.Integer, + V8U8_ER = ColorSpace.YCbCr601_ER | ColorSwizzle._0YX0 | ColorComponent.Y8X8 | ColorDataType.Integer, + V8_ER = ColorSpace.YCbCr601_ER | ColorSwizzle._00X0 | ColorComponent.X8 | ColorDataType.Integer, + U8_V8_ER = ColorSpace.YCbCr601_ER | ColorSwizzle._0XY0 | ColorComponent.X8Y8 | ColorDataType.Integer, + U8V8_ER = ColorSpace.YCbCr601_ER | ColorSwizzle._0XY0 | ColorComponent.Y8X8 | ColorDataType.Integer, + U8_ER = ColorSpace.YCbCr601_ER | ColorSwizzle._0X00 | ColorComponent.X8 | ColorDataType.Integer, + Y8_ER = ColorSpace.YCbCr601_ER | ColorSwizzle.X000 | ColorComponent.X8 | ColorDataType.Integer, + V8_U8_709 = ColorSpace.YCbCr709 | ColorSwizzle._0YX0 | ColorComponent.X8Y8 | ColorDataType.Integer, + V8U8_709 = ColorSpace.YCbCr709 | ColorSwizzle._0YX0 | ColorComponent.Y8X8 | ColorDataType.Integer, + V10U10_709 = ColorSpace.YCbCr709 | ColorSwizzle._0ZY0 | ColorComponent.Y10X10 | ColorDataType.Integer, + V12U12_709 = ColorSpace.YCbCr709 | ColorSwizzle._0ZY0 | ColorComponent.Y12X12 | ColorDataType.Integer, + V8_709 = ColorSpace.YCbCr709 | ColorSwizzle._00X0 | ColorComponent.X8 | ColorDataType.Integer, + V10_709 = ColorSpace.YCbCr709 | ColorSwizzle._00X0 | ColorComponent.X10 | ColorDataType.Integer, + V12_709 = ColorSpace.YCbCr709 | ColorSwizzle._00X0 | ColorComponent.X12 | ColorDataType.Integer, + U8_V8_709 = ColorSpace.YCbCr709 | ColorSwizzle._0XY0 | ColorComponent.X8Y8 | ColorDataType.Integer, + U8V8_709 = ColorSpace.YCbCr709 | ColorSwizzle._0XY0 | ColorComponent.Y8X8 | ColorDataType.Integer, + U10V10_709 = ColorSpace.YCbCr709 | ColorSwizzle._0XZ0 | ColorComponent.Y10X10 | ColorDataType.Integer, + U12V12_709 = ColorSpace.YCbCr709 | ColorSwizzle._0XZ0 | ColorComponent.Y12X12 | ColorDataType.Integer, + U8_709 = ColorSpace.YCbCr709 | ColorSwizzle._0X00 | ColorComponent.X8 | ColorDataType.Integer, + U10_709 = ColorSpace.YCbCr709 | ColorSwizzle._0X00 | ColorComponent.X10 | ColorDataType.Integer, + U12_709 = ColorSpace.YCbCr709 | ColorSwizzle._0X00 | ColorComponent.X12 | ColorDataType.Integer, + Y8_709 = ColorSpace.YCbCr709 | ColorSwizzle.X000 | ColorComponent.X8 | ColorDataType.Integer, + Y10_709 = ColorSpace.YCbCr709 | ColorSwizzle.X000 | ColorComponent.X10 | ColorDataType.Integer, + Y12_709 = ColorSpace.YCbCr709 | ColorSwizzle.X000 | ColorComponent.X12 | ColorDataType.Integer, + V8_U8_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._0YX0 | ColorComponent.X8Y8 | ColorDataType.Integer, + V8U8_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._0YX0 | ColorComponent.Y8X8 | ColorDataType.Integer, + V10U10_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._0ZY0 | ColorComponent.Y10X10 | ColorDataType.Integer, + V12U12_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._0ZY0 | ColorComponent.Y12X12 | ColorDataType.Integer, + V8_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._00X0 | ColorComponent.X8 | ColorDataType.Integer, + V10_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._00X0 | ColorComponent.X10 | ColorDataType.Integer, + V12_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._00X0 | ColorComponent.X12 | ColorDataType.Integer, + U8_V8_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._0XY0 | ColorComponent.X8Y8 | ColorDataType.Integer, + U8V8_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._0XY0 | ColorComponent.Y8X8 | ColorDataType.Integer, + U10V10_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._0XZ0 | ColorComponent.Y10X10 | ColorDataType.Integer, + U12V12_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._0XZ0 | ColorComponent.Y12X12 | ColorDataType.Integer, + U8_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._0X00 | ColorComponent.X8 | ColorDataType.Integer, + U10_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._0X00 | ColorComponent.X10 | ColorDataType.Integer, + U12_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._0X00 | ColorComponent.X12 | ColorDataType.Integer, + Y8_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle.X000 | ColorComponent.X8 | ColorDataType.Integer, + Y10_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle.X000 | ColorComponent.X10 | ColorDataType.Integer, + Y12_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle.X000 | ColorComponent.X12 | ColorDataType.Integer, + V10U10_2020 = ColorSpace.YCbCr709_ER | ColorSwizzle._0ZY0 | ColorComponent.Y10X10 | ColorDataType.Integer, + V12U12_2020 = ColorSpace.YCbCr709_ER | ColorSwizzle._0ZY0 | ColorComponent.Y12X12 | ColorDataType.Integer, + V10_2020 = ColorSpace.YCbCr709_ER | ColorSwizzle._00X0 | ColorComponent.X10 | ColorDataType.Integer, + V12_2020 = ColorSpace.YCbCr709_ER | ColorSwizzle._00X0 | ColorComponent.X12 | ColorDataType.Integer, + U10V10_2020 = ColorSpace.YCbCr709_ER | ColorSwizzle._0XZ0 | ColorComponent.Y10X10 | ColorDataType.Integer, + U12V12_2020 = ColorSpace.YCbCr709_ER | ColorSwizzle._0XZ0 | ColorComponent.Y12X12 | ColorDataType.Integer, + U10_2020 = ColorSpace.YCbCr709_ER | ColorSwizzle._0X00 | ColorComponent.X10 | ColorDataType.Integer, + U12_2020 = ColorSpace.YCbCr709_ER | ColorSwizzle._0X00 | ColorComponent.X12 | ColorDataType.Integer, + Y10_2020 = ColorSpace.YCbCr709_ER | ColorSwizzle.X000 | ColorComponent.X10 | ColorDataType.Integer, + Y12_2020 = ColorSpace.YCbCr709_ER | ColorSwizzle.X000 | ColorComponent.X12 | ColorDataType.Integer, + Bayer8RGGB = ColorSpace.BayerRGGB | ColorSwizzle.X000 | ColorComponent.X8 | ColorDataType.Integer, + Bayer16RGGB = ColorSpace.BayerRGGB | ColorSwizzle.X000 | ColorComponent.X16 | ColorDataType.Integer, + BayerS16RGGB = ColorSpace.BayerRGGB | ColorSwizzle.X000 | ColorComponent.X16 | ColorDataType.Stencil, + X2Bayer14RGGB = ColorSpace.BayerRGGB | ColorSwizzle.Y000 | ColorComponent.Y2X14 | ColorDataType.Integer, + X4Bayer12RGGB = ColorSpace.BayerRGGB | ColorSwizzle.Y000 | ColorComponent.Y4X12 | ColorDataType.Integer, + X6Bayer10RGGB = ColorSpace.BayerRGGB | ColorSwizzle.Y000 | ColorComponent.Y6X10 | ColorDataType.Integer, + Bayer8BGGR = ColorSpace.BayerBGGR | ColorSwizzle.X000 | ColorComponent.X8 | ColorDataType.Integer, + Bayer16BGGR = ColorSpace.BayerBGGR | ColorSwizzle.X000 | ColorComponent.X16 | ColorDataType.Integer, + BayerS16BGGR = ColorSpace.BayerBGGR | ColorSwizzle.X000 | ColorComponent.X16 | ColorDataType.Stencil, + X2Bayer14BGGR = ColorSpace.BayerBGGR | ColorSwizzle.Y000 | ColorComponent.Y2X14 | ColorDataType.Integer, + X4Bayer12BGGR = ColorSpace.BayerBGGR | ColorSwizzle.Y000 | ColorComponent.Y4X12 | ColorDataType.Integer, + X6Bayer10BGGR = ColorSpace.BayerBGGR | ColorSwizzle.Y000 | ColorComponent.Y6X10 | ColorDataType.Integer, + Bayer8GRBG = ColorSpace.BayerGRBG | ColorSwizzle.X000 | ColorComponent.X8 | ColorDataType.Integer, + Bayer16GRBG = ColorSpace.BayerGRBG | ColorSwizzle.X000 | ColorComponent.X16 | ColorDataType.Integer, + BayerS16GRBG = ColorSpace.BayerGRBG | ColorSwizzle.X000 | ColorComponent.X16 | ColorDataType.Stencil, + X2Bayer14GRBG = ColorSpace.BayerGRBG | ColorSwizzle.Y000 | ColorComponent.Y2X14 | ColorDataType.Integer, + X4Bayer12GRBG = ColorSpace.BayerGRBG | ColorSwizzle.Y000 | ColorComponent.Y4X12 | ColorDataType.Integer, + X6Bayer10GRBG = ColorSpace.BayerGRBG | ColorSwizzle.Y000 | ColorComponent.Y6X10 | ColorDataType.Integer, + Bayer8GBRG = ColorSpace.BayerGBRG | ColorSwizzle.X000 | ColorComponent.X8 | ColorDataType.Integer, + Bayer16GBRG = ColorSpace.BayerGBRG | ColorSwizzle.X000 | ColorComponent.X16 | ColorDataType.Integer, + BayerS16GBRG = ColorSpace.BayerGBRG | ColorSwizzle.X000 | ColorComponent.X16 | ColorDataType.Stencil, + X2Bayer14GBRG = ColorSpace.BayerGBRG | ColorSwizzle.Y000 | ColorComponent.Y2X14 | ColorDataType.Integer, + X4Bayer12GBRG = ColorSpace.BayerGBRG | ColorSwizzle.Y000 | ColorComponent.Y4X12 | ColorDataType.Integer, + X6Bayer10GBRG = ColorSpace.BayerGBRG | ColorSwizzle.Y000 | ColorComponent.Y6X10 | ColorDataType.Integer, + XYZ = ColorSpace.XYZ | ColorSwizzle.XYZ1 | ColorComponent.X20Y20Z20 | ColorDataType.Float, + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorShift.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorShift.cs new file mode 100644 index 0000000000..3ad216a8eb --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorShift.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + class ColorShift + { + public const int Swizzle = 16; + public const int DataType = 14; + public const int Space = 32; + public const int Component = 8; + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorSpace.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorSpace.cs new file mode 100644 index 0000000000..9003a00ba3 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorSpace.cs @@ -0,0 +1,33 @@ +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + enum ColorSpace : ulong + { + NonColor = 0x0L << ColorShift.Space, + LinearRGBA = 0x1L << ColorShift.Space, + SRGB = 0x2L << ColorShift.Space, + + RGB709 = 0x3L << ColorShift.Space, + LinearRGB709 = 0x4L << ColorShift.Space, + + LinearScRGB = 0x5L << ColorShift.Space, + + RGB2020 = 0x6L << ColorShift.Space, + LinearRGB2020 = 0x7L << ColorShift.Space, + RGB2020_PQ = 0x8L << ColorShift.Space, + + ColorIndex = 0x9L << ColorShift.Space, + + YCbCr601 = 0xAL << ColorShift.Space, + YCbCr601_RR = 0xBL << ColorShift.Space, + YCbCr601_ER = 0xCL << ColorShift.Space, + YCbCr709 = 0xDL << ColorShift.Space, + YCbCr709_ER = 0xEL << ColorShift.Space, + + BayerRGGB = 0x10L << ColorShift.Space, + BayerBGGR = 0x11L << ColorShift.Space, + BayerGRBG = 0x12L << ColorShift.Space, + BayerGBRG = 0x13L << ColorShift.Space, + + XYZ = 0x14L << ColorShift.Space, + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorSwizzle.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorSwizzle.cs new file mode 100644 index 0000000000..4c1370c713 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorSwizzle.cs @@ -0,0 +1,31 @@ +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + enum ColorSwizzle + { + XYZW = 0x688 << ColorShift.Swizzle, + ZYXW = 0x60a << ColorShift.Swizzle, + WZYX = 0x053 << ColorShift.Swizzle, + YZWX = 0x0d1 << ColorShift.Swizzle, + XYZ1 = 0xa88 << ColorShift.Swizzle, + YZW1 = 0xad1 << ColorShift.Swizzle, + XXX1 = 0xa00 << ColorShift.Swizzle, + XZY1 = 0xa50 << ColorShift.Swizzle, + ZYX1 = 0xa0a << ColorShift.Swizzle, + WZY1 = 0xa53 << ColorShift.Swizzle, + X000 = 0x920 << ColorShift.Swizzle, + Y000 = 0x921 << ColorShift.Swizzle, + XY01 = 0xb08 << ColorShift.Swizzle, + X001 = 0xb20 << ColorShift.Swizzle, + X00X = 0x121 << ColorShift.Swizzle, + X00Y = 0x320 << ColorShift.Swizzle, + _0YX0 = 0x80c << ColorShift.Swizzle, + _0ZY0 = 0x814 << ColorShift.Swizzle, + _0XZ0 = 0x884 << ColorShift.Swizzle, + _0X00 = 0x904 << ColorShift.Swizzle, + _00X0 = 0x824 << ColorShift.Swizzle, + _000X = 0x124 << ColorShift.Swizzle, + _0XY0 = 0x844 << ColorShift.Swizzle, + XXXY = 0x200 << ColorShift.Swizzle, + YYYX = 0x049 << ColorShift.Swizzle + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/GbpBuffer.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/GbpBuffer.cs new file mode 100644 index 0000000000..b93947fdec --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/GbpBuffer.cs @@ -0,0 +1,37 @@ +using Ryujinx.Common; +using System; +using System.IO; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + struct GbpBuffer + { + public GraphicBufferHeader Header { get; private set; } + public NvGraphicBuffer Buffer { get; private set; } + + public int Size => Marshal.SizeOf() + Marshal.SizeOf(); + + public GbpBuffer(BinaryReader reader) + { + Header = reader.ReadStruct(); + + // ignore fds + // TODO: check if that is used in official implementation + reader.BaseStream.Position += Header.FdsCount * 4; + + if (Header.IntsCount != 0x51) + { + throw new NotImplementedException($"Unexpected Graphic Buffer ints count (expected 0x51, found 0x{Header.IntsCount:x}"); + } + + Buffer = reader.ReadStruct(); + } + + public void Write(BinaryWriter writer) + { + writer.WriteStruct(Header); + writer.WriteStruct(Buffer); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/GraphicBufferHeader.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/GraphicBufferHeader.cs new file mode 100644 index 0000000000..fae0002f5b --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/GraphicBufferHeader.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + [StructLayout(LayoutKind.Sequential, Size = 0x28)] + struct GraphicBufferHeader + { + public int Magic; + public int Width; + public int Height; + public int Stride; + public int Format; + public int Usage; + + public int Pid; + public int RefCount; + + public int FdsCount; + public int IntsCount; + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/HalTransform.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/HalTransform.cs new file mode 100644 index 0000000000..a1efed0bc3 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/HalTransform.cs @@ -0,0 +1,14 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + [Flags] + enum HalTransform + { + FlipX = 1, + FlipY = 2, + Rotate90 = 4, + Rotate180 = FlipX | FlipY, + Rotate270 = Rotate90 | Rotate180 + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/MultiFence.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/MultiFence.cs new file mode 100644 index 0000000000..20e0723b88 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/MultiFence.cs @@ -0,0 +1,24 @@ +using Ryujinx.HLE.HOS.Services.Nv.Types; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + [StructLayout(LayoutKind.Explicit, Size = 0x24)] + struct MultiFence + { + [FieldOffset(0x0)] + public int FenceCount; + + [FieldOffset(0x4)] + public NvFence Fence0; + + [FieldOffset(0xC)] + public NvFence Fence1; + + [FieldOffset(0x14)] + public NvFence Fence2; + + [FieldOffset(0x1C)] + public NvFence Fence3; + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/NvGraphicBuffer.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/NvGraphicBuffer.cs new file mode 100644 index 0000000000..9a52245cf4 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/NvGraphicBuffer.cs @@ -0,0 +1,41 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + [StructLayout(LayoutKind.Explicit, Size = 0x144)] + struct NvGraphicBuffer + { + [FieldOffset(0x4)] + public int NvMapId; + + [FieldOffset(0xC)] + public int Magic; + + [FieldOffset(0x10)] + public int Pid; + + [FieldOffset(0x14)] + public int Type; + + [FieldOffset(0x18)] + public int Usage; + + [FieldOffset(0x1C)] + public int PixelFormat; + + [FieldOffset(0x20)] + public int ExternalPixelFormat; + + [FieldOffset(0x24)] + public int Stride; + + [FieldOffset(0x28)] + public int FrameBufferSize; + + [FieldOffset(0x2C)] + public int PlanesCount; + + [FieldOffset(0x34)] + public NvGraphicBufferSurfaceArray Surfaces; + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/NvGraphicBufferSurface.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/NvGraphicBufferSurface.cs new file mode 100644 index 0000000000..e084bc73ef --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/NvGraphicBufferSurface.cs @@ -0,0 +1,44 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + [StructLayout(LayoutKind.Explicit, Size = 0x58)] + struct NvGraphicBufferSurface + { + [FieldOffset(0)] + public uint Width; + + [FieldOffset(0x4)] + public uint Height; + + [FieldOffset(0x8)] + public ColorFormat ColorFormat; + + [FieldOffset(0x10)] + public int Layout; + + [FieldOffset(0x14)] + public int Pitch; + + [FieldOffset(0x18)] + public int NvMapHandle; + + [FieldOffset(0x1C)] + public int Offset; + + [FieldOffset(0x20)] + public int Kind; + + [FieldOffset(0x24)] + public int BlockHeightLog2; + + [FieldOffset(0x28)] + public int ScanFormat; + + [FieldOffset(0x30)] + public long Flags; + + [FieldOffset(0x38)] + public long Size; + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/NvGraphicBufferSurfaceArray.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/NvGraphicBufferSurfaceArray.cs new file mode 100644 index 0000000000..7327b5598c --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/NvGraphicBufferSurfaceArray.cs @@ -0,0 +1,39 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + [StructLayout(LayoutKind.Explicit)] + struct NvGraphicBufferSurfaceArray + { + [FieldOffset(0x0)] + private NvGraphicBufferSurface Surface0; + + [FieldOffset(0x58)] + private NvGraphicBufferSurface Surface1; + + [FieldOffset(0xb0)] + private NvGraphicBufferSurface Surface2; + + public NvGraphicBufferSurface this[int index] + { + get + { + if (index == 0) + { + return Surface0; + } + else if (index == 1) + { + return Surface1; + } + else if (index == 2) + { + return Surface2; + } + + throw new IndexOutOfRangeException(); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/QueueBufferObject.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/QueueBufferObject.cs new file mode 100644 index 0000000000..684f856a91 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/QueueBufferObject.cs @@ -0,0 +1,35 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + [StructLayout(LayoutKind.Explicit)] + struct QueueBufferObject + { + [FieldOffset(0x0)] + public long Timestamp; + + [FieldOffset(0x8)] + public int IsAutoTimestamp; + + [FieldOffset(0xC)] + public Rect Crop; + + [FieldOffset(0x1C)] + public int ScalingMode; + + [FieldOffset(0x20)] + public HalTransform Transform; + + [FieldOffset(0x24)] + public int StickyTransform; + + [FieldOffset(0x28)] + public int Unknown; + + [FieldOffset(0x2C)] + public int SwapInterval; + + [FieldOffset(0x30)] + public MultiFence Fence; + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Rect.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Rect.cs new file mode 100644 index 0000000000..c2f51eeaf8 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Rect.cs @@ -0,0 +1,13 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + [StructLayout(LayoutKind.Sequential, Size = 0x10)] + struct Rect + { + public int Top; + public int Left; + public int Right; + public int Bottom; + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Time/Clock/EphemeralNetworkSystemClockContextWriter.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/EphemeralNetworkSystemClockContextWriter.cs new file mode 100644 index 0000000000..14d3cb244a --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Time/Clock/EphemeralNetworkSystemClockContextWriter.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Services.Time.Clock +{ + class EphemeralNetworkSystemClockContextWriter : SystemClockContextUpdateCallback + { + protected override ResultCode Update() + { + return ResultCode.Success; + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Time/Clock/EphemeralNetworkSystemClockCore.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/EphemeralNetworkSystemClockCore.cs new file mode 100644 index 0000000000..003863e4cf --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Time/Clock/EphemeralNetworkSystemClockCore.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.HLE.HOS.Services.Time.Clock +{ + class EphemeralNetworkSystemClockCore : SystemClockCore + { + public EphemeralNetworkSystemClockCore(SteadyClockCore steadyClockCore) : base(steadyClockCore) { } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Time/Clock/LocalSystemClockContextWriter.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/LocalSystemClockContextWriter.cs new file mode 100644 index 0000000000..fb7ebdc5b6 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Time/Clock/LocalSystemClockContextWriter.cs @@ -0,0 +1,19 @@ +namespace Ryujinx.HLE.HOS.Services.Time.Clock +{ + class LocalSystemClockContextWriter : SystemClockContextUpdateCallback + { + private TimeSharedMemory _sharedMemory; + + public LocalSystemClockContextWriter(TimeSharedMemory sharedMemory) + { + _sharedMemory = sharedMemory; + } + + protected override ResultCode Update() + { + _sharedMemory.UpdateLocalSystemClockContext(_context); + + return ResultCode.Success; + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Time/Clock/NetworkSystemClockContextWriter.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/NetworkSystemClockContextWriter.cs new file mode 100644 index 0000000000..36468ec141 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Time/Clock/NetworkSystemClockContextWriter.cs @@ -0,0 +1,19 @@ +namespace Ryujinx.HLE.HOS.Services.Time.Clock +{ + class NetworkSystemClockContextWriter : SystemClockContextUpdateCallback + { + private TimeSharedMemory _sharedMemory; + + public NetworkSystemClockContextWriter(TimeSharedMemory sharedMemory) + { + _sharedMemory = sharedMemory; + } + + protected override ResultCode Update() + { + _sharedMemory.UpdateNetworkSystemClockContext(_context); + + return ResultCode.Success; + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Time/Clock/StandardLocalSystemClockCore.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/StandardLocalSystemClockCore.cs new file mode 100644 index 0000000000..20c334e862 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Time/Clock/StandardLocalSystemClockCore.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.HLE.HOS.Services.Time.Clock +{ + class StandardLocalSystemClockCore : SystemClockCore + { + public StandardLocalSystemClockCore(StandardSteadyClockCore steadyClockCore) : base(steadyClockCore) {} + } +} diff --git a/Ryujinx.HLE/HOS/Services/Time/Clock/StandardNetworkSystemClockCore.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/StandardNetworkSystemClockCore.cs new file mode 100644 index 0000000000..b86f703dbe --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Time/Clock/StandardNetworkSystemClockCore.cs @@ -0,0 +1,36 @@ +using Ryujinx.HLE.HOS.Kernel.Threading; + +namespace Ryujinx.HLE.HOS.Services.Time.Clock +{ + class StandardNetworkSystemClockCore : SystemClockCore + { + private TimeSpanType _standardNetworkClockSufficientAccuracy; + + public StandardNetworkSystemClockCore(StandardSteadyClockCore steadyClockCore) : base(steadyClockCore) + { + _standardNetworkClockSufficientAccuracy = new TimeSpanType(0); + } + + public bool IsStandardNetworkSystemClockAccuracySufficient(KThread thread) + { + SteadyClockCore steadyClockCore = GetSteadyClockCore(); + SteadyClockTimePoint currentTimePoint = steadyClockCore.GetCurrentTimePoint(thread); + + bool isStandardNetworkClockSufficientAccuracy = false; + + ResultCode result = GetClockContext(thread, out SystemClockContext context); + + if (result == ResultCode.Success && context.SteadyTimePoint.GetSpanBetween(currentTimePoint, out long outSpan) == ResultCode.Success) + { + isStandardNetworkClockSufficientAccuracy = outSpan * 1000000000 < _standardNetworkClockSufficientAccuracy.NanoSeconds; + } + + return isStandardNetworkClockSufficientAccuracy; + } + + public void SetStandardNetworkClockSufficientAccuracy(TimeSpanType standardNetworkClockSufficientAccuracy) + { + _standardNetworkClockSufficientAccuracy = standardNetworkClockSufficientAccuracy; + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Time/Clock/StandardSteadyClockCore.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/StandardSteadyClockCore.cs new file mode 100644 index 0000000000..370e7d73c9 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Time/Clock/StandardSteadyClockCore.cs @@ -0,0 +1,82 @@ +using Ryujinx.HLE.HOS.Kernel.Threading; + +namespace Ryujinx.HLE.HOS.Services.Time.Clock +{ + class StandardSteadyClockCore : SteadyClockCore + { + private TimeSpanType _setupValue; + private TimeSpanType _testOffset; + private TimeSpanType _internalOffset; + private TimeSpanType _cachedRawTimePoint; + + public StandardSteadyClockCore() + { + _setupValue = TimeSpanType.Zero; + _testOffset = TimeSpanType.Zero; + _internalOffset = TimeSpanType.Zero; + _cachedRawTimePoint = TimeSpanType.Zero; + } + + public override SteadyClockTimePoint GetTimePoint(KThread thread) + { + SteadyClockTimePoint result = new SteadyClockTimePoint + { + TimePoint = GetCurrentRawTimePoint(thread).ToSeconds(), + ClockSourceId = GetClockSourceId() + }; + + return result; + } + + public override TimeSpanType GetTestOffset() + { + return _testOffset; + } + + public override void SetTestOffset(TimeSpanType testOffset) + { + _testOffset = testOffset; + } + + public override TimeSpanType GetInternalOffset() + { + return _internalOffset; + } + + public override void SetInternalOffset(TimeSpanType internalOffset) + { + _internalOffset = internalOffset; + } + + public override TimeSpanType GetCurrentRawTimePoint(KThread thread) + { + TimeSpanType ticksTimeSpan; + + // As this may be called before the guest code, we support passing a null thread to make this api usable. + if (thread == null) + { + ticksTimeSpan = TimeSpanType.FromSeconds(0); + } + else + { + ticksTimeSpan = TimeSpanType.FromTicks(thread.Context.CntpctEl0, thread.Context.CntfrqEl0); + } + + TimeSpanType rawTimePoint = new TimeSpanType(_setupValue.NanoSeconds + ticksTimeSpan.NanoSeconds); + + if (rawTimePoint.NanoSeconds < _cachedRawTimePoint.NanoSeconds) + { + rawTimePoint.NanoSeconds = _cachedRawTimePoint.NanoSeconds; + } + + _cachedRawTimePoint = rawTimePoint; + + return rawTimePoint; + } + + public void SetSetupValue(TimeSpanType setupValue) + { + _setupValue = setupValue; + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Time/Clock/StandardUserSystemClockCore.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/StandardUserSystemClockCore.cs new file mode 100644 index 0000000000..42bc05fabe --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Time/Clock/StandardUserSystemClockCore.cs @@ -0,0 +1,107 @@ +using Ryujinx.HLE.HOS.Kernel.Threading; +using System; + +namespace Ryujinx.HLE.HOS.Services.Time.Clock +{ + class StandardUserSystemClockCore : SystemClockCore + { + private StandardLocalSystemClockCore _localSystemClockCore; + private StandardNetworkSystemClockCore _networkSystemClockCore; + private bool _autoCorrectionEnabled; + private SteadyClockTimePoint _autoCorrectionTime; + private KEvent _autoCorrectionEvent; + + public StandardUserSystemClockCore(StandardLocalSystemClockCore localSystemClockCore, StandardNetworkSystemClockCore networkSystemClockCore) : base(localSystemClockCore.GetSteadyClockCore()) + { + _localSystemClockCore = localSystemClockCore; + _networkSystemClockCore = networkSystemClockCore; + _autoCorrectionEnabled = false; + _autoCorrectionTime = SteadyClockTimePoint.GetRandom(); + _autoCorrectionEvent = null; + } + + protected override ResultCode Flush(SystemClockContext context) + { + // As UserSystemClock isn't a real system clock, this shouldn't happens. + throw new NotImplementedException(); + } + + public override ResultCode GetClockContext(KThread thread, out SystemClockContext context) + { + ResultCode result = ApplyAutomaticCorrection(thread, false); + + context = new SystemClockContext(); + + if (result == ResultCode.Success) + { + return _localSystemClockCore.GetClockContext(thread, out context); + } + + return result; + } + + public override ResultCode SetClockContext(SystemClockContext context) + { + return ResultCode.NotImplemented; + } + + private ResultCode ApplyAutomaticCorrection(KThread thread, bool autoCorrectionEnabled) + { + ResultCode result = ResultCode.Success; + + if (_autoCorrectionEnabled != autoCorrectionEnabled && _networkSystemClockCore.IsClockSetup(thread)) + { + result = _networkSystemClockCore.GetClockContext(thread, out SystemClockContext context); + + if (result == ResultCode.Success) + { + _localSystemClockCore.SetClockContext(context); + } + } + + return result; + } + + internal void CreateAutomaticCorrectionEvent(Horizon system) + { + _autoCorrectionEvent = new KEvent(system); + } + + public ResultCode SetAutomaticCorrectionEnabled(KThread thread, bool autoCorrectionEnabled) + { + ResultCode result = ApplyAutomaticCorrection(thread, autoCorrectionEnabled); + + if (result == ResultCode.Success) + { + _autoCorrectionEnabled = autoCorrectionEnabled; + } + + return result; + } + + public bool IsAutomaticCorrectionEnabled() + { + return _autoCorrectionEnabled; + } + + public KReadableEvent GetAutomaticCorrectionReadableEvent() + { + return _autoCorrectionEvent.ReadableEvent; + } + + public void SetAutomaticCorrectionUpdatedTime(SteadyClockTimePoint steadyClockTimePoint) + { + _autoCorrectionTime = steadyClockTimePoint; + } + + public SteadyClockTimePoint GetAutomaticCorrectionUpdatedTime() + { + return _autoCorrectionTime; + } + + public void SignalAutomaticCorrectionEvent() + { + _autoCorrectionEvent.WritableEvent.Signal(); + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Time/Clock/SteadyClockCore.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/SteadyClockCore.cs new file mode 100644 index 0000000000..83ace9814f --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Time/Clock/SteadyClockCore.cs @@ -0,0 +1,98 @@ +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.Utilities; +using System; + +namespace Ryujinx.HLE.HOS.Services.Time.Clock +{ + abstract class SteadyClockCore + { + private UInt128 _clockSourceId; + private bool _isRtcResetDetected; + private bool _isInitialized; + + public SteadyClockCore() + { + _clockSourceId = new UInt128(Guid.NewGuid().ToByteArray()); + _isRtcResetDetected = false; + _isInitialized = false; + } + + public UInt128 GetClockSourceId() + { + return _clockSourceId; + } + + public void SetClockSourceId(UInt128 clockSourceId) + { + _clockSourceId = clockSourceId; + } + + public void SetRtcReset() + { + _isRtcResetDetected = true; + } + + public virtual TimeSpanType GetTestOffset() + { + return new TimeSpanType(0); + } + + public virtual void SetTestOffset(TimeSpanType testOffset) {} + + public ResultCode GetRtcValue(out ulong rtcValue) + { + rtcValue = 0; + + return ResultCode.NotImplemented; + } + + public bool IsRtcResetDetected() + { + return _isRtcResetDetected; + } + + public ResultCode GetSetupResultValue() + { + return ResultCode.Success; + } + + public virtual TimeSpanType GetInternalOffset() + { + return new TimeSpanType(0); + } + + public virtual void SetInternalOffset(TimeSpanType internalOffset) {} + + public virtual SteadyClockTimePoint GetTimePoint(KThread thread) + { + throw new NotImplementedException(); + } + + public virtual TimeSpanType GetCurrentRawTimePoint(KThread thread) + { + SteadyClockTimePoint timePoint = GetTimePoint(thread); + + return TimeSpanType.FromSeconds(timePoint.TimePoint); + } + + public SteadyClockTimePoint GetCurrentTimePoint(KThread thread) + { + SteadyClockTimePoint result = GetTimePoint(thread); + + result.TimePoint += GetTestOffset().ToSeconds(); + result.TimePoint += GetInternalOffset().ToSeconds(); + + return result; + } + + public bool IsInitialized() + { + return _isInitialized; + } + + public void MarkInitialized() + { + _isInitialized = true; + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Time/Clock/SystemClockContextUpdateCallback.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/SystemClockContextUpdateCallback.cs new file mode 100644 index 0000000000..629d8ee114 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Time/Clock/SystemClockContextUpdateCallback.cs @@ -0,0 +1,72 @@ +using Ryujinx.HLE.HOS.Kernel.Threading; +using System; +using System.Collections.Generic; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Services.Time.Clock +{ + abstract class SystemClockContextUpdateCallback + { + private List _operationEventList; + protected SystemClockContext _context; + private bool _hasContext; + + public SystemClockContextUpdateCallback() + { + _operationEventList = new List(); + _context = new SystemClockContext(); + _hasContext = false; + } + + private bool NeedUpdate(SystemClockContext context) + { + if (_hasContext) + { + return _context.Offset != context.Offset || _context.SteadyTimePoint.ClockSourceId != context.SteadyTimePoint.ClockSourceId; + } + + return true; + } + + public void RegisterOperationEvent(KWritableEvent writableEvent) + { + Monitor.Enter(_operationEventList); + _operationEventList.Add(writableEvent); + Monitor.Exit(_operationEventList); + } + + private void BroadcastOperationEvent() + { + Monitor.Enter(_operationEventList); + + foreach (KWritableEvent e in _operationEventList) + { + e.Signal(); + } + + Monitor.Exit(_operationEventList); + } + + protected abstract ResultCode Update(); + + public ResultCode Update(SystemClockContext context) + { + ResultCode result = ResultCode.Success; + + if (NeedUpdate(context)) + { + _context = context; + _hasContext = true; + + result = Update(); + + if (result == ResultCode.Success) + { + BroadcastOperationEvent(); + } + } + + return result; + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Time/Clock/SystemClockCore.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/SystemClockCore.cs new file mode 100644 index 0000000000..865b1c0982 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Time/Clock/SystemClockCore.cs @@ -0,0 +1,144 @@ +using System; +using Ryujinx.HLE.HOS.Kernel.Threading; + +namespace Ryujinx.HLE.HOS.Services.Time.Clock +{ + abstract class SystemClockCore + { + private SteadyClockCore _steadyClockCore; + private SystemClockContext _context; + private bool _isInitialized; + private SystemClockContextUpdateCallback _systemClockContextUpdateCallback; + + public SystemClockCore(SteadyClockCore steadyClockCore) + { + _steadyClockCore = steadyClockCore; + _context = new SystemClockContext(); + _isInitialized = false; + + _context.SteadyTimePoint.ClockSourceId = steadyClockCore.GetClockSourceId(); + _systemClockContextUpdateCallback = null; + } + + public virtual SteadyClockCore GetSteadyClockCore() + { + return _steadyClockCore; + } + + public ResultCode GetCurrentTime(KThread thread, out long posixTime) + { + posixTime = 0; + + SteadyClockTimePoint currentTimePoint = _steadyClockCore.GetCurrentTimePoint(thread); + + ResultCode result = GetClockContext(thread, out SystemClockContext clockContext); + + if (result == ResultCode.Success) + { + result = ResultCode.TimeMismatch; + + if (currentTimePoint.ClockSourceId == clockContext.SteadyTimePoint.ClockSourceId) + { + posixTime = clockContext.Offset + currentTimePoint.TimePoint; + + result = 0; + } + } + + return result; + } + + public ResultCode SetCurrentTime(KThread thread, long posixTime) + { + SteadyClockTimePoint currentTimePoint = _steadyClockCore.GetCurrentTimePoint(thread); + + SystemClockContext clockContext = new SystemClockContext() + { + Offset = posixTime - currentTimePoint.TimePoint, + SteadyTimePoint = currentTimePoint + }; + + ResultCode result = SetClockContext(clockContext); + + if (result == ResultCode.Success) + { + result = Flush(clockContext); + } + + return result; + } + + public virtual ResultCode GetClockContext(KThread thread, out SystemClockContext context) + { + context = _context; + + return ResultCode.Success; + } + + public virtual ResultCode SetClockContext(SystemClockContext context) + { + _context = context; + + return ResultCode.Success; + } + + protected virtual ResultCode Flush(SystemClockContext context) + { + if (_systemClockContextUpdateCallback == null) + { + return ResultCode.Success; + } + + return _systemClockContextUpdateCallback.Update(context); + } + + public void SetUpdateCallbackInstance(SystemClockContextUpdateCallback systemClockContextUpdateCallback) + { + _systemClockContextUpdateCallback = systemClockContextUpdateCallback; + } + + public void RegisterOperationEvent(KWritableEvent writableEvent) + { + if (_systemClockContextUpdateCallback != null) + { + _systemClockContextUpdateCallback.RegisterOperationEvent(writableEvent); + } + } + + public ResultCode SetSystemClockContext(SystemClockContext context) + { + ResultCode result = SetClockContext(context); + + if (result == ResultCode.Success) + { + result = Flush(context); + } + + return result; + } + + public bool IsInitialized() + { + return _isInitialized; + } + + public void MarkInitialized() + { + _isInitialized = true; + } + + public bool IsClockSetup(KThread thread) + { + ResultCode result = GetClockContext(thread, out SystemClockContext context); + + if (result == ResultCode.Success) + { + SteadyClockTimePoint steadyClockTimePoint = _steadyClockCore.GetCurrentTimePoint(thread); + + return steadyClockTimePoint.ClockSourceId == context.SteadyTimePoint.ClockSourceId; + } + + return false; + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Time/Clock/TickBasedSteadyClockCore.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/TickBasedSteadyClockCore.cs new file mode 100644 index 0000000000..0650208276 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Time/Clock/TickBasedSteadyClockCore.cs @@ -0,0 +1,34 @@ +using Ryujinx.HLE.HOS.Kernel.Threading; + +namespace Ryujinx.HLE.HOS.Services.Time.Clock +{ + class TickBasedSteadyClockCore : SteadyClockCore + { + public TickBasedSteadyClockCore() {} + + public override SteadyClockTimePoint GetTimePoint(KThread thread) + { + SteadyClockTimePoint result = new SteadyClockTimePoint + { + TimePoint = 0, + ClockSourceId = GetClockSourceId() + }; + + TimeSpanType ticksTimeSpan; + + // As this may be called before the guest code, we support passing a null thread to make this api usable. + if (thread == null) + { + ticksTimeSpan = TimeSpanType.FromSeconds(0); + } + else + { + ticksTimeSpan = TimeSpanType.FromTicks(thread.Context.CntpctEl0, thread.Context.CntfrqEl0); + } + + result.TimePoint = ticksTimeSpan.ToSeconds(); + + return result; + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Time/Clock/Types/ClockSnapshot.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/Types/ClockSnapshot.cs new file mode 100644 index 0000000000..df1f151fe7 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Time/Clock/Types/ClockSnapshot.cs @@ -0,0 +1,41 @@ +using Ryujinx.HLE.HOS.Services.Time.TimeZone; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Time.Clock +{ + [StructLayout(LayoutKind.Sequential, Size = 0xD0)] + struct ClockSnapshot + { + public SystemClockContext UserContext; + public SystemClockContext NetworkContext; + public long UserTime; + public long NetworkTime; + public CalendarTime UserCalendarTime; + public CalendarTime NetworkCalendarTime; + public CalendarAdditionalInfo UserCalendarAdditionalTime; + public CalendarAdditionalInfo NetworkCalendarAdditionalTime; + public SteadyClockTimePoint SteadyClockTimePoint; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x24)] + public char[] LocationName; + + [MarshalAs(UnmanagedType.I1)] + public bool IsAutomaticCorrectionEnabled; + public byte Type; + public ushort Unknown; + + public static ResultCode GetCurrentTime(out long currentTime, SteadyClockTimePoint steadyClockTimePoint, SystemClockContext context) + { + currentTime = 0; + + if (steadyClockTimePoint.ClockSourceId == context.SteadyTimePoint.ClockSourceId) + { + currentTime = steadyClockTimePoint.TimePoint + context.Offset; + + return ResultCode.Success; + } + + return ResultCode.TimeMismatch; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Time/Clock/Types/SteadyClockTimePoint.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/Types/SteadyClockTimePoint.cs new file mode 100644 index 0000000000..71fb45212f --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Time/Clock/Types/SteadyClockTimePoint.cs @@ -0,0 +1,43 @@ +using Ryujinx.HLE.Utilities; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Time.Clock +{ + [StructLayout(LayoutKind.Sequential)] + struct SteadyClockTimePoint + { + public long TimePoint; + public UInt128 ClockSourceId; + + public ResultCode GetSpanBetween(SteadyClockTimePoint other, out long outSpan) + { + outSpan = 0; + + if (ClockSourceId == other.ClockSourceId) + { + try + { + outSpan = checked(other.TimePoint - TimePoint); + + return ResultCode.Success; + } + catch (OverflowException) + { + return ResultCode.Overflow; + } + } + + return ResultCode.Overflow; + } + + public static SteadyClockTimePoint GetRandom() + { + return new SteadyClockTimePoint + { + TimePoint = 0, + ClockSourceId = new UInt128(Guid.NewGuid().ToByteArray()) + }; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Time/Clock/Types/SystemClockContext.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/Types/SystemClockContext.cs new file mode 100644 index 0000000000..38e10480ef --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Time/Clock/Types/SystemClockContext.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Time.Clock +{ + [StructLayout(LayoutKind.Sequential)] + struct SystemClockContext + { + public long Offset; + public SteadyClockTimePoint SteadyTimePoint; + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Time/Clock/Types/TimeSpanType.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/Types/TimeSpanType.cs new file mode 100644 index 0000000000..c336ad4196 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Time/Clock/Types/TimeSpanType.cs @@ -0,0 +1,34 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Time.Clock +{ + [StructLayout(LayoutKind.Sequential)] + struct TimeSpanType + { + private const long NanoSecondsPerSecond = 1000000000; + + public static readonly TimeSpanType Zero = new TimeSpanType(0); + + public long NanoSeconds; + + public TimeSpanType(long nanoSeconds) + { + NanoSeconds = nanoSeconds; + } + + public long ToSeconds() + { + return NanoSeconds / NanoSecondsPerSecond; + } + + public static TimeSpanType FromSeconds(long seconds) + { + return new TimeSpanType(seconds * NanoSecondsPerSecond); + } + + public static TimeSpanType FromTicks(ulong ticks, ulong frequency) + { + return FromSeconds((long)ticks / (long)frequency); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Time/IAlarmService.cs b/Ryujinx.HLE/HOS/Services/Time/IAlarmService.cs new file mode 100644 index 0000000000..092fa8ce5e --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Time/IAlarmService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Time +{ + [Service("time:al")] // 9.0.0+ + class IAlarmService : IpcService + { + public IAlarmService(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Time/IPowerStateRequestHandler.cs b/Ryujinx.HLE/HOS/Services/Time/IPowerStateRequestHandler.cs new file mode 100644 index 0000000000..8ec55c1594 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Time/IPowerStateRequestHandler.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Time +{ + [Service("time:p")] // 9.0.0+ + class IPowerStateRequestHandler : IpcService + { + public IPowerStateRequestHandler(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Time/IStaticServiceForGlue.cs b/Ryujinx.HLE/HOS/Services/Time/IStaticServiceForGlue.cs new file mode 100644 index 0000000000..605cbbbd7a --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Time/IStaticServiceForGlue.cs @@ -0,0 +1,182 @@ +using Ryujinx.Common; +using Ryujinx.HLE.HOS.Services.Pcv.Bpc; +using Ryujinx.HLE.HOS.Services.Settings; +using Ryujinx.HLE.HOS.Services.Time.Clock; +using Ryujinx.HLE.HOS.Services.Time.StaticService; +using System; + +namespace Ryujinx.HLE.HOS.Services.Time +{ + [Service("time:a", TimePermissions.Admin)] + [Service("time:r", TimePermissions.Repair)] + [Service("time:u", TimePermissions.User)] + class IStaticServiceForGlue : IpcService + { + private IStaticServiceForPsc _inner; + private TimePermissions _permissions; + + public IStaticServiceForGlue(ServiceCtx context, TimePermissions permissions) + { + _permissions = permissions; + _inner = new IStaticServiceForPsc(context, permissions); + } + + [Command(0)] + // GetStandardUserSystemClock() -> object + public ResultCode GetStandardUserSystemClock(ServiceCtx context) + { + return _inner.GetStandardUserSystemClock(context); + } + + [Command(1)] + // GetStandardNetworkSystemClock() -> object + public ResultCode GetStandardNetworkSystemClock(ServiceCtx context) + { + return _inner.GetStandardNetworkSystemClock(context); + } + + [Command(2)] + // GetStandardSteadyClock() -> object + public ResultCode GetStandardSteadyClock(ServiceCtx context) + { + return _inner.GetStandardSteadyClock(context); + } + + [Command(3)] + // GetTimeZoneService() -> object + public ResultCode GetTimeZoneService(ServiceCtx context) + { + MakeObject(context, new ITimeZoneServiceForGlue(TimeManager.Instance.TimeZone, (_permissions & TimePermissions.TimeZoneWritableMask) != 0)); + + return ResultCode.Success; + } + + [Command(4)] + // GetStandardLocalSystemClock() -> object + public ResultCode GetStandardLocalSystemClock(ServiceCtx context) + { + return _inner.GetStandardLocalSystemClock(context); + } + + [Command(5)] // 4.0.0+ + // GetEphemeralNetworkSystemClock() -> object + public ResultCode GetEphemeralNetworkSystemClock(ServiceCtx context) + { + return _inner.GetEphemeralNetworkSystemClock(context); + } + + [Command(20)] // 6.0.0+ + // GetSharedMemoryNativeHandle() -> handle + public ResultCode GetSharedMemoryNativeHandle(ServiceCtx context) + { + return _inner.GetSharedMemoryNativeHandle(context); + } + + [Command(50)] // 4.0.0+ + // SetStandardSteadyClockInternalOffset(nn::TimeSpanType internal_offset) + public ResultCode SetStandardSteadyClockInternalOffset(ServiceCtx context) + { + if ((_permissions & TimePermissions.BypassUninitialized) == 0) + { + return ResultCode.PermissionDenied; + } + + TimeSpanType internalOffset = context.RequestData.ReadStruct(); + + // TODO: set:sys SetExternalSteadyClockInternalOffset(internalOffset.ToSeconds()) + + return ResultCode.Success; + } + + [Command(51)] // 9.0.0+ + // GetStandardSteadyClockRtcValue() -> u64 + public ResultCode GetStandardSteadyClockRtcValue(ServiceCtx context) + { + ResultCode result = (ResultCode)IRtcManager.GetExternalRtcValue(out ulong rtcValue); + + if (result == ResultCode.Success) + { + context.ResponseData.Write(rtcValue); + } + + return result; + } + + [Command(100)] + // IsStandardUserSystemClockAutomaticCorrectionEnabled() -> bool + public ResultCode IsStandardUserSystemClockAutomaticCorrectionEnabled(ServiceCtx context) + { + return _inner.IsStandardUserSystemClockAutomaticCorrectionEnabled(context); + } + + [Command(101)] + // SetStandardUserSystemClockAutomaticCorrectionEnabled(b8) + public ResultCode SetStandardUserSystemClockAutomaticCorrectionEnabled(ServiceCtx context) + { + return _inner.SetStandardUserSystemClockAutomaticCorrectionEnabled(context); + } + + [Command(102)] // 5.0.0+ + // GetStandardUserSystemClockInitialYear() -> u32 + public ResultCode GetStandardUserSystemClockInitialYear(ServiceCtx context) + { + if (!NxSettings.Settings.TryGetValue("time!standard_user_clock_initial_year", out object standardUserSystemClockInitialYear)) + { + throw new InvalidOperationException("standard_user_clock_initial_year isn't defined in system settings!"); + } + + context.ResponseData.Write((int)standardUserSystemClockInitialYear); + + return ResultCode.Success; + } + + [Command(200)] // 3.0.0+ + // IsStandardNetworkSystemClockAccuracySufficient() -> bool + public ResultCode IsStandardNetworkSystemClockAccuracySufficient(ServiceCtx context) + { + return _inner.IsStandardNetworkSystemClockAccuracySufficient(context); + } + + [Command(201)] // 6.0.0+ + // GetStandardUserSystemClockAutomaticCorrectionUpdatedTime() -> nn::time::SteadyClockTimePoint + public ResultCode GetStandardUserSystemClockAutomaticCorrectionUpdatedTime(ServiceCtx context) + { + return _inner.GetStandardUserSystemClockAutomaticCorrectionUpdatedTime(context); + } + + [Command(300)] // 4.0.0+ + // CalculateMonotonicSystemClockBaseTimePoint(nn::time::SystemClockContext) -> s64 + public ResultCode CalculateMonotonicSystemClockBaseTimePoint(ServiceCtx context) + { + return _inner.CalculateMonotonicSystemClockBaseTimePoint(context); + } + + [Command(400)] // 4.0.0+ + // GetClockSnapshot(u8) -> buffer + public ResultCode GetClockSnapshot(ServiceCtx context) + { + return _inner.GetClockSnapshot(context); + } + + [Command(401)] // 4.0.0+ + // GetClockSnapshotFromSystemClockContext(u8, nn::time::SystemClockContext, nn::time::SystemClockContext) -> buffer + public ResultCode GetClockSnapshotFromSystemClockContext(ServiceCtx context) + { + return _inner.GetClockSnapshotFromSystemClockContext(context); + } + + [Command(500)] // 4.0.0+ + // CalculateStandardUserSystemClockDifferenceByUser(buffer, buffer) -> nn::TimeSpanType + public ResultCode CalculateStandardUserSystemClockDifferenceByUser(ServiceCtx context) + { + return _inner.CalculateStandardUserSystemClockDifferenceByUser(context); + } + + [Command(501)] // 4.0.0+ + // CalculateSpanBetween(buffer, buffer) -> nn::TimeSpanType + public ResultCode CalculateSpanBetween(ServiceCtx context) + { + return _inner.CalculateSpanBetween(context); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Time/IStaticServiceForPsc.cs b/Ryujinx.HLE/HOS/Services/Time/IStaticServiceForPsc.cs new file mode 100644 index 0000000000..f5cecdbb84 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Time/IStaticServiceForPsc.cs @@ -0,0 +1,425 @@ +using Ryujinx.Common; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Time.Clock; +using Ryujinx.HLE.HOS.Services.Time.StaticService; +using Ryujinx.HLE.HOS.Services.Time.TimeZone; +using System; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Time +{ + [Service("time:s", TimePermissions.System)] + [Service("time:su", TimePermissions.SystemUpdate)] + class IStaticServiceForPsc : IpcService + { + private TimeManager _timeManager; + private TimePermissions _permissions; + + private int _timeSharedMemoryNativeHandle = 0; + + public IStaticServiceForPsc(ServiceCtx context, TimePermissions permissions) : this(TimeManager.Instance, permissions) {} + + public IStaticServiceForPsc(TimeManager manager, TimePermissions permissions) + { + _permissions = permissions; + _timeManager = manager; + } + + [Command(0)] + // GetStandardUserSystemClock() -> object + public ResultCode GetStandardUserSystemClock(ServiceCtx context) + { + MakeObject(context, new ISystemClock(_timeManager.StandardUserSystemClock, + (_permissions & TimePermissions.UserSystemClockWritableMask) != 0, + (_permissions & TimePermissions.BypassUninitialized) != 0)); + + return ResultCode.Success; + } + + [Command(1)] + // GetStandardNetworkSystemClock() -> object + public ResultCode GetStandardNetworkSystemClock(ServiceCtx context) + { + MakeObject(context, new ISystemClock(_timeManager.StandardNetworkSystemClock, + (_permissions & TimePermissions.NetworkSystemClockWritableMask) != 0, + (_permissions & TimePermissions.BypassUninitialized) != 0)); + + return ResultCode.Success; + } + + [Command(2)] + // GetStandardSteadyClock() -> object + public ResultCode GetStandardSteadyClock(ServiceCtx context) + { + MakeObject(context, new ISteadyClock(_timeManager.StandardSteadyClock, + (_permissions & TimePermissions.SteadyClockWritableMask) != 0, + (_permissions & TimePermissions.BypassUninitialized) != 0)); + + return ResultCode.Success; + } + + [Command(3)] + // GetTimeZoneService() -> object + public ResultCode GetTimeZoneService(ServiceCtx context) + { + MakeObject(context, new ITimeZoneServiceForPsc(_timeManager.TimeZone.Manager, + (_permissions & TimePermissions.TimeZoneWritableMask) != 0)); + + return ResultCode.Success; + } + + [Command(4)] + // GetStandardLocalSystemClock() -> object + public ResultCode GetStandardLocalSystemClock(ServiceCtx context) + { + MakeObject(context, new ISystemClock(_timeManager.StandardLocalSystemClock, + (_permissions & TimePermissions.LocalSystemClockWritableMask) != 0, + (_permissions & TimePermissions.BypassUninitialized) != 0)); + + return ResultCode.Success; + } + + [Command(5)] // 4.0.0+ + // GetEphemeralNetworkSystemClock() -> object + public ResultCode GetEphemeralNetworkSystemClock(ServiceCtx context) + { + MakeObject(context, new ISystemClock(_timeManager.StandardNetworkSystemClock, + (_permissions & TimePermissions.NetworkSystemClockWritableMask) != 0, + (_permissions & TimePermissions.BypassUninitialized) != 0)); + + return ResultCode.Success; + } + + [Command(20)] // 6.0.0+ + // GetSharedMemoryNativeHandle() -> handle + public ResultCode GetSharedMemoryNativeHandle(ServiceCtx context) + { + if (_timeSharedMemoryNativeHandle == 0) + { + if (context.Process.HandleTable.GenerateHandle(_timeManager.SharedMemory.GetSharedMemory(), out _timeSharedMemoryNativeHandle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_timeSharedMemoryNativeHandle); + + return ResultCode.Success; + } + + [Command(50)] // 4.0.0+ + // SetStandardSteadyClockInternalOffset(nn::TimeSpanType internal_offset) + public ResultCode SetStandardSteadyClockInternalOffset(ServiceCtx context) + { + // This is only implemented in glue's StaticService. + return ResultCode.NotImplemented; + } + + [Command(51)] // 9.0.0+ + // GetStandardSteadyClockRtcValue() -> u64 + public ResultCode GetStandardSteadyClockRtcValue(ServiceCtx context) + { + // This is only implemented in glue's StaticService. + return ResultCode.NotImplemented; + } + + [Command(100)] + // IsStandardUserSystemClockAutomaticCorrectionEnabled() -> bool + public ResultCode IsStandardUserSystemClockAutomaticCorrectionEnabled(ServiceCtx context) + { + StandardUserSystemClockCore userClock = _timeManager.StandardUserSystemClock; + + if (!userClock.IsInitialized()) + { + return ResultCode.UninitializedClock; + } + + context.ResponseData.Write(userClock.IsAutomaticCorrectionEnabled()); + + return ResultCode.Success; + } + + [Command(101)] + // SetStandardUserSystemClockAutomaticCorrectionEnabled(b8) + public ResultCode SetStandardUserSystemClockAutomaticCorrectionEnabled(ServiceCtx context) + { + SteadyClockCore steadyClock = _timeManager.StandardSteadyClock; + StandardUserSystemClockCore userClock = _timeManager.StandardUserSystemClock; + + if (!userClock.IsInitialized() || !steadyClock.IsInitialized()) + { + return ResultCode.UninitializedClock; + } + + if ((_permissions & TimePermissions.UserSystemClockWritableMask) == 0) + { + return ResultCode.PermissionDenied; + } + + bool autoCorrectionEnabled = context.RequestData.ReadBoolean(); + + ResultCode result = userClock.SetAutomaticCorrectionEnabled(context.Thread, autoCorrectionEnabled); + + if (result == ResultCode.Success) + { + _timeManager.SharedMemory.SetAutomaticCorrectionEnabled(autoCorrectionEnabled); + + SteadyClockTimePoint currentTimePoint = userClock.GetSteadyClockCore().GetCurrentTimePoint(context.Thread); + + userClock.SetAutomaticCorrectionUpdatedTime(currentTimePoint); + userClock.SignalAutomaticCorrectionEvent(); + } + + return result; + } + + [Command(102)] // 5.0.0+ + // GetStandardUserSystemClockInitialYear() -> u32 + public ResultCode GetStandardUserSystemClockInitialYear(ServiceCtx context) + { + // This is only implemented in glue's StaticService. + return ResultCode.NotImplemented; + } + + [Command(200)] // 3.0.0+ + // IsStandardNetworkSystemClockAccuracySufficient() -> bool + public ResultCode IsStandardNetworkSystemClockAccuracySufficient(ServiceCtx context) + { + context.ResponseData.Write(_timeManager.StandardNetworkSystemClock.IsStandardNetworkSystemClockAccuracySufficient(context.Thread)); + + return ResultCode.Success; + } + + [Command(201)] // 6.0.0+ + // GetStandardUserSystemClockAutomaticCorrectionUpdatedTime() -> nn::time::SteadyClockTimePoint + public ResultCode GetStandardUserSystemClockAutomaticCorrectionUpdatedTime(ServiceCtx context) + { + StandardUserSystemClockCore userClock = _timeManager.StandardUserSystemClock; + + if (!userClock.IsInitialized()) + { + return ResultCode.UninitializedClock; + } + + context.ResponseData.WriteStruct(userClock.GetAutomaticCorrectionUpdatedTime()); + + return ResultCode.Success; + } + + [Command(300)] // 4.0.0+ + // CalculateMonotonicSystemClockBaseTimePoint(nn::time::SystemClockContext) -> s64 + public ResultCode CalculateMonotonicSystemClockBaseTimePoint(ServiceCtx context) + { + SteadyClockCore steadyClock = _timeManager.StandardSteadyClock; + + if (!steadyClock.IsInitialized()) + { + return ResultCode.UninitializedClock; + } + + SystemClockContext otherContext = context.RequestData.ReadStruct(); + SteadyClockTimePoint currentTimePoint = steadyClock.GetCurrentTimePoint(context.Thread); + + ResultCode result = ResultCode.TimeMismatch; + + if (currentTimePoint.ClockSourceId == otherContext.SteadyTimePoint.ClockSourceId) + { + TimeSpanType ticksTimeSpan = TimeSpanType.FromTicks(context.Thread.Context.CntpctEl0, context.Thread.Context.CntfrqEl0); + long baseTimePoint = otherContext.Offset + currentTimePoint.TimePoint - ticksTimeSpan.ToSeconds(); + + context.ResponseData.Write(baseTimePoint); + + result = ResultCode.Success; + } + + return result; + } + + [Command(400)] // 4.0.0+ + // GetClockSnapshot(u8) -> buffer + public ResultCode GetClockSnapshot(ServiceCtx context) + { + byte type = context.RequestData.ReadByte(); + + ResultCode result = _timeManager.StandardUserSystemClock.GetClockContext(context.Thread, out SystemClockContext userContext); + + if (result == ResultCode.Success) + { + result = _timeManager.StandardNetworkSystemClock.GetClockContext(context.Thread, out SystemClockContext networkContext); + + if (result == ResultCode.Success) + { + result = GetClockSnapshotFromSystemClockContextInternal(context.Thread, userContext, networkContext, type, out ClockSnapshot clockSnapshot); + + if (result == ResultCode.Success) + { + WriteClockSnapshotFromBuffer(context, context.Request.RecvListBuff[0], clockSnapshot); + } + } + } + + return result; + } + + [Command(401)] // 4.0.0+ + // GetClockSnapshotFromSystemClockContext(u8, nn::time::SystemClockContext, nn::time::SystemClockContext) -> buffer + public ResultCode GetClockSnapshotFromSystemClockContext(ServiceCtx context) + { + byte type = context.RequestData.ReadByte(); + + context.RequestData.BaseStream.Position += 7; + + SystemClockContext userContext = context.RequestData.ReadStruct(); + SystemClockContext networkContext = context.RequestData.ReadStruct(); + + ResultCode result = GetClockSnapshotFromSystemClockContextInternal(context.Thread, userContext, networkContext, type, out ClockSnapshot clockSnapshot); + + if (result == ResultCode.Success) + { + WriteClockSnapshotFromBuffer(context, context.Request.RecvListBuff[0], clockSnapshot); + } + + return result; + } + + [Command(500)] // 4.0.0+ + // CalculateStandardUserSystemClockDifferenceByUser(buffer, buffer) -> nn::TimeSpanType + public ResultCode CalculateStandardUserSystemClockDifferenceByUser(ServiceCtx context) + { + ClockSnapshot clockSnapshotA = ReadClockSnapshotFromBuffer(context, context.Request.PtrBuff[0]); + ClockSnapshot clockSnapshotB = ReadClockSnapshotFromBuffer(context, context.Request.PtrBuff[1]); + TimeSpanType difference = TimeSpanType.FromSeconds(clockSnapshotB.UserContext.Offset - clockSnapshotA.UserContext.Offset); + + if (clockSnapshotB.UserContext.SteadyTimePoint.ClockSourceId != clockSnapshotA.UserContext.SteadyTimePoint.ClockSourceId || (clockSnapshotB.IsAutomaticCorrectionEnabled && clockSnapshotA.IsAutomaticCorrectionEnabled)) + { + difference = new TimeSpanType(0); + } + + context.ResponseData.Write(difference.NanoSeconds); + + return ResultCode.Success; + } + + [Command(501)] // 4.0.0+ + // CalculateSpanBetween(buffer, buffer) -> nn::TimeSpanType + public ResultCode CalculateSpanBetween(ServiceCtx context) + { + ClockSnapshot clockSnapshotA = ReadClockSnapshotFromBuffer(context, context.Request.PtrBuff[0]); + ClockSnapshot clockSnapshotB = ReadClockSnapshotFromBuffer(context, context.Request.PtrBuff[1]); + + TimeSpanType result; + + ResultCode resultCode = clockSnapshotA.SteadyClockTimePoint.GetSpanBetween(clockSnapshotB.SteadyClockTimePoint, out long timeSpan); + + if (resultCode != ResultCode.Success) + { + resultCode = ResultCode.TimeNotFound; + + if (clockSnapshotA.NetworkTime != 0 && clockSnapshotB.NetworkTime != 0) + { + result = TimeSpanType.FromSeconds(clockSnapshotB.NetworkTime - clockSnapshotA.NetworkTime); + resultCode = ResultCode.Success; + } + else + { + return resultCode; + } + } + else + { + result = TimeSpanType.FromSeconds(timeSpan); + } + + context.ResponseData.Write(result.NanoSeconds); + + return resultCode; + } + + private ResultCode GetClockSnapshotFromSystemClockContextInternal(KThread thread, SystemClockContext userContext, SystemClockContext networkContext, byte type, out ClockSnapshot clockSnapshot) + { + clockSnapshot = new ClockSnapshot(); + + SteadyClockCore steadyClockCore = _timeManager.StandardSteadyClock; + SteadyClockTimePoint currentTimePoint = steadyClockCore.GetCurrentTimePoint(thread); + + clockSnapshot.IsAutomaticCorrectionEnabled = _timeManager.StandardUserSystemClock.IsAutomaticCorrectionEnabled(); + clockSnapshot.UserContext = userContext; + clockSnapshot.NetworkContext = networkContext; + + ResultCode result = _timeManager.TimeZone.Manager.GetDeviceLocationName(out string deviceLocationName); + + if (result != ResultCode.Success) + { + return result; + } + + char[] tzName = deviceLocationName.ToCharArray(); + char[] locationName = new char[0x24]; + + Array.Copy(tzName, locationName, tzName.Length); + + clockSnapshot.LocationName = locationName; + + result = ClockSnapshot.GetCurrentTime(out clockSnapshot.UserTime, currentTimePoint, clockSnapshot.UserContext); + + if (result == ResultCode.Success) + { + result = _timeManager.TimeZone.Manager.ToCalendarTimeWithMyRules(clockSnapshot.UserTime, out CalendarInfo userCalendarInfo); + + if (result == ResultCode.Success) + { + clockSnapshot.UserCalendarTime = userCalendarInfo.Time; + clockSnapshot.UserCalendarAdditionalTime = userCalendarInfo.AdditionalInfo; + + if (ClockSnapshot.GetCurrentTime(out clockSnapshot.NetworkTime, currentTimePoint, clockSnapshot.NetworkContext) != ResultCode.Success) + { + clockSnapshot.NetworkTime = 0; + } + + result = _timeManager.TimeZone.Manager.ToCalendarTimeWithMyRules(clockSnapshot.NetworkTime, out CalendarInfo networkCalendarInfo); + + if (result == ResultCode.Success) + { + clockSnapshot.NetworkCalendarTime = networkCalendarInfo.Time; + clockSnapshot.NetworkCalendarAdditionalTime = networkCalendarInfo.AdditionalInfo; + clockSnapshot.Type = type; + + // Probably a version field? + clockSnapshot.Unknown = 0; + } + } + } + + return result; + } + + private ClockSnapshot ReadClockSnapshotFromBuffer(ServiceCtx context, IpcPtrBuffDesc ipcDesc) + { + Debug.Assert(ipcDesc.Size == Marshal.SizeOf()); + + using (BinaryReader bufferReader = new BinaryReader(new MemoryStream(context.Memory.ReadBytes(ipcDesc.Position, ipcDesc.Size)))) + { + return bufferReader.ReadStruct(); + } + } + + private void WriteClockSnapshotFromBuffer(ServiceCtx context, IpcRecvListBuffDesc ipcDesc, ClockSnapshot clockSnapshot) + { + Debug.Assert(ipcDesc.Size == Marshal.SizeOf()); + + MemoryStream memory = new MemoryStream((int)ipcDesc.Size); + + using (BinaryWriter bufferWriter = new BinaryWriter(memory)) + { + bufferWriter.WriteStruct(clockSnapshot); + } + + context.Memory.WriteBytes(ipcDesc.Position, memory.ToArray()); + memory.Dispose(); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Time/ITimeServiceManager.cs b/Ryujinx.HLE/HOS/Services/Time/ITimeServiceManager.cs new file mode 100644 index 0000000000..a897b3f78f --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Time/ITimeServiceManager.cs @@ -0,0 +1,219 @@ +using Ryujinx.Common; +using Ryujinx.HLE.Exceptions; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Services.Time.Clock; +using Ryujinx.HLE.Utilities; +using System; +using System.IO; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Time +{ + [Service("time:m")] // 9.0.0+ + class ITimeServiceManager : IpcService + { + private TimeManager _timeManager; + private int _automaticCorrectionEvent; + + public ITimeServiceManager(ServiceCtx context) + { + _timeManager = TimeManager.Instance; + _automaticCorrectionEvent = 0; + } + + [Command(0)] + // GetUserStaticService() -> object + public ResultCode GetUserStaticService(ServiceCtx context) + { + MakeObject(context, new IStaticServiceForPsc(_timeManager, TimePermissions.User)); + + return ResultCode.Success; + } + + [Command(5)] + // GetAdminStaticService() -> object + public ResultCode GetAdminStaticService(ServiceCtx context) + { + MakeObject(context, new IStaticServiceForPsc(_timeManager, TimePermissions.Admin)); + + return ResultCode.Success; + } + + [Command(6)] + // GetRepairStaticService() -> object + public ResultCode GetRepairStaticService(ServiceCtx context) + { + MakeObject(context, new IStaticServiceForPsc(_timeManager, TimePermissions.Repair)); + + return ResultCode.Success; + } + + [Command(9)] + // GetManufactureStaticService() -> object + public ResultCode GetManufactureStaticService(ServiceCtx context) + { + MakeObject(context, new IStaticServiceForPsc(_timeManager, TimePermissions.Manufacture)); + + return ResultCode.Success; + } + + [Command(10)] + // SetupStandardSteadyClock(nn::util::Uuid clock_source_id, nn::TimeSpanType setup_value, nn::TimeSpanType internal_offset, nn::TimeSpanType test_offset, bool is_rtc_reset_detected) + public ResultCode SetupStandardSteadyClock(ServiceCtx context) + { + UInt128 clockSourceId = context.RequestData.ReadStruct(); + TimeSpanType setupValue = context.RequestData.ReadStruct(); + TimeSpanType internalOffset = context.RequestData.ReadStruct(); + TimeSpanType testOffset = context.RequestData.ReadStruct(); + bool isRtcResetDetected = context.RequestData.ReadBoolean(); + + _timeManager.SetupStandardSteadyClock(context.Thread, clockSourceId, setupValue, internalOffset, testOffset, isRtcResetDetected); + + return ResultCode.Success; + } + + [Command(11)] + // SetupStandardLocalSystemClock(nn::time::SystemClockContext context, nn::time::PosixTime posix_time) + public ResultCode SetupStandardLocalSystemClock(ServiceCtx context) + { + SystemClockContext clockContext = context.RequestData.ReadStruct(); + long posixTime = context.RequestData.ReadInt64(); + + _timeManager.SetupStandardLocalSystemClock(context.Thread, clockContext, posixTime); + + return ResultCode.Success; + } + + [Command(12)] + // SetupStandardNetworkSystemClock(nn::time::SystemClockContext context, nn::TimeSpanType sufficient_accuracy) + public ResultCode SetupStandardNetworkSystemClock(ServiceCtx context) + { + SystemClockContext clockContext = context.RequestData.ReadStruct(); + TimeSpanType sufficientAccuracy = context.RequestData.ReadStruct(); + + _timeManager.SetupStandardNetworkSystemClock(clockContext, sufficientAccuracy); + + return ResultCode.Success; + } + + [Command(13)] + // SetupStandardUserSystemClock(bool automatic_correction_enabled, nn::time::SteadyClockTimePoint steady_clock_timepoint) + public ResultCode SetupStandardUserSystemClock(ServiceCtx context) + { + bool isAutomaticCorrectionEnabled = context.RequestData.ReadBoolean(); + + context.RequestData.BaseStream.Position += 7; + + SteadyClockTimePoint steadyClockTimePoint = context.RequestData.ReadStruct(); + + _timeManager.SetupStandardUserSystemClock(context.Thread, isAutomaticCorrectionEnabled, steadyClockTimePoint); + + return ResultCode.Success; + } + + [Command(14)] + // SetupTimeZoneManager(nn::time::LocationName location_name, nn::time::SteadyClockTimePoint timezone_update_timepoint, u32 total_location_name_count, nn::time::TimeZoneRuleVersion timezone_rule_version, buffer timezone_binary) + public ResultCode SetupTimeZoneManager(ServiceCtx context) + { + string locationName = Encoding.ASCII.GetString(context.RequestData.ReadBytes(0x24)).TrimEnd('\0'); + SteadyClockTimePoint timeZoneUpdateTimePoint = context.RequestData.ReadStruct(); + uint totalLocationNameCount = context.RequestData.ReadUInt32(); + UInt128 timeZoneRuleVersion = context.RequestData.ReadStruct(); + + (long bufferPosition, long bufferSize) = context.Request.GetBufferType0x21(); + + using (MemoryStream timeZoneBinaryStream = new MemoryStream(context.Memory.ReadBytes(bufferPosition, bufferSize))) + { + _timeManager.SetupTimeZoneManager(locationName, timeZoneUpdateTimePoint, totalLocationNameCount, timeZoneRuleVersion, timeZoneBinaryStream); + } + + return ResultCode.Success; + } + + [Command(15)] + // SetupEphemeralNetworkSystemClock() + public ResultCode SetupEphemeralNetworkSystemClock(ServiceCtx context) + { + _timeManager.SetupEphemeralNetworkSystemClock(); + + return ResultCode.Success; + } + + [Command(50)] + // Unknown50() -> handle + public ResultCode Unknown50(ServiceCtx context) + { + // TODO: figure out the usage of this event + throw new ServiceNotImplementedException(context); + } + + [Command(51)] + // Unknown51() -> handle + public ResultCode Unknown51(ServiceCtx context) + { + // TODO: figure out the usage of this event + throw new ServiceNotImplementedException(context); + } + + [Command(52)] + // Unknown52() -> handle + public ResultCode Unknown52(ServiceCtx context) + { + // TODO: figure out the usage of this event + throw new ServiceNotImplementedException(context); + } + + [Command(60)] + // GetStandardUserSystemClockAutomaticCorrectionEvent() -> handle + public ResultCode GetStandardUserSystemClockAutomaticCorrectionEvent(ServiceCtx context) + { + if (_automaticCorrectionEvent == 0) + { + if (context.Process.HandleTable.GenerateHandle(_timeManager.StandardUserSystemClock.GetAutomaticCorrectionReadableEvent(), out _automaticCorrectionEvent) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_automaticCorrectionEvent); + + return ResultCode.Success; + } + + [Command(100)] + // SetStandardSteadyClockRtcOffset(nn::TimeSpanType rtc_offset) + public ResultCode SetStandardSteadyClockRtcOffset(ServiceCtx context) + { + TimeSpanType rtcOffset = context.RequestData.ReadStruct(); + + _timeManager.SetStandardSteadyClockRtcOffset(context.Thread, rtcOffset); + + return ResultCode.Success; + } + + [Command(200)] + // GetAlarmRegistrationEvent() -> handle + public ResultCode GetAlarmRegistrationEvent(ServiceCtx context) + { + // TODO + throw new ServiceNotImplementedException(context); + } + + [Command(201)] + // UpdateSteadyAlarms() + public ResultCode UpdateSteadyAlarms(ServiceCtx context) + { + // TODO + throw new ServiceNotImplementedException(context); + } + + [Command(202)] + // TryGetNextSteadyClockAlarmSnapshot() -> (bool, nn::time::SteadyClockAlarmSnapshot) + public ResultCode TryGetNextSteadyClockAlarmSnapshot(ServiceCtx context) + { + // TODO + throw new ServiceNotImplementedException(context); + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Time/ResultCode.cs b/Ryujinx.HLE/HOS/Services/Time/ResultCode.cs new file mode 100644 index 0000000000..1ef895c258 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Time/ResultCode.cs @@ -0,0 +1,21 @@ +namespace Ryujinx.HLE.HOS.Services.Time +{ + public enum ResultCode + { + ModuleId = 116, + ErrorCodeShift = 9, + + Success = 0, + + PermissionDenied = (1 << ErrorCodeShift) | ModuleId, + TimeMismatch = (102 << ErrorCodeShift) | ModuleId, + UninitializedClock = (103 << ErrorCodeShift) | ModuleId, + TimeNotFound = (200 << ErrorCodeShift) | ModuleId, + Overflow = (201 << ErrorCodeShift) | ModuleId, + LocationNameTooLong = (801 << ErrorCodeShift) | ModuleId, + OutOfRange = (902 << ErrorCodeShift) | ModuleId, + TimeZoneConversionFailed = (903 << ErrorCodeShift) | ModuleId, + TimeZoneNotFound = (989 << ErrorCodeShift) | ModuleId, + NotImplemented = (990 << ErrorCodeShift) | ModuleId, + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Time/StaticService/ISteadyClock.cs b/Ryujinx.HLE/HOS/Services/Time/StaticService/ISteadyClock.cs new file mode 100644 index 0000000000..bf6a4fd10d --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Time/StaticService/ISteadyClock.cs @@ -0,0 +1,152 @@ +using Ryujinx.Common; +using Ryujinx.HLE.HOS.Services.Time.Clock; + +namespace Ryujinx.HLE.HOS.Services.Time.StaticService +{ + class ISteadyClock : IpcService + { + private SteadyClockCore _steadyClock; + private bool _writePermission; + private bool _bypassUninitializedClock; + + public ISteadyClock(SteadyClockCore steadyClock, bool writePermission, bool bypassUninitializedClock) + { + _steadyClock = steadyClock; + _writePermission = writePermission; + _bypassUninitializedClock = bypassUninitializedClock; + } + + [Command(0)] + // GetCurrentTimePoint() -> nn::time::SteadyClockTimePoint + public ResultCode GetCurrentTimePoint(ServiceCtx context) + { + if (!_bypassUninitializedClock && !_steadyClock.IsInitialized()) + { + return ResultCode.UninitializedClock; + } + + SteadyClockTimePoint currentTimePoint = _steadyClock.GetCurrentTimePoint(context.Thread); + + context.ResponseData.WriteStruct(currentTimePoint); + + return ResultCode.Success; + } + + [Command(2)] + // GetTestOffset() -> nn::TimeSpanType + public ResultCode GetTestOffset(ServiceCtx context) + { + if (!_bypassUninitializedClock && !_steadyClock.IsInitialized()) + { + return ResultCode.UninitializedClock; + } + + context.ResponseData.WriteStruct(_steadyClock.GetTestOffset()); + + return ResultCode.Success; + } + + [Command(3)] + // SetTestOffset(nn::TimeSpanType) + public ResultCode SetTestOffset(ServiceCtx context) + { + if (!_writePermission) + { + return ResultCode.PermissionDenied; + } + + if (!_bypassUninitializedClock && !_steadyClock.IsInitialized()) + { + return ResultCode.UninitializedClock; + } + + TimeSpanType testOffset = context.RequestData.ReadStruct(); + + _steadyClock.SetTestOffset(testOffset); + + return ResultCode.Success; + } + + [Command(100)] // 2.0.0+ + // GetRtcValue() -> u64 + public ResultCode GetRtcValue(ServiceCtx context) + { + if (!_bypassUninitializedClock && !_steadyClock.IsInitialized()) + { + return ResultCode.UninitializedClock; + } + + ResultCode result = _steadyClock.GetRtcValue(out ulong rtcValue); + + if (result == ResultCode.Success) + { + context.ResponseData.Write(rtcValue); + } + + return result; + } + + [Command(101)] // 2.0.0+ + // IsRtcResetDetected() -> bool + public ResultCode IsRtcResetDetected(ServiceCtx context) + { + if (!_bypassUninitializedClock && !_steadyClock.IsInitialized()) + { + return ResultCode.UninitializedClock; + } + + context.ResponseData.Write(_steadyClock.IsRtcResetDetected()); + + return ResultCode.Success; + } + + [Command(102)] // 2.0.0+ + // GetSetupResultValue() -> u32 + public ResultCode GetSetupResultValue(ServiceCtx context) + { + if (!_bypassUninitializedClock && !_steadyClock.IsInitialized()) + { + return ResultCode.UninitializedClock; + } + + context.ResponseData.Write((uint)_steadyClock.GetSetupResultValue()); + + return ResultCode.Success; + } + + [Command(200)] // 3.0.0+ + // GetInternalOffset() -> nn::TimeSpanType + public ResultCode GetInternalOffset(ServiceCtx context) + { + if (!_bypassUninitializedClock && !_steadyClock.IsInitialized()) + { + return ResultCode.UninitializedClock; + } + + context.ResponseData.WriteStruct(_steadyClock.GetInternalOffset()); + + return ResultCode.Success; + } + + [Command(201)] // 3.0.0-3.0.2 + // SetInternalOffset(nn::TimeSpanType) + public ResultCode SetInternalOffset(ServiceCtx context) + { + if (!_writePermission) + { + return ResultCode.PermissionDenied; + } + + if (!_bypassUninitializedClock && !_steadyClock.IsInitialized()) + { + return ResultCode.UninitializedClock; + } + + TimeSpanType internalOffset = context.RequestData.ReadStruct(); + + _steadyClock.SetInternalOffset(internalOffset); + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Time/StaticService/ISystemClock.cs b/Ryujinx.HLE/HOS/Services/Time/StaticService/ISystemClock.cs new file mode 100644 index 0000000000..d5b21f8c95 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Time/StaticService/ISystemClock.cs @@ -0,0 +1,124 @@ +using Ryujinx.Common; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Time.Clock; +using System; + +namespace Ryujinx.HLE.HOS.Services.Time.StaticService +{ + class ISystemClock : IpcService + { + private SystemClockCore _clockCore; + private bool _writePermission; + private bool _bypassUninitializedClock; + private int _operationEventReadableHandle; + + public ISystemClock(SystemClockCore clockCore, bool writePermission, bool bypassUninitializedClock) + { + _clockCore = clockCore; + _writePermission = writePermission; + _bypassUninitializedClock = bypassUninitializedClock; + _operationEventReadableHandle = 0; + } + + [Command(0)] + // GetCurrentTime() -> nn::time::PosixTime + public ResultCode GetCurrentTime(ServiceCtx context) + { + if (!_bypassUninitializedClock && !_clockCore.IsInitialized()) + { + return ResultCode.UninitializedClock; + } + + ResultCode result = _clockCore.GetCurrentTime(context.Thread, out long posixTime); + + if (result == ResultCode.Success) + { + context.ResponseData.Write(posixTime); + } + + return result; + } + + [Command(1)] + // SetCurrentTime(nn::time::PosixTime) + public ResultCode SetCurrentTime(ServiceCtx context) + { + if (!_writePermission) + { + return ResultCode.PermissionDenied; + } + + if (!_bypassUninitializedClock && !_clockCore.IsInitialized()) + { + return ResultCode.UninitializedClock; + } + + long posixTime = context.RequestData.ReadInt64(); + + return _clockCore.SetCurrentTime(context.Thread, posixTime); + } + + [Command(2)] + // GetClockContext() -> nn::time::SystemClockContext + public ResultCode GetSystemClockContext(ServiceCtx context) + { + if (!_bypassUninitializedClock && !_clockCore.IsInitialized()) + { + return ResultCode.UninitializedClock; + } + + ResultCode result = _clockCore.GetClockContext(context.Thread, out SystemClockContext clockContext); + + if (result == ResultCode.Success) + { + context.ResponseData.WriteStruct(clockContext); + } + + return result; + } + + [Command(3)] + // SetClockContext(nn::time::SystemClockContext) + public ResultCode SetSystemClockContext(ServiceCtx context) + { + if (!_writePermission) + { + return ResultCode.PermissionDenied; + } + + if (!_bypassUninitializedClock && !_clockCore.IsInitialized()) + { + return ResultCode.UninitializedClock; + } + + SystemClockContext clockContext = context.RequestData.ReadStruct(); + + ResultCode result = _clockCore.SetSystemClockContext(clockContext); + + return result; + } + + [Command(4)] // 9.0.0+ + // GetOperationEventReadableHandle() -> handle + public ResultCode GetOperationEventReadableHandle(ServiceCtx context) + { + if (_operationEventReadableHandle == 0) + { + KEvent kEvent = new KEvent(context.Device.System); + + _clockCore.RegisterOperationEvent(kEvent.WritableEvent); + + if (context.Process.HandleTable.GenerateHandle(kEvent.ReadableEvent, out _operationEventReadableHandle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_operationEventReadableHandle); + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Time/StaticService/ITimeZoneServiceForGlue.cs b/Ryujinx.HLE/HOS/Services/Time/StaticService/ITimeZoneServiceForGlue.cs new file mode 100644 index 0000000000..7acb025726 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Time/StaticService/ITimeZoneServiceForGlue.cs @@ -0,0 +1,142 @@ +using ARMeilleure.Memory; +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Services.Time.TimeZone; +using System; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Time.StaticService +{ + class ITimeZoneServiceForGlue : IpcService + { + private TimeZoneContentManager _timeZoneContentManager; + private ITimeZoneServiceForPsc _inner; + private bool _writePermission; + + public ITimeZoneServiceForGlue(TimeZoneContentManager timeZoneContentManager, bool writePermission) + { + _timeZoneContentManager = timeZoneContentManager; + _writePermission = writePermission; + _inner = new ITimeZoneServiceForPsc(timeZoneContentManager.Manager, writePermission); + } + + [Command(0)] + // GetDeviceLocationName() -> nn::time::LocationName + public ResultCode GetDeviceLocationName(ServiceCtx context) + { + return _inner.GetDeviceLocationName(context); + } + + [Command(1)] + // SetDeviceLocationName(nn::time::LocationName) + public ResultCode SetDeviceLocationName(ServiceCtx context) + { + if (!_writePermission) + { + return ResultCode.PermissionDenied; + } + + string locationName = Encoding.ASCII.GetString(context.RequestData.ReadBytes(0x24)).TrimEnd('\0'); + + return _timeZoneContentManager.SetDeviceLocationName(locationName); + } + + [Command(2)] + // GetTotalLocationNameCount() -> u32 + public ResultCode GetTotalLocationNameCount(ServiceCtx context) + { + return _inner.GetTotalLocationNameCount(context); + } + + [Command(3)] + // LoadLocationNameList(u32 index) -> (u32 outCount, buffer) + public ResultCode LoadLocationNameList(ServiceCtx context) + { + uint index = context.RequestData.ReadUInt32(); + long bufferPosition = context.Request.ReceiveBuff[0].Position; + long bufferSize = context.Request.ReceiveBuff[0].Size; + + ResultCode errorCode = _timeZoneContentManager.LoadLocationNameList(index, out string[] locationNameArray, (uint)bufferSize / 0x24); + + if (errorCode == 0) + { + uint offset = 0; + + foreach (string locationName in locationNameArray) + { + int padding = 0x24 - locationName.Length; + + if (padding < 0) + { + return ResultCode.LocationNameTooLong; + } + + context.Memory.WriteBytes(bufferPosition + offset, Encoding.ASCII.GetBytes(locationName)); + MemoryHelper.FillWithZeros(context.Memory, bufferPosition + offset + locationName.Length, padding); + + offset += 0x24; + } + + context.ResponseData.Write((uint)locationNameArray.Length); + } + + return errorCode; + } + + [Command(4)] + // LoadTimeZoneRule(nn::time::LocationName locationName) -> buffer + public ResultCode LoadTimeZoneRule(ServiceCtx context) + { + long bufferPosition = context.Request.ReceiveBuff[0].Position; + long bufferSize = context.Request.ReceiveBuff[0].Size; + + if (bufferSize != 0x4000) + { + // TODO: find error code here + Logger.PrintError(LogClass.ServiceTime, $"TimeZoneRule buffer size is 0x{bufferSize:x} (expected 0x4000)"); + + throw new InvalidOperationException(); + } + + string locationName = Encoding.ASCII.GetString(context.RequestData.ReadBytes(0x24)).TrimEnd('\0'); + + ResultCode resultCode = _timeZoneContentManager.LoadTimeZoneRule(out TimeZoneRule rules, locationName); + + // Write TimeZoneRule if success + if (resultCode == ResultCode.Success) + { + MemoryHelper.Write(context.Memory, bufferPosition, rules); + } + + return resultCode; + } + + [Command(100)] + // ToCalendarTime(nn::time::PosixTime time, buffer rules) -> (nn::time::CalendarTime, nn::time::sf::CalendarAdditionalInfo) + public ResultCode ToCalendarTime(ServiceCtx context) + { + return _inner.ToCalendarTime(context); + } + + [Command(101)] + // ToCalendarTimeWithMyRule(nn::time::PosixTime) -> (nn::time::CalendarTime, nn::time::sf::CalendarAdditionalInfo) + public ResultCode ToCalendarTimeWithMyRule(ServiceCtx context) + { + return _inner.ToCalendarTimeWithMyRule(context); + } + + [Command(201)] + // ToPosixTime(nn::time::CalendarTime calendarTime, buffer rules) -> (u32 outCount, buffer) + public ResultCode ToPosixTime(ServiceCtx context) + { + return _inner.ToPosixTime(context); + } + + [Command(202)] + // ToPosixTimeWithMyRule(nn::time::CalendarTime calendarTime) -> (u32 outCount, buffer) + public ResultCode ToPosixTimeWithMyRule(ServiceCtx context) + { + return _inner.ToPosixTimeWithMyRule(context); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Time/StaticService/ITimeZoneServiceForPsc.cs b/Ryujinx.HLE/HOS/Services/Time/StaticService/ITimeZoneServiceForPsc.cs new file mode 100644 index 0000000000..ed31fe7c53 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Time/StaticService/ITimeZoneServiceForPsc.cs @@ -0,0 +1,294 @@ +using ARMeilleure.Memory; +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Services.Time.Clock; +using Ryujinx.HLE.HOS.Services.Time.TimeZone; +using Ryujinx.HLE.Utilities; +using System; +using System.Diagnostics; +using System.IO; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Time.StaticService +{ + class ITimeZoneServiceForPsc : IpcService + { + private TimeZoneManager _timeZoneManager; + private bool _writePermission; + + public ITimeZoneServiceForPsc(TimeZoneManager timeZoneManager, bool writePermission) + { + _timeZoneManager = timeZoneManager; + _writePermission = writePermission; + } + + [Command(0)] + // GetDeviceLocationName() -> nn::time::LocationName + public ResultCode GetDeviceLocationName(ServiceCtx context) + { + ResultCode result = _timeZoneManager.GetDeviceLocationName(out string deviceLocationName); + + if (result == ResultCode.Success) + { + WriteLocationName(context, deviceLocationName); + } + + return result; + } + + [Command(1)] + // SetDeviceLocationName(nn::time::LocationName) + public ResultCode SetDeviceLocationName(ServiceCtx context) + { + if (!_writePermission) + { + return ResultCode.PermissionDenied; + } + + return ResultCode.NotImplemented; + } + + [Command(2)] + // GetTotalLocationNameCount() -> u32 + public ResultCode GetTotalLocationNameCount(ServiceCtx context) + { + ResultCode result = _timeZoneManager.GetTotalLocationNameCount(out uint totalLocationNameCount); + + if (result == ResultCode.Success) + { + context.ResponseData.Write(totalLocationNameCount); + } + + return ResultCode.Success; + } + + [Command(3)] + // LoadLocationNameList(u32 index) -> (u32 outCount, buffer) + public ResultCode LoadLocationNameList(ServiceCtx context) + { + return ResultCode.NotImplemented; + } + + [Command(4)] + // LoadTimeZoneRule(nn::time::LocationName locationName) -> buffer + public ResultCode LoadTimeZoneRule(ServiceCtx context) + { + return ResultCode.NotImplemented; + } + + [Command(5)] // 2.0.0+ + // GetTimeZoneRuleVersion() -> nn::time::TimeZoneRuleVersion + public ResultCode GetTimeZoneRuleVersion(ServiceCtx context) + { + ResultCode result = _timeZoneManager.GetTimeZoneRuleVersion(out UInt128 timeZoneRuleVersion); + + if (result == ResultCode.Success) + { + context.ResponseData.WriteStruct(timeZoneRuleVersion); + } + + return result; + } + + [Command(6)] // 5.0.0+ + // GetDeviceLocationNameAndUpdatedTime() -> (nn::time::LocationName, nn::time::SteadyClockTimePoint) + public ResultCode GetDeviceLocationNameAndUpdatedTime(ServiceCtx context) + { + ResultCode result = _timeZoneManager.GetDeviceLocationName(out string deviceLocationName); + + if (result == ResultCode.Success) + { + result = _timeZoneManager.GetUpdatedTime(out SteadyClockTimePoint timeZoneUpdateTimePoint); + + if (result == ResultCode.Success) + { + WriteLocationName(context, deviceLocationName); + + // Skip padding + context.ResponseData.BaseStream.Position += 0x4; + + context.ResponseData.WriteStruct(timeZoneUpdateTimePoint); + } + } + + return result; + } + + [Command(7)] // 9.0.0+ + // SetDeviceLocationNameWithTimeZoneRule(nn::time::LocationName locationName, buffer timeZoneBinary) + public ResultCode SetDeviceLocationNameWithTimeZoneRule(ServiceCtx context) + { + if (!_writePermission) + { + return ResultCode.PermissionDenied; + } + + (long bufferPosition, long bufferSize) = context.Request.GetBufferType0x21(); + + string locationName = Encoding.ASCII.GetString(context.RequestData.ReadBytes(0x24)).TrimEnd('\0'); + + ResultCode result; + + using (MemoryStream timeZoneBinaryStream = new MemoryStream(context.Memory.ReadBytes(bufferPosition, bufferSize))) + { + result = _timeZoneManager.SetDeviceLocationNameWithTimeZoneRule(locationName, timeZoneBinaryStream); + } + + return result; + } + + [Command(8)] // 9.0.0+ + // ParseTimeZoneBinary(buffer timeZoneBinary) -> buffer + public ResultCode ParseTimeZoneBinary(ServiceCtx context) + { + (long bufferPosition, long bufferSize) = context.Request.GetBufferType0x21(); + + long timeZoneRuleBufferPosition = context.Request.ReceiveBuff[0].Position; + long timeZoneRuleBufferSize = context.Request.ReceiveBuff[0].Size; + + if (timeZoneRuleBufferSize != 0x4000) + { + // TODO: find error code here + Logger.PrintError(LogClass.ServiceTime, $"TimeZoneRule buffer size is 0x{timeZoneRuleBufferSize:x} (expected 0x4000)"); + + throw new InvalidOperationException(); + } + + ResultCode result; + + using (MemoryStream timeZoneBinaryStream = new MemoryStream(context.Memory.ReadBytes(bufferPosition, bufferSize))) + { + result = _timeZoneManager.ParseTimeZoneRuleBinary(out TimeZoneRule timeZoneRule, timeZoneBinaryStream); + + if (result == ResultCode.Success) + { + MemoryHelper.Write(context.Memory, timeZoneRuleBufferPosition, timeZoneRule); + } + } + + return result; + } + + [Command(20)] // 9.0.0+ + // GetDeviceLocationNameOperationEventReadableHandle() -> handle + public ResultCode GetDeviceLocationNameOperationEventReadableHandle(ServiceCtx context) + { + return ResultCode.NotImplemented; + } + + [Command(100)] + // ToCalendarTime(nn::time::PosixTime time, buffer rules) -> (nn::time::CalendarTime, nn::time::sf::CalendarAdditionalInfo) + public ResultCode ToCalendarTime(ServiceCtx context) + { + long posixTime = context.RequestData.ReadInt64(); + long bufferPosition = context.Request.SendBuff[0].Position; + long bufferSize = context.Request.SendBuff[0].Size; + + if (bufferSize != 0x4000) + { + // TODO: find error code here + Logger.PrintError(LogClass.ServiceTime, $"TimeZoneRule buffer size is 0x{bufferSize:x} (expected 0x4000)"); + + throw new InvalidOperationException(); + } + + TimeZoneRule rules = MemoryHelper.Read(context.Memory, bufferPosition); + + ResultCode resultCode = _timeZoneManager.ToCalendarTime(rules, posixTime, out CalendarInfo calendar); + + if (resultCode == 0) + { + context.ResponseData.WriteStruct(calendar); + } + + return resultCode; + } + + [Command(101)] + // ToCalendarTimeWithMyRule(nn::time::PosixTime) -> (nn::time::CalendarTime, nn::time::sf::CalendarAdditionalInfo) + public ResultCode ToCalendarTimeWithMyRule(ServiceCtx context) + { + long posixTime = context.RequestData.ReadInt64(); + + ResultCode resultCode = _timeZoneManager.ToCalendarTimeWithMyRules(posixTime, out CalendarInfo calendar); + + if (resultCode == 0) + { + context.ResponseData.WriteStruct(calendar); + } + + return resultCode; + } + + [Command(201)] + // ToPosixTime(nn::time::CalendarTime calendarTime, buffer rules) -> (u32 outCount, buffer) + public ResultCode ToPosixTime(ServiceCtx context) + { + long inBufferPosition = context.Request.SendBuff[0].Position; + long inBufferSize = context.Request.SendBuff[0].Size; + + CalendarTime calendarTime = context.RequestData.ReadStruct(); + + if (inBufferSize != 0x4000) + { + // TODO: find error code here + Logger.PrintError(LogClass.ServiceTime, $"TimeZoneRule buffer size is 0x{inBufferSize:x} (expected 0x4000)"); + + throw new InvalidOperationException(); + } + + TimeZoneRule rules = MemoryHelper.Read(context.Memory, inBufferPosition); + + ResultCode resultCode = _timeZoneManager.ToPosixTime(rules, calendarTime, out long posixTime); + + if (resultCode == 0) + { + long outBufferPosition = context.Request.RecvListBuff[0].Position; + long outBufferSize = context.Request.RecvListBuff[0].Size; + + context.Memory.WriteInt64(outBufferPosition, posixTime); + context.ResponseData.Write(1); + } + + return resultCode; + } + + [Command(202)] + // ToPosixTimeWithMyRule(nn::time::CalendarTime calendarTime) -> (u32 outCount, buffer) + public ResultCode ToPosixTimeWithMyRule(ServiceCtx context) + { + CalendarTime calendarTime = context.RequestData.ReadStruct(); + + ResultCode resultCode = _timeZoneManager.ToPosixTimeWithMyRules(calendarTime, out long posixTime); + + if (resultCode == 0) + { + long outBufferPosition = context.Request.RecvListBuff[0].Position; + long outBufferSize = context.Request.RecvListBuff[0].Size; + + context.Memory.WriteInt64(outBufferPosition, posixTime); + + // There could be only one result on one calendar as leap seconds aren't supported. + context.ResponseData.Write(1); + } + + return resultCode; + } + + private void WriteLocationName(ServiceCtx context, string locationName) + { + char[] locationNameArray = locationName.ToCharArray(); + + int padding = 0x24 - locationNameArray.Length; + + Debug.Assert(padding >= 0, "LocationName exceeded limit (0x24 bytes)"); + + context.ResponseData.Write(locationNameArray); + + for (int index = 0; index < padding; index++) + { + context.ResponseData.Write((byte)0); + } + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Time/TimeManager.cs b/Ryujinx.HLE/HOS/Services/Time/TimeManager.cs new file mode 100644 index 0000000000..03ed6fbab5 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Time/TimeManager.cs @@ -0,0 +1,184 @@ +using System; +using System.IO; +using Ryujinx.HLE.Exceptions; +using Ryujinx.HLE.HOS.Kernel.Memory; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Time.Clock; +using Ryujinx.HLE.HOS.Services.Time.TimeZone; +using Ryujinx.HLE.Utilities; + +namespace Ryujinx.HLE.HOS.Services.Time +{ + class TimeManager + { + private static TimeManager _instance; + + public static TimeManager Instance + { + get + { + if (_instance == null) + { + _instance = new TimeManager(); + } + + return _instance; + } + } + + public StandardSteadyClockCore StandardSteadyClock { get; } + public TickBasedSteadyClockCore TickBasedSteadyClock { get; } + public StandardLocalSystemClockCore StandardLocalSystemClock { get; } + public StandardNetworkSystemClockCore StandardNetworkSystemClock { get; } + public StandardUserSystemClockCore StandardUserSystemClock { get; } + public TimeZoneContentManager TimeZone { get; } + public EphemeralNetworkSystemClockCore EphemeralNetworkSystemClock { get; } + public TimeSharedMemory SharedMemory { get; } + public LocalSystemClockContextWriter LocalClockContextWriter { get; } + public NetworkSystemClockContextWriter NetworkClockContextWriter { get; } + public EphemeralNetworkSystemClockContextWriter EphemeralClockContextWriter { get; } + + // TODO: 9.0.0+ power states and alarms + + public TimeManager() + { + StandardSteadyClock = new StandardSteadyClockCore(); + TickBasedSteadyClock = new TickBasedSteadyClockCore(); + StandardLocalSystemClock = new StandardLocalSystemClockCore(StandardSteadyClock); + StandardNetworkSystemClock = new StandardNetworkSystemClockCore(StandardSteadyClock); + StandardUserSystemClock = new StandardUserSystemClockCore(StandardLocalSystemClock, StandardNetworkSystemClock); + TimeZone = new TimeZoneContentManager(); + EphemeralNetworkSystemClock = new EphemeralNetworkSystemClockCore(TickBasedSteadyClock); + SharedMemory = new TimeSharedMemory(); + LocalClockContextWriter = new LocalSystemClockContextWriter(SharedMemory); + NetworkClockContextWriter = new NetworkSystemClockContextWriter(SharedMemory); + EphemeralClockContextWriter = new EphemeralNetworkSystemClockContextWriter(); + } + + public void Initialize(Switch device, Horizon system, KSharedMemory sharedMemory, long timeSharedMemoryAddress, int timeSharedMemorySize) + { + SharedMemory.Initialize(device, sharedMemory, timeSharedMemoryAddress, timeSharedMemorySize); + + // Here we use system on purpose as device. System isn't initialized at this point. + StandardUserSystemClock.CreateAutomaticCorrectionEvent(system); + } + + public void InitializeTimeZone(Switch device) + { + TimeZone.Initialize(this, device); + } + + + public void SetupStandardSteadyClock(KThread thread, UInt128 clockSourceId, TimeSpanType setupValue, TimeSpanType internalOffset, TimeSpanType testOffset, bool isRtcResetDetected) + { + SetupInternalStandardSteadyClock(clockSourceId, setupValue, internalOffset, testOffset, isRtcResetDetected); + + TimeSpanType currentTimePoint = StandardSteadyClock.GetCurrentRawTimePoint(thread); + + SharedMemory.SetupStandardSteadyClock(thread, clockSourceId, currentTimePoint); + + // TODO: propagate IPC late binding of "time:s" and "time:p" + } + + private void SetupInternalStandardSteadyClock(UInt128 clockSourceId, TimeSpanType setupValue, TimeSpanType internalOffset, TimeSpanType testOffset, bool isRtcResetDetected) + { + StandardSteadyClock.SetClockSourceId(clockSourceId); + StandardSteadyClock.SetSetupValue(setupValue); + StandardSteadyClock.SetInternalOffset(internalOffset); + StandardSteadyClock.SetTestOffset(testOffset); + + if (isRtcResetDetected) + { + StandardSteadyClock.SetRtcReset(); + } + + StandardSteadyClock.MarkInitialized(); + + // TODO: propagate IPC late binding of "time:s" and "time:p" + } + + public void SetupStandardLocalSystemClock(KThread thread, SystemClockContext clockContext, long posixTime) + { + StandardLocalSystemClock.SetUpdateCallbackInstance(LocalClockContextWriter); + + SteadyClockTimePoint currentTimePoint = StandardLocalSystemClock.GetSteadyClockCore().GetCurrentTimePoint(thread); + if (currentTimePoint.ClockSourceId == clockContext.SteadyTimePoint.ClockSourceId) + { + StandardLocalSystemClock.SetSystemClockContext(clockContext); + } + else + { + if (StandardLocalSystemClock.SetCurrentTime(thread, posixTime) != ResultCode.Success) + { + throw new InternalServiceException("Cannot set current local time"); + } + } + + StandardLocalSystemClock.MarkInitialized(); + + // TODO: propagate IPC late binding of "time:s" and "time:p" + } + + public void SetupStandardNetworkSystemClock(SystemClockContext clockContext, TimeSpanType sufficientAccuracy) + { + StandardNetworkSystemClock.SetUpdateCallbackInstance(NetworkClockContextWriter); + + if (StandardNetworkSystemClock.SetSystemClockContext(clockContext) != ResultCode.Success) + { + throw new InternalServiceException("Cannot set network SystemClockContext"); + } + + StandardNetworkSystemClock.SetStandardNetworkClockSufficientAccuracy(sufficientAccuracy); + StandardNetworkSystemClock.MarkInitialized(); + + // TODO: propagate IPC late binding of "time:s" and "time:p" + } + + public void SetupTimeZoneManager(string locationName, SteadyClockTimePoint timeZoneUpdatedTimePoint, uint totalLocationNameCount, UInt128 timeZoneRuleVersion, Stream timeZoneBinaryStream) + { + if (TimeZone.Manager.SetDeviceLocationNameWithTimeZoneRule(locationName, timeZoneBinaryStream) != ResultCode.Success) + { + throw new InternalServiceException("Cannot set DeviceLocationName with a given TimeZoneBinary"); + } + + TimeZone.Manager.SetUpdatedTime(timeZoneUpdatedTimePoint, true); + TimeZone.Manager.SetTotalLocationNameCount(totalLocationNameCount); + TimeZone.Manager.SetTimeZoneRuleVersion(timeZoneRuleVersion); + TimeZone.Manager.MarkInitialized(); + + // TODO: propagate IPC late binding of "time:s" and "time:p" + } + + public void SetupEphemeralNetworkSystemClock() + { + EphemeralNetworkSystemClock.SetUpdateCallbackInstance(EphemeralClockContextWriter); + EphemeralNetworkSystemClock.MarkInitialized(); + + // TODO: propagate IPC late binding of "time:s" and "time:p" + } + + public void SetupStandardUserSystemClock(KThread thread, bool isAutomaticCorrectionEnabled, SteadyClockTimePoint steadyClockTimePoint) + { + if (StandardUserSystemClock.SetAutomaticCorrectionEnabled(thread, isAutomaticCorrectionEnabled) != ResultCode.Success) + { + throw new InternalServiceException("Cannot set automatic user time correction state"); + } + + StandardUserSystemClock.SetAutomaticCorrectionUpdatedTime(steadyClockTimePoint); + StandardUserSystemClock.MarkInitialized(); + + SharedMemory.SetAutomaticCorrectionEnabled(isAutomaticCorrectionEnabled); + + // TODO: propagate IPC late binding of "time:s" and "time:p" + } + + public void SetStandardSteadyClockRtcOffset(KThread thread, TimeSpanType rtcOffset) + { + StandardSteadyClock.SetSetupValue(rtcOffset); + + TimeSpanType currentTimePoint = StandardSteadyClock.GetCurrentRawTimePoint(thread); + + SharedMemory.SetSteadyClockRawTimePoint(thread, currentTimePoint); + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Time/TimeSharedMemory.cs b/Ryujinx.HLE/HOS/Services/Time/TimeSharedMemory.cs new file mode 100644 index 0000000000..f714c66230 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Time/TimeSharedMemory.cs @@ -0,0 +1,126 @@ +using System; +using System.Runtime.InteropServices; +using System.Threading; +using Ryujinx.HLE.HOS.Kernel.Memory; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Time.Clock; +using Ryujinx.HLE.HOS.Services.Time.Types; +using Ryujinx.HLE.Utilities; + +namespace Ryujinx.HLE.HOS.Services.Time +{ + class TimeSharedMemory + { + private Switch _device; + private KSharedMemory _sharedMemory; + private long _timeSharedMemoryAddress; + private int _timeSharedMemorySize; + + private const uint SteadyClockContextOffset = 0x00; + private const uint LocalSystemClockContextOffset = 0x38; + private const uint NetworkSystemClockContextOffset = 0x80; + private const uint AutomaticCorrectionEnabledOffset = 0xC8; + + public void Initialize(Switch device, KSharedMemory sharedMemory, long timeSharedMemoryAddress, int timeSharedMemorySize) + { + _device = device; + _sharedMemory = sharedMemory; + _timeSharedMemoryAddress = timeSharedMemoryAddress; + _timeSharedMemorySize = timeSharedMemorySize; + + // Clean the shared memory + _device.Memory.FillWithZeros(_timeSharedMemoryAddress, _timeSharedMemorySize); + } + + public KSharedMemory GetSharedMemory() + { + return _sharedMemory; + } + + public void SetupStandardSteadyClock(KThread thread, UInt128 clockSourceId, TimeSpanType currentTimePoint) + { + TimeSpanType ticksTimeSpan; + + // As this may be called before the guest code, we support passing a null thread to make this api usable. + if (thread == null) + { + ticksTimeSpan = TimeSpanType.FromSeconds(0); + } + else + { + ticksTimeSpan = TimeSpanType.FromTicks(thread.Context.CntpctEl0, thread.Context.CntfrqEl0); + } + + SteadyClockContext context = new SteadyClockContext + { + InternalOffset = (ulong)(currentTimePoint.NanoSeconds - ticksTimeSpan.NanoSeconds), + ClockSourceId = clockSourceId + }; + + WriteObjectToSharedMemory(SteadyClockContextOffset, 4, context); + } + + public void SetAutomaticCorrectionEnabled(bool isAutomaticCorrectionEnabled) + { + // We convert the bool to byte here as a bool in C# takes 4 bytes... + WriteObjectToSharedMemory(AutomaticCorrectionEnabledOffset, 0, Convert.ToByte(isAutomaticCorrectionEnabled)); + } + + public void SetSteadyClockRawTimePoint(KThread thread, TimeSpanType currentTimePoint) + { + SteadyClockContext context = ReadObjectFromSharedMemory(SteadyClockContextOffset, 4); + TimeSpanType ticksTimeSpan = TimeSpanType.FromTicks(thread.Context.CntpctEl0, thread.Context.CntfrqEl0); + + context.InternalOffset = (ulong)(currentTimePoint.NanoSeconds - ticksTimeSpan.NanoSeconds); + + WriteObjectToSharedMemory(SteadyClockContextOffset, 4, context); + } + + public void UpdateLocalSystemClockContext(SystemClockContext context) + { + WriteObjectToSharedMemory(LocalSystemClockContextOffset, 4, context); + } + + public void UpdateNetworkSystemClockContext(SystemClockContext context) + { + WriteObjectToSharedMemory(NetworkSystemClockContextOffset, 4, context); + } + + private T ReadObjectFromSharedMemory(long offset, long padding) + { + long indexOffset = _timeSharedMemoryAddress + offset; + + T result; + uint index; + uint possiblyNewIndex; + + do + { + index = _device.Memory.ReadUInt32(indexOffset); + + long objectOffset = indexOffset + 4 + padding + (index & 1) * Marshal.SizeOf(); + + result = _device.Memory.ReadStruct(objectOffset); + + Thread.MemoryBarrier(); + + possiblyNewIndex = _device.Memory.ReadUInt32(indexOffset); + } while (index != possiblyNewIndex); + + return result; + } + + private void WriteObjectToSharedMemory(long offset, long padding, T value) + { + long indexOffset = _timeSharedMemoryAddress + offset; + uint newIndex = _device.Memory.ReadUInt32(indexOffset) + 1; + long objectOffset = indexOffset + 4 + padding + (newIndex & 1) * Marshal.SizeOf(); + + _device.Memory.WriteStruct(objectOffset, value); + + Thread.MemoryBarrier(); + + _device.Memory.WriteUInt32(indexOffset, newIndex); + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZone.cs b/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZone.cs new file mode 100644 index 0000000000..496c678680 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZone.cs @@ -0,0 +1,1739 @@ +using Ryujinx.Common; +using Ryujinx.HLE.Utilities; +using System; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; + +using static Ryujinx.HLE.HOS.Services.Time.TimeZone.TimeZoneRule; + +namespace Ryujinx.HLE.HOS.Services.Time.TimeZone +{ + public class TimeZone + { + private const int TimeTypeSize = 8; + private const int EpochYear = 1970; + private const int YearBase = 1900; + private const int EpochWeekDay = 4; + private const int SecondsPerMinute = 60; + private const int MinutesPerHour = 60; + private const int HoursPerDays = 24; + private const int DaysPerWekk = 7; + private const int DaysPerNYear = 365; + private const int DaysPerLYear = 366; + private const int MonthsPerYear = 12; + private const int SecondsPerHour = SecondsPerMinute * MinutesPerHour; + private const int SecondsPerDay = SecondsPerHour * HoursPerDays; + + private const int YearsPerRepeat = 400; + private const long AverageSecondsPerYear = 31556952; + private const long SecondsPerRepeat = YearsPerRepeat * AverageSecondsPerYear; + + private static readonly int[] YearLengths = { DaysPerNYear, DaysPerLYear }; + private static readonly int[][] MonthsLengths = new int[][] + { + new int[] { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }, + new int[] { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } + }; + + private const string TimeZoneDefaultRule = ",M4.1.0,M10.5.0"; + + [StructLayout(LayoutKind.Sequential, Pack = 0x4, Size = 0x10)] + private struct CalendarTimeInternal + { + // NOTE: On the IPC side this is supposed to be a 16 bits value but internally this need to be a 64 bits value for ToPosixTime. + public long Year; + public sbyte Month; + public sbyte Day; + public sbyte Hour; + public sbyte Minute; + public sbyte Second; + + public int CompareTo(CalendarTimeInternal other) + { + if (Year != other.Year) + { + if (Year < other.Year) + { + return -1; + } + + return 1; + } + + if (Month != other.Month) + { + return Month - other.Month; + } + + if (Day != other.Day) + { + return Day - other.Day; + } + + if (Hour != other.Hour) + { + return Hour - other.Hour; + } + + if (Minute != other.Minute) + { + return Minute - other.Minute; + } + + if (Second != other.Second) + { + return Second - other.Second; + } + + return 0; + } + } + + private enum RuleType + { + JulianDay, + DayOfYear, + MonthNthDayOfWeek + } + + private struct Rule + { + public RuleType Type; + public int Day; + public int Week; + public int Month; + public int TransitionTime; + } + + private static int Detzcode32(byte[] bytes) + { + if (BitConverter.IsLittleEndian) + { + Array.Reverse(bytes, 0, bytes.Length); + } + + return BitConverter.ToInt32(bytes, 0); + } + + private static unsafe int Detzcode32(int* data) + { + int result = *data; + if (BitConverter.IsLittleEndian) + { + byte[] bytes = BitConverter.GetBytes(result); + Array.Reverse(bytes, 0, bytes.Length); + result = BitConverter.ToInt32(bytes, 0); + } + + return result; + } + + private static unsafe long Detzcode64(long* data) + { + long result = *data; + if (BitConverter.IsLittleEndian) + { + byte[] bytes = BitConverter.GetBytes(result); + Array.Reverse(bytes, 0, bytes.Length); + result = BitConverter.ToInt64(bytes, 0); + } + + return result; + } + + private static bool DifferByRepeat(long t1, long t0) + { + return (t1 - t0) == SecondsPerRepeat; + } + + private static unsafe bool TimeTypeEquals(TimeZoneRule outRules, byte aIndex, byte bIndex) + { + if (aIndex < 0 || aIndex >= outRules.TypeCount || bIndex < 0 || bIndex >= outRules.TypeCount) + { + return false; + } + + TimeTypeInfo a = outRules.Ttis[aIndex]; + TimeTypeInfo b = outRules.Ttis[bIndex]; + + fixed (char* chars = outRules.Chars) + { + return a.GmtOffset == b.GmtOffset && + a.IsDaySavingTime == b.IsDaySavingTime && + a.IsStandardTimeDaylight == b.IsStandardTimeDaylight && + a.IsGMT == b.IsGMT && + StringUtils.CompareCStr(chars + a.AbbreviationListIndex, chars + b.AbbreviationListIndex) == 0; + } + } + + private static int GetQZName(char[] name, int namePosition, char delimiter) + { + int i = namePosition; + + while (name[i] != '\0' && name[i] != delimiter) + { + i++; + } + + return i; + } + + private static int GetTZName(char[] name, int namePosition) + { + int i = namePosition; + + char c = name[i]; + + while (c != '\0' && !char.IsDigit(c) && c != ',' && c != '-' && c != '+') + { + c = name[i]; + i++; + } + + return i; + } + + private static bool GetNum(char[] name, ref int namePosition, out int num, int min, int max) + { + num = 0; + + if (namePosition >= name.Length) + { + return false; + } + + char c = name[namePosition]; + + if (!char.IsDigit(c)) + { + return false; + } + + do + { + num = num * 10 + (c - '0'); + if (num > max) + { + return false; + } + + if (++namePosition >= name.Length) + { + return false; + } + + c = name[namePosition]; + } + while (char.IsDigit(c)); + + if (num < min) + { + return false; + } + + return true; + } + + private static bool GetSeconds(char[] name, ref int namePosition, out int seconds) + { + seconds = 0; + + + bool isValid = GetNum(name, ref namePosition, out int num, 0, HoursPerDays * DaysPerWekk - 1); + if (!isValid) + { + return false; + } + + seconds = num * SecondsPerHour; + + if (namePosition >= name.Length) + { + return false; + } + + if (name[namePosition] == ':') + { + namePosition++; + isValid = GetNum(name, ref namePosition, out num, 0, MinutesPerHour - 1); + if (!isValid) + { + return false; + } + + seconds += num * SecondsPerMinute; + + if (namePosition >= name.Length) + { + return false; + } + + if (name[namePosition] == ':') + { + namePosition++; + isValid = GetNum(name, ref namePosition, out num, 0, SecondsPerMinute); + if (!isValid) + { + return false; + } + + seconds += num; + } + } + return true; + } + + private static bool GetOffset(char[] name, ref int namePosition, ref int offset) + { + bool isNegative = false; + + if (namePosition >= name.Length) + { + return false; + } + + if (name[namePosition] == '-') + { + isNegative = true; + namePosition++; + } + else if (name[namePosition] == '+') + { + namePosition++; + } + + if (namePosition >= name.Length) + { + return false; + } + + bool isValid = GetSeconds(name, ref namePosition, out offset); + if (!isValid) + { + return false; + } + + if (isNegative) + { + offset = -offset; + } + + return true; + } + + private static bool GetRule(char[] name, ref int namePosition, out Rule rule) + { + rule = new Rule(); + + bool isValid = false; + + if (name[namePosition] == 'J') + { + namePosition++; + + rule.Type = RuleType.JulianDay; + isValid = GetNum(name, ref namePosition, out rule.Day, 1, DaysPerNYear); + } + else if (name[namePosition] == 'M') + { + namePosition++; + + rule.Type = RuleType.MonthNthDayOfWeek; + isValid = GetNum(name, ref namePosition, out rule.Month, 1, MonthsPerYear); + + if (!isValid) + { + return false; + } + + if (name[namePosition++] != '.') + { + return false; + } + + isValid = GetNum(name, ref namePosition, out rule.Week, 1, 5); + if (!isValid) + { + return false; + } + + if (name[namePosition++] != '.') + { + return false; + } + + isValid = GetNum(name, ref namePosition, out rule.Day, 0, DaysPerWekk - 1); + } + else if (char.IsDigit(name[namePosition])) + { + rule.Type = RuleType.DayOfYear; + isValid = GetNum(name, ref namePosition, out rule.Day, 0, DaysPerLYear - 1); + } + else + { + return false; + } + + if (!isValid) + { + return false; + } + + if (name[namePosition] == '/') + { + namePosition++; + return GetOffset(name, ref namePosition, ref rule.TransitionTime); + } + else + { + rule.TransitionTime = 2 * SecondsPerHour; + } + + return true; + } + + private static int IsLeap(int year) + { + if (((year) % 4) == 0 && (((year) % 100) != 0 || ((year) % 400) == 0)) + { + return 1; + } + + return 0; + } + + private static bool ParsePosixName(Span name, out TimeZoneRule outRules, bool lastDitch) + { + outRules = new TimeZoneRule + { + Ats = new long[TzMaxTimes], + Types = new byte[TzMaxTimes], + Ttis = new TimeTypeInfo[TzMaxTypes], + Chars = new char[TzCharsArraySize] + }; + + int stdLen; + Span stdName = name; + int namePosition = 0; + int stdOffset = 0; + + if (lastDitch) + { + stdLen = 3; + namePosition += stdLen; + } + else + { + if (name[namePosition] == '<') + { + namePosition++; + + stdName = name.Slice(namePosition); + + int stdNamePosition = namePosition; + + namePosition = GetQZName(name.ToArray(), namePosition, '>'); + if (name[namePosition] != '>') + { + return false; + } + + stdLen = namePosition - stdNamePosition; + namePosition++; + } + else + { + namePosition = GetTZName(name.ToArray(), namePosition); + stdLen = namePosition; + } + + if (stdLen == 0) + { + return false; + } + + bool isValid = GetOffset(name.ToArray(), ref namePosition, ref stdOffset); + + if (!isValid) + { + return false; + } + } + + int charCount = stdLen + 1; + int destLen = 0; + int dstOffset = 0; + + Span destName = name.Slice(namePosition); + + if (TzCharsArraySize < charCount) + { + return false; + } + + if (name[namePosition] != '\0') + { + if (name[namePosition] == '<') + { + destName = name.Slice(++namePosition); + int destNamePosition = namePosition; + + namePosition = GetQZName(name.ToArray(), namePosition, '>'); + + if (name[namePosition] != '>') + { + return false; + } + + destLen = namePosition - destNamePosition; + namePosition++; + } + else + { + destName = name.Slice(namePosition); + namePosition = GetTZName(name.ToArray(), namePosition); + destLen = namePosition; + } + + if (destLen == 0) + { + return false; + } + + charCount += destLen + 1; + if (TzCharsArraySize < charCount) + { + return false; + } + + if (name[namePosition] != '\0' && name[namePosition] != ',' && name[namePosition] != ';') + { + bool isValid = GetOffset(name.ToArray(), ref namePosition, ref dstOffset); + + if (!isValid) + { + return false; + } + } + else + { + dstOffset = stdOffset - SecondsPerHour; + } + + if (name[namePosition] == '\0') + { + name = TimeZoneDefaultRule.ToCharArray(); + namePosition = 0; + } + + if (name[namePosition] == ',' || name[namePosition] == ';') + { + namePosition++; + + bool IsRuleValid = GetRule(name.ToArray(), ref namePosition, out Rule start); + if (!IsRuleValid) + { + return false; + } + + if (name[namePosition++] != ',') + { + return false; + } + + IsRuleValid = GetRule(name.ToArray(), ref namePosition, out Rule end); + if (!IsRuleValid) + { + return false; + } + + if (name[namePosition] != '\0') + { + return false; + } + + outRules.TypeCount = 2; + + outRules.Ttis[0] = new TimeTypeInfo + { + GmtOffset = -dstOffset, + IsDaySavingTime = true, + AbbreviationListIndex = stdLen + 1 + }; + + outRules.Ttis[1] = new TimeTypeInfo + { + GmtOffset = -stdOffset, + IsDaySavingTime = false, + AbbreviationListIndex = 0 + }; + + outRules.DefaultType = 0; + + int timeCount = 0; + long janFirst = 0; + int janOffset = 0; + int yearBegining = EpochYear; + + do + { + int yearSeconds = YearLengths[IsLeap(yearBegining - 1)] * SecondsPerDay; + yearBegining--; + if (IncrementOverflow64(ref janFirst, -yearSeconds)) + { + janOffset = -yearSeconds; + break; + } + } + while (EpochYear - YearsPerRepeat / 2 < yearBegining); + + int yearLimit = yearBegining + YearsPerRepeat + 1; + int year; + for (year = yearBegining; year < yearLimit; year++) + { + int startTime = TransitionTime(year, start, stdOffset); + int endTime = TransitionTime(year, end, dstOffset); + + int yearSeconds = YearLengths[IsLeap(year)] * SecondsPerDay; + + bool isReversed = endTime < startTime; + if (isReversed) + { + int swap = startTime; + + startTime = endTime; + endTime = swap; + } + + if (isReversed || (startTime < endTime && (endTime - startTime < (yearSeconds + (stdOffset - dstOffset))))) + { + if (TzMaxTimes - 2 < timeCount) + { + break; + } + + outRules.Ats[timeCount] = janFirst; + if (!IncrementOverflow64(ref outRules.Ats[timeCount], janOffset + startTime)) + { + outRules.Types[timeCount++] = isReversed ? (byte)1 : (byte)0; + } + else if (janOffset != 0) + { + outRules.DefaultType = isReversed ? 1 : 0; + } + + outRules.Ats[timeCount] = janFirst; + if (!IncrementOverflow64(ref outRules.Ats[timeCount], janOffset + endTime)) + { + outRules.Types[timeCount++] = isReversed ? (byte)0 : (byte)1; + yearLimit = year + YearsPerRepeat + 1; + } + else if (janOffset != 0) + { + outRules.DefaultType = isReversed ? 0 : 1; + } + } + + if (IncrementOverflow64(ref janFirst, janOffset + yearSeconds)) + { + break; + } + + janOffset = 0; + } + + outRules.TimeCount = timeCount; + + // There is no time variation, this is then a perpetual DST rule + if (timeCount == 0) + { + outRules.TypeCount = 1; + } + else if (YearsPerRepeat < year - yearBegining) + { + outRules.GoBack = true; + outRules.GoAhead = true; + } + } + else + { + if (name[namePosition] == '\0') + { + return false; + } + + long theirStdOffset = 0; + for (int i = 0; i < outRules.TimeCount; i++) + { + int j = outRules.Types[i]; + if (outRules.Ttis[j].IsStandardTimeDaylight) + { + theirStdOffset = -outRules.Ttis[j].GmtOffset; + } + } + + long theirDstOffset = 0; + for (int i = 0; i < outRules.TimeCount; i++) + { + int j = outRules.Types[i]; + if (outRules.Ttis[j].IsDaySavingTime) + { + theirDstOffset = -outRules.Ttis[j].GmtOffset; + } + } + + bool isDaySavingTime = false; + long theirOffset = theirStdOffset; + for (int i = 0; i < outRules.TimeCount; i++) + { + int j = outRules.Types[i]; + outRules.Types[i] = outRules.Ttis[j].IsDaySavingTime ? (byte)1 : (byte)0; + if (!outRules.Ttis[j].IsGMT) + { + if (isDaySavingTime && !outRules.Ttis[j].IsStandardTimeDaylight) + { + outRules.Ats[i] += dstOffset - theirStdOffset; + } + else + { + outRules.Ats[i] += stdOffset - theirStdOffset; + } + } + + theirOffset = -outRules.Ttis[j].GmtOffset; + if (outRules.Ttis[j].IsDaySavingTime) + { + theirDstOffset = theirOffset; + } + else + { + theirStdOffset = theirOffset; + } + } + + outRules.Ttis[0] = new TimeTypeInfo + { + GmtOffset = -stdOffset, + IsDaySavingTime = false, + AbbreviationListIndex = 0 + }; + + outRules.Ttis[1] = new TimeTypeInfo + { + GmtOffset = -dstOffset, + IsDaySavingTime = true, + AbbreviationListIndex = stdLen + 1 + }; + + outRules.TypeCount = 2; + outRules.DefaultType = 0; + } + } + else + { + // default is perpetual standard time + outRules.TypeCount = 1; + outRules.TimeCount = 0; + outRules.DefaultType = 0; + outRules.Ttis[0] = new TimeTypeInfo + { + GmtOffset = -stdOffset, + IsDaySavingTime = false, + AbbreviationListIndex = 0 + }; + } + + outRules.CharCount = charCount; + + int charsPosition = 0; + + for (int i = 0; i < stdLen; i++) + { + outRules.Chars[i] = stdName[i]; + } + + charsPosition += stdLen; + outRules.Chars[charsPosition++] = '\0'; + + if (destLen != 0) + { + for (int i = 0; i < destLen; i++) + { + outRules.Chars[charsPosition + i] = destName[i]; + } + outRules.Chars[charsPosition + destLen] = '\0'; + } + + return true; + } + + private static int TransitionTime(int year, Rule rule, int offset) + { + int leapYear = IsLeap(year); + + int value; + switch (rule.Type) + { + case RuleType.JulianDay: + value = (rule.Day - 1) * SecondsPerDay; + if (leapYear == 1 && rule.Day >= 60) + { + value += SecondsPerDay; + } + break; + + case RuleType.DayOfYear: + value = rule.Day * SecondsPerDay; + break; + + case RuleType.MonthNthDayOfWeek: + // Here we use Zeller's Congruence to get the day of week of the first month. + + int m1 = (rule.Month + 9) % 12 + 1; + int yy0 = (rule.Month <= 2) ? (year - 1) : year; + int yy1 = yy0 / 100; + int yy2 = yy0 % 100; + + int dayOfWeek = ((26 * m1 - 2) / 10 + 1 + yy2 + yy2 / 4 + yy1 / 4 - 2 * yy1) % 7; + + if (dayOfWeek < 0) + { + dayOfWeek += DaysPerWekk; + } + + // Get the zero origin + int d = rule.Day - dayOfWeek; + + if (d < 0) + { + d += DaysPerWekk; + } + + for (int i = 1; i < rule.Week; i++) + { + if (d + DaysPerWekk >= MonthsLengths[leapYear][rule.Month - 1]) + { + break; + } + + d += DaysPerWekk; + } + + value = d * SecondsPerDay; + for (int i = 0; i < rule.Month - 1; i++) + { + value += MonthsLengths[leapYear][i] * SecondsPerDay; + } + + break; + default: + throw new NotImplementedException("Unknown time transition!"); + } + + return value + rule.TransitionTime + offset; + } + + private static bool NormalizeOverflow32(ref int ip, ref int unit, int baseValue) + { + int delta; + + if (unit >= 0) + { + delta = unit / baseValue; + } + else + { + delta = -1 - (-1 - unit) / baseValue; + } + + unit -= delta * baseValue; + + return IncrementOverflow32(ref ip, delta); + } + + private static bool NormalizeOverflow64(ref long ip, ref long unit, long baseValue) + { + long delta; + + if (unit >= 0) + { + delta = unit / baseValue; + } + else + { + delta = -1 - (-1 - unit) / baseValue; + } + + unit -= delta * baseValue; + + return IncrementOverflow64(ref ip, delta); + } + + private static bool IncrementOverflow32(ref int time, int j) + { + try + { + time = checked(time + j); + + return false; + } + catch (OverflowException) + { + return true; + } + } + + private static bool IncrementOverflow64(ref long time, long j) + { + try + { + time = checked(time + j); + + return false; + } + catch (OverflowException) + { + return true; + } + } + + internal static bool ParsePosixName(string name, out TimeZoneRule outRules) + { + return ParsePosixName(name.ToCharArray(), out outRules, false); + } + + internal static unsafe bool ParseTimeZoneBinary(out TimeZoneRule outRules, Stream inputData) + { + outRules = new TimeZoneRule + { + Ats = new long[TzMaxTimes], + Types = new byte[TzMaxTimes], + Ttis = new TimeTypeInfo[TzMaxTypes], + Chars = new char[TzCharsArraySize] + }; + + BinaryReader reader = new BinaryReader(inputData); + + long streamLength = reader.BaseStream.Length; + + if (streamLength < Marshal.SizeOf()) + { + return false; + } + + TzifHeader header = reader.ReadStruct(); + + streamLength -= Marshal.SizeOf(); + + int ttisGMTCount = Detzcode32(header.TtisGMTCount); + int ttisSTDCount = Detzcode32(header.TtisSTDCount); + int leapCount = Detzcode32(header.LeapCount); + int timeCount = Detzcode32(header.TimeCount); + int typeCount = Detzcode32(header.TypeCount); + int charCount = Detzcode32(header.CharCount); + + if (!(0 <= leapCount + && leapCount < TzMaxLeaps + && 0 < typeCount + && typeCount < TzMaxTypes + && 0 <= timeCount + && timeCount < TzMaxTimes + && 0 <= charCount + && charCount < TzMaxChars + && (ttisSTDCount == typeCount || ttisSTDCount == 0) + && (ttisGMTCount == typeCount || ttisGMTCount == 0))) + { + return false; + } + + + if (streamLength < (timeCount * TimeTypeSize + + timeCount + + typeCount * 6 + + charCount + + leapCount * (TimeTypeSize + 4) + + ttisSTDCount + + ttisGMTCount)) + { + return false; + } + + outRules.TimeCount = timeCount; + outRules.TypeCount = typeCount; + outRules.CharCount = charCount; + + byte[] workBuffer = StreamUtils.StreamToBytes(inputData); + + timeCount = 0; + + fixed (byte* workBufferPtrStart = workBuffer) + { + byte* p = workBufferPtrStart; + for (int i = 0; i < outRules.TimeCount; i++) + { + long at = Detzcode64((long*)p); + outRules.Types[i] = 1; + + if (timeCount != 0 && at <= outRules.Ats[timeCount - 1]) + { + if (at < outRules.Ats[timeCount - 1]) + { + return false; + } + + outRules.Types[i - 1] = 0; + timeCount--; + } + + outRules.Ats[timeCount++] = at; + + p += TimeTypeSize; + } + + timeCount = 0; + for (int i = 0; i < outRules.TimeCount; i++) + { + byte type = *p++; + if (outRules.TypeCount <= type) + { + return false; + } + + if (outRules.Types[i] != 0) + { + outRules.Types[timeCount++] = type; + } + } + + outRules.TimeCount = timeCount; + + for (int i = 0; i < outRules.TypeCount; i++) + { + TimeTypeInfo ttis = outRules.Ttis[i]; + ttis.GmtOffset = Detzcode32((int*)p); + p += 4; + + if (*p >= 2) + { + return false; + } + + ttis.IsDaySavingTime = *p != 0; + p++; + + int abbreviationListIndex = *p++; + if (abbreviationListIndex >= outRules.CharCount) + { + return false; + } + + ttis.AbbreviationListIndex = abbreviationListIndex; + + outRules.Ttis[i] = ttis; + } + + fixed (char* chars = outRules.Chars) + { + Encoding.ASCII.GetChars(p, outRules.CharCount, chars, outRules.CharCount); + } + + p += outRules.CharCount; + outRules.Chars[outRules.CharCount] = '\0'; + + for (int i = 0; i < outRules.TypeCount; i++) + { + if (ttisSTDCount == 0) + { + outRules.Ttis[i].IsStandardTimeDaylight = false; + } + else + { + if (*p >= 2) + { + return false; + } + + outRules.Ttis[i].IsStandardTimeDaylight = *p++ != 0; + } + + } + + for (int i = 0; i < outRules.TypeCount; i++) + { + if (ttisSTDCount == 0) + { + outRules.Ttis[i].IsGMT = false; + } + else + { + if (*p >= 2) + { + return false; + } + + outRules.Ttis[i].IsGMT = *p++ != 0; + } + + } + + long position = (p - workBufferPtrStart); + long nRead = streamLength - position; + + if (nRead < 0) + { + return false; + } + + // Nintendo abort in case of a TzIf file with a POSIX TZ Name too long to fit inside a TimeZoneRule. + // As it's impossible in normal usage to achive this, we also force a crash. + if (nRead > (TzNameMax + 1)) + { + throw new InvalidOperationException(); + } + + char[] tempName = new char[TzNameMax + 1]; + Array.Copy(workBuffer, position, tempName, 0, nRead); + + if (nRead > 2 && tempName[0] == '\n' && tempName[nRead - 1] == '\n' && outRules.TypeCount + 2 <= TzMaxTypes) + { + tempName[nRead - 1] = '\0'; + + char[] name = new char[TzNameMax]; + Array.Copy(tempName, 1, name, 0, nRead - 1); + + if (ParsePosixName(name, out TimeZoneRule tempRules, false)) + { + int abbreviationCount = 0; + charCount = outRules.CharCount; + + fixed (char* chars = outRules.Chars) + { + for (int i = 0; i < tempRules.TypeCount; i++) + { + fixed (char* tempChars = tempRules.Chars) + { + char* tempAbbreviation = tempChars + tempRules.Ttis[i].AbbreviationListIndex; + int j; + + for (j = 0; j < charCount; j++) + { + if (StringUtils.CompareCStr(chars + j, tempAbbreviation) == 0) + { + tempRules.Ttis[i].AbbreviationListIndex = j; + abbreviationCount++; + break; + } + } + + if (j >= charCount) + { + int abbreviationLength = StringUtils.LengthCstr(tempAbbreviation); + if (j + abbreviationLength < TzMaxChars) + { + for (int x = 0; x < abbreviationLength; x++) + { + chars[j + x] = tempAbbreviation[x]; + } + + charCount = j + abbreviationLength + 1; + + tempRules.Ttis[i].AbbreviationListIndex = j; + abbreviationCount++; + } + } + } + } + + if (abbreviationCount == tempRules.TypeCount) + { + outRules.CharCount = charCount; + + // Remove trailing + while (1 < outRules.TimeCount && (outRules.Types[outRules.TimeCount - 1] == outRules.Types[outRules.TimeCount - 2])) + { + outRules.TimeCount--; + } + + int i; + + for (i = 0; i < tempRules.TimeCount; i++) + { + if (outRules.TimeCount == 0 || outRules.Ats[outRules.TimeCount - 1] < tempRules.Ats[i]) + { + break; + } + } + + while (i < tempRules.TimeCount && outRules.TimeCount < TzMaxTimes) + { + outRules.Ats[outRules.TimeCount] = tempRules.Ats[i]; + outRules.Types[outRules.TimeCount] = (byte)(outRules.TypeCount + (byte)tempRules.Types[i]); + + outRules.TimeCount++; + i++; + } + + for (i = 0; i < tempRules.TypeCount; i++) + { + outRules.Ttis[outRules.TypeCount++] = tempRules.Ttis[i]; + } + } + } + } + } + + if (outRules.TypeCount == 0) + { + return false; + } + + if (outRules.TimeCount > 1) + { + for (int i = 1; i < outRules.TimeCount; i++) + { + if (TimeTypeEquals(outRules, outRules.Types[i], outRules.Types[0]) && DifferByRepeat(outRules.Ats[i], outRules.Ats[0])) + { + outRules.GoBack = true; + break; + } + } + + for (int i = outRules.TimeCount - 2; i >= 0; i--) + { + if (TimeTypeEquals(outRules, outRules.Types[outRules.TimeCount - 1], outRules.Types[i]) && DifferByRepeat(outRules.Ats[outRules.TimeCount - 1], outRules.Ats[i])) + { + outRules.GoAhead = true; + break; + } + } + } + + int defaultType; + + for (defaultType = 0; defaultType < outRules.TimeCount; defaultType++) + { + if (outRules.Types[defaultType] == 0) + { + break; + } + } + + defaultType = defaultType < outRules.TimeCount ? -1 : 0; + + if (defaultType < 0 && outRules.TimeCount > 0 && outRules.Ttis[outRules.Types[0]].IsDaySavingTime) + { + defaultType = outRules.Types[0]; + while (--defaultType >= 0) + { + if (!outRules.Ttis[defaultType].IsDaySavingTime) + { + break; + } + } + } + + if (defaultType < 0) + { + defaultType = 0; + while (outRules.Ttis[defaultType].IsDaySavingTime) + { + if (++defaultType >= outRules.TypeCount) + { + defaultType = 0; + break; + } + } + } + + outRules.DefaultType = defaultType; + } + + return true; + } + + private static long GetLeapDaysNotNeg(long year) + { + return year / 4 - year / 100 + year / 400; + } + + private static long GetLeapDays(long year) + { + if (year < 0) + { + return -1 - GetLeapDaysNotNeg(-1 - year); + } + else + { + return GetLeapDaysNotNeg(year); + } + } + + private static ResultCode CreateCalendarTime(long time, int gmtOffset, out CalendarTimeInternal calendarTime, out CalendarAdditionalInfo calendarAdditionalInfo) + { + long year = EpochYear; + long timeDays = time / SecondsPerDay; + long remainingSeconds = time % SecondsPerDay; + + calendarTime = new CalendarTimeInternal(); + calendarAdditionalInfo = new CalendarAdditionalInfo() + { + TimezoneName = new char[8] + }; + + while (timeDays < 0 || timeDays >= YearLengths[IsLeap((int)year)]) + { + long timeDelta = timeDays / DaysPerLYear; + long delta = timeDelta; + + if (delta == 0) + { + delta = timeDays < 0 ? -1 : 1; + } + + long newYear = year; + + if (IncrementOverflow64(ref newYear, delta)) + { + return ResultCode.OutOfRange; + } + + long leapDays = GetLeapDays(newYear - 1) - GetLeapDays(year - 1); + timeDays -= (newYear - year) * DaysPerNYear; + timeDays -= leapDays; + year = newYear; + } + + long dayOfYear = timeDays; + remainingSeconds += gmtOffset; + while (remainingSeconds < 0) + { + remainingSeconds += SecondsPerDay; + dayOfYear -= 1; + } + + while (remainingSeconds >= SecondsPerDay) + { + remainingSeconds -= SecondsPerDay; + dayOfYear += 1; + } + + while (dayOfYear < 0) + { + if (IncrementOverflow64(ref year, -1)) + { + return ResultCode.OutOfRange; + } + + dayOfYear += YearLengths[IsLeap((int)year)]; + } + + while (dayOfYear >= YearLengths[IsLeap((int)year)]) + { + dayOfYear -= YearLengths[IsLeap((int)year)]; + + if (IncrementOverflow64(ref year, 1)) + { + return ResultCode.OutOfRange; + } + } + + calendarTime.Year = year; + calendarAdditionalInfo.DayOfYear = (uint)dayOfYear; + + long dayOfWeek = (EpochWeekDay + ((year - EpochYear) % DaysPerWekk) * (DaysPerNYear % DaysPerWekk) + GetLeapDays(year - 1) - GetLeapDays(EpochYear - 1) + dayOfYear) % DaysPerWekk; + if (dayOfWeek < 0) + { + dayOfWeek += DaysPerWekk; + } + + calendarAdditionalInfo.DayOfWeek = (uint)dayOfWeek; + + calendarTime.Hour = (sbyte)((remainingSeconds / SecondsPerHour) % SecondsPerHour); + remainingSeconds %= SecondsPerHour; + + calendarTime.Minute = (sbyte)(remainingSeconds / SecondsPerMinute); + calendarTime.Second = (sbyte)(remainingSeconds % SecondsPerMinute); + + int[] ip = MonthsLengths[IsLeap((int)year)]; + + for (calendarTime.Month = 0; dayOfYear >= ip[calendarTime.Month]; ++calendarTime.Month) + { + dayOfYear -= ip[calendarTime.Month]; + } + + calendarTime.Day = (sbyte)(dayOfYear + 1); + + calendarAdditionalInfo.IsDaySavingTime = false; + calendarAdditionalInfo.GmtOffset = gmtOffset; + + return 0; + } + + private static ResultCode ToCalendarTimeInternal(TimeZoneRule rules, long time, out CalendarTimeInternal calendarTime, out CalendarAdditionalInfo calendarAdditionalInfo) + { + calendarTime = new CalendarTimeInternal(); + calendarAdditionalInfo = new CalendarAdditionalInfo() + { + TimezoneName = new char[8] + }; + + ResultCode result; + + if ((rules.GoAhead && time < rules.Ats[0]) || (rules.GoBack && time > rules.Ats[rules.TimeCount - 1])) + { + long newTime = time; + + long seconds; + long years; + + if (time < rules.Ats[0]) + { + seconds = rules.Ats[0] - time; + } + else + { + seconds = time - rules.Ats[rules.TimeCount - 1]; + } + + seconds -= 1; + + years = (seconds / SecondsPerRepeat + 1) * YearsPerRepeat; + seconds = years * AverageSecondsPerYear; + + if (time < rules.Ats[0]) + { + newTime += seconds; + } + else + { + newTime -= seconds; + } + + if (newTime < rules.Ats[0] && newTime > rules.Ats[rules.TimeCount - 1]) + { + return ResultCode.TimeNotFound; + } + + result = ToCalendarTimeInternal(rules, newTime, out calendarTime, out calendarAdditionalInfo); + if (result != 0) + { + return result; + } + + if (time < rules.Ats[0]) + { + calendarTime.Year -= years; + } + else + { + calendarTime.Year += years; + } + + return ResultCode.Success; + } + + int ttiIndex; + + if (rules.TimeCount == 0 || time < rules.Ats[0]) + { + ttiIndex = rules.DefaultType; + } + else + { + int low = 1; + int high = rules.TimeCount; + + while (low < high) + { + int mid = (low + high) >> 1; + + if (time < rules.Ats[mid]) + { + high = mid; + } + else + { + low = mid + 1; + } + } + + ttiIndex = rules.Types[low - 1]; + } + + result = CreateCalendarTime(time, rules.Ttis[ttiIndex].GmtOffset, out calendarTime, out calendarAdditionalInfo); + + if (result == 0) + { + calendarAdditionalInfo.IsDaySavingTime = rules.Ttis[ttiIndex].IsDaySavingTime; + + unsafe + { + fixed (char* timeZoneAbbreviation = &rules.Chars[rules.Ttis[ttiIndex].AbbreviationListIndex]) + { + int timeZoneSize = Math.Min(StringUtils.LengthCstr(timeZoneAbbreviation), 8); + for (int i = 0; i < timeZoneSize; i++) + { + calendarAdditionalInfo.TimezoneName[i] = timeZoneAbbreviation[i]; + } + } + } + } + + return result; + } + + private static ResultCode ToPosixTimeInternal(TimeZoneRule rules, CalendarTimeInternal calendarTime, out long posixTime) + { + posixTime = 0; + + int hour = calendarTime.Hour; + int minute = calendarTime.Minute; + + if (NormalizeOverflow32(ref hour, ref minute, MinutesPerHour)) + { + return ResultCode.Overflow; + } + + calendarTime.Minute = (sbyte)minute; + + int day = calendarTime.Day; + if (NormalizeOverflow32(ref day, ref hour, HoursPerDays)) + { + return ResultCode.Overflow; + } + + calendarTime.Day = (sbyte)day; + calendarTime.Hour = (sbyte)hour; + + long year = calendarTime.Year; + long month = calendarTime.Month; + + if (NormalizeOverflow64(ref year, ref month, MonthsPerYear)) + { + return ResultCode.Overflow; + } + + calendarTime.Month = (sbyte)month; + + if (IncrementOverflow64(ref year, YearBase)) + { + return ResultCode.Overflow; + } + + while (day <= 0) + { + if (IncrementOverflow64(ref year, -1)) + { + return ResultCode.Overflow; + } + + long li = year; + + if (1 < calendarTime.Month) + { + li++; + } + + day += YearLengths[IsLeap((int)li)]; + } + + while (day > DaysPerLYear) + { + long li = year; + + if (1 < calendarTime.Month) + { + li++; + } + + day -= YearLengths[IsLeap((int)li)]; + + if (IncrementOverflow64(ref year, 1)) + { + return ResultCode.Overflow; + } + } + + while (true) + { + int i = MonthsLengths[IsLeap((int)year)][calendarTime.Month]; + + if (day <= i) + { + break; + } + + day -= i; + calendarTime.Month += 1; + + if (calendarTime.Month >= MonthsPerYear) + { + calendarTime.Month = 0; + if (IncrementOverflow64(ref year, 1)) + { + return ResultCode.Overflow; + } + } + } + + calendarTime.Day = (sbyte)day; + + if (IncrementOverflow64(ref year, -YearBase)) + { + return ResultCode.Overflow; + } + + calendarTime.Year = year; + + int savedSeconds; + + if (calendarTime.Second >= 0 && calendarTime.Second < SecondsPerMinute) + { + savedSeconds = 0; + } + else if (year + YearBase < EpochYear) + { + int second = calendarTime.Second; + if (IncrementOverflow32(ref second, 1 - SecondsPerMinute)) + { + return ResultCode.Overflow; + } + + savedSeconds = second; + calendarTime.Second = 1 - SecondsPerMinute; + } + else + { + savedSeconds = calendarTime.Second; + calendarTime.Second = 0; + } + + long low = long.MinValue; + long high = long.MaxValue; + + while (true) + { + long pivot = low / 2 + high / 2; + + if (pivot < low) + { + pivot = low; + } + else if (pivot > high) + { + pivot = high; + } + + int direction; + + ResultCode result = ToCalendarTimeInternal(rules, pivot, out CalendarTimeInternal candidateCalendarTime, out _); + if (result != 0) + { + if (pivot > 0) + { + direction = 1; + } + else + { + direction = -1; + } + } + else + { + direction = candidateCalendarTime.CompareTo(calendarTime); + } + + if (direction == 0) + { + long timeResult = pivot + savedSeconds; + + if ((timeResult < pivot) != (savedSeconds < 0)) + { + return ResultCode.Overflow; + } + + posixTime = timeResult; + break; + } + else + { + if (pivot == low) + { + if (pivot == long.MaxValue) + { + return ResultCode.TimeNotFound; + } + + pivot += 1; + low += 1; + } + else if (pivot == high) + { + if (pivot == long.MinValue) + { + return ResultCode.TimeNotFound; + } + + pivot -= 1; + high -= 1; + } + + if (low > high) + { + return ResultCode.TimeNotFound; + } + + if (direction > 0) + { + high = pivot; + } + else + { + low = pivot; + } + } + } + + return ResultCode.Success; + } + + internal static ResultCode ToCalendarTime(TimeZoneRule rules, long time, out CalendarInfo calendar) + { + ResultCode result = ToCalendarTimeInternal(rules, time, out CalendarTimeInternal calendarTime, out CalendarAdditionalInfo calendarAdditionalInfo); + + calendar = new CalendarInfo() + { + Time = new CalendarTime() + { + Year = (short)calendarTime.Year, + // NOTE: Nintendo's month range is 1-12, internal range is 0-11. + Month = (sbyte)(calendarTime.Month + 1), + Day = calendarTime.Day, + Hour = calendarTime.Hour, + Minute = calendarTime.Minute, + Second = calendarTime.Second + }, + AdditionalInfo = calendarAdditionalInfo + }; + + return result; + } + + internal static ResultCode ToPosixTime(TimeZoneRule rules, CalendarTime calendarTime, out long posixTime) + { + CalendarTimeInternal calendarTimeInternal = new CalendarTimeInternal() + { + Year = calendarTime.Year, + // NOTE: Nintendo's month range is 1-12, internal range is 0-11. + Month = (sbyte)(calendarTime.Month - 1), + Day = calendarTime.Day, + Hour = calendarTime.Hour, + Minute = calendarTime.Minute, + Second = calendarTime.Second + }; + + return ToPosixTimeInternal(rules, calendarTimeInternal, out posixTime); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneContentManager.cs b/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneContentManager.cs new file mode 100644 index 0000000000..f48452f31a --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneContentManager.cs @@ -0,0 +1,202 @@ +using LibHac; +using LibHac.Fs; +using LibHac.FsSystem; +using LibHac.FsSystem.NcaUtils; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.Exceptions; +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.HOS.Services.Time.Clock; +using Ryujinx.HLE.Utilities; +using System.Collections.Generic; +using System.IO; + +using static Ryujinx.HLE.HOS.Services.Time.TimeZone.TimeZoneRule; + +namespace Ryujinx.HLE.HOS.Services.Time.TimeZone +{ + class TimeZoneContentManager + { + private const long TimeZoneBinaryTitleId = 0x010000000000080E; + + private Switch _device; + private string[] _locationNameCache; + + public TimeZoneManager Manager { get; private set; } + + public TimeZoneContentManager() + { + Manager = new TimeZoneManager(); + } + + internal void Initialize(TimeManager timeManager, Switch device) + { + _device = device; + + InitializeLocationNameCache(); + + SteadyClockTimePoint timeZoneUpdatedTimePoint = timeManager.StandardSteadyClock.GetCurrentTimePoint(null); + + ResultCode result = GetTimeZoneBinary("UTC", out Stream timeZoneBinaryStream, out LocalStorage ncaFile); + + if (result == ResultCode.Success) + { + // TODO: Read TimeZoneVersion from sysarchive. + timeManager.SetupTimeZoneManager("UTC", timeZoneUpdatedTimePoint, (uint)_locationNameCache.Length, new UInt128(), timeZoneBinaryStream); + + ncaFile.Dispose(); + } + else + { + // In the case the user don't have the timezone system archive, we just mark the manager as initialized. + Manager.MarkInitialized(); + } + } + + private void InitializeLocationNameCache() + { + if (HasTimeZoneBinaryTitle()) + { + using (IStorage ncaFileStream = new LocalStorage(_device.FileSystem.SwitchPathToSystemPath(GetTimeZoneBinaryTitleContentPath()), FileAccess.Read, FileMode.Open)) + { + Nca nca = new Nca(_device.System.KeySet, ncaFileStream); + IFileSystem romfs = nca.OpenFileSystem(NcaSectionType.Data, _device.System.FsIntegrityCheckLevel); + + romfs.OpenFile(out IFile binaryListFile, "/binaryList.txt", OpenMode.Read).ThrowIfFailure(); + + StreamReader reader = new StreamReader(binaryListFile.AsStream()); + + List locationNameList = new List(); + + string locationName; + while ((locationName = reader.ReadLine()) != null) + { + locationNameList.Add(locationName); + } + + _locationNameCache = locationNameList.ToArray(); + } + } + else + { + _locationNameCache = new string[0]; + + Logger.PrintWarning(LogClass.ServiceTime, "TimeZoneBinary system title not found! TimeZone conversions will not work, provide the system archive to fix this warning. (See https://github.com/Ryujinx/Ryujinx#requirements for more informations)"); + } + } + + private bool IsLocationNameValid(string locationName) + { + foreach (string cachedLocationName in _locationNameCache) + { + if (cachedLocationName.Equals(locationName)) + { + return true; + } + } + + return false; + } + + public ResultCode SetDeviceLocationName(string locationName) + { + ResultCode result = GetTimeZoneBinary(locationName, out Stream timeZoneBinaryStream, out LocalStorage ncaFile); + + if (result == ResultCode.Success) + { + result = Manager.SetDeviceLocationNameWithTimeZoneRule(locationName, timeZoneBinaryStream); + + ncaFile.Dispose(); + } + + return result; + } + + public ResultCode LoadLocationNameList(uint index, out string[] outLocationNameArray, uint maxLength) + { + List locationNameList = new List(); + + for (int i = 0; i < _locationNameCache.Length && i < maxLength; i++) + { + if (i < index) + { + continue; + } + + string locationName = _locationNameCache[i]; + + // If the location name is too long, error out. + if (locationName.Length > 0x24) + { + outLocationNameArray = new string[0]; + + return ResultCode.LocationNameTooLong; + } + + locationNameList.Add(locationName); + } + + outLocationNameArray = locationNameList.ToArray(); + + return ResultCode.Success; + } + + public string GetTimeZoneBinaryTitleContentPath() + { + return _device.System.ContentManager.GetInstalledContentPath(TimeZoneBinaryTitleId, StorageId.NandSystem, NcaContentType.Data); + } + + public bool HasTimeZoneBinaryTitle() + { + return !string.IsNullOrEmpty(GetTimeZoneBinaryTitleContentPath()); + } + + internal ResultCode GetTimeZoneBinary(string locationName, out Stream timeZoneBinaryStream, out LocalStorage ncaFile) + { + timeZoneBinaryStream = null; + ncaFile = null; + + if (!IsLocationNameValid(locationName)) + { + return ResultCode.TimeZoneNotFound; + } + + ncaFile = new LocalStorage(_device.FileSystem.SwitchPathToSystemPath(GetTimeZoneBinaryTitleContentPath()), FileAccess.Read, FileMode.Open); + + Nca nca = new Nca(_device.System.KeySet, ncaFile); + IFileSystem romfs = nca.OpenFileSystem(NcaSectionType.Data, _device.System.FsIntegrityCheckLevel); + + Result result = romfs.OpenFile(out IFile timeZoneBinaryFile, $"/zoneinfo/{locationName}", OpenMode.Read); + + timeZoneBinaryStream = timeZoneBinaryFile.AsStream(); + + return (ResultCode)result.Value; + } + + internal ResultCode LoadTimeZoneRule(out TimeZoneRule outRules, string locationName) + { + outRules = new TimeZoneRule + { + Ats = new long[TzMaxTimes], + Types = new byte[TzMaxTimes], + Ttis = new TimeTypeInfo[TzMaxTypes], + Chars = new char[TzCharsArraySize] + }; + + if (!HasTimeZoneBinaryTitle()) + { + throw new InvalidSystemResourceException($"TimeZoneBinary system title not found! Please provide it. (See https://github.com/Ryujinx/Ryujinx#requirements for more informations)"); + } + + ResultCode result = GetTimeZoneBinary(locationName, out Stream timeZoneBinaryStream, out LocalStorage ncaFile); + + if (result == ResultCode.Success) + { + result = Manager.ParseTimeZoneRuleBinary(out outRules, timeZoneBinaryStream); + + ncaFile.Dispose(); + } + + return result; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneManager.cs b/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneManager.cs new file mode 100644 index 0000000000..1a80365ad9 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneManager.cs @@ -0,0 +1,267 @@ +using Ryujinx.HLE.HOS.Services.Time.Clock; +using Ryujinx.HLE.Utilities; +using System.IO; +using static Ryujinx.HLE.HOS.Services.Time.TimeZone.TimeZoneRule; + +namespace Ryujinx.HLE.HOS.Services.Time.TimeZone +{ + class TimeZoneManager + { + private bool _isInitialized; + private TimeZoneRule _myRules; + private string _deviceLocationName; + private UInt128 _timeZoneRuleVersion; + private uint _totalLocationNameCount; + private SteadyClockTimePoint _timeZoneUpdateTimePoint; + private object _lock; + + public TimeZoneManager() + { + _isInitialized = false; + _deviceLocationName = "UTC"; + _timeZoneRuleVersion = new UInt128(); + _lock = new object(); + + // Empty rules + _myRules = new TimeZoneRule + { + Ats = new long[TzMaxTimes], + Types = new byte[TzMaxTimes], + Ttis = new TimeTypeInfo[TzMaxTypes], + Chars = new char[TzCharsArraySize] + }; + + _timeZoneUpdateTimePoint = SteadyClockTimePoint.GetRandom(); + } + + public bool IsInitialized() + { + bool res; + + lock (_lock) + { + res = _isInitialized; + } + + return res; + } + + public void MarkInitialized() + { + lock (_lock) + { + _isInitialized = true; + } + } + + public ResultCode GetDeviceLocationName(out string deviceLocationName) + { + ResultCode result = ResultCode.UninitializedClock; + + deviceLocationName = null; + + lock (_lock) + { + if (_isInitialized) + { + deviceLocationName = _deviceLocationName; + result = ResultCode.Success; + } + } + + return result; + } + + public ResultCode SetDeviceLocationNameWithTimeZoneRule(string locationName, Stream timeZoneBinaryStream) + { + ResultCode result = ResultCode.TimeZoneConversionFailed; + + lock (_lock) + { + bool timeZoneConversionSuccess = TimeZone.ParseTimeZoneBinary(out TimeZoneRule rules, timeZoneBinaryStream); + + if (timeZoneConversionSuccess) + { + _deviceLocationName = locationName; + _myRules = rules; + result = ResultCode.Success; + } + } + + return result; + } + + public void SetTotalLocationNameCount(uint totalLocationNameCount) + { + lock (_lock) + { + _totalLocationNameCount = totalLocationNameCount; + } + } + + public ResultCode GetTotalLocationNameCount(out uint totalLocationNameCount) + { + ResultCode result = ResultCode.UninitializedClock; + + totalLocationNameCount = 0; + + lock (_lock) + { + if (_isInitialized) + { + totalLocationNameCount = _totalLocationNameCount; + result = ResultCode.Success; + } + } + + return result; + } + + public ResultCode SetUpdatedTime(SteadyClockTimePoint timeZoneUpdatedTimePoint, bool bypassUninitialized = false) + { + ResultCode result = ResultCode.UninitializedClock; + + lock (_lock) + { + if (_isInitialized || bypassUninitialized) + { + _timeZoneUpdateTimePoint = timeZoneUpdatedTimePoint; + result = ResultCode.Success; + } + } + + return result; + } + + public ResultCode GetUpdatedTime(out SteadyClockTimePoint timeZoneUpdatedTimePoint) + { + ResultCode result; + + lock (_lock) + { + if (_isInitialized) + { + timeZoneUpdatedTimePoint = _timeZoneUpdateTimePoint; + result = ResultCode.Success; + } + else + { + timeZoneUpdatedTimePoint = SteadyClockTimePoint.GetRandom(); + result = ResultCode.UninitializedClock; + } + } + + return result; + } + + public ResultCode ParseTimeZoneRuleBinary(out TimeZoneRule outRules, Stream timeZoneBinaryStream) + { + ResultCode result = ResultCode.Success; + + lock (_lock) + { + bool timeZoneConversionSuccess = TimeZone.ParseTimeZoneBinary(out outRules, timeZoneBinaryStream); + + if (!timeZoneConversionSuccess) + { + result = ResultCode.TimeZoneConversionFailed; + } + } + + return result; + } + + public void SetTimeZoneRuleVersion(UInt128 timeZoneRuleVersion) + { + lock (_lock) + { + _timeZoneRuleVersion = timeZoneRuleVersion; + } + } + + public ResultCode GetTimeZoneRuleVersion(out UInt128 timeZoneRuleVersion) + { + ResultCode result; + + lock (_lock) + { + if (_isInitialized) + { + timeZoneRuleVersion = _timeZoneRuleVersion; + result = ResultCode.Success; + } + else + { + timeZoneRuleVersion = new UInt128(); + result = ResultCode.UninitializedClock; + } + } + + return result; + } + + public ResultCode ToCalendarTimeWithMyRules(long time, out CalendarInfo calendar) + { + ResultCode result; + + lock (_lock) + { + if (_isInitialized) + { + result = ToCalendarTime(_myRules, time, out calendar); + } + else + { + calendar = new CalendarInfo(); + result = ResultCode.UninitializedClock; + } + } + + return result; + } + + public ResultCode ToCalendarTime(TimeZoneRule rules, long time, out CalendarInfo calendar) + { + ResultCode result; + + lock (_lock) + { + result = TimeZone.ToCalendarTime(rules, time, out calendar); + } + + return result; + } + + public ResultCode ToPosixTimeWithMyRules(CalendarTime calendarTime, out long posixTime) + { + ResultCode result; + + lock (_lock) + { + if (_isInitialized) + { + result = ToPosixTime(_myRules, calendarTime, out posixTime); + } + else + { + posixTime = 0; + result = ResultCode.UninitializedClock; + } + } + + return result; + } + + public ResultCode ToPosixTime(TimeZoneRule rules, CalendarTime calendarTime, out long posixTime) + { + ResultCode result; + + lock (_lock) + { + result = TimeZone.ToPosixTime(rules, calendarTime, out posixTime); + } + + return result; + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/CalendarAdditionalInfo.cs b/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/CalendarAdditionalInfo.cs new file mode 100644 index 0000000000..ef9b87e794 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/CalendarAdditionalInfo.cs @@ -0,0 +1,22 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Time.TimeZone +{ + [StructLayout(LayoutKind.Sequential, Pack = 0x4, Size = 0x18, CharSet = CharSet.Ansi)] + struct CalendarAdditionalInfo + { + public uint DayOfWeek; + public uint DayOfYear; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public char[] TimezoneName; + + [MarshalAs(UnmanagedType.I1)] + public bool IsDaySavingTime; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public char[] Padding; + + public int GmtOffset; + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/CalendarInfo.cs b/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/CalendarInfo.cs new file mode 100644 index 0000000000..68e6245b37 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/CalendarInfo.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Time.TimeZone +{ + [StructLayout(LayoutKind.Sequential, Pack = 0x4, Size = 0x20, CharSet = CharSet.Ansi)] + struct CalendarInfo + { + public CalendarTime Time; + public CalendarAdditionalInfo AdditionalInfo; + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/CalendarTime.cs b/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/CalendarTime.cs new file mode 100644 index 0000000000..d594223d4d --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/CalendarTime.cs @@ -0,0 +1,15 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Time.TimeZone +{ + [StructLayout(LayoutKind.Sequential, Pack = 0x4, Size = 0x8)] + struct CalendarTime + { + public short Year; + public sbyte Month; + public sbyte Day; + public sbyte Hour; + public sbyte Minute; + public sbyte Second; + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/TimeTypeInfo.cs b/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/TimeTypeInfo.cs new file mode 100644 index 0000000000..399e070077 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/TimeTypeInfo.cs @@ -0,0 +1,27 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Time.TimeZone +{ + [StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 4)] + struct TimeTypeInfo + { + public int GmtOffset; + + [MarshalAs(UnmanagedType.I1)] + public bool IsDaySavingTime; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public char[] Padding1; + + public int AbbreviationListIndex; + + [MarshalAs(UnmanagedType.I1)] + public bool IsStandardTimeDaylight; + + [MarshalAs(UnmanagedType.I1)] + public bool IsGMT; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] + public char[] Padding2; + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/TimeZoneRule.cs b/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/TimeZoneRule.cs new file mode 100644 index 0000000000..1af7a81acd --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/TimeZoneRule.cs @@ -0,0 +1,39 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Time.TimeZone +{ + [StructLayout(LayoutKind.Sequential, Pack = 4, Size = 0x4000, CharSet = CharSet.Ansi)] + struct TimeZoneRule + { + public const int TzMaxTypes = 128; + public const int TzMaxChars = 50; + public const int TzMaxLeaps = 50; + public const int TzMaxTimes = 1000; + public const int TzNameMax = 255; + public const int TzCharsArraySize = 2 * (TzNameMax + 1); + + public int TimeCount; + public int TypeCount; + public int CharCount; + + [MarshalAs(UnmanagedType.I1)] + public bool GoBack; + + [MarshalAs(UnmanagedType.I1)] + public bool GoAhead; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = TzMaxTimes)] + public long[] Ats; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = TzMaxTimes)] + public byte[] Types; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = TzMaxTypes)] + public TimeTypeInfo[] Ttis; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = TzCharsArraySize)] + public char[] Chars; + + public int DefaultType; + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/TzifHeader.cs b/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/TzifHeader.cs new file mode 100644 index 0000000000..1a033c33ec --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/TzifHeader.cs @@ -0,0 +1,34 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Time.TimeZone +{ + [StructLayout(LayoutKind.Sequential, Pack = 0x4, Size = 0x2C)] + struct TzifHeader + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public char[] Magic; + + public char Version; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 15)] + public byte[] Reserved; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] TtisGMTCount; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] TtisSTDCount; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] LeapCount; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] TimeCount; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] TypeCount; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] CharCount; + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Time/Types/SteadyClockContext.cs b/Ryujinx.HLE/HOS/Services/Time/Types/SteadyClockContext.cs new file mode 100644 index 0000000000..4cf1fc9918 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Time/Types/SteadyClockContext.cs @@ -0,0 +1,12 @@ +using Ryujinx.HLE.Utilities; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Time.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct SteadyClockContext + { + public ulong InternalOffset; + public UInt128 ClockSourceId; + } +} diff --git a/Ryujinx.HLE/HOS/Services/Time/Types/TimePermissions.cs b/Ryujinx.HLE/HOS/Services/Time/Types/TimePermissions.cs new file mode 100644 index 0000000000..3fcd3a1444 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Time/Types/TimePermissions.cs @@ -0,0 +1,22 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Time +{ + [Flags] + enum TimePermissions + { + LocalSystemClockWritableMask = 0x1, + UserSystemClockWritableMask = 0x2, + NetworkSystemClockWritableMask = 0x4, + TimeZoneWritableMask = 0x8, + SteadyClockWritableMask = 0x10, + BypassUninitialized = 0x20, + + User = 0, + Admin = LocalSystemClockWritableMask | UserSystemClockWritableMask | TimeZoneWritableMask, + System = NetworkSystemClockWritableMask, + SystemUpdate = BypassUninitialized, + Repair = SteadyClockWritableMask, + Manufacture = LocalSystemClockWritableMask | UserSystemClockWritableMask | NetworkSystemClockWritableMask | TimeZoneWritableMask | SteadyClockWritableMask + } +} diff --git a/Ryujinx.HLE/HOS/Services/Usb/IClientRootSession.cs b/Ryujinx.HLE/HOS/Services/Usb/IClientRootSession.cs new file mode 100644 index 0000000000..56b12af084 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Usb/IClientRootSession.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Usb +{ + [Service("usb:hs")] + [Service("usb:hs:a")] // 7.0.0+ + class IClientRootSession : IpcService + { + public IClientRootSession(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Usb/IDsService.cs b/Ryujinx.HLE/HOS/Services/Usb/IDsService.cs new file mode 100644 index 0000000000..4dbb6fc1d4 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Usb/IDsService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Usb +{ + [Service("usb:ds")] + class IDsService : IpcService + { + public IDsService(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Usb/IPdCradleManager.cs b/Ryujinx.HLE/HOS/Services/Usb/IPdCradleManager.cs new file mode 100644 index 0000000000..cecdbc313f --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Usb/IPdCradleManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Usb +{ + [Service("usb:pd:c")] + class IPdCradleManager : IpcService + { + public IPdCradleManager(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Usb/IPdManager.cs b/Ryujinx.HLE/HOS/Services/Usb/IPdManager.cs new file mode 100644 index 0000000000..1fb574d29a --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Usb/IPdManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Usb +{ + [Service("usb:pd")] + class IPdManager : IpcService + { + public IPdManager(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Usb/IPmService.cs b/Ryujinx.HLE/HOS/Services/Usb/IPmService.cs new file mode 100644 index 0000000000..38beee079b --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Usb/IPmService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Usb +{ + [Service("usb:pm")] + class IPmService : IpcService + { + public IPmService(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Usb/IUnknown1.cs b/Ryujinx.HLE/HOS/Services/Usb/IUnknown1.cs new file mode 100644 index 0000000000..0981e4ff4b --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Usb/IUnknown1.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Usb +{ + [Service("usb:qdb")] // 7.0.0+ + class IUnknown1 : IpcService + { + public IUnknown1(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Usb/IUnknown2.cs b/Ryujinx.HLE/HOS/Services/Usb/IUnknown2.cs new file mode 100644 index 0000000000..563696bb8e --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Usb/IUnknown2.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Usb +{ + [Service("usb:obsv")] // 8.0.0+ + class IUnknown2 : IpcService + { + public IUnknown2(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Vi/IApplicationRootService.cs b/Ryujinx.HLE/HOS/Services/Vi/IApplicationRootService.cs new file mode 100644 index 0000000000..dbadd90b70 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Vi/IApplicationRootService.cs @@ -0,0 +1,21 @@ +using Ryujinx.HLE.HOS.Services.Vi.RootService; + +namespace Ryujinx.HLE.HOS.Services.Vi +{ + [Service("vi:u")] + class IApplicationRootService : IpcService + { + public IApplicationRootService(ServiceCtx context) { } + + [Command(0)] + // GetDisplayService(u32) -> object + public ResultCode GetDisplayService(ServiceCtx context) + { + int serviceType = context.RequestData.ReadInt32(); + + MakeObject(context, new IApplicationDisplayService()); + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Vi/IManagerRootService.cs b/Ryujinx.HLE/HOS/Services/Vi/IManagerRootService.cs new file mode 100644 index 0000000000..31996ff1ed --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Vi/IManagerRootService.cs @@ -0,0 +1,21 @@ +using Ryujinx.HLE.HOS.Services.Vi.RootService; + +namespace Ryujinx.HLE.HOS.Services.Vi +{ + [Service("vi:m")] + class IManagerRootService : IpcService + { + public IManagerRootService(ServiceCtx context) { } + + [Command(2)] + // GetDisplayService(u32) -> object + public ResultCode GetDisplayService(ServiceCtx context) + { + int serviceType = context.RequestData.ReadInt32(); + + MakeObject(context, new IApplicationDisplayService()); + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Vi/ISystemRootService.cs b/Ryujinx.HLE/HOS/Services/Vi/ISystemRootService.cs new file mode 100644 index 0000000000..8d64e47548 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Vi/ISystemRootService.cs @@ -0,0 +1,21 @@ +using Ryujinx.HLE.HOS.Services.Vi.RootService; + +namespace Ryujinx.HLE.HOS.Services.Vi +{ + [Service("vi:s")] + class ISystemRootService : IpcService + { + public ISystemRootService(ServiceCtx context) { } + + [Command(1)] + // GetDisplayService(u32) -> object + public ResultCode GetDisplayService(ServiceCtx context) + { + int serviceType = context.RequestData.ReadInt32(); + + MakeObject(context, new IApplicationDisplayService()); + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Vi/ResultCode.cs b/Ryujinx.HLE/HOS/Services/Vi/ResultCode.cs new file mode 100644 index 0000000000..35ee6ae276 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Vi/ResultCode.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.HLE.HOS.Services.Vi +{ + enum ResultCode + { + ModuleId = 114, + ErrorCodeShift = 9, + + Success = 0, + + InvalidArguments = (1 << ErrorCodeShift) | ModuleId, + InvalidScalingMode = (6 << ErrorCodeShift) | ModuleId + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/Display.cs b/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/Display.cs new file mode 100644 index 0000000000..47c7b2aeac --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/Display.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.HLE.HOS.Services.Vi +{ + class Display + { + public string Name { get; private set; } + + public Display(string name) + { + Name = name; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/IHOSBinderDriver.cs b/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/IHOSBinderDriver.cs new file mode 100644 index 0000000000..e30d159cac --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/IHOSBinderDriver.cs @@ -0,0 +1,99 @@ +using Ryujinx.Graphics.GAL; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.SurfaceFlinger; +using System; + +namespace Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService +{ + class IHOSBinderDriver : IpcService, IDisposable + { + private KEvent _binderEvent; + + private NvFlinger _flinger; + + public IHOSBinderDriver(Horizon system, IRenderer renderer) + { + _binderEvent = new KEvent(system); + + _binderEvent.ReadableEvent.Signal(); + + _flinger = new NvFlinger(renderer, _binderEvent); + } + + [Command(0)] + // TransactParcel(s32, u32, u32, buffer) -> buffer + public ResultCode TransactParcel(ServiceCtx context) + { + int id = context.RequestData.ReadInt32(); + int code = context.RequestData.ReadInt32(); + + long dataPos = context.Request.SendBuff[0].Position; + long dataSize = context.Request.SendBuff[0].Size; + + byte[] data = context.Memory.ReadBytes(dataPos, dataSize); + + data = Parcel.GetParcelData(data); + + return (ResultCode)_flinger.ProcessParcelRequest(context, data, code); + } + + [Command(1)] + // AdjustRefcount(s32, s32, s32) + public ResultCode AdjustRefcount(ServiceCtx context) + { + int id = context.RequestData.ReadInt32(); + int addVal = context.RequestData.ReadInt32(); + int type = context.RequestData.ReadInt32(); + + return ResultCode.Success; + } + + [Command(2)] + // GetNativeHandle(s32, s32) -> handle + public ResultCode GetNativeHandle(ServiceCtx context) + { + int id = context.RequestData.ReadInt32(); + uint unk = context.RequestData.ReadUInt32(); + + if (context.Process.HandleTable.GenerateHandle(_binderEvent.ReadableEvent, out int handle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeMove(handle); + + return ResultCode.Success; + } + + [Command(3)] // 3.0.0+ + // TransactParcelAuto(s32, u32, u32, buffer) -> buffer + public ResultCode TransactParcelAuto(ServiceCtx context) + { + int id = context.RequestData.ReadInt32(); + int code = context.RequestData.ReadInt32(); + + (long dataPos, long dataSize) = context.Request.GetBufferType0x21(); + + byte[] data = context.Memory.ReadBytes(dataPos, dataSize); + + data = Parcel.GetParcelData(data); + + return (ResultCode)_flinger.ProcessParcelRequest(context, data, code); + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _flinger.Dispose(); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/IManagerDisplayService.cs b/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/IManagerDisplayService.cs new file mode 100644 index 0000000000..24e73244ff --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/IManagerDisplayService.cs @@ -0,0 +1,61 @@ +using Ryujinx.Common.Logging; + +namespace Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService +{ + class IManagerDisplayService : IpcService + { + private static IApplicationDisplayService _applicationDisplayService; + + public IManagerDisplayService(IApplicationDisplayService applicationDisplayService) + { + _applicationDisplayService = applicationDisplayService; + } + + [Command(2010)] + // CreateManagedLayer(u32, u64, nn::applet::AppletResourceUserId) -> u64 + public ResultCode CreateManagedLayer(ServiceCtx context) + { + Logger.PrintStub(LogClass.ServiceVi); + + context.ResponseData.Write(0L); //LayerId + + return ResultCode.Success; + } + + [Command(2011)] + // DestroyManagedLayer(u64) + public ResultCode DestroyManagedLayer(ServiceCtx context) + { + Logger.PrintStub(LogClass.ServiceVi); + + return ResultCode.Success; + } + + [Command(2012)] // 7.0.0+ + // CreateStrayLayer(u32, u64) -> (u64, u64, buffer) + public ResultCode CreateStrayLayer(ServiceCtx context) + { + Logger.PrintStub(LogClass.ServiceVi); + + return _applicationDisplayService.CreateStrayLayer(context); + } + + [Command(6000)] + // AddToLayerStack(u32, u64) + public ResultCode AddToLayerStack(ServiceCtx context) + { + Logger.PrintStub(LogClass.ServiceVi); + + return ResultCode.Success; + } + + [Command(6002)] + // SetLayerVisibility(b8, u64) + public ResultCode SetLayerVisibility(ServiceCtx context) + { + Logger.PrintStub(LogClass.ServiceVi); + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/ISystemDisplayService.cs b/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/ISystemDisplayService.cs new file mode 100644 index 0000000000..1e615bd233 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/ISystemDisplayService.cs @@ -0,0 +1,54 @@ +using Ryujinx.Common.Logging; + +namespace Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService +{ + class ISystemDisplayService : IpcService + { + private static IApplicationDisplayService _applicationDisplayService; + + public ISystemDisplayService(IApplicationDisplayService applicationDisplayService) + { + _applicationDisplayService = applicationDisplayService; + } + + [Command(2205)] + // SetLayerZ(u64, u64) + public ResultCode SetLayerZ(ServiceCtx context) + { + Logger.PrintStub(LogClass.ServiceVi); + + return ResultCode.Success; + } + + [Command(2207)] + // SetLayerVisibility(b8, u64) + public ResultCode SetLayerVisibility(ServiceCtx context) + { + Logger.PrintStub(LogClass.ServiceVi); + + return ResultCode.Success; + } + + [Command(2312)] // 1.0.0-6.2.0 + // CreateStrayLayer(u32, u64) -> (u64, u64, buffer) + public ResultCode CreateStrayLayer(ServiceCtx context) + { + Logger.PrintStub(LogClass.ServiceVi); + + return _applicationDisplayService.CreateStrayLayer(context); + } + + [Command(3200)] + // GetDisplayMode(u64) -> nn::vi::DisplayModeInfo + public ResultCode GetDisplayMode(ServiceCtx context) + { + // TODO: De-hardcode resolution. + context.ResponseData.Write(1280); + context.ResponseData.Write(720); + context.ResponseData.Write(60.0f); + context.ResponseData.Write(0); + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/Types/DestinationScalingMode.cs b/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/Types/DestinationScalingMode.cs new file mode 100644 index 0000000000..cf459cb2e4 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/Types/DestinationScalingMode.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService +{ + enum DestinationScalingMode + { + Freeze, + ScaleToWindow, + ScaleAndCrop, + None, + PreserveAspectRatio + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/Types/SourceScalingMode.cs b/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/Types/SourceScalingMode.cs new file mode 100644 index 0000000000..ac8c3e0275 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/Types/SourceScalingMode.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService +{ + enum SourceScalingMode + { + None, + Freeze, + ScaleToWindow, + ScaleAndCrop, + PreserveAspectRatio + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Vi/RootService/IApplicationDisplayService.cs b/Ryujinx.HLE/HOS/Services/Vi/RootService/IApplicationDisplayService.cs new file mode 100644 index 0000000000..a2b57e7487 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Vi/RootService/IApplicationDisplayService.cs @@ -0,0 +1,288 @@ +using ARMeilleure.Memory; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService; +using System; +using System.IO; +using System.Text; + +using static Ryujinx.HLE.HOS.Services.SurfaceFlinger.Parcel; + +namespace Ryujinx.HLE.HOS.Services.Vi.RootService +{ + class IApplicationDisplayService : IpcService + { + private IdDictionary _displays; + + public IApplicationDisplayService() + { + _displays = new IdDictionary(); + } + + [Command(100)] + // GetRelayService() -> object + public ResultCode GetRelayService(ServiceCtx context) + { + MakeObject(context, new IHOSBinderDriver( + context.Device.System, + context.Device.Gpu.Renderer)); + + return ResultCode.Success; + } + + [Command(101)] + // GetSystemDisplayService() -> object + public ResultCode GetSystemDisplayService(ServiceCtx context) + { + MakeObject(context, new ISystemDisplayService(this)); + + return ResultCode.Success; + } + + [Command(102)] + // GetManagerDisplayService() -> object + public ResultCode GetManagerDisplayService(ServiceCtx context) + { + MakeObject(context, new IManagerDisplayService(this)); + + return ResultCode.Success; + } + + [Command(103)] // 2.0.0+ + // GetIndirectDisplayTransactionService() -> object + public ResultCode GetIndirectDisplayTransactionService(ServiceCtx context) + { + MakeObject(context, new IHOSBinderDriver( + context.Device.System, + context.Device.Gpu.Renderer)); + + return ResultCode.Success; + } + + [Command(1000)] + // ListDisplays() -> (u64, buffer) + public ResultCode ListDisplays(ServiceCtx context) + { + long recBuffPtr = context.Request.ReceiveBuff[0].Position; + + MemoryHelper.FillWithZeros(context.Memory, recBuffPtr, 0x60); + + // Add only the default display to buffer + context.Memory.WriteBytes(recBuffPtr, Encoding.ASCII.GetBytes("Default")); + context.Memory.WriteInt64(recBuffPtr + 0x40, 0x1L); + context.Memory.WriteInt64(recBuffPtr + 0x48, 0x1L); + context.Memory.WriteInt64(recBuffPtr + 0x50, 1920L); + context.Memory.WriteInt64(recBuffPtr + 0x58, 1080L); + + context.ResponseData.Write(1L); + + return ResultCode.Success; + } + + [Command(1010)] + // OpenDisplay(nn::vi::DisplayName) -> u64 + public ResultCode OpenDisplay(ServiceCtx context) + { + string name = GetDisplayName(context); + + long displayId = _displays.Add(new Display(name)); + + context.ResponseData.Write(displayId); + + return ResultCode.Success; + } + + [Command(1020)] + // CloseDisplay(u64) + public ResultCode CloseDisplay(ServiceCtx context) + { + int displayId = context.RequestData.ReadInt32(); + + _displays.Delete(displayId); + + return ResultCode.Success; + } + + [Command(1102)] + // GetDisplayResolution(u64) -> (u64, u64) + public ResultCode GetDisplayResolution(ServiceCtx context) + { + long displayId = context.RequestData.ReadInt32(); + + context.ResponseData.Write(1280); + context.ResponseData.Write(720); + + return ResultCode.Success; + } + + [Command(2020)] + // OpenLayer(nn::vi::DisplayName, u64, nn::applet::AppletResourceUserId, pid) -> (u64, buffer) + public ResultCode OpenLayer(ServiceCtx context) + { + long layerId = context.RequestData.ReadInt64(); + long userId = context.RequestData.ReadInt64(); + + long parcelPtr = context.Request.ReceiveBuff[0].Position; + + byte[] parcel = MakeIGraphicsBufferProducer(parcelPtr); + + context.Memory.WriteBytes(parcelPtr, parcel); + + context.ResponseData.Write((long)parcel.Length); + + return ResultCode.Success; + } + + [Command(2021)] + // CloseLayer(u64) + public ResultCode CloseLayer(ServiceCtx context) + { + long layerId = context.RequestData.ReadInt64(); + + return ResultCode.Success; + } + + [Command(2030)] + // CreateStrayLayer(u32, u64) -> (u64, u64, buffer) + public ResultCode CreateStrayLayer(ServiceCtx context) + { + long layerFlags = context.RequestData.ReadInt64(); + long displayId = context.RequestData.ReadInt64(); + + long parcelPtr = context.Request.ReceiveBuff[0].Position; + + Display disp = _displays.GetData((int)displayId); + + byte[] parcel = MakeIGraphicsBufferProducer(parcelPtr); + + context.Memory.WriteBytes(parcelPtr, parcel); + + context.ResponseData.Write(0L); + context.ResponseData.Write((long)parcel.Length); + + return ResultCode.Success; + } + + [Command(2031)] + // DestroyStrayLayer(u64) + public ResultCode DestroyStrayLayer(ServiceCtx context) + { + return ResultCode.Success; + } + + [Command(2101)] + // SetLayerScalingMode(u32, u64) + public ResultCode SetLayerScalingMode(ServiceCtx context) + { + int scalingMode = context.RequestData.ReadInt32(); + long unknown = context.RequestData.ReadInt64(); + + return ResultCode.Success; + } + + [Command(2102)] // 5.0.0+ + // ConvertScalingMode(unknown) -> unknown + public ResultCode ConvertScalingMode(ServiceCtx context) + { + SourceScalingMode scalingMode = (SourceScalingMode)context.RequestData.ReadInt32(); + + DestinationScalingMode? convertedScalingMode = ConvertScalingMode(scalingMode); + + if (!convertedScalingMode.HasValue) + { + // Scaling mode out of the range of valid values. + return ResultCode.InvalidArguments; + } + + if (scalingMode != SourceScalingMode.ScaleToWindow && + scalingMode != SourceScalingMode.PreserveAspectRatio) + { + // Invalid scaling mode specified. + return ResultCode.InvalidScalingMode; + } + + context.ResponseData.Write((ulong)convertedScalingMode); + + return ResultCode.Success; + } + + private DestinationScalingMode? ConvertScalingMode(SourceScalingMode source) + { + switch (source) + { + case SourceScalingMode.None: return DestinationScalingMode.None; + case SourceScalingMode.Freeze: return DestinationScalingMode.Freeze; + case SourceScalingMode.ScaleAndCrop: return DestinationScalingMode.ScaleAndCrop; + case SourceScalingMode.ScaleToWindow: return DestinationScalingMode.ScaleToWindow; + case SourceScalingMode.PreserveAspectRatio: return DestinationScalingMode.PreserveAspectRatio; + } + + return null; + } + + [Command(5202)] + // GetDisplayVsyncEvent(u64) -> handle + public ResultCode GetDisplayVSyncEvent(ServiceCtx context) + { + string name = GetDisplayName(context); + + if (context.Process.HandleTable.GenerateHandle(context.Device.System.VsyncEvent.ReadableEvent, out int handle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle); + + return ResultCode.Success; + } + + private byte[] MakeIGraphicsBufferProducer(long basePtr) + { + long id = 0x20; + long cookiePtr = 0L; + + using (MemoryStream ms = new MemoryStream()) + { + BinaryWriter writer = new BinaryWriter(ms); + + // flat_binder_object (size is 0x28) + writer.Write(2); //Type (BINDER_TYPE_WEAK_BINDER) + writer.Write(0); //Flags + writer.Write((int)(id >> 0)); + writer.Write((int)(id >> 32)); + writer.Write((int)(cookiePtr >> 0)); + writer.Write((int)(cookiePtr >> 32)); + writer.Write((byte)'d'); + writer.Write((byte)'i'); + writer.Write((byte)'s'); + writer.Write((byte)'p'); + writer.Write((byte)'d'); + writer.Write((byte)'r'); + writer.Write((byte)'v'); + writer.Write((byte)'\0'); + writer.Write(0L); //Pad + + return MakeParcel(ms.ToArray(), new byte[] { 0, 0, 0, 0 }); + } + } + + private string GetDisplayName(ServiceCtx context) + { + string name = string.Empty; + + for (int index = 0; index < 8 && + context.RequestData.BaseStream.Position < + context.RequestData.BaseStream.Length; index++) + { + byte chr = context.RequestData.ReadByte(); + + if (chr >= 0x20 && chr < 0x7f) + { + name += (char)chr; + } + } + + return name; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Wlan/IInfraManager.cs b/Ryujinx.HLE/HOS/Services/Wlan/IInfraManager.cs new file mode 100644 index 0000000000..0416868a8e --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Wlan/IInfraManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Wlan +{ + [Service("wlan:inf")] + class IInfraManager : IpcService + { + public IInfraManager(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Wlan/ILocalGetActionFrame.cs b/Ryujinx.HLE/HOS/Services/Wlan/ILocalGetActionFrame.cs new file mode 100644 index 0000000000..6c2e20a452 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Wlan/ILocalGetActionFrame.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Wlan +{ + [Service("wlan:lga")] + class ILocalGetActionFrame : IpcService + { + public ILocalGetActionFrame(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Wlan/ILocalGetFrame.cs b/Ryujinx.HLE/HOS/Services/Wlan/ILocalGetFrame.cs new file mode 100644 index 0000000000..a224a192d4 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Wlan/ILocalGetFrame.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Wlan +{ + [Service("wlan:lg")] + class ILocalGetFrame : IpcService + { + public ILocalGetFrame(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Wlan/ILocalManager.cs b/Ryujinx.HLE/HOS/Services/Wlan/ILocalManager.cs new file mode 100644 index 0000000000..4cc2c4b2e7 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Wlan/ILocalManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Wlan +{ + [Service("wlan:lcl")] + class ILocalManager : IpcService + { + public ILocalManager(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Wlan/ISocketGetFrame.cs b/Ryujinx.HLE/HOS/Services/Wlan/ISocketGetFrame.cs new file mode 100644 index 0000000000..ab5b21933d --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Wlan/ISocketGetFrame.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Wlan +{ + [Service("wlan:sg")] + class ISocketGetFrame : IpcService + { + public ISocketGetFrame(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Wlan/ISocketManager.cs b/Ryujinx.HLE/HOS/Services/Wlan/ISocketManager.cs new file mode 100644 index 0000000000..afa1bede2c --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Wlan/ISocketManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Wlan +{ + [Service("wlan:soc")] + class ISocketManager : IpcService + { + public ISocketManager(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Wlan/IUnknown1.cs b/Ryujinx.HLE/HOS/Services/Wlan/IUnknown1.cs new file mode 100644 index 0000000000..dfae18e5d3 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Wlan/IUnknown1.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Wlan +{ + [Service("wlan:dtc")] // 6.0.0+ + class IUnknown1 : IpcService + { + public IUnknown1(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/SystemState/AppletStateMgr.cs b/Ryujinx.HLE/HOS/SystemState/AppletStateMgr.cs new file mode 100644 index 0000000000..341f2a31b4 --- /dev/null +++ b/Ryujinx.HLE/HOS/SystemState/AppletStateMgr.cs @@ -0,0 +1,48 @@ +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy; +using System.Collections.Concurrent; + +namespace Ryujinx.HLE.HOS.SystemState +{ + class AppletStateMgr + { + private ConcurrentQueue _messages; + + public FocusState FocusState { get; private set; } + + public KEvent MessageEvent { get; private set; } + + public AppletStateMgr(Horizon system) + { + _messages = new ConcurrentQueue(); + + MessageEvent = new KEvent(system); + } + + public void SetFocus(bool isFocused) + { + FocusState = isFocused + ? FocusState.InFocus + : FocusState.OutOfFocus; + + EnqueueMessage(MessageInfo.FocusStateChanged); + } + + public void EnqueueMessage(MessageInfo message) + { + _messages.Enqueue(message); + + MessageEvent.ReadableEvent.Signal(); + } + + public bool TryDequeueMessage(out MessageInfo message) + { + if (_messages.Count < 2) + { + MessageEvent.ReadableEvent.Clear(); + } + + return _messages.TryDequeue(out message); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/Settings/ColorSet.cs b/Ryujinx.HLE/HOS/SystemState/ColorSet.cs similarity index 69% rename from Ryujinx.HLE/Settings/ColorSet.cs rename to Ryujinx.HLE/HOS/SystemState/ColorSet.cs index 77485d228e..4d7a7e2f0d 100644 --- a/Ryujinx.HLE/Settings/ColorSet.cs +++ b/Ryujinx.HLE/HOS/SystemState/ColorSet.cs @@ -1,4 +1,4 @@ -namespace Ryujinx.HLE.Settings +namespace Ryujinx.HLE.HOS.SystemState { public enum ColorSet { diff --git a/Ryujinx.HLE/OsHle/SystemLanguage.cs b/Ryujinx.HLE/HOS/SystemState/SystemLanguage.cs similarity index 91% rename from Ryujinx.HLE/OsHle/SystemLanguage.cs rename to Ryujinx.HLE/HOS/SystemState/SystemLanguage.cs index 4f19087650..2046ed62f5 100644 --- a/Ryujinx.HLE/OsHle/SystemLanguage.cs +++ b/Ryujinx.HLE/HOS/SystemState/SystemLanguage.cs @@ -1,4 +1,4 @@ -namespace Ryujinx.HLE.OsHle +namespace Ryujinx.HLE.HOS.SystemState { public enum SystemLanguage { diff --git a/Ryujinx.HLE/HOS/SystemState/SystemStateMgr.cs b/Ryujinx.HLE/HOS/SystemState/SystemStateMgr.cs new file mode 100644 index 0000000000..2c3b188fe7 --- /dev/null +++ b/Ryujinx.HLE/HOS/SystemState/SystemStateMgr.cs @@ -0,0 +1,116 @@ +using Ryujinx.HLE.HOS.Services.Account.Acc; +using Ryujinx.HLE.Utilities; +using System; + +namespace Ryujinx.HLE.HOS.SystemState +{ + public class SystemStateMgr + { + internal static string[] LanguageCodes = new string[] + { + "ja", + "en-US", + "fr", + "de", + "it", + "es", + "zh-CN", + "ko", + "nl", + "pt", + "ru", + "zh-TW", + "en-GB", + "fr-CA", + "es-419", + "zh-Hans", + "zh-Hant" + }; + + internal static string[] AudioOutputs = new string[] + { + "AudioTvOutput", + "AudioStereoJackOutput", + "AudioBuiltInSpeakerOutput" + }; + + internal long DesiredLanguageCode { get; private set; } + + public TitleLanguage DesiredTitleLanguage { get; private set; } + + internal string ActiveAudioOutput { get; private set; } + + public bool DockedMode { get; set; } + + public ColorSet ThemeColor { get; set; } + + public bool InstallContents { get; set; } + + public AccountUtils Account { get; private set; } + + public SystemStateMgr() + { + SetAudioOutputAsBuiltInSpeaker(); + + Account = new AccountUtils(); + + UInt128 defaultUid = new UInt128("00000000000000000000000000000001"); + + Account.AddUser(defaultUid, "Player"); + Account.OpenUser(defaultUid); + } + + public void SetLanguage(SystemLanguage language) + { + DesiredLanguageCode = GetLanguageCode((int)language); + + switch (language) + { + case SystemLanguage.Taiwanese: + case SystemLanguage.TraditionalChinese: + DesiredTitleLanguage = TitleLanguage.Taiwanese; + break; + case SystemLanguage.Chinese: + case SystemLanguage.SimplifiedChinese: + DesiredTitleLanguage = TitleLanguage.Chinese; + break; + default: + DesiredTitleLanguage = Enum.Parse(Enum.GetName(typeof(SystemLanguage), language)); + break; + } + } + + public void SetAudioOutputAsTv() + { + ActiveAudioOutput = AudioOutputs[0]; + } + + public void SetAudioOutputAsStereoJack() + { + ActiveAudioOutput = AudioOutputs[1]; + } + + public void SetAudioOutputAsBuiltInSpeaker() + { + ActiveAudioOutput = AudioOutputs[2]; + } + + internal static long GetLanguageCode(int index) + { + if ((uint)index >= LanguageCodes.Length) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + long code = 0; + int shift = 0; + + foreach (char chr in LanguageCodes[index]) + { + code |= (long)(byte)chr << shift++ * 8; + } + + return code; + } + } +} diff --git a/Ryujinx.HLE/HOS/SystemState/TitleLanguage.cs b/Ryujinx.HLE/HOS/SystemState/TitleLanguage.cs new file mode 100644 index 0000000000..f481ac2932 --- /dev/null +++ b/Ryujinx.HLE/HOS/SystemState/TitleLanguage.cs @@ -0,0 +1,21 @@ +namespace Ryujinx.HLE.HOS.SystemState +{ + public enum TitleLanguage + { + AmericanEnglish, + BritishEnglish, + Japanese, + French, + German, + LatinAmericanSpanish, + Spanish, + Italian, + Dutch, + CanadianFrench, + Portuguese, + Russian, + Korean, + Taiwanese, + Chinese + } +} diff --git a/Ryujinx.HLE/Hid/Hid.cs b/Ryujinx.HLE/Hid/Hid.cs deleted file mode 100644 index 2f007f1fb0..0000000000 --- a/Ryujinx.HLE/Hid/Hid.cs +++ /dev/null @@ -1,277 +0,0 @@ -using ChocolArm64.Memory; -using Ryujinx.HLE.Logging; -using Ryujinx.HLE.OsHle; -using Ryujinx.HLE.OsHle.Handles; -using System; - -namespace Ryujinx.HLE.Input -{ - public class Hid - { - /* - * Reference: - * https://github.com/reswitched/libtransistor/blob/development/lib/hid.c - * https://github.com/reswitched/libtransistor/blob/development/include/libtransistor/hid.h - * https://github.com/switchbrew/libnx/blob/master/nx/source/services/hid.c - * https://github.com/switchbrew/libnx/blob/master/nx/include/switch/services/hid.h - */ - - private const int HidHeaderSize = 0x400; - private const int HidTouchScreenSize = 0x3000; - private const int HidMouseSize = 0x400; - private const int HidKeyboardSize = 0x400; - private const int HidUnkSection1Size = 0x400; - private const int HidUnkSection2Size = 0x400; - private const int HidUnkSection3Size = 0x400; - private const int HidUnkSection4Size = 0x400; - private const int HidUnkSection5Size = 0x200; - private const int HidUnkSection6Size = 0x200; - private const int HidUnkSection7Size = 0x200; - private const int HidUnkSection8Size = 0x800; - private const int HidControllerSerialsSize = 0x4000; - private const int HidControllersSize = 0x32000; - private const int HidUnkSection9Size = 0x800; - - private const int HidTouchHeaderSize = 0x28; - private const int HidTouchEntrySize = 0x298; - - private const int HidTouchEntryHeaderSize = 0x10; - private const int HidTouchEntryTouchSize = 0x28; - - private const int HidControllerSize = 0x5000; - private const int HidControllerHeaderSize = 0x28; - private const int HidControllerLayoutsSize = 0x350; - - private const int HidControllersLayoutHeaderSize = 0x20; - private const int HidControllersInputEntrySize = 0x30; - - private const int HidHeaderOffset = 0; - private const int HidTouchScreenOffset = HidHeaderOffset + HidHeaderSize; - private const int HidMouseOffset = HidTouchScreenOffset + HidTouchScreenSize; - private const int HidKeyboardOffset = HidMouseOffset + HidMouseSize; - private const int HidUnkSection1Offset = HidKeyboardOffset + HidKeyboardSize; - private const int HidUnkSection2Offset = HidUnkSection1Offset + HidUnkSection1Size; - private const int HidUnkSection3Offset = HidUnkSection2Offset + HidUnkSection2Size; - private const int HidUnkSection4Offset = HidUnkSection3Offset + HidUnkSection3Size; - private const int HidUnkSection5Offset = HidUnkSection4Offset + HidUnkSection4Size; - private const int HidUnkSection6Offset = HidUnkSection5Offset + HidUnkSection5Size; - private const int HidUnkSection7Offset = HidUnkSection6Offset + HidUnkSection6Size; - private const int HidUnkSection8Offset = HidUnkSection7Offset + HidUnkSection7Size; - private const int HidControllerSerialsOffset = HidUnkSection8Offset + HidUnkSection8Size; - private const int HidControllersOffset = HidControllerSerialsOffset + HidControllerSerialsSize; - private const int HidUnkSection9Offset = HidControllersOffset + HidControllersSize; - - private const int HidEntryCount = 17; - - private Logger Log; - - private object ShMemLock; - - private (AMemory, long)[] ShMemPositions; - - public Hid(Logger Log) - { - this.Log = Log; - - ShMemLock = new object(); - - ShMemPositions = new (AMemory, long)[0]; - } - - internal void ShMemMap(object sender, EventArgs e) - { - HSharedMem SharedMem = (HSharedMem)sender; - - lock (ShMemLock) - { - ShMemPositions = SharedMem.GetVirtualPositions(); - - (AMemory Memory, long Position) = ShMemPositions[ShMemPositions.Length - 1]; - - for (long Offset = 0; Offset < Horizon.HidSize; Offset += 8) - { - Memory.WriteInt64Unchecked(Position + Offset, 0); - } - - Log.PrintInfo(LogClass.Hid, $"HID shared memory successfully mapped to 0x{Position:x16}!"); - - Init(Memory, Position); - } - } - - internal void ShMemUnmap(object sender, EventArgs e) - { - HSharedMem SharedMem = (HSharedMem)sender; - - lock (ShMemLock) - { - ShMemPositions = SharedMem.GetVirtualPositions(); - } - } - - private void Init(AMemory Memory, long Position) - { - InitializeJoyconPair( - Memory, - Position, - JoyConColor.Body_Neon_Red, - JoyConColor.Buttons_Neon_Red, - JoyConColor.Body_Neon_Blue, - JoyConColor.Buttons_Neon_Blue); - } - - private void InitializeJoyconPair( - AMemory Memory, - long Position, - JoyConColor LeftColorBody, - JoyConColor LeftColorButtons, - JoyConColor RightColorBody, - JoyConColor RightColorButtons) - { - long BaseControllerOffset = Position + HidControllersOffset + 8 * HidControllerSize; - - HidControllerType Type = HidControllerType.ControllerType_Handheld; - - bool IsHalf = false; - - HidControllerColorDesc SingleColorDesc = - HidControllerColorDesc.ColorDesc_ColorsNonexistent; - - JoyConColor SingleColorBody = JoyConColor.Black; - JoyConColor SingleColorButtons = JoyConColor.Black; - - HidControllerColorDesc SplitColorDesc = 0; - - Memory.WriteInt32Unchecked(BaseControllerOffset + 0x0, (int)Type); - - Memory.WriteInt32Unchecked(BaseControllerOffset + 0x4, IsHalf ? 1 : 0); - - Memory.WriteInt32Unchecked(BaseControllerOffset + 0x8, (int)SingleColorDesc); - Memory.WriteInt32Unchecked(BaseControllerOffset + 0xc, (int)SingleColorBody); - Memory.WriteInt32Unchecked(BaseControllerOffset + 0x10, (int)SingleColorButtons); - Memory.WriteInt32Unchecked(BaseControllerOffset + 0x14, (int)SplitColorDesc); - - Memory.WriteInt32Unchecked(BaseControllerOffset + 0x18, (int)LeftColorBody); - Memory.WriteInt32Unchecked(BaseControllerOffset + 0x1c, (int)LeftColorButtons); - - Memory.WriteInt32Unchecked(BaseControllerOffset + 0x20, (int)RightColorBody); - Memory.WriteInt32Unchecked(BaseControllerOffset + 0x24, (int)RightColorButtons); - } - - public void SetJoyconButton( - HidControllerId ControllerId, - HidControllerLayouts ControllerLayout, - HidControllerButtons Buttons, - HidJoystickPosition LeftStick, - HidJoystickPosition RightStick) - { - lock (ShMemLock) - { - foreach ((AMemory Memory, long Position) in ShMemPositions) - { - long ControllerOffset = Position + HidControllersOffset; - - ControllerOffset += (int)ControllerId * HidControllerSize; - - ControllerOffset += HidControllerHeaderSize; - - ControllerOffset += (int)ControllerLayout * HidControllerLayoutsSize; - - long LastEntry = Memory.ReadInt64Unchecked(ControllerOffset + 0x10); - - long CurrEntry = (LastEntry + 1) % HidEntryCount; - - long Timestamp = GetTimestamp(); - - Memory.WriteInt64Unchecked(ControllerOffset + 0x0, Timestamp); - Memory.WriteInt64Unchecked(ControllerOffset + 0x8, HidEntryCount); - Memory.WriteInt64Unchecked(ControllerOffset + 0x10, CurrEntry); - Memory.WriteInt64Unchecked(ControllerOffset + 0x18, HidEntryCount - 1); - - ControllerOffset += HidControllersLayoutHeaderSize; - - long LastEntryOffset = ControllerOffset + LastEntry * HidControllersInputEntrySize; - - ControllerOffset += CurrEntry * HidControllersInputEntrySize; - - long SampleCounter = Memory.ReadInt64Unchecked(LastEntryOffset) + 1; - - Memory.WriteInt64Unchecked(ControllerOffset + 0x0, SampleCounter); - Memory.WriteInt64Unchecked(ControllerOffset + 0x8, SampleCounter); - - Memory.WriteInt64Unchecked(ControllerOffset + 0x10, (uint)Buttons); - - Memory.WriteInt32Unchecked(ControllerOffset + 0x18, LeftStick.DX); - Memory.WriteInt32Unchecked(ControllerOffset + 0x1c, LeftStick.DY); - - Memory.WriteInt32Unchecked(ControllerOffset + 0x20, RightStick.DX); - Memory.WriteInt32Unchecked(ControllerOffset + 0x24, RightStick.DY); - - Memory.WriteInt64Unchecked(ControllerOffset + 0x28, - (uint)HidControllerConnState.Controller_State_Connected | - (uint)HidControllerConnState.Controller_State_Wired); - } - } - } - - public void SetTouchPoints(params HidTouchPoint[] Points) - { - lock (ShMemLock) - { - foreach ((AMemory Memory, long Position) in ShMemPositions) - { - long TouchScreenOffset = Position + HidTouchScreenOffset; - - long LastEntry = Memory.ReadInt64Unchecked(TouchScreenOffset + 0x10); - - long CurrEntry = (LastEntry + 1) % HidEntryCount; - - long Timestamp = GetTimestamp(); - - Memory.WriteInt64Unchecked(TouchScreenOffset + 0x0, Timestamp); - Memory.WriteInt64Unchecked(TouchScreenOffset + 0x8, HidEntryCount); - Memory.WriteInt64Unchecked(TouchScreenOffset + 0x10, CurrEntry); - Memory.WriteInt64Unchecked(TouchScreenOffset + 0x18, HidEntryCount - 1); - Memory.WriteInt64Unchecked(TouchScreenOffset + 0x20, Timestamp); - - long TouchEntryOffset = TouchScreenOffset + HidTouchHeaderSize; - - long LastEntryOffset = TouchEntryOffset + LastEntry * HidTouchEntrySize; - - long SampleCounter = Memory.ReadInt64Unchecked(LastEntryOffset) + 1; - - TouchEntryOffset += CurrEntry * HidTouchEntrySize; - - Memory.WriteInt64Unchecked(TouchEntryOffset + 0x0, SampleCounter); - Memory.WriteInt64Unchecked(TouchEntryOffset + 0x8, Points.Length); - - TouchEntryOffset += HidTouchEntryHeaderSize; - - const int Padding = 0; - - int Index = 0; - - foreach (HidTouchPoint Point in Points) - { - Memory.WriteInt64Unchecked(TouchEntryOffset + 0x0, Timestamp); - Memory.WriteInt32Unchecked(TouchEntryOffset + 0x8, Padding); - Memory.WriteInt32Unchecked(TouchEntryOffset + 0xc, Index++); - Memory.WriteInt32Unchecked(TouchEntryOffset + 0x10, Point.X); - Memory.WriteInt32Unchecked(TouchEntryOffset + 0x14, Point.Y); - Memory.WriteInt32Unchecked(TouchEntryOffset + 0x18, Point.DiameterX); - Memory.WriteInt32Unchecked(TouchEntryOffset + 0x1c, Point.DiameterY); - Memory.WriteInt32Unchecked(TouchEntryOffset + 0x20, Point.Angle); - Memory.WriteInt32Unchecked(TouchEntryOffset + 0x24, Padding); - - TouchEntryOffset += HidTouchEntryTouchSize; - } - } - } - } - - private static long GetTimestamp() - { - return (long)((ulong)Environment.TickCount * 19_200); - } - } -} diff --git a/Ryujinx.HLE/Hid/HidControllerButtons.cs b/Ryujinx.HLE/Hid/HidControllerButtons.cs deleted file mode 100644 index f41d17e1d1..0000000000 --- a/Ryujinx.HLE/Hid/HidControllerButtons.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; - -namespace Ryujinx.HLE.Input -{ - [Flags] - public enum HidControllerButtons - { - KEY_A = (1 << 0), - KEY_B = (1 << 1), - KEY_X = (1 << 2), - KEY_Y = (1 << 3), - KEY_LSTICK = (1 << 4), - KEY_RSTICK = (1 << 5), - KEY_L = (1 << 6), - KEY_R = (1 << 7), - KEY_ZL = (1 << 8), - KEY_ZR = (1 << 9), - KEY_PLUS = (1 << 10), - KEY_MINUS = (1 << 11), - KEY_DLEFT = (1 << 12), - KEY_DUP = (1 << 13), - KEY_DRIGHT = (1 << 14), - KEY_DDOWN = (1 << 15), - KEY_LSTICK_LEFT = (1 << 16), - KEY_LSTICK_UP = (1 << 17), - KEY_LSTICK_RIGHT = (1 << 18), - KEY_LSTICK_DOWN = (1 << 19), - KEY_RSTICK_LEFT = (1 << 20), - KEY_RSTICK_UP = (1 << 21), - KEY_RSTICK_RIGHT = (1 << 22), - KEY_RSTICK_DOWN = (1 << 23), - KEY_SL = (1 << 24), - KEY_SR = (1 << 25) - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/Hid/HidControllerColorDesc.cs b/Ryujinx.HLE/Hid/HidControllerColorDesc.cs deleted file mode 100644 index b8cf2a5e9b..0000000000 --- a/Ryujinx.HLE/Hid/HidControllerColorDesc.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; - -namespace Ryujinx.HLE.Input -{ - [Flags] - public enum HidControllerColorDesc - { - ColorDesc_ColorsNonexistent = (1 << 1) - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/Hid/HidControllerConnState.cs b/Ryujinx.HLE/Hid/HidControllerConnState.cs deleted file mode 100644 index 1fc9482a0a..0000000000 --- a/Ryujinx.HLE/Hid/HidControllerConnState.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; - -namespace Ryujinx.HLE.Input -{ - [Flags] - public enum HidControllerConnState - { - Controller_State_Connected = (1 << 0), - Controller_State_Wired = (1 << 1) - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/Hid/HidControllerId.cs b/Ryujinx.HLE/Hid/HidControllerId.cs deleted file mode 100644 index e4a0e26cbe..0000000000 --- a/Ryujinx.HLE/Hid/HidControllerId.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace Ryujinx.HLE.Input -{ - public enum HidControllerId - { - CONTROLLER_PLAYER_1 = 0, - CONTROLLER_PLAYER_2 = 1, - CONTROLLER_PLAYER_3 = 2, - CONTROLLER_PLAYER_4 = 3, - CONTROLLER_PLAYER_5 = 4, - CONTROLLER_PLAYER_6 = 5, - CONTROLLER_PLAYER_7 = 6, - CONTROLLER_PLAYER_8 = 7, - CONTROLLER_HANDHELD = 8, - CONTROLLER_UNKNOWN = 9 - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/Hid/HidControllerLayouts.cs b/Ryujinx.HLE/Hid/HidControllerLayouts.cs deleted file mode 100644 index 39fdd3febd..0000000000 --- a/Ryujinx.HLE/Hid/HidControllerLayouts.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Ryujinx.HLE.Input -{ - public enum HidControllerLayouts - { - Pro_Controller = 0, - Handheld_Joined = 1, - Joined = 2, - Left = 3, - Right = 4, - Main_No_Analog = 5, - Main = 6 - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/Hid/HidControllerType.cs b/Ryujinx.HLE/Hid/HidControllerType.cs deleted file mode 100644 index ea8ddfd4e6..0000000000 --- a/Ryujinx.HLE/Hid/HidControllerType.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace Ryujinx.HLE.Input -{ - [Flags] - public enum HidControllerType - { - ControllerType_ProController = (1 << 0), - ControllerType_Handheld = (1 << 1), - ControllerType_JoyconPair = (1 << 2), - ControllerType_JoyconLeft = (1 << 3), - ControllerType_JoyconRight = (1 << 4) - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/Hid/HidJoystickPosition.cs b/Ryujinx.HLE/Hid/HidJoystickPosition.cs deleted file mode 100644 index a06ef7b2c9..0000000000 --- a/Ryujinx.HLE/Hid/HidJoystickPosition.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Ryujinx.HLE.Input -{ - public struct HidJoystickPosition - { - public int DX; - public int DY; - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/Hid/JoyConColor.cs b/Ryujinx.HLE/Hid/JoyConColor.cs deleted file mode 100644 index 514ec21b08..0000000000 --- a/Ryujinx.HLE/Hid/JoyConColor.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace Ryujinx.HLE.Input -{ - public enum JoyConColor //Thanks to CTCaer - { - Black = 0, - - Body_Grey = 0x828282, - Body_Neon_Blue = 0x0AB9E6, - Body_Neon_Red = 0xFF3C28, - Body_Neon_Yellow = 0xE6FF00, - Body_Neon_Pink = 0xFF3278, - Body_Neon_Green = 0x1EDC00, - Body_Red = 0xE10F00, - - Buttons_Grey = 0x0F0F0F, - Buttons_Neon_Blue = 0x001E1E, - Buttons_Neon_Red = 0x1E0A0A, - Buttons_Neon_Yellow = 0x142800, - Buttons_Neon_Pink = 0x28001E, - Buttons_Neon_Green = 0x002800, - Buttons_Red = 0x280A0A - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/Homebrew.npdm b/Ryujinx.HLE/Homebrew.npdm new file mode 100644 index 0000000000..8141161465 Binary files /dev/null and b/Ryujinx.HLE/Homebrew.npdm differ diff --git a/Ryujinx.HLE/Input/Controller/BaseController.cs b/Ryujinx.HLE/Input/Controller/BaseController.cs new file mode 100644 index 0000000000..dfd54a8324 --- /dev/null +++ b/Ryujinx.HLE/Input/Controller/BaseController.cs @@ -0,0 +1,142 @@ +using static Ryujinx.HLE.Input.Hid; + +namespace Ryujinx.HLE.Input +{ + public abstract class BaseController : IHidDevice + { + protected ControllerStatus HidControllerType; + protected ControllerId ControllerId; + + private long _currentLayoutOffset; + private long _mainLayoutOffset; + + protected long DeviceStateOffset => Offset + 0x4188; + + protected Switch Device { get; } + + public long Offset { get; private set; } + public bool Connected { get; protected set; } + + public ControllerHeader Header { get; private set; } + public ControllerStateHeader CurrentStateHeader { get; private set; } + public ControllerDeviceState DeviceState { get; private set; } + public ControllerLayouts CurrentLayout { get; private set; } + public ControllerState LastInputState { get; set; } + public ControllerConnectionState ConnectionState { get; protected set; } + + public BaseController(Switch device, ControllerStatus controllerType) + { + Device = device; + HidControllerType = controllerType; + } + + protected void Initialize( + bool isHalf, + (NpadColor left, NpadColor right) bodyColors, + (NpadColor left, NpadColor right) buttonColors, + ControllerColorDescription singleColorDesc = 0, + ControllerColorDescription splitColorDesc = 0, + NpadColor singleBodyColor = 0, + NpadColor singleButtonColor = 0 + ) + { + Header = new ControllerHeader() + { + IsJoyConHalf = isHalf ? 1 : 0, + LeftBodyColor = bodyColors.left, + LeftButtonColor = buttonColors.left, + RightBodyColor = bodyColors.right, + RightButtonColor = buttonColors.right, + Status = HidControllerType, + SingleBodyColor = singleBodyColor, + SingleButtonColor = singleButtonColor, + SplitColorDescription = splitColorDesc, + SingleColorDescription = singleColorDesc, + }; + + CurrentStateHeader = new ControllerStateHeader + { + EntryCount = HidEntryCount, + MaxEntryCount = HidEntryCount - 1, + CurrentEntryIndex = -1 + }; + + DeviceState = new ControllerDeviceState() + { + PowerInfo0BatteryState = BatteryState.Percent100, + PowerInfo1BatteryState = BatteryState.Percent100, + PowerInfo2BatteryState = BatteryState.Percent100, + DeviceType = ControllerDeviceType.NPadLeftController | ControllerDeviceType.NPadRightController, + DeviceFlags = DeviceFlags.PowerInfo0Connected + | DeviceFlags.PowerInfo1Connected + | DeviceFlags.PowerInfo2Connected + }; + + LastInputState = new ControllerState() + { + SamplesTimestamp = -1, + SamplesTimestamp2 = -1 + }; + } + + public virtual void Connect(ControllerId controllerId) + { + ControllerId = controllerId; + + Offset = Device.Hid.HidPosition + HidControllersOffset + (int)controllerId * HidControllerSize; + + _mainLayoutOffset = Offset + HidControllerHeaderSize + + ((int)ControllerLayouts.Main * HidControllerLayoutsSize); + + Device.Memory.FillWithZeros(Offset, 0x5000); + Device.Memory.WriteStruct(Offset, Header); + Device.Memory.WriteStruct(DeviceStateOffset, DeviceState); + + Connected = true; + } + + public void SetLayout(ControllerLayouts controllerLayout) + { + CurrentLayout = controllerLayout; + + _currentLayoutOffset = Offset + HidControllerHeaderSize + + ((int)controllerLayout * HidControllerLayoutsSize); + } + + public void SendInput( + ControllerButtons buttons, + JoystickPosition leftStick, + JoystickPosition rightStick) + { + ControllerState currentInput = new ControllerState() + { + SamplesTimestamp = (long)LastInputState.SamplesTimestamp + 1, + SamplesTimestamp2 = (long)LastInputState.SamplesTimestamp + 1, + ButtonState = buttons, + ConnectionState = ConnectionState, + LeftStick = leftStick, + RightStick = rightStick + }; + + ControllerStateHeader newInputStateHeader = new ControllerStateHeader + { + EntryCount = HidEntryCount, + MaxEntryCount = HidEntryCount - 1, + CurrentEntryIndex = (CurrentStateHeader.CurrentEntryIndex + 1) % HidEntryCount, + Timestamp = GetTimestamp(), + }; + + Device.Memory.WriteStruct(_currentLayoutOffset, newInputStateHeader); + Device.Memory.WriteStruct(_mainLayoutOffset, newInputStateHeader); + + long currentInputStateOffset = HidControllersLayoutHeaderSize + + newInputStateHeader.CurrentEntryIndex * HidControllersInputEntrySize; + + Device.Memory.WriteStruct(_currentLayoutOffset + currentInputStateOffset, currentInput); + Device.Memory.WriteStruct(_mainLayoutOffset + currentInputStateOffset, currentInput); + + LastInputState = currentInput; + CurrentStateHeader = newInputStateHeader; + } + } +} diff --git a/Ryujinx.HLE/Input/Controller/NpadController.cs b/Ryujinx.HLE/Input/Controller/NpadController.cs new file mode 100644 index 0000000000..b4304b8f3f --- /dev/null +++ b/Ryujinx.HLE/Input/Controller/NpadController.cs @@ -0,0 +1,68 @@ +namespace Ryujinx.HLE.Input +{ + public class NpadController : BaseController + { + private (NpadColor Left, NpadColor Right) _npadBodyColors; + private (NpadColor Left, NpadColor Right) _npadButtonColors; + + private bool _isHalf; + + public NpadController( + ControllerStatus controllerStatus, + Switch device, + (NpadColor, NpadColor) npadBodyColors, + (NpadColor, NpadColor) npadButtonColors) : base(device, controllerStatus) + { + _npadBodyColors = npadBodyColors; + _npadButtonColors = npadButtonColors; + } + + public override void Connect(ControllerId controllerId) + { + if (HidControllerType != ControllerStatus.NpadLeft && HidControllerType != ControllerStatus.NpadRight) + { + _isHalf = false; + } + + ConnectionState = ControllerConnectionState.ControllerStateConnected; + + if (controllerId == ControllerId.ControllerHandheld) + ConnectionState |= ControllerConnectionState.ControllerStateWired; + + ControllerColorDescription singleColorDesc = + ControllerColorDescription.ColorDescriptionColorsNonexistent; + + ControllerColorDescription splitColorDesc = 0; + + NpadColor singleBodyColor = NpadColor.Black; + NpadColor singleButtonColor = NpadColor.Black; + + Initialize(_isHalf, + (_npadBodyColors.Left, _npadBodyColors.Right), + (_npadButtonColors.Left, _npadButtonColors.Right), + singleColorDesc, + splitColorDesc, + singleBodyColor, + singleButtonColor ); + + base.Connect(controllerId); + + var _currentLayout = ControllerLayouts.HandheldJoined; + + switch (HidControllerType) + { + case ControllerStatus.NpadLeft: + _currentLayout = ControllerLayouts.Left; + break; + case ControllerStatus.NpadRight: + _currentLayout = ControllerLayouts.Right; + break; + case ControllerStatus.NpadPair: + _currentLayout = ControllerLayouts.Joined; + break; + } + + SetLayout(_currentLayout); + } + } +} diff --git a/Ryujinx.HLE/Input/Controller/ProController.cs b/Ryujinx.HLE/Input/Controller/ProController.cs new file mode 100644 index 0000000000..ae574260fd --- /dev/null +++ b/Ryujinx.HLE/Input/Controller/ProController.cs @@ -0,0 +1,42 @@ +namespace Ryujinx.HLE.Input +{ + public class ProController : BaseController + { + private bool _wired = false; + + private NpadColor _bodyColor; + private NpadColor _buttonColor; + + public ProController(Switch device, + NpadColor bodyColor, + NpadColor buttonColor) : base(device, ControllerStatus.ProController) + { + _wired = true; + + _bodyColor = bodyColor; + _buttonColor = buttonColor; + } + + public override void Connect(ControllerId controllerId) + { + ControllerColorDescription singleColorDesc = + ControllerColorDescription.ColorDescriptionColorsNonexistent; + + ControllerColorDescription splitColorDesc = 0; + + ConnectionState = ControllerConnectionState.ControllerStateConnected | ControllerConnectionState.ControllerStateWired; + + Initialize(false, + (0, 0), + (0, 0), + singleColorDesc, + splitColorDesc, + _bodyColor, + _buttonColor); + + base.Connect(controllerId); + + SetLayout(ControllerLayouts.ProController); + } + } +} diff --git a/Ryujinx.HLE/Input/Controller/Types/BatteryState.cs b/Ryujinx.HLE/Input/Controller/Types/BatteryState.cs new file mode 100644 index 0000000000..4279d7a03e --- /dev/null +++ b/Ryujinx.HLE/Input/Controller/Types/BatteryState.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.HLE.Input +{ + public enum BatteryState : int + { + // TODO : Check if these are the correct states + Percent0 = 0, + Percent25 = 1, + Percent50 = 2, + Percent75 = 3, + Percent100 = 4 + } +} diff --git a/Ryujinx.HLE/Input/Controller/Types/ControllerButtons.cs b/Ryujinx.HLE/Input/Controller/Types/ControllerButtons.cs new file mode 100644 index 0000000000..879257f265 --- /dev/null +++ b/Ryujinx.HLE/Input/Controller/Types/ControllerButtons.cs @@ -0,0 +1,35 @@ +using System; + +namespace Ryujinx.HLE.Input +{ + [Flags] + public enum ControllerButtons : long + { + A = 1 << 0, + B = 1 << 1, + X = 1 << 2, + Y = 1 << 3, + StickLeft = 1 << 4, + StickRight = 1 << 5, + L = 1 << 6, + R = 1 << 7, + Zl = 1 << 8, + Zr = 1 << 9, + Plus = 1 << 10, + Minus = 1 << 11, + DpadLeft = 1 << 12, + DpadUp = 1 << 13, + DPadRight = 1 << 14, + DpadDown = 1 << 15, + LStickLeft = 1 << 16, + LStickUp = 1 << 17, + LStickRight = 1 << 18, + LStickDown = 1 << 19, + RStickLeft = 1 << 20, + RStickUp = 1 << 21, + RStickRight = 1 << 22, + RStickDown = 1 << 23, + Sl = 1 << 24, + Sr = 1 << 25 + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/Input/Controller/Types/ControllerColorDescription.cs b/Ryujinx.HLE/Input/Controller/Types/ControllerColorDescription.cs new file mode 100644 index 0000000000..c31f41a3f7 --- /dev/null +++ b/Ryujinx.HLE/Input/Controller/Types/ControllerColorDescription.cs @@ -0,0 +1,10 @@ +using System; + +namespace Ryujinx.HLE.Input +{ + [Flags] + public enum ControllerColorDescription : int + { + ColorDescriptionColorsNonexistent = (1 << 1) + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/Input/Controller/Types/ControllerConnectionState.cs b/Ryujinx.HLE/Input/Controller/Types/ControllerConnectionState.cs new file mode 100644 index 0000000000..526da1ff19 --- /dev/null +++ b/Ryujinx.HLE/Input/Controller/Types/ControllerConnectionState.cs @@ -0,0 +1,11 @@ +using System; + +namespace Ryujinx.HLE.Input +{ + [Flags] + public enum ControllerConnectionState : long + { + ControllerStateConnected = (1 << 0), + ControllerStateWired = (1 << 1) + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/Input/Controller/Types/ControllerDeviceState.cs b/Ryujinx.HLE/Input/Controller/Types/ControllerDeviceState.cs new file mode 100644 index 0000000000..45895a1e99 --- /dev/null +++ b/Ryujinx.HLE/Input/Controller/Types/ControllerDeviceState.cs @@ -0,0 +1,18 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.Input +{ + [StructLayout(LayoutKind.Sequential)] + public unsafe struct ControllerDeviceState + { + public ControllerDeviceType DeviceType; + public int Padding; + public DeviceFlags DeviceFlags; + public int UnintendedHomeButtonInputProtectionEnabled; + public BatteryState PowerInfo0BatteryState; + public BatteryState PowerInfo1BatteryState; + public BatteryState PowerInfo2BatteryState; + public fixed byte ControllerMac[16]; + public fixed byte ControllerMac2[16]; + } +} diff --git a/Ryujinx.HLE/Input/Controller/Types/ControllerDeviceType.cs b/Ryujinx.HLE/Input/Controller/Types/ControllerDeviceType.cs new file mode 100644 index 0000000000..8043d8a0ff --- /dev/null +++ b/Ryujinx.HLE/Input/Controller/Types/ControllerDeviceType.cs @@ -0,0 +1,12 @@ +using System; + +namespace Ryujinx.HLE.Input +{ + [Flags] + public enum ControllerDeviceType : int + { + ProController = 1 << 0, + NPadLeftController = 1 << 4, + NPadRightController = 1 << 5, + } +} diff --git a/Ryujinx.HLE/Input/Controller/Types/ControllerHeader.cs b/Ryujinx.HLE/Input/Controller/Types/ControllerHeader.cs new file mode 100644 index 0000000000..cbb5b6f560 --- /dev/null +++ b/Ryujinx.HLE/Input/Controller/Types/ControllerHeader.cs @@ -0,0 +1,19 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.Input +{ + [StructLayout(LayoutKind.Sequential)] + public struct ControllerHeader + { + public ControllerStatus Status; + public int IsJoyConHalf; + public ControllerColorDescription SingleColorDescription; + public NpadColor SingleBodyColor; + public NpadColor SingleButtonColor; + public ControllerColorDescription SplitColorDescription; + public NpadColor RightBodyColor; + public NpadColor RightButtonColor; + public NpadColor LeftBodyColor; + public NpadColor LeftButtonColor; + } +} diff --git a/Ryujinx.HLE/Input/Controller/Types/ControllerId.cs b/Ryujinx.HLE/Input/Controller/Types/ControllerId.cs new file mode 100644 index 0000000000..c82056c68b --- /dev/null +++ b/Ryujinx.HLE/Input/Controller/Types/ControllerId.cs @@ -0,0 +1,16 @@ +namespace Ryujinx.HLE.Input +{ + public enum ControllerId + { + ControllerPlayer1 = 0, + ControllerPlayer2 = 1, + ControllerPlayer3 = 2, + ControllerPlayer4 = 3, + ControllerPlayer5 = 4, + ControllerPlayer6 = 5, + ControllerPlayer7 = 6, + ControllerPlayer8 = 7, + ControllerHandheld = 8, + ControllerUnknown = 9 + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/Input/Controller/Types/ControllerLayouts.cs b/Ryujinx.HLE/Input/Controller/Types/ControllerLayouts.cs new file mode 100644 index 0000000000..fedc0399a9 --- /dev/null +++ b/Ryujinx.HLE/Input/Controller/Types/ControllerLayouts.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.HLE.Input +{ + public enum ControllerLayouts + { + ProController = 0, + HandheldJoined = 1, + Joined = 2, + Left = 3, + Right = 4, + MainNoAnalog = 5, + Main = 6 + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/Input/Controller/Types/ControllerState.cs b/Ryujinx.HLE/Input/Controller/Types/ControllerState.cs new file mode 100644 index 0000000000..4847438ddf --- /dev/null +++ b/Ryujinx.HLE/Input/Controller/Types/ControllerState.cs @@ -0,0 +1,15 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.Input +{ + [StructLayout(LayoutKind.Sequential)] + public struct ControllerState + { + public long SamplesTimestamp; + public long SamplesTimestamp2; + public ControllerButtons ButtonState; + public JoystickPosition LeftStick; + public JoystickPosition RightStick; + public ControllerConnectionState ConnectionState; + } +} diff --git a/Ryujinx.HLE/Input/Controller/Types/ControllerStateHeader.cs b/Ryujinx.HLE/Input/Controller/Types/ControllerStateHeader.cs new file mode 100644 index 0000000000..f885c00c75 --- /dev/null +++ b/Ryujinx.HLE/Input/Controller/Types/ControllerStateHeader.cs @@ -0,0 +1,13 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.Input +{ + [StructLayout(LayoutKind.Sequential)] + public struct ControllerStateHeader + { + public long Timestamp; + public long EntryCount; + public long CurrentEntryIndex; + public long MaxEntryCount; + } +} diff --git a/Ryujinx.HLE/Input/Controller/Types/ControllerStatus.cs b/Ryujinx.HLE/Input/Controller/Types/ControllerStatus.cs new file mode 100644 index 0000000000..9444d7b0e5 --- /dev/null +++ b/Ryujinx.HLE/Input/Controller/Types/ControllerStatus.cs @@ -0,0 +1,14 @@ +using System; + +namespace Ryujinx.HLE.Input +{ + [Flags] + public enum ControllerStatus : int + { + ProController = 1 << 0, + Handheld = 1 << 1, + NpadPair = 1 << 2, + NpadLeft = 1 << 3, + NpadRight = 1 << 4 + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/Input/Controller/Types/DeviceFlags.cs b/Ryujinx.HLE/Input/Controller/Types/DeviceFlags.cs new file mode 100644 index 0000000000..5391317570 --- /dev/null +++ b/Ryujinx.HLE/Input/Controller/Types/DeviceFlags.cs @@ -0,0 +1,22 @@ +using System; + +namespace Ryujinx.HLE.Input +{ + [Flags] + public enum DeviceFlags : long + { + PowerInfo0Charging = 1 << 0, + PowerInfo1Charging = 1 << 1, + PowerInfo2Charging = 1 << 2, + PowerInfo0Connected = 1 << 3, + PowerInfo1Connected = 1 << 4, + PowerInfo2Connected = 1 << 5, + UnsupportedButtonPressedNpadSystem = 1 << 9, + UnsupportedButtonPressedNpadSystemExt = 1 << 10, + AbxyButtonOriented = 1 << 11, + SlSrButtonOriented = 1 << 12, + PlusButtonCapability = 1 << 13, + MinusButtonCapability = 1 << 14, + DirectionalButtonsSupported = 1 << 15 + } +} diff --git a/Ryujinx.HLE/Input/Controller/Types/HotkeyButtons.cs b/Ryujinx.HLE/Input/Controller/Types/HotkeyButtons.cs new file mode 100644 index 0000000000..be76ee1e34 --- /dev/null +++ b/Ryujinx.HLE/Input/Controller/Types/HotkeyButtons.cs @@ -0,0 +1,10 @@ +using System; + +namespace Ryujinx.HLE.Input +{ + [Flags] + public enum HotkeyButtons + { + ToggleVSync = 1 << 0, + } +} diff --git a/Ryujinx.HLE/Input/Controller/Types/JoystickPosition.cs b/Ryujinx.HLE/Input/Controller/Types/JoystickPosition.cs new file mode 100644 index 0000000000..1442bc60cd --- /dev/null +++ b/Ryujinx.HLE/Input/Controller/Types/JoystickPosition.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.Input +{ + public struct JoystickPosition + { + public int Dx; + public int Dy; + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/Input/Controller/Types/NpadColor.cs b/Ryujinx.HLE/Input/Controller/Types/NpadColor.cs new file mode 100644 index 0000000000..a60f94aae9 --- /dev/null +++ b/Ryujinx.HLE/Input/Controller/Types/NpadColor.cs @@ -0,0 +1,23 @@ +namespace Ryujinx.HLE.Input +{ + public enum NpadColor : int //Thanks to CTCaer + { + Black = 0, + + BodyGrey = 0x828282, + BodyNeonBlue = 0x0AB9E6, + BodyNeonRed = 0xFF3C28, + BodyNeonYellow = 0xE6FF00, + BodyNeonPink = 0xFF3278, + BodyNeonGreen = 0x1EDC00, + BodyRed = 0xE10F00, + + ButtonsGrey = 0x0F0F0F, + ButtonsNeonBlue = 0x001E1E, + ButtonsNeonRed = 0x1E0A0A, + ButtonsNeonYellow = 0x142800, + ButtonsNeonPink = 0x28001E, + ButtonsNeonGreen = 0x002800, + ButtonsRed = 0x280A0A + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/Input/Hid.cs b/Ryujinx.HLE/Input/Hid.cs new file mode 100644 index 0000000000..5cb7f09de6 --- /dev/null +++ b/Ryujinx.HLE/Input/Hid.cs @@ -0,0 +1,218 @@ +using Ryujinx.Common; +using Ryujinx.Configuration.Hid; +using Ryujinx.HLE.HOS; +using System; + +namespace Ryujinx.HLE.Input +{ + public partial class Hid + { + private Switch _device; + + private long _touchScreenOffset; + private long _touchEntriesOffset; + private long _keyboardOffset; + + private TouchHeader _currentTouchHeader; + private KeyboardHeader _currentKeyboardHeader; + private KeyboardEntry _currentKeyboardEntry; + + public BaseController PrimaryController { get; private set; } + + internal long HidPosition; + + public Hid(Switch device, long hidPosition) + { + _device = device; + HidPosition = hidPosition; + + device.Memory.FillWithZeros(hidPosition, Horizon.HidSize); + + _currentTouchHeader = new TouchHeader() + { + CurrentEntryIndex = -1, + }; + + _currentKeyboardHeader = new KeyboardHeader() + { + CurrentEntryIndex = -1, + }; + + _currentKeyboardEntry = new KeyboardEntry() + { + SamplesTimestamp = -1, + SamplesTimestamp2 = -1 + }; + + _touchScreenOffset = HidPosition + HidTouchScreenOffset; + _touchEntriesOffset = _touchScreenOffset + HidTouchHeaderSize; + _keyboardOffset = HidPosition + HidKeyboardOffset; + } + + private static ControllerStatus ConvertControllerTypeToState(ControllerType controllerType) + { + switch (controllerType) + { + case ControllerType.Handheld: return ControllerStatus.Handheld; + case ControllerType.NpadLeft: return ControllerStatus.NpadLeft; + case ControllerType.NpadRight: return ControllerStatus.NpadRight; + case ControllerType.NpadPair: return ControllerStatus.NpadPair; + case ControllerType.ProController: return ControllerStatus.ProController; + default: throw new NotImplementedException(); + } + } + + public void InitializePrimaryController(ControllerType controllerType) + { + ControllerId controllerId = controllerType == ControllerType.Handheld ? + ControllerId.ControllerHandheld : ControllerId.ControllerPlayer1; + + if (controllerType == ControllerType.ProController) + { + PrimaryController = new ProController(_device, NpadColor.Black, NpadColor.Black); + } + else + { + PrimaryController = new NpadController(ConvertControllerTypeToState(controllerType), + _device, + (NpadColor.BodyNeonRed, NpadColor.BodyNeonRed), + (NpadColor.ButtonsNeonBlue, NpadColor.ButtonsNeonBlue)); + } + + PrimaryController.Connect(controllerId); + } + + public ControllerButtons UpdateStickButtons( + JoystickPosition leftStick, + JoystickPosition rightStick) + { + ControllerButtons result = 0; + + if (rightStick.Dx < 0) + { + result |= ControllerButtons.RStickLeft; + } + + if (rightStick.Dx > 0) + { + result |= ControllerButtons.RStickRight; + } + + if (rightStick.Dy < 0) + { + result |= ControllerButtons.RStickDown; + } + + if (rightStick.Dy > 0) + { + result |= ControllerButtons.RStickUp; + } + + if (leftStick.Dx < 0) + { + result |= ControllerButtons.LStickLeft; + } + + if (leftStick.Dx > 0) + { + result |= ControllerButtons.LStickRight; + } + + if (leftStick.Dy < 0) + { + result |= ControllerButtons.LStickDown; + } + + if (leftStick.Dy > 0) + { + result |= ControllerButtons.LStickUp; + } + + return result; + } + public void SetTouchPoints(params TouchPoint[] points) + { + long timestamp = GetTimestamp(); + long sampleCounter = _currentTouchHeader.SamplesTimestamp + 1; + + var newTouchHeader = new TouchHeader + { + CurrentEntryIndex = (_currentTouchHeader.CurrentEntryIndex + 1) % HidEntryCount, + EntryCount = HidEntryCount, + MaxEntries = HidEntryCount - 1, + SamplesTimestamp = sampleCounter, + Timestamp = timestamp, + }; + + long currentTouchEntryOffset = _touchEntriesOffset + newTouchHeader.CurrentEntryIndex * HidTouchEntrySize; + + TouchEntry touchEntry = new TouchEntry() + { + SamplesTimestamp = sampleCounter, + TouchCount = points.Length + }; + + _device.Memory.WriteStruct(currentTouchEntryOffset, touchEntry); + + currentTouchEntryOffset += HidTouchEntryHeaderSize; + + for (int i = 0; i < points.Length; i++) + { + TouchData touch = new TouchData() + { + Angle = points[i].Angle, + DiameterX = points[i].DiameterX, + DiameterY = points[i].DiameterY, + Index = i, + SampleTimestamp = sampleCounter, + X = points[i].X, + Y = points[i].Y + }; + + _device.Memory.WriteStruct(currentTouchEntryOffset, touch); + + currentTouchEntryOffset += HidTouchEntryTouchSize; + } + + _device.Memory.WriteStruct(_touchScreenOffset, newTouchHeader); + + _currentTouchHeader = newTouchHeader; + } + + public unsafe void WriteKeyboard(Keyboard keyboard) + { + long timestamp = GetTimestamp(); + + var newKeyboardHeader = new KeyboardHeader() + { + CurrentEntryIndex = (_currentKeyboardHeader.CurrentEntryIndex + 1) % HidEntryCount, + EntryCount = HidEntryCount, + MaxEntries = HidEntryCount - 1, + Timestamp = timestamp, + }; + + _device.Memory.WriteStruct(_keyboardOffset, newKeyboardHeader); + + long keyboardEntryOffset = _keyboardOffset + HidKeyboardHeaderSize; + keyboardEntryOffset += newKeyboardHeader.CurrentEntryIndex * HidKeyboardEntrySize; + + var newkeyboardEntry = new KeyboardEntry() + { + SamplesTimestamp = _currentKeyboardEntry.SamplesTimestamp + 1, + SamplesTimestamp2 = _currentKeyboardEntry.SamplesTimestamp2 + 1, + Keys = keyboard.Keys, + Modifier = keyboard.Modifier, + }; + + _device.Memory.WriteStruct(keyboardEntryOffset, newkeyboardEntry); + + _currentKeyboardEntry = newkeyboardEntry; + _currentKeyboardHeader = newKeyboardHeader; + } + + internal static long GetTimestamp() + { + return PerformanceCounter.ElapsedMilliseconds * 19200; + } + } +} diff --git a/Ryujinx.HLE/Input/HidValues.cs b/Ryujinx.HLE/Input/HidValues.cs new file mode 100644 index 0000000000..06fe8fc0d4 --- /dev/null +++ b/Ryujinx.HLE/Input/HidValues.cs @@ -0,0 +1,63 @@ +namespace Ryujinx.HLE.Input +{ + public partial class Hid + { + /* + * Reference: + * https://github.com/reswitched/libtransistor/blob/development/lib/hid.c + * https://github.com/reswitched/libtransistor/blob/development/include/libtransistor/hid.h + * https://github.com/switchbrew/libnx/blob/master/nx/source/services/hid.c + * https://github.com/switchbrew/libnx/blob/master/nx/include/switch/services/hid.h + */ + + internal const int HidHeaderSize = 0x400; + internal const int HidTouchScreenSize = 0x3000; + internal const int HidMouseSize = 0x400; + internal const int HidKeyboardSize = 0x400; + internal const int HidUnkSection1Size = 0x400; + internal const int HidUnkSection2Size = 0x400; + internal const int HidUnkSection3Size = 0x400; + internal const int HidUnkSection4Size = 0x400; + internal const int HidUnkSection5Size = 0x200; + internal const int HidUnkSection6Size = 0x200; + internal const int HidUnkSection7Size = 0x200; + internal const int HidUnkSection8Size = 0x800; + internal const int HidControllerSerialsSize = 0x4000; + internal const int HidControllersSize = 0x32000; + internal const int HidUnkSection9Size = 0x800; + + internal const int HidKeyboardHeaderSize = 0x20; + internal const int HidKeyboardEntrySize = 0x38; + + internal const int HidTouchHeaderSize = 0x28; + internal const int HidTouchEntrySize = 0x298; + + internal const int HidTouchEntryHeaderSize = 0x10; + internal const int HidTouchEntryTouchSize = 0x28; + + internal const int HidControllerSize = 0x5000; + internal const int HidControllerHeaderSize = 0x28; + internal const int HidControllerLayoutsSize = 0x350; + + internal const int HidControllersLayoutHeaderSize = 0x20; + internal const int HidControllersInputEntrySize = 0x30; + + internal const int HidHeaderOffset = 0; + internal const int HidTouchScreenOffset = HidHeaderOffset + HidHeaderSize; + internal const int HidMouseOffset = HidTouchScreenOffset + HidTouchScreenSize; + internal const int HidKeyboardOffset = HidMouseOffset + HidMouseSize; + internal const int HidUnkSection1Offset = HidKeyboardOffset + HidKeyboardSize; + internal const int HidUnkSection2Offset = HidUnkSection1Offset + HidUnkSection1Size; + internal const int HidUnkSection3Offset = HidUnkSection2Offset + HidUnkSection2Size; + internal const int HidUnkSection4Offset = HidUnkSection3Offset + HidUnkSection3Size; + internal const int HidUnkSection5Offset = HidUnkSection4Offset + HidUnkSection4Size; + internal const int HidUnkSection6Offset = HidUnkSection5Offset + HidUnkSection5Size; + internal const int HidUnkSection7Offset = HidUnkSection6Offset + HidUnkSection6Size; + internal const int HidUnkSection8Offset = HidUnkSection7Offset + HidUnkSection7Size; + internal const int HidControllerSerialsOffset = HidUnkSection8Offset + HidUnkSection8Size; + internal const int HidControllersOffset = HidControllerSerialsOffset + HidControllerSerialsSize; + internal const int HidUnkSection9Offset = HidControllersOffset + HidControllersSize; + + internal const int HidEntryCount = 17; + } +} diff --git a/Ryujinx.HLE/Input/IHidDevice.cs b/Ryujinx.HLE/Input/IHidDevice.cs new file mode 100644 index 0000000000..0b07e767fc --- /dev/null +++ b/Ryujinx.HLE/Input/IHidDevice.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.Input +{ + interface IHidDevice + { + long Offset { get; } + bool Connected { get; } + } +} diff --git a/Ryujinx.HLE/Input/Keyboard/Keyboard.cs b/Ryujinx.HLE/Input/Keyboard/Keyboard.cs new file mode 100644 index 0000000000..7220e51871 --- /dev/null +++ b/Ryujinx.HLE/Input/Keyboard/Keyboard.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.Input +{ + public struct Keyboard + { + public int Modifier; + public int[] Keys; + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/Input/Keyboard/KeyboardEntry.cs b/Ryujinx.HLE/Input/Keyboard/KeyboardEntry.cs new file mode 100644 index 0000000000..be7d9399d1 --- /dev/null +++ b/Ryujinx.HLE/Input/Keyboard/KeyboardEntry.cs @@ -0,0 +1,15 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.Input +{ + [StructLayout(LayoutKind.Sequential)] + public struct KeyboardEntry + { + public long SamplesTimestamp; + public long SamplesTimestamp2; + public long Modifier; + + [MarshalAs(UnmanagedType.ByValArray , SizeConst = 0x8)] + public int[] Keys; + } +} diff --git a/Ryujinx.HLE/Input/Keyboard/KeyboardHeader.cs b/Ryujinx.HLE/Input/Keyboard/KeyboardHeader.cs new file mode 100644 index 0000000000..882ccbabe3 --- /dev/null +++ b/Ryujinx.HLE/Input/Keyboard/KeyboardHeader.cs @@ -0,0 +1,13 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.Input +{ + [StructLayout(LayoutKind.Sequential)] + public struct KeyboardHeader + { + public long Timestamp; + public long EntryCount; + public long CurrentEntryIndex; + public long MaxEntries; + } +} diff --git a/Ryujinx.HLE/Input/Touch/TouchData.cs b/Ryujinx.HLE/Input/Touch/TouchData.cs new file mode 100644 index 0000000000..8489ef7095 --- /dev/null +++ b/Ryujinx.HLE/Input/Touch/TouchData.cs @@ -0,0 +1,18 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.Input +{ + [StructLayout(LayoutKind.Sequential)] + public struct TouchData + { + public long SampleTimestamp; + public int Padding; + public int Index; + public int X; + public int Y; + public int DiameterX; + public int DiameterY; + public int Angle; + public int Padding2; + } +} diff --git a/Ryujinx.HLE/Input/Touch/TouchEntry.cs b/Ryujinx.HLE/Input/Touch/TouchEntry.cs new file mode 100644 index 0000000000..2ef09d7523 --- /dev/null +++ b/Ryujinx.HLE/Input/Touch/TouchEntry.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.Input +{ + [StructLayout(LayoutKind.Sequential)] + public unsafe struct TouchEntry + { + public long SamplesTimestamp; + public long TouchCount; + } +} diff --git a/Ryujinx.HLE/Input/Touch/TouchHeader.cs b/Ryujinx.HLE/Input/Touch/TouchHeader.cs new file mode 100644 index 0000000000..dd93137c2b --- /dev/null +++ b/Ryujinx.HLE/Input/Touch/TouchHeader.cs @@ -0,0 +1,14 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.Input +{ + [StructLayout(LayoutKind.Sequential)] + public struct TouchHeader + { + public long Timestamp; + public long EntryCount; + public long CurrentEntryIndex; + public long MaxEntries; + public long SamplesTimestamp; + } +} diff --git a/Ryujinx.HLE/Hid/HidTouchPoint.cs b/Ryujinx.HLE/Input/Touch/TouchPoint.cs similarity index 84% rename from Ryujinx.HLE/Hid/HidTouchPoint.cs rename to Ryujinx.HLE/Input/Touch/TouchPoint.cs index 25412456bb..a9b095de2c 100644 --- a/Ryujinx.HLE/Hid/HidTouchPoint.cs +++ b/Ryujinx.HLE/Input/Touch/TouchPoint.cs @@ -1,6 +1,6 @@ namespace Ryujinx.HLE.Input { - public struct HidTouchPoint + public struct TouchPoint { public int X; public int Y; diff --git a/Ryujinx.HLE/Loaders/Compression/BackwardsLz.cs b/Ryujinx.HLE/Loaders/Compression/BackwardsLz.cs new file mode 100644 index 0000000000..0a76dc950c --- /dev/null +++ b/Ryujinx.HLE/Loaders/Compression/BackwardsLz.cs @@ -0,0 +1,107 @@ +using System; + +namespace Ryujinx.HLE.Loaders.Compression +{ + static class BackwardsLz + { + private class BackwardsReader + { + private byte[] _data; + + private int _position; + + public int Position => _position; + + public BackwardsReader(byte[] data, int end) + { + _data = data; + _position = end; + } + + public void SeekCurrent(int offset) + { + _position += offset; + } + + public byte ReadByte() + { + return _data[--_position]; + } + + public short ReadInt16() + { + return (short)((ReadByte() << 8) | (ReadByte() << 0)); + } + + public int ReadInt32() + { + return ((ReadByte() << 24) | + (ReadByte() << 16) | + (ReadByte() << 8) | + (ReadByte() << 0)); + } + } + + public static void DecompressInPlace(byte[] buffer, int headerEnd) + { + BackwardsReader reader = new BackwardsReader(buffer, headerEnd); + + int additionalDecLength = reader.ReadInt32(); + int startOffset = reader.ReadInt32(); + int compressedLength = reader.ReadInt32(); + + reader.SeekCurrent(12 - startOffset); + + int decBase = headerEnd - compressedLength; + + int decPos = compressedLength + additionalDecLength; + + byte mask = 0; + byte header = 0; + + while (decPos > 0) + { + if ((mask >>= 1) == 0) + { + header = reader.ReadByte(); + mask = 0x80; + } + + if ((header & mask) == 0) + { + buffer[decBase + --decPos] = reader.ReadByte(); + } + else + { + ushort pair = (ushort)reader.ReadInt16(); + + int length = (pair >> 12) + 3; + int position = (pair & 0xfff) + 3; + + if (length > decPos) + { + length = decPos; + } + + decPos -= length; + + int dstPos = decBase + decPos; + + if (length <= position) + { + int srcPos = dstPos + position; + + Buffer.BlockCopy(buffer, srcPos, buffer, dstPos, length); + } + else + { + for (int offset = 0; offset < length; offset++) + { + buffer[dstPos + offset] = buffer[dstPos + position + offset]; + } + } + } + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/Loaders/Compression/Lz4.cs b/Ryujinx.HLE/Loaders/Compression/Lz4.cs index cfb4955172..2001a8dc93 100644 --- a/Ryujinx.HLE/Loaders/Compression/Lz4.cs +++ b/Ryujinx.HLE/Loaders/Compression/Lz4.cs @@ -4,75 +4,75 @@ namespace Ryujinx.HLE.Loaders.Compression { static class Lz4 { - public static byte[] Decompress(byte[] Cmp, int DecLength) + public static byte[] Decompress(byte[] cmp, int decLength) { - byte[] Dec = new byte[DecLength]; + byte[] dec = new byte[decLength]; - int CmpPos = 0; - int DecPos = 0; + int cmpPos = 0; + int decPos = 0; - int GetLength(int Length) + int GetLength(int length) { - byte Sum; + byte sum; - if (Length == 0xf) + if (length == 0xf) { do { - Length += (Sum = Cmp[CmpPos++]); + length += (sum = cmp[cmpPos++]); } - while (Sum == 0xff); + while (sum == 0xff); } - return Length; + return length; } do { - byte Token = Cmp[CmpPos++]; + byte token = cmp[cmpPos++]; - int EncCount = (Token >> 0) & 0xf; - int LitCount = (Token >> 4) & 0xf; + int encCount = (token >> 0) & 0xf; + int litCount = (token >> 4) & 0xf; - //Copy literal chunck - LitCount = GetLength(LitCount); + // Copy literal chunk + litCount = GetLength(litCount); - Buffer.BlockCopy(Cmp, CmpPos, Dec, DecPos, LitCount); + Buffer.BlockCopy(cmp, cmpPos, dec, decPos, litCount); - CmpPos += LitCount; - DecPos += LitCount; + cmpPos += litCount; + decPos += litCount; - if (CmpPos >= Cmp.Length) + if (cmpPos >= cmp.Length) { break; } - //Copy compressed chunck - int Back = Cmp[CmpPos++] << 0 | - Cmp[CmpPos++] << 8; + // Copy compressed chunk + int back = cmp[cmpPos++] << 0 | + cmp[cmpPos++] << 8; - EncCount = GetLength(EncCount) + 4; + encCount = GetLength(encCount) + 4; - int EncPos = DecPos - Back; + int encPos = decPos - back; - if (EncCount <= Back) + if (encCount <= back) { - Buffer.BlockCopy(Dec, EncPos, Dec, DecPos, EncCount); + Buffer.BlockCopy(dec, encPos, dec, decPos, encCount); - DecPos += EncCount; + decPos += encCount; } else { - while (EncCount-- > 0) + while (encCount-- > 0) { - Dec[DecPos++] = Dec[EncPos++]; + dec[decPos++] = dec[encPos++]; } } } - while (CmpPos < Cmp.Length && - DecPos < Dec.Length); + while (cmpPos < cmp.Length && + decPos < dec.Length); - return Dec; + return dec; } } } \ No newline at end of file diff --git a/Ryujinx.HLE/Loaders/Elf/ElfDynamic.cs b/Ryujinx.HLE/Loaders/Elf/ElfDynamic.cs new file mode 100644 index 0000000000..f489e85aff --- /dev/null +++ b/Ryujinx.HLE/Loaders/Elf/ElfDynamic.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.HLE.Loaders.Elf +{ + struct ElfDynamic + { + public ElfDynamicTag Tag { get; private set; } + + public long Value { get; private set; } + + public ElfDynamic(ElfDynamicTag tag, long value) + { + Tag = tag; + Value = value; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/Loaders/ElfDynTag.cs b/Ryujinx.HLE/Loaders/Elf/ElfDynamicTag.cs similarity index 94% rename from Ryujinx.HLE/Loaders/ElfDynTag.cs rename to Ryujinx.HLE/Loaders/Elf/ElfDynamicTag.cs index 5915d4d123..eb37d612df 100644 --- a/Ryujinx.HLE/Loaders/ElfDynTag.cs +++ b/Ryujinx.HLE/Loaders/Elf/ElfDynamicTag.cs @@ -1,6 +1,9 @@ -namespace Ryujinx.HLE.Loaders +using System.Diagnostics.CodeAnalysis; + +namespace Ryujinx.HLE.Loaders.Elf { - enum ElfDynTag + [SuppressMessage("ReSharper", "InconsistentNaming")] + enum ElfDynamicTag { DT_NULL = 0, DT_NEEDED = 1, diff --git a/Ryujinx.HLE/Loaders/Elf/ElfSymbol.cs b/Ryujinx.HLE/Loaders/Elf/ElfSymbol.cs new file mode 100644 index 0000000000..9961afe195 --- /dev/null +++ b/Ryujinx.HLE/Loaders/Elf/ElfSymbol.cs @@ -0,0 +1,40 @@ +namespace Ryujinx.HLE.Loaders.Elf +{ + struct ElfSymbol + { + public string Name { get; private set; } + + public ElfSymbolType Type { get; private set; } + public ElfSymbolBinding Binding { get; private set; } + public ElfSymbolVisibility Visibility { get; private set; } + + public bool IsFuncOrObject => + Type == ElfSymbolType.SttFunc || + Type == ElfSymbolType.SttObject; + + public bool IsGlobalOrWeak => + Binding == ElfSymbolBinding.StbGlobal || + Binding == ElfSymbolBinding.StbWeak; + + public int ShIdx { get; private set; } + public ulong Value { get; private set; } + public ulong Size { get; private set; } + + public ElfSymbol( + string name, + int info, + int other, + int shIdx, + ulong value, + ulong size) + { + Name = name; + Type = (ElfSymbolType)(info & 0xf); + Binding = (ElfSymbolBinding)(info >> 4); + Visibility = (ElfSymbolVisibility)other; + ShIdx = shIdx; + Value = value; + Size = size; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/Loaders/Elf/ElfSymbol32.cs b/Ryujinx.HLE/Loaders/Elf/ElfSymbol32.cs new file mode 100644 index 0000000000..b7d9850636 --- /dev/null +++ b/Ryujinx.HLE/Loaders/Elf/ElfSymbol32.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.HLE.Loaders.Elf +{ + struct ElfSymbol32 + { + public uint NameOffset; + public uint ValueAddress; + public uint Size; + public char Info; + public char Other; + public ushort SectionIndex; + } +} diff --git a/Ryujinx.HLE/Loaders/Elf/ElfSymbol64.cs b/Ryujinx.HLE/Loaders/Elf/ElfSymbol64.cs new file mode 100644 index 0000000000..662de1ff1b --- /dev/null +++ b/Ryujinx.HLE/Loaders/Elf/ElfSymbol64.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.HLE.Loaders.Elf +{ + struct ElfSymbol64 + { + public uint NameOffset; + public char Info; + public char Other; + public ushort SectionIndex; + public ulong ValueAddress; + public ulong Size; + } +} diff --git a/Ryujinx.HLE/Loaders/Elf/ElfSymbolBinding.cs b/Ryujinx.HLE/Loaders/Elf/ElfSymbolBinding.cs new file mode 100644 index 0000000000..92274fded4 --- /dev/null +++ b/Ryujinx.HLE/Loaders/Elf/ElfSymbolBinding.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.Loaders.Elf +{ + enum ElfSymbolBinding + { + StbLocal = 0, + StbGlobal = 1, + StbWeak = 2 + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/Loaders/Elf/ElfSymbolType.cs b/Ryujinx.HLE/Loaders/Elf/ElfSymbolType.cs new file mode 100644 index 0000000000..4110d4c3ec --- /dev/null +++ b/Ryujinx.HLE/Loaders/Elf/ElfSymbolType.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.HLE.Loaders.Elf +{ + enum ElfSymbolType + { + SttNoType = 0, + SttObject = 1, + SttFunc = 2, + SttSection = 3, + SttFile = 4, + SttCommon = 5, + SttTls = 6 + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/Loaders/Elf/ElfSymbolVisibility.cs b/Ryujinx.HLE/Loaders/Elf/ElfSymbolVisibility.cs new file mode 100644 index 0000000000..f026fca899 --- /dev/null +++ b/Ryujinx.HLE/Loaders/Elf/ElfSymbolVisibility.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.Loaders.Elf +{ + enum ElfSymbolVisibility + { + StvDefault = 0, + StvInternal = 1, + StvHidden = 2, + StvProtected = 3 + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/Loaders/ElfDyn.cs b/Ryujinx.HLE/Loaders/ElfDyn.cs deleted file mode 100644 index 3508e6e4e0..0000000000 --- a/Ryujinx.HLE/Loaders/ElfDyn.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Ryujinx.HLE.Loaders -{ - struct ElfDyn - { - public ElfDynTag Tag { get; private set; } - - public long Value { get; private set; } - - public ElfDyn(ElfDynTag Tag, long Value) - { - this.Tag = Tag; - this.Value = Value; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/Loaders/ElfRel.cs b/Ryujinx.HLE/Loaders/ElfRel.cs deleted file mode 100644 index cfc31d8910..0000000000 --- a/Ryujinx.HLE/Loaders/ElfRel.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace Ryujinx.HLE.Loaders -{ - struct ElfRel - { - public long Offset { get; private set; } - public long Addend { get; private set; } - - public ElfSym Symbol { get; private set; } - public ElfRelType Type { get; private set; } - - public ElfRel(long Offset, long Addend, ElfSym Symbol, ElfRelType Type) - { - this.Offset = Offset; - this.Addend = Addend; - this.Symbol = Symbol; - this.Type = Type; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/Loaders/ElfRelType.cs b/Ryujinx.HLE/Loaders/ElfRelType.cs deleted file mode 100644 index 7da5eec363..0000000000 --- a/Ryujinx.HLE/Loaders/ElfRelType.cs +++ /dev/null @@ -1,128 +0,0 @@ -namespace Ryujinx.HLE.Loaders -{ - enum ElfRelType - { - R_AARCH64_NONE = 0, - R_AARCH64_ABS64 = 257, - R_AARCH64_ABS32 = 258, - R_AARCH64_ABS16 = 259, - R_AARCH64_PREL64 = 260, - R_AARCH64_PREL32 = 261, - R_AARCH64_PREL16 = 262, - R_AARCH64_MOVW_UABS_G0 = 263, - R_AARCH64_MOVW_UABS_G0_NC = 264, - R_AARCH64_MOVW_UABS_G1 = 265, - R_AARCH64_MOVW_UABS_G1_NC = 266, - R_AARCH64_MOVW_UABS_G2 = 267, - R_AARCH64_MOVW_UABS_G2_NC = 268, - R_AARCH64_MOVW_UABS_G3 = 269, - R_AARCH64_MOVW_SABS_G0 = 270, - R_AARCH64_MOVW_SABS_G1 = 271, - R_AARCH64_MOVW_SABS_G2 = 272, - R_AARCH64_LD_PREL_LO19 = 273, - R_AARCH64_ADR_PREL_LO21 = 274, - R_AARCH64_ADR_PREL_PG_HI21 = 275, - R_AARCH64_ADR_PREL_PG_HI21_NC = 276, - R_AARCH64_ADD_ABS_LO12_NC = 277, - R_AARCH64_LDST8_ABS_LO12_NC = 278, - R_AARCH64_TSTBR14 = 279, - R_AARCH64_CONDBR19 = 280, - R_AARCH64_JUMP26 = 282, - R_AARCH64_CALL26 = 283, - R_AARCH64_LDST16_ABS_LO12_NC = 284, - R_AARCH64_LDST32_ABS_LO12_NC = 285, - R_AARCH64_LDST64_ABS_LO12_NC = 286, - R_AARCH64_MOVW_PREL_G0 = 287, - R_AARCH64_MOVW_PREL_G0_NC = 288, - R_AARCH64_MOVW_PREL_G1 = 289, - R_AARCH64_MOVW_PREL_G1_NC = 290, - R_AARCH64_MOVW_PREL_G2 = 291, - R_AARCH64_MOVW_PREL_G2_NC = 292, - R_AARCH64_MOVW_PREL_G3 = 293, - R_AARCH64_LDST128_ABS_LO12_NC = 299, - R_AARCH64_MOVW_GOTOFF_G0 = 300, - R_AARCH64_MOVW_GOTOFF_G0_NC = 301, - R_AARCH64_MOVW_GOTOFF_G1 = 302, - R_AARCH64_MOVW_GOTOFF_G1_NC = 303, - R_AARCH64_MOVW_GOTOFF_G2 = 304, - R_AARCH64_MOVW_GOTOFF_G2_NC = 305, - R_AARCH64_MOVW_GOTOFF_G3 = 306, - R_AARCH64_GOTREL64 = 307, - R_AARCH64_GOTREL32 = 308, - R_AARCH64_GOT_LD_PREL19 = 309, - R_AARCH64_LD64_GOTOFF_LO15 = 310, - R_AARCH64_ADR_GOT_PAGE = 311, - R_AARCH64_LD64_GOT_LO12_NC = 312, - R_AARCH64_LD64_GOTPAGE_LO15 = 313, - R_AARCH64_TLSGD_ADR_PREL21 = 512, - R_AARCH64_TLSGD_ADR_PAGE21 = 513, - R_AARCH64_TLSGD_ADD_LO12_NC = 514, - R_AARCH64_TLSGD_MOVW_G1 = 515, - R_AARCH64_TLSGD_MOVW_G0_NC = 516, - R_AARCH64_TLSLD_ADR_PREL21 = 517, - R_AARCH64_TLSLD_ADR_PAGE21 = 518, - R_AARCH64_TLSLD_ADD_LO12_NC = 519, - R_AARCH64_TLSLD_MOVW_G1 = 520, - R_AARCH64_TLSLD_MOVW_G0_NC = 521, - R_AARCH64_TLSLD_LD_PREL19 = 522, - R_AARCH64_TLSLD_MOVW_DTPREL_G2 = 523, - R_AARCH64_TLSLD_MOVW_DTPREL_G1 = 524, - R_AARCH64_TLSLD_MOVW_DTPREL_G1_NC = 525, - R_AARCH64_TLSLD_MOVW_DTPREL_G0 = 526, - R_AARCH64_TLSLD_MOVW_DTPREL_G0_NC = 527, - R_AARCH64_TLSLD_ADD_DTPREL_HI12 = 528, - R_AARCH64_TLSLD_ADD_DTPREL_LO12 = 529, - R_AARCH64_TLSLD_ADD_DTPREL_LO12_NC = 530, - R_AARCH64_TLSLD_LDST8_DTPREL_LO12 = 531, - R_AARCH64_TLSLD_LDST8_DTPREL_LO12_NC = 532, - R_AARCH64_TLSLD_LDST16_DTPREL_LO12 = 533, - R_AARCH64_TLSLD_LDST16_DTPREL_LO12_NC = 534, - R_AARCH64_TLSLD_LDST32_DTPREL_LO12 = 535, - R_AARCH64_TLSLD_LDST32_DTPREL_LO12_NC = 536, - R_AARCH64_TLSLD_LDST64_DTPREL_LO12 = 537, - R_AARCH64_TLSLD_LDST64_DTPREL_LO12_NC = 538, - R_AARCH64_TLSIE_MOVW_GOTTPREL_G1 = 539, - R_AARCH64_TLSIE_MOVW_GOTTPREL_G0_NC = 540, - R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21 = 541, - R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC = 542, - R_AARCH64_TLSIE_LD_GOTTPREL_PREL19 = 543, - R_AARCH64_TLSLE_MOVW_TPREL_G2 = 544, - R_AARCH64_TLSLE_MOVW_TPREL_G1 = 545, - R_AARCH64_TLSLE_MOVW_TPREL_G1_NC = 546, - R_AARCH64_TLSLE_MOVW_TPREL_G0 = 547, - R_AARCH64_TLSLE_MOVW_TPREL_G0_NC = 548, - R_AARCH64_TLSLE_ADD_TPREL_HI12 = 549, - R_AARCH64_TLSLE_ADD_TPREL_LO12 = 550, - R_AARCH64_TLSLE_ADD_TPREL_LO12_NC = 551, - R_AARCH64_TLSLE_LDST8_TPREL_LO12 = 552, - R_AARCH64_TLSLE_LDST8_TPREL_LO12_NC = 553, - R_AARCH64_TLSLE_LDST16_TPREL_LO12 = 554, - R_AARCH64_TLSLE_LDST16_TPREL_LO12_NC = 555, - R_AARCH64_TLSLE_LDST32_TPREL_LO12 = 556, - R_AARCH64_TLSLE_LDST32_TPREL_LO12_NC = 557, - R_AARCH64_TLSLE_LDST64_TPREL_LO12 = 558, - R_AARCH64_TLSLE_LDST64_TPREL_LO12_NC = 559, - R_AARCH64_TLSDESC_LD_PREL19 = 560, - R_AARCH64_TLSDESC_ADR_PREL21 = 561, - R_AARCH64_TLSDESC_ADR_PAGE21 = 562, - R_AARCH64_TLSDESC_LD64_LO12 = 563, - R_AARCH64_TLSDESC_ADD_LO12 = 564, - R_AARCH64_TLSDESC_OFF_G1 = 565, - R_AARCH64_TLSDESC_OFF_G0_NC = 566, - R_AARCH64_TLSDESC_LDR = 567, - R_AARCH64_TLSDESC_ADD = 568, - R_AARCH64_TLSDESC_CALL = 569, - R_AARCH64_TLSLE_LDST128_TPREL_LO12 = 570, - R_AARCH64_TLSLE_LDST128_TPREL_LO12_NC = 571, - R_AARCH64_TLSLD_LDST128_DTPREL_LO12 = 572, - R_AARCH64_TLSLD_LDST128_DTPREL_LO12_NC = 573, - R_AARCH64_COPY = 1024, - R_AARCH64_GLOB_DAT = 1025, - R_AARCH64_JUMP_SLOT = 1026, - R_AARCH64_RELATIVE = 1027, - R_AARCH64_TLS_DTPMOD64 = 1028, - R_AARCH64_TLS_DTPREL64 = 1029, - R_AARCH64_TLS_TPREL64 = 1030, - R_AARCH64_TLSDESC = 1031 - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/Loaders/ElfSym.cs b/Ryujinx.HLE/Loaders/ElfSym.cs deleted file mode 100644 index 869938d357..0000000000 --- a/Ryujinx.HLE/Loaders/ElfSym.cs +++ /dev/null @@ -1,40 +0,0 @@ -namespace Ryujinx.HLE.Loaders -{ - struct ElfSym - { - public string Name { get; private set; } - - public ElfSymType Type { get; private set; } - public ElfSymBinding Binding { get; private set; } - public ElfSymVisibility Visibility { get; private set; } - - public bool IsFuncOrObject => - Type == ElfSymType.STT_FUNC || - Type == ElfSymType.STT_OBJECT; - - public bool IsGlobalOrWeak => - Binding == ElfSymBinding.STB_GLOBAL || - Binding == ElfSymBinding.STB_WEAK; - - public int SHIdx { get; private set; } - public long Value { get; private set; } - public long Size { get; private set; } - - public ElfSym( - string Name, - int Info, - int Other, - int SHIdx, - long Value, - long Size) - { - this.Name = Name; - this.Type = (ElfSymType)(Info & 0xf); - this.Binding = (ElfSymBinding)(Info >> 4); - this.Visibility = (ElfSymVisibility)Other; - this.SHIdx = SHIdx; - this.Value = Value; - this.Size = Size; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/Loaders/ElfSymBinding.cs b/Ryujinx.HLE/Loaders/ElfSymBinding.cs deleted file mode 100644 index f1d249ec24..0000000000 --- a/Ryujinx.HLE/Loaders/ElfSymBinding.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Ryujinx.HLE.Loaders -{ - enum ElfSymBinding - { - STB_LOCAL = 0, - STB_GLOBAL = 1, - STB_WEAK = 2 - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/Loaders/ElfSymType.cs b/Ryujinx.HLE/Loaders/ElfSymType.cs deleted file mode 100644 index 478064bc4b..0000000000 --- a/Ryujinx.HLE/Loaders/ElfSymType.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Ryujinx.HLE.Loaders -{ - enum ElfSymType - { - STT_NOTYPE = 0, - STT_OBJECT = 1, - STT_FUNC = 2, - STT_SECTION = 3, - STT_FILE = 4, - STT_COMMON = 5, - STT_TLS = 6 - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/Loaders/ElfSymVisibility.cs b/Ryujinx.HLE/Loaders/ElfSymVisibility.cs deleted file mode 100644 index fe7243a7ae..0000000000 --- a/Ryujinx.HLE/Loaders/ElfSymVisibility.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Ryujinx.HLE.Loaders -{ - enum ElfSymVisibility - { - STV_DEFAULT = 0, - STV_INTERNAL = 1, - STV_HIDDEN = 2, - STV_PROTECTED = 3 - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/Loaders/Executable.cs b/Ryujinx.HLE/Loaders/Executable.cs deleted file mode 100644 index 618ee241a6..0000000000 --- a/Ryujinx.HLE/Loaders/Executable.cs +++ /dev/null @@ -1,173 +0,0 @@ -using ChocolArm64.Memory; -using Ryujinx.HLE.Loaders.Executables; -using Ryujinx.HLE.OsHle; -using System.Collections.Generic; - -namespace Ryujinx.HLE.Loaders -{ - class Executable - { - private List Dynamic; - - private Dictionary m_SymbolTable; - - public IReadOnlyDictionary SymbolTable => m_SymbolTable; - - public string Name { get; private set; } - - private AMemory Memory; - - public long ImageBase { get; private set; } - public long ImageEnd { get; private set; } - - public Executable(IExecutable Exe, AMemory Memory, long ImageBase) - { - Dynamic = new List(); - - m_SymbolTable = new Dictionary(); - - Name = Exe.Name; - - this.Memory = Memory; - this.ImageBase = ImageBase; - this.ImageEnd = ImageBase; - - WriteData(ImageBase + Exe.TextOffset, Exe.Text, MemoryType.CodeStatic, AMemoryPerm.RX); - WriteData(ImageBase + Exe.ROOffset, Exe.RO, MemoryType.CodeMutable, AMemoryPerm.Read); - WriteData(ImageBase + Exe.DataOffset, Exe.Data, MemoryType.CodeMutable, AMemoryPerm.RW); - - if (Exe.Mod0Offset == 0) - { - int BssOffset = Exe.DataOffset + Exe.Data.Length; - int BssSize = Exe.BssSize; - - MapBss(ImageBase + BssOffset, BssSize); - - ImageEnd = ImageBase + BssOffset + BssSize; - - return; - } - - long Mod0Offset = ImageBase + Exe.Mod0Offset; - - int Mod0Magic = Memory.ReadInt32(Mod0Offset + 0x0); - long DynamicOffset = Memory.ReadInt32(Mod0Offset + 0x4) + Mod0Offset; - long BssStartOffset = Memory.ReadInt32(Mod0Offset + 0x8) + Mod0Offset; - long BssEndOffset = Memory.ReadInt32(Mod0Offset + 0xc) + Mod0Offset; - long EhHdrStartOffset = Memory.ReadInt32(Mod0Offset + 0x10) + Mod0Offset; - long EhHdrEndOffset = Memory.ReadInt32(Mod0Offset + 0x14) + Mod0Offset; - long ModObjOffset = Memory.ReadInt32(Mod0Offset + 0x18) + Mod0Offset; - - MapBss(BssStartOffset, BssEndOffset - BssStartOffset); - - ImageEnd = BssEndOffset; - - while (true) - { - long TagVal = Memory.ReadInt64(DynamicOffset + 0); - long Value = Memory.ReadInt64(DynamicOffset + 8); - - DynamicOffset += 0x10; - - ElfDynTag Tag = (ElfDynTag)TagVal; - - if (Tag == ElfDynTag.DT_NULL) - { - break; - } - - Dynamic.Add(new ElfDyn(Tag, Value)); - } - - long StrTblAddr = ImageBase + GetFirstValue(ElfDynTag.DT_STRTAB); - long SymTblAddr = ImageBase + GetFirstValue(ElfDynTag.DT_SYMTAB); - - long SymEntSize = GetFirstValue(ElfDynTag.DT_SYMENT); - - while ((ulong)SymTblAddr < (ulong)StrTblAddr) - { - ElfSym Sym = GetSymbol(SymTblAddr, StrTblAddr); - - m_SymbolTable.TryAdd(Sym.Value, Sym.Name); - - SymTblAddr += SymEntSize; - } - } - - private void WriteData( - long Position, - byte[] Data, - MemoryType Type, - AMemoryPerm Perm) - { - Memory.Manager.Map(Position, Data.Length, (int)Type, AMemoryPerm.Write); - - Memory.WriteBytes(Position, Data); - - Memory.Manager.Reprotect(Position, Data.Length, Perm); - } - - private void MapBss(long Position, long Size) - { - Memory.Manager.Map(Position, Size, (int)MemoryType.Normal, AMemoryPerm.RW); - } - - private ElfRel GetRelocation(long Position) - { - long Offset = Memory.ReadInt64(Position + 0); - long Info = Memory.ReadInt64(Position + 8); - long Addend = Memory.ReadInt64(Position + 16); - - int RelType = (int)(Info >> 0); - int SymIdx = (int)(Info >> 32); - - ElfSym Symbol = GetSymbol(SymIdx); - - return new ElfRel(Offset, Addend, Symbol, (ElfRelType)RelType); - } - - private ElfSym GetSymbol(int Index) - { - long StrTblAddr = ImageBase + GetFirstValue(ElfDynTag.DT_STRTAB); - long SymTblAddr = ImageBase + GetFirstValue(ElfDynTag.DT_SYMTAB); - - long SymEntSize = GetFirstValue(ElfDynTag.DT_SYMENT); - - long Position = SymTblAddr + Index * SymEntSize; - - return GetSymbol(Position, StrTblAddr); - } - - private ElfSym GetSymbol(long Position, long StrTblAddr) - { - int NameIndex = Memory.ReadInt32(Position + 0); - int Info = Memory.ReadByte(Position + 4); - int Other = Memory.ReadByte(Position + 5); - int SHIdx = Memory.ReadInt16(Position + 6); - long Value = Memory.ReadInt64(Position + 8); - long Size = Memory.ReadInt64(Position + 16); - - string Name = string.Empty; - - for (int Chr; (Chr = Memory.ReadByte(StrTblAddr + NameIndex++)) != 0;) - { - Name += (char)Chr; - } - - return new ElfSym(Name, Info, Other, SHIdx, Value, Size); - } - - private long GetFirstValue(ElfDynTag Tag) - { - foreach (ElfDyn Entry in Dynamic) - { - if (Entry.Tag == Tag) - { - return Entry.Value; - } - } - - return 0; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/Loaders/Executables/IExecutable.cs b/Ryujinx.HLE/Loaders/Executables/IExecutable.cs index 1e8e569a5b..440e8f5fae 100644 --- a/Ryujinx.HLE/Loaders/Executables/IExecutable.cs +++ b/Ryujinx.HLE/Loaders/Executables/IExecutable.cs @@ -1,17 +1,15 @@ namespace Ryujinx.HLE.Loaders.Executables { - public interface IExecutable + interface IExecutable { - string Name { get; } - byte[] Text { get; } - byte[] RO { get; } + byte[] Ro { get; } byte[] Data { get; } - int Mod0Offset { get; } int TextOffset { get; } - int ROOffset { get; } + int RoOffset { get; } int DataOffset { get; } + int BssOffset { get; } int BssSize { get; } } } \ No newline at end of file diff --git a/Ryujinx.HLE/Loaders/Executables/KernelInitialProcess.cs b/Ryujinx.HLE/Loaders/Executables/KernelInitialProcess.cs new file mode 100644 index 0000000000..d6a1cb66bd --- /dev/null +++ b/Ryujinx.HLE/Loaders/Executables/KernelInitialProcess.cs @@ -0,0 +1,147 @@ +using Ryujinx.HLE.Loaders.Compression; +using System.IO; + +namespace Ryujinx.HLE.Loaders.Executables +{ + class KernelInitialProcess : IExecutable + { + public string Name { get; private set; } + + public ulong TitleId { get; private set; } + + public int ProcessCategory { get; private set; } + + public byte MainThreadPriority { get; private set; } + public byte DefaultProcessorId { get; private set; } + + public bool Is64Bits { get; private set; } + public bool Addr39Bits { get; private set; } + public bool IsService { get; private set; } + + public byte[] Text { get; private set; } + public byte[] Ro { get; private set; } + public byte[] Data { get; private set; } + + public int TextOffset { get; private set; } + public int RoOffset { get; private set; } + public int DataOffset { get; private set; } + public int BssOffset { get; private set; } + public int BssSize { get; private set; } + + public int MainThreadStackSize { get; private set; } + + public int[] Capabilities { get; private set; } + + private struct SegmentHeader + { + public int Offset { get; private set; } + public int DecompressedSize { get; private set; } + public int CompressedSize { get; private set; } + public int Attribute { get; private set; } + + public SegmentHeader( + int offset, + int decompressedSize, + int compressedSize, + int attribute) + { + Offset = offset; + DecompressedSize = decompressedSize; + CompressedSize = compressedSize; + Attribute = attribute; + } + } + + public KernelInitialProcess(Stream input) + { + BinaryReader reader = new BinaryReader(input); + + string magic = ReadString(reader, 4); + + if (magic != "KIP1") + { + + } + + Name = ReadString(reader, 12); + + TitleId = reader.ReadUInt64(); + + ProcessCategory = reader.ReadInt32(); + + MainThreadPriority = reader.ReadByte(); + DefaultProcessorId = reader.ReadByte(); + + byte reserved = reader.ReadByte(); + byte flags = reader.ReadByte(); + + Is64Bits = (flags & 0x08) != 0; + Addr39Bits = (flags & 0x10) != 0; + IsService = (flags & 0x20) != 0; + + SegmentHeader[] segments = new SegmentHeader[6]; + + for (int index = 0; index < segments.Length; index++) + { + segments[index] = new SegmentHeader( + reader.ReadInt32(), + reader.ReadInt32(), + reader.ReadInt32(), + reader.ReadInt32()); + } + + TextOffset = segments[0].Offset; + RoOffset = segments[1].Offset; + DataOffset = segments[2].Offset; + BssOffset = segments[3].Offset; + BssSize = segments[3].DecompressedSize; + + MainThreadStackSize = segments[1].Attribute; + + Capabilities = new int[32]; + + for (int index = 0; index < Capabilities.Length; index++) + { + Capabilities[index] = reader.ReadInt32(); + } + + input.Seek(0x100, SeekOrigin.Begin); + + Text = ReadSegment(segments[0], input); + Ro = ReadSegment(segments[1], input); + Data = ReadSegment(segments[2], input); + } + + private byte[] ReadSegment(SegmentHeader header, Stream input) + { + byte[] data = new byte[header.DecompressedSize]; + + input.Read(data, 0, header.CompressedSize); + + BackwardsLz.DecompressInPlace(data, header.CompressedSize); + + return data; + } + + private static string ReadString(BinaryReader reader, int maxSize) + { + string value = string.Empty; + + for (int index = 0; index < maxSize; index++) + { + char chr = (char)reader.ReadByte(); + + if (chr == '\0') + { + reader.BaseStream.Seek(maxSize - index - 1, SeekOrigin.Current); + + break; + } + + value += chr; + } + + return value; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/Loaders/Executables/Nro.cs b/Ryujinx.HLE/Loaders/Executables/Nro.cs deleted file mode 100644 index 9e2b7e9073..0000000000 --- a/Ryujinx.HLE/Loaders/Executables/Nro.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System.IO; - -namespace Ryujinx.HLE.Loaders.Executables -{ - class Nro : IExecutable - { - public string Name { get; private set; } - - public byte[] Text { get; private set; } - public byte[] RO { get; private set; } - public byte[] Data { get; private set; } - - public int Mod0Offset { get; private set; } - public int TextOffset { get; private set; } - public int ROOffset { get; private set; } - public int DataOffset { get; private set; } - public int BssSize { get; private set; } - - public Nro(Stream Input, string Name) - { - this.Name = Name; - - BinaryReader Reader = new BinaryReader(Input); - - Input.Seek(4, SeekOrigin.Begin); - - int Mod0Offset = Reader.ReadInt32(); - int Padding8 = Reader.ReadInt32(); - int Paddingc = Reader.ReadInt32(); - int NroMagic = Reader.ReadInt32(); - int Unknown14 = Reader.ReadInt32(); - int FileSize = Reader.ReadInt32(); - int Unknown1c = Reader.ReadInt32(); - int TextOffset = Reader.ReadInt32(); - int TextSize = Reader.ReadInt32(); - int ROOffset = Reader.ReadInt32(); - int ROSize = Reader.ReadInt32(); - int DataOffset = Reader.ReadInt32(); - int DataSize = Reader.ReadInt32(); - int BssSize = Reader.ReadInt32(); - - this.Mod0Offset = Mod0Offset; - this.TextOffset = TextOffset; - this.ROOffset = ROOffset; - this.DataOffset = DataOffset; - this.BssSize = BssSize; - - byte[] Read(long Position, int Size) - { - Input.Seek(Position, SeekOrigin.Begin); - - return Reader.ReadBytes(Size); - } - - Text = Read(TextOffset, TextSize); - RO = Read(ROOffset, ROSize); - Data = Read(DataOffset, DataSize); - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/Loaders/Executables/Nso.cs b/Ryujinx.HLE/Loaders/Executables/Nso.cs deleted file mode 100644 index a5e1a361ee..0000000000 --- a/Ryujinx.HLE/Loaders/Executables/Nso.cs +++ /dev/null @@ -1,121 +0,0 @@ -using Ryujinx.HLE.Loaders.Compression; -using System; -using System.IO; - -namespace Ryujinx.HLE.Loaders.Executables -{ - class Nso : IExecutable - { - public string Name { get; private set; } - - public byte[] Text { get; private set; } - public byte[] RO { get; private set; } - public byte[] Data { get; private set; } - - public int Mod0Offset { get; private set; } - public int TextOffset { get; private set; } - public int ROOffset { get; private set; } - public int DataOffset { get; private set; } - public int BssSize { get; private set; } - - [Flags] - private enum NsoFlags - { - IsTextCompressed = 1 << 0, - IsROCompressed = 1 << 1, - IsDataCompressed = 1 << 2, - HasTextHash = 1 << 3, - HasROHash = 1 << 4, - HasDataHash = 1 << 5 - } - - public Nso(Stream Input, string Name) - { - this.Name = Name; - - BinaryReader Reader = new BinaryReader(Input); - - Input.Seek(0, SeekOrigin.Begin); - - int NsoMagic = Reader.ReadInt32(); - int Version = Reader.ReadInt32(); - int Reserved = Reader.ReadInt32(); - int FlagsMsk = Reader.ReadInt32(); - int TextOffset = Reader.ReadInt32(); - int TextMemOffset = Reader.ReadInt32(); - int TextDecSize = Reader.ReadInt32(); - int ModNameOffset = Reader.ReadInt32(); - int ROOffset = Reader.ReadInt32(); - int ROMemOffset = Reader.ReadInt32(); - int RODecSize = Reader.ReadInt32(); - int ModNameSize = Reader.ReadInt32(); - int DataOffset = Reader.ReadInt32(); - int DataMemOffset = Reader.ReadInt32(); - int DataDecSize = Reader.ReadInt32(); - int BssSize = Reader.ReadInt32(); - - byte[] BuildId = Reader.ReadBytes(0x20); - - int TextSize = Reader.ReadInt32(); - int ROSize = Reader.ReadInt32(); - int DataSize = Reader.ReadInt32(); - - Input.Seek(0x24, SeekOrigin.Current); - - int DynStrOffset = Reader.ReadInt32(); - int DynStrSize = Reader.ReadInt32(); - int DynSymOffset = Reader.ReadInt32(); - int DynSymSize = Reader.ReadInt32(); - - byte[] TextHash = Reader.ReadBytes(0x20); - byte[] ROHash = Reader.ReadBytes(0x20); - byte[] DataHash = Reader.ReadBytes(0x20); - - NsoFlags Flags = (NsoFlags)FlagsMsk; - - this.TextOffset = TextMemOffset; - this.ROOffset = ROMemOffset; - this.DataOffset = DataMemOffset; - this.BssSize = BssSize; - - //Text segment - Input.Seek(TextOffset, SeekOrigin.Begin); - - Text = Reader.ReadBytes(TextSize); - - if (Flags.HasFlag(NsoFlags.IsTextCompressed) || true) - { - Text = Lz4.Decompress(Text, TextDecSize); - } - - //Read-only data segment - Input.Seek(ROOffset, SeekOrigin.Begin); - - RO = Reader.ReadBytes(ROSize); - - if (Flags.HasFlag(NsoFlags.IsROCompressed) || true) - { - RO = Lz4.Decompress(RO, RODecSize); - } - - //Data segment - Input.Seek(DataOffset, SeekOrigin.Begin); - - Data = Reader.ReadBytes(DataSize); - - if (Flags.HasFlag(NsoFlags.IsDataCompressed) || true) - { - Data = Lz4.Decompress(Data, DataDecSize); - } - - using (MemoryStream TextMS = new MemoryStream(Text)) - { - BinaryReader TextReader = new BinaryReader(TextMS); - - TextMS.Seek(4, SeekOrigin.Begin); - - Mod0Offset = TextReader.ReadInt32(); - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/Loaders/Executables/NxRelocatableObject.cs b/Ryujinx.HLE/Loaders/Executables/NxRelocatableObject.cs new file mode 100644 index 0000000000..e68fe2670e --- /dev/null +++ b/Ryujinx.HLE/Loaders/Executables/NxRelocatableObject.cs @@ -0,0 +1,67 @@ +using System.IO; + +namespace Ryujinx.HLE.Loaders.Executables +{ + class NxRelocatableObject : IExecutable + { + public byte[] Text { get; private set; } + public byte[] Ro { get; private set; } + public byte[] Data { get; private set; } + + public int Mod0Offset { get; private set; } + public int TextOffset { get; private set; } + public int RoOffset { get; private set; } + public int DataOffset { get; private set; } + public int BssSize { get; private set; } + public int FileSize { get; private set; } + + public int BssOffset => DataOffset + Data.Length; + + public ulong SourceAddress { get; private set; } + public ulong BssAddress { get; private set; } + + public NxRelocatableObject(Stream input, ulong sourceAddress = 0, ulong bssAddress = 0) + { + SourceAddress = sourceAddress; + BssAddress = bssAddress; + + BinaryReader reader = new BinaryReader(input); + + input.Seek(4, SeekOrigin.Begin); + + int mod0Offset = reader.ReadInt32(); + int padding8 = reader.ReadInt32(); + int paddingC = reader.ReadInt32(); + int nroMagic = reader.ReadInt32(); + int unknown14 = reader.ReadInt32(); + int fileSize = reader.ReadInt32(); + int unknown1C = reader.ReadInt32(); + int textOffset = reader.ReadInt32(); + int textSize = reader.ReadInt32(); + int roOffset = reader.ReadInt32(); + int roSize = reader.ReadInt32(); + int dataOffset = reader.ReadInt32(); + int dataSize = reader.ReadInt32(); + int bssSize = reader.ReadInt32(); + + Mod0Offset = mod0Offset; + TextOffset = textOffset; + RoOffset = roOffset; + DataOffset = dataOffset; + BssSize = bssSize; + + byte[] Read(long position, int size) + { + input.Seek(position, SeekOrigin.Begin); + + return reader.ReadBytes(size); + } + + Text = Read(textOffset, textSize); + Ro = Read(roOffset, roSize); + Data = Read(dataOffset, dataSize); + + FileSize = fileSize; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/Loaders/Executables/NxStaticObject.cs b/Ryujinx.HLE/Loaders/Executables/NxStaticObject.cs new file mode 100644 index 0000000000..03c3b39a47 --- /dev/null +++ b/Ryujinx.HLE/Loaders/Executables/NxStaticObject.cs @@ -0,0 +1,109 @@ +using Ryujinx.HLE.Loaders.Compression; +using System; +using System.IO; + +namespace Ryujinx.HLE.Loaders.Executables +{ + class NxStaticObject : IExecutable + { + public byte[] Text { get; private set; } + public byte[] Ro { get; private set; } + public byte[] Data { get; private set; } + + public int TextOffset { get; private set; } + public int RoOffset { get; private set; } + public int DataOffset { get; private set; } + public int BssSize { get; private set; } + + public int BssOffset => DataOffset + Data.Length; + + [Flags] + private enum NsoFlags + { + IsTextCompressed = 1 << 0, + IsRoCompressed = 1 << 1, + IsDataCompressed = 1 << 2, + HasTextHash = 1 << 3, + HasRoHash = 1 << 4, + HasDataHash = 1 << 5 + } + + public NxStaticObject(Stream input) + { + BinaryReader reader = new BinaryReader(input); + + input.Seek(0, SeekOrigin.Begin); + + int nsoMagic = reader.ReadInt32(); + int version = reader.ReadInt32(); + int reserved = reader.ReadInt32(); + int flagsMsk = reader.ReadInt32(); + int textOffset = reader.ReadInt32(); + int textMemOffset = reader.ReadInt32(); + int textDecSize = reader.ReadInt32(); + int modNameOffset = reader.ReadInt32(); + int roOffset = reader.ReadInt32(); + int roMemOffset = reader.ReadInt32(); + int roDecSize = reader.ReadInt32(); + int modNameSize = reader.ReadInt32(); + int dataOffset = reader.ReadInt32(); + int dataMemOffset = reader.ReadInt32(); + int dataDecSize = reader.ReadInt32(); + int bssSize = reader.ReadInt32(); + + byte[] buildId = reader.ReadBytes(0x20); + + int textSize = reader.ReadInt32(); + int roSize = reader.ReadInt32(); + int dataSize = reader.ReadInt32(); + + input.Seek(0x24, SeekOrigin.Current); + + int dynStrOffset = reader.ReadInt32(); + int dynStrSize = reader.ReadInt32(); + int dynSymOffset = reader.ReadInt32(); + int dynSymSize = reader.ReadInt32(); + + byte[] textHash = reader.ReadBytes(0x20); + byte[] roHash = reader.ReadBytes(0x20); + byte[] dataHash = reader.ReadBytes(0x20); + + NsoFlags flags = (NsoFlags)flagsMsk; + + TextOffset = textMemOffset; + RoOffset = roMemOffset; + DataOffset = dataMemOffset; + BssSize = bssSize; + + // Text segment + input.Seek(textOffset, SeekOrigin.Begin); + + Text = reader.ReadBytes(textSize); + + if (flags.HasFlag(NsoFlags.IsTextCompressed) && textSize != 0) + { + Text = Lz4.Decompress(Text, textDecSize); + } + + // Read-only data segment + input.Seek(roOffset, SeekOrigin.Begin); + + Ro = reader.ReadBytes(roSize); + + if (flags.HasFlag(NsoFlags.IsRoCompressed) && roSize != 0) + { + Ro = Lz4.Decompress(Ro, roDecSize); + } + + // Data segment + input.Seek(dataOffset, SeekOrigin.Begin); + + Data = reader.ReadBytes(dataSize); + + if (flags.HasFlag(NsoFlags.IsDataCompressed) && dataSize != 0) + { + Data = Lz4.Decompress(Data, dataDecSize); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/Loaders/Npdm/ACI0.cs b/Ryujinx.HLE/Loaders/Npdm/ACI0.cs index 47b30a3c2a..209e79d1e4 100644 --- a/Ryujinx.HLE/Loaders/Npdm/ACI0.cs +++ b/Ryujinx.HLE/Loaders/Npdm/ACI0.cs @@ -1,55 +1,53 @@ -using System; -using System.IO; - -namespace Ryujinx.HLE.Loaders.Npdm -{ - class ACI0 - { - public string TitleId; - - private int FSAccessHeaderOffset; - private int FSAccessHeaderSize; - private int ServiceAccessControlOffset; - private int ServiceAccessControlSize; - private int KernelAccessControlOffset; - private int KernelAccessControlSize; - - public FSAccessHeader FSAccessHeader; - public ServiceAccessControl ServiceAccessControl; - public KernelAccessControl KernelAccessControl; - - public const long ACI0Magic = 'A' << 0 | 'C' << 8 | 'I' << 16 | '0' << 24; - - public ACI0(Stream ACI0Stream, int Offset) - { - ACI0Stream.Seek(Offset, SeekOrigin.Begin); - - BinaryReader Reader = new BinaryReader(ACI0Stream); - - if (Reader.ReadInt32() != ACI0Magic) - { - throw new InvalidNpdmException("ACI0 Stream doesn't contain ACI0 section!"); - } - - ACI0Stream.Seek(0x0C, SeekOrigin.Current); - - byte[] TempTitleId = Reader.ReadBytes(8); - Array.Reverse(TempTitleId); - TitleId = BitConverter.ToString(TempTitleId).Replace("-", ""); - - // Reserved (Not currently used, potentially to be used for lowest title ID in future.) - ACI0Stream.Seek(0x08, SeekOrigin.Current); - - FSAccessHeaderOffset = Reader.ReadInt32(); - FSAccessHeaderSize = Reader.ReadInt32(); - ServiceAccessControlOffset = Reader.ReadInt32(); - ServiceAccessControlSize = Reader.ReadInt32(); - KernelAccessControlOffset = Reader.ReadInt32(); - KernelAccessControlSize = Reader.ReadInt32(); - - FSAccessHeader = new FSAccessHeader(ACI0Stream, Offset + FSAccessHeaderOffset, FSAccessHeaderSize); - ServiceAccessControl = new ServiceAccessControl(ACI0Stream, Offset + ServiceAccessControlOffset, ServiceAccessControlSize); - KernelAccessControl = new KernelAccessControl(ACI0Stream, Offset + KernelAccessControlOffset, KernelAccessControlSize); - } - } -} +using Ryujinx.HLE.Exceptions; +using System.IO; + +namespace Ryujinx.HLE.Loaders.Npdm +{ + public class Aci0 + { + private const int Aci0Magic = 'A' << 0 | 'C' << 8 | 'I' << 16 | '0' << 24; + + public ulong TitleId { get; set; } + + public int FsVersion { get; private set; } + public ulong FsPermissionsBitmask { get; private set; } + + public ServiceAccessControl ServiceAccessControl { get; private set; } + public KernelAccessControl KernelAccessControl { get; private set; } + + public Aci0(Stream stream, int offset) + { + stream.Seek(offset, SeekOrigin.Begin); + + BinaryReader reader = new BinaryReader(stream); + + if (reader.ReadInt32() != Aci0Magic) + { + throw new InvalidNpdmException("ACI0 Stream doesn't contain ACI0 section!"); + } + + stream.Seek(0xc, SeekOrigin.Current); + + TitleId = reader.ReadUInt64(); + + // Reserved. + stream.Seek(8, SeekOrigin.Current); + + int fsAccessHeaderOffset = reader.ReadInt32(); + int fsAccessHeaderSize = reader.ReadInt32(); + int serviceAccessControlOffset = reader.ReadInt32(); + int serviceAccessControlSize = reader.ReadInt32(); + int kernelAccessControlOffset = reader.ReadInt32(); + int kernelAccessControlSize = reader.ReadInt32(); + + FsAccessHeader fsAccessHeader = new FsAccessHeader(stream, offset + fsAccessHeaderOffset, fsAccessHeaderSize); + + FsVersion = fsAccessHeader.Version; + FsPermissionsBitmask = fsAccessHeader.PermissionsBitmask; + + ServiceAccessControl = new ServiceAccessControl(stream, offset + serviceAccessControlOffset, serviceAccessControlSize); + + KernelAccessControl = new KernelAccessControl(stream, offset + kernelAccessControlOffset, kernelAccessControlSize); + } + } +} diff --git a/Ryujinx.HLE/Loaders/Npdm/ACID.cs b/Ryujinx.HLE/Loaders/Npdm/ACID.cs index 09768a9283..365495c60b 100644 --- a/Ryujinx.HLE/Loaders/Npdm/ACID.cs +++ b/Ryujinx.HLE/Loaders/Npdm/ACID.cs @@ -1,67 +1,61 @@ -using System; -using System.IO; - -namespace Ryujinx.HLE.Loaders.Npdm -{ - class ACID - { - public byte[] RSA2048Signature; - public byte[] RSA2048Modulus; - public int Unknown1; - public int Flags; - - public string TitleIdRangeMin; - public string TitleIdRangeMax; - - private int FSAccessControlOffset; - private int FSAccessControlSize; - private int ServiceAccessControlOffset; - private int ServiceAccessControlSize; - private int KernelAccessControlOffset; - private int KernelAccessControlSize; - - public FSAccessControl FSAccessControl; - public ServiceAccessControl ServiceAccessControl; - public KernelAccessControl KernelAccessControl; - - public const long ACIDMagic = 'A' << 0 | 'C' << 8 | 'I' << 16 | 'D' << 24; - - public ACID(Stream ACIDStream, int Offset) - { - ACIDStream.Seek(Offset, SeekOrigin.Begin); - - BinaryReader Reader = new BinaryReader(ACIDStream); - - RSA2048Signature = Reader.ReadBytes(0x100); - RSA2048Modulus = Reader.ReadBytes(0x100); - - if (Reader.ReadInt32() != ACIDMagic) - { - throw new InvalidNpdmException("ACID Stream doesn't contain ACID section!"); - } - - Unknown1 = Reader.ReadInt32(); // Size field used with the above signature(?). - Reader.ReadInt32(); // Padding / Unused - Flags = Reader.ReadInt32(); // Bit0 must be 1 on retail, on devunit 0 is also allowed. Bit1 is unknown. - - byte[] TempTitleIdRangeMin = Reader.ReadBytes(8); - Array.Reverse(TempTitleIdRangeMin); - TitleIdRangeMin = BitConverter.ToString(TempTitleIdRangeMin).Replace("-", ""); - - byte[] TempTitleIdRangeMax = Reader.ReadBytes(8); - Array.Reverse(TempTitleIdRangeMax); - TitleIdRangeMax = BitConverter.ToString(TempTitleIdRangeMax).Replace("-", ""); - - FSAccessControlOffset = Reader.ReadInt32(); - FSAccessControlSize = Reader.ReadInt32(); - ServiceAccessControlOffset = Reader.ReadInt32(); - ServiceAccessControlSize = Reader.ReadInt32(); - KernelAccessControlOffset = Reader.ReadInt32(); - KernelAccessControlSize = Reader.ReadInt32(); - - FSAccessControl = new FSAccessControl(ACIDStream, Offset + FSAccessControlOffset, FSAccessControlSize); - ServiceAccessControl = new ServiceAccessControl(ACIDStream, Offset + ServiceAccessControlOffset, ServiceAccessControlSize); - KernelAccessControl = new KernelAccessControl(ACIDStream, Offset + KernelAccessControlOffset, KernelAccessControlSize); - } - } -} +using Ryujinx.HLE.Exceptions; +using System.IO; + +namespace Ryujinx.HLE.Loaders.Npdm +{ + public class Acid + { + private const int AcidMagic = 'A' << 0 | 'C' << 8 | 'I' << 16 | 'D' << 24; + + public byte[] Rsa2048Signature { get; private set; } + public byte[] Rsa2048Modulus { get; private set; } + public int Unknown1 { get; private set; } + public int Flags { get; private set; } + + public long TitleIdRangeMin { get; private set; } + public long TitleIdRangeMax { get; private set; } + + public FsAccessControl FsAccessControl { get; private set; } + public ServiceAccessControl ServiceAccessControl { get; private set; } + public KernelAccessControl KernelAccessControl { get; private set; } + + public Acid(Stream stream, int offset) + { + stream.Seek(offset, SeekOrigin.Begin); + + BinaryReader reader = new BinaryReader(stream); + + Rsa2048Signature = reader.ReadBytes(0x100); + Rsa2048Modulus = reader.ReadBytes(0x100); + + if (reader.ReadInt32() != AcidMagic) + { + throw new InvalidNpdmException("ACID Stream doesn't contain ACID section!"); + } + + // Size field used with the above signature (?). + Unknown1 = reader.ReadInt32(); + + reader.ReadInt32(); + + // Bit0 must be 1 on retail, on devunit 0 is also allowed. Bit1 is unknown. + Flags = reader.ReadInt32(); + + TitleIdRangeMin = reader.ReadInt64(); + TitleIdRangeMax = reader.ReadInt64(); + + int fsAccessControlOffset = reader.ReadInt32(); + int fsAccessControlSize = reader.ReadInt32(); + int serviceAccessControlOffset = reader.ReadInt32(); + int serviceAccessControlSize = reader.ReadInt32(); + int kernelAccessControlOffset = reader.ReadInt32(); + int kernelAccessControlSize = reader.ReadInt32(); + + FsAccessControl = new FsAccessControl(stream, offset + fsAccessControlOffset, fsAccessControlSize); + + ServiceAccessControl = new ServiceAccessControl(stream, offset + serviceAccessControlOffset, serviceAccessControlSize); + + KernelAccessControl = new KernelAccessControl(stream, offset + kernelAccessControlOffset, kernelAccessControlSize); + } + } +} diff --git a/Ryujinx.HLE/Loaders/Npdm/FSAccessControl.cs b/Ryujinx.HLE/Loaders/Npdm/FSAccessControl.cs deleted file mode 100644 index ca8eac2e76..0000000000 --- a/Ryujinx.HLE/Loaders/Npdm/FSAccessControl.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.IO; - -namespace Ryujinx.HLE.Loaders.Npdm -{ - public class FSAccessControl - { - public int Version; - public ulong PermissionsBitmask; - public int Unknown1; - public int Unknown2; - public int Unknown3; - public int Unknown4; - - public FSAccessControl(Stream FSAccessHeaderStream, int Offset, int Size) - { - FSAccessHeaderStream.Seek(Offset, SeekOrigin.Begin); - - BinaryReader Reader = new BinaryReader(FSAccessHeaderStream); - - Version = Reader.ReadInt32(); - PermissionsBitmask = Reader.ReadUInt64(); - Unknown1 = Reader.ReadInt32(); - Unknown2 = Reader.ReadInt32(); - Unknown3 = Reader.ReadInt32(); - Unknown4 = Reader.ReadInt32(); - } - } -} diff --git a/Ryujinx.HLE/Loaders/Npdm/FSAccessHeader.cs b/Ryujinx.HLE/Loaders/Npdm/FSAccessHeader.cs deleted file mode 100644 index 0ba3af7341..0000000000 --- a/Ryujinx.HLE/Loaders/Npdm/FSAccessHeader.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.IO; - -namespace Ryujinx.HLE.Loaders.Npdm -{ - public class FSAccessHeader - { - public int Version; - public ulong PermissionsBitmask; - public int DataSize; - public int ContentOwnerIDSize; - public int DataAndContentOwnerIDSize; - - public FSAccessHeader(Stream FSAccessHeaderStream, int Offset, int Size) - { - FSAccessHeaderStream.Seek(Offset, SeekOrigin.Begin); - - BinaryReader Reader = new BinaryReader(FSAccessHeaderStream); - - Version = Reader.ReadInt32(); - PermissionsBitmask = Reader.ReadUInt64(); - DataSize = Reader.ReadInt32(); - - if (DataSize != 0x1C) - { - throw new InvalidNpdmException("FSAccessHeader is corrupted!"); - } - - ContentOwnerIDSize = Reader.ReadInt32(); - DataAndContentOwnerIDSize = Reader.ReadInt32(); - - if (DataAndContentOwnerIDSize != 0x1C) - { - throw new InvalidNpdmException("ContentOwnerID section is not implemented!"); - } - } - } -} diff --git a/Ryujinx.HLE/Loaders/Npdm/FsAccessControl.cs b/Ryujinx.HLE/Loaders/Npdm/FsAccessControl.cs new file mode 100644 index 0000000000..d0f349eaf3 --- /dev/null +++ b/Ryujinx.HLE/Loaders/Npdm/FsAccessControl.cs @@ -0,0 +1,28 @@ +using System.IO; + +namespace Ryujinx.HLE.Loaders.Npdm +{ + public class FsAccessControl + { + public int Version { get; private set; } + public ulong PermissionsBitmask { get; private set; } + public int Unknown1 { get; private set; } + public int Unknown2 { get; private set; } + public int Unknown3 { get; private set; } + public int Unknown4 { get; private set; } + + public FsAccessControl(Stream stream, int offset, int size) + { + stream.Seek(offset, SeekOrigin.Begin); + + BinaryReader reader = new BinaryReader(stream); + + Version = reader.ReadInt32(); + PermissionsBitmask = reader.ReadUInt64(); + Unknown1 = reader.ReadInt32(); + Unknown2 = reader.ReadInt32(); + Unknown3 = reader.ReadInt32(); + Unknown4 = reader.ReadInt32(); + } + } +} diff --git a/Ryujinx.HLE/Loaders/Npdm/FsAccessHeader.cs b/Ryujinx.HLE/Loaders/Npdm/FsAccessHeader.cs new file mode 100644 index 0000000000..564b8dc391 --- /dev/null +++ b/Ryujinx.HLE/Loaders/Npdm/FsAccessHeader.cs @@ -0,0 +1,37 @@ +using Ryujinx.HLE.Exceptions; +using System; +using System.IO; + +namespace Ryujinx.HLE.Loaders.Npdm +{ + class FsAccessHeader + { + public int Version { get; private set; } + public ulong PermissionsBitmask { get; private set; } + + public FsAccessHeader(Stream stream, int offset, int size) + { + stream.Seek(offset, SeekOrigin.Begin); + + BinaryReader reader = new BinaryReader(stream); + + Version = reader.ReadInt32(); + PermissionsBitmask = reader.ReadUInt64(); + + int dataSize = reader.ReadInt32(); + + if (dataSize != 0x1c) + { + throw new InvalidNpdmException("FsAccessHeader is corrupted!"); + } + + int contentOwnerIdSize = reader.ReadInt32(); + int dataAndContentOwnerIdSize = reader.ReadInt32(); + + if (dataAndContentOwnerIdSize != 0x1c) + { + throw new NotImplementedException("ContentOwnerId section is not implemented!"); + } + } + } +} diff --git a/Ryujinx.HLE/Loaders/Npdm/KernelAccessControl.cs b/Ryujinx.HLE/Loaders/Npdm/KernelAccessControl.cs index 46fad63e8e..39803642c9 100644 --- a/Ryujinx.HLE/Loaders/Npdm/KernelAccessControl.cs +++ b/Ryujinx.HLE/Loaders/Npdm/KernelAccessControl.cs @@ -1,208 +1,23 @@ -using System.Collections.Generic; -using System.IO; - -namespace Ryujinx.HLE.Loaders.Npdm -{ - public class KernelAccessControlIRQ - { - public uint IRQ0; - public uint IRQ1; - } - - public class KernelAccessControlMMIO - { - public ulong Address; - public ulong Size; - public bool IsRO; - public bool IsNormal; - } - - public class KernelAccessControlItems - { - public bool HasKernelFlags; - public uint LowestThreadPriority; - public uint HighestThreadPriority; - public uint LowestCpuId; - public uint HighestCpuId; - - public bool HasSVCFlags; - public int[] SVCsAllowed; - - public List NormalMMIO; - public List PageMMIO; - public List IRQ; - - public bool HasApplicationType; - public int ApplicationType; - - public bool HasKernelVersion; - public int KernelVersionRelease; - - public bool HasHandleTableSize; - public int HandleTableSize; - - public bool HasDebugFlags; - public bool AllowDebug; - public bool ForceDebug; - } - - public class KernelAccessControl - { - public KernelAccessControlItems[] Items; - - public KernelAccessControl(Stream FSAccessControlsStream, int Offset, int Size) - { - FSAccessControlsStream.Seek(Offset, SeekOrigin.Begin); - - BinaryReader Reader = new BinaryReader(FSAccessControlsStream); - - Items = new KernelAccessControlItems[Size / 4]; - - for (int i = 0; i < Size / 4; i++) - { - uint Descriptor = Reader.ReadUInt32(); - - if (Descriptor == 0xFFFFFFFF) //Ignore the descriptor - { - continue; - } - - Items[i] = new KernelAccessControlItems(); - - int LowBits = 0; - - while ((Descriptor & 1) != 0) - { - Descriptor >>= 1; - LowBits++; - } - - Descriptor >>= 1; - - switch (LowBits) - { - case 3: // Kernel flags - { - Items[i].HasKernelFlags = true; - - Items[i].HighestThreadPriority = Descriptor & 0x3F; - Items[i].LowestThreadPriority = (Descriptor >> 6) & 0x3F; - Items[i].LowestCpuId = (Descriptor >> 12) & 0xFF; - Items[i].HighestCpuId = (Descriptor >> 20) & 0xFF; - - break; - } - - case 4: // Syscall mask - { - Items[i].HasSVCFlags = true; - - Items[i].SVCsAllowed = new int[0x80]; - - int SysCallBase = (int)(Descriptor >> 24) * 0x18; - - for (int SysCall = 0; SysCall < 0x18 && SysCallBase + SysCall < 0x80; SysCall++) - { - Items[i].SVCsAllowed[SysCallBase + SysCall] = (int)Descriptor & 1; - Descriptor >>= 1; - } - - break; - } - - case 6: // Map IO/Normal - Never tested. - { - KernelAccessControlMMIO TempNormalMMIO = new KernelAccessControlMMIO - { - Address = (Descriptor & 0xFFFFFF) << 12, - IsRO = (Descriptor >> 24) != 0 - }; - - if (i == Size / 4 - 1) - { - throw new InvalidNpdmException("Invalid Kernel Access Control Descriptors!"); - } - - Descriptor = Reader.ReadUInt32(); - - if ((Descriptor & 0x7F) != 0x3F) - { - throw new InvalidNpdmException("Invalid Kernel Access Control Descriptors!"); - } - - Descriptor >>= 7; - TempNormalMMIO.Size = (Descriptor & 0xFFFFFF) << 12; - TempNormalMMIO.IsNormal = (Descriptor >> 24) != 0; - - Items[i].NormalMMIO.Add(TempNormalMMIO); - i++; - - break; - } - - case 7: // Map Normal Page - Never tested. - { - KernelAccessControlMMIO TempPageMMIO = new KernelAccessControlMMIO - { - Address = Descriptor << 12, - Size = 0x1000, - IsRO = false, - IsNormal = false - }; - - Items[i].PageMMIO.Add(TempPageMMIO); - - break; - } - - case 11: // IRQ Pair - Never tested. - { - KernelAccessControlIRQ TempIRQ = new KernelAccessControlIRQ - { - IRQ0 = Descriptor & 0x3FF, - IRQ1 = (Descriptor >> 10) & 0x3FF - }; - - break; - } - - case 13: // App Type - { - Items[i].HasApplicationType = true; - Items[i].ApplicationType = (int)Descriptor & 7; - - break; - } - - case 14: // Kernel Release Version - { - Items[i].HasKernelVersion = true; - - Items[i].KernelVersionRelease = (int)Descriptor; - - break; - } - - case 15: // Handle Table Size - { - Items[i].HasHandleTableSize = true; - - Items[i].HandleTableSize = (int)Descriptor; - - break; - } - - case 16: // Debug Flags - { - Items[i].HasDebugFlags = true; - - Items[i].AllowDebug = (Descriptor & 1) != 0; - Items[i].ForceDebug = ((Descriptor >> 1) & 1) != 0; - - break; - } - } - } - } - } -} +using System.IO; + +namespace Ryujinx.HLE.Loaders.Npdm +{ + public class KernelAccessControl + { + public int[] Capabilities { get; private set; } + + public KernelAccessControl(Stream stream, int offset, int size) + { + stream.Seek(offset, SeekOrigin.Begin); + + Capabilities = new int[size / 4]; + + BinaryReader reader = new BinaryReader(stream); + + for (int index = 0; index < Capabilities.Length; index++) + { + Capabilities[index] = reader.ReadInt32(); + } + } + } +} diff --git a/Ryujinx.HLE/Loaders/Npdm/Npdm.cs b/Ryujinx.HLE/Loaders/Npdm/Npdm.cs index eaa662f03b..4400793f14 100644 --- a/Ryujinx.HLE/Loaders/Npdm/Npdm.cs +++ b/Ryujinx.HLE/Loaders/Npdm/Npdm.cs @@ -1,85 +1,72 @@ -using Ryujinx.HLE.OsHle.Utilities; -using System.IO; -using System.Text; - -namespace Ryujinx.HLE.Loaders.Npdm -{ - //https://github.com/SciresM/hactool/blob/master/npdm.c - //https://github.com/SciresM/hactool/blob/master/npdm.h - //http://switchbrew.org/index.php?title=NPDM - class Npdm - { - public bool Is64Bits; - public int AddressSpaceWidth; - public byte MainThreadPriority; - public byte DefaultCpuId; - public int SystemResourceSize; - public int ProcessCategory; - public int MainEntrypointStackSize; - public string TitleName; - public byte[] ProductCode; - public ulong FSPerms; - - private int ACI0Offset; - private int ACI0Size; - private int ACIDOffset; - private int ACIDSize; - - public ACI0 ACI0; - public ACID ACID; - - public const long NpdmMagic = 'M' << 0 | 'E' << 8 | 'T' << 16 | 'A' << 24; - - public Npdm(Stream NPDMStream) - { - BinaryReader Reader = new BinaryReader(NPDMStream); - - if (Reader.ReadInt32() != NpdmMagic) - { - throw new InvalidNpdmException("NPDM Stream doesn't contain NPDM file!"); - } - - Reader.ReadInt64(); // Padding / Unused - - // MmuFlags, bit0: 64-bit instructions, bits1-3: address space width (1=64-bit, 2=32-bit). Needs to be <= 0xF - byte MmuFlags = Reader.ReadByte(); - Is64Bits = (MmuFlags & 1) != 0; - AddressSpaceWidth = (MmuFlags >> 1) & 7; - - Reader.ReadByte(); // Padding / Unused - - MainThreadPriority = Reader.ReadByte(); // (0-63) - DefaultCpuId = Reader.ReadByte(); - - Reader.ReadInt32(); // Padding / Unused - - // System resource size (max size as of 5.x: 534773760). Unknown usage. - SystemResourceSize = EndianSwap.Swap32(Reader.ReadInt32()); - - // ProcessCategory (0: regular title, 1: kernel built-in). Should be 0 here. - ProcessCategory = EndianSwap.Swap32(Reader.ReadInt32()); - - // Main entrypoint stack size - // (Should(?) be page-aligned. In non-nspwn scenarios, values of 0 can also rarely break in Horizon. - // This might be something auto-adapting or a security feature of some sort ?) - MainEntrypointStackSize = Reader.ReadInt32(); - - byte[] TempTitleName = Reader.ReadBytes(0x10); - TitleName = Encoding.UTF8.GetString(TempTitleName, 0, TempTitleName.Length).Trim('\0'); - - ProductCode = Reader.ReadBytes(0x10); // Unknown value - - NPDMStream.Seek(0x30, SeekOrigin.Current); // Skip reserved bytes - - ACI0Offset = Reader.ReadInt32(); - ACI0Size = Reader.ReadInt32(); - ACIDOffset = Reader.ReadInt32(); - ACIDSize = Reader.ReadInt32(); - - ACI0 = new ACI0(NPDMStream, ACI0Offset); - ACID = new ACID(NPDMStream, ACIDOffset); - - FSPerms = ACI0.FSAccessHeader.PermissionsBitmask & ACID.FSAccessControl.PermissionsBitmask; - } - } -} +using Ryujinx.HLE.Exceptions; +using System.IO; +using System.Text; + +namespace Ryujinx.HLE.Loaders.Npdm +{ + // https://github.com/SciresM/hactool/blob/master/npdm.c + // https://github.com/SciresM/hactool/blob/master/npdm.h + // http://switchbrew.org/index.php?title=NPDM + public class Npdm + { + private const int MetaMagic = 'M' << 0 | 'E' << 8 | 'T' << 16 | 'A' << 24; + + public byte MmuFlags { get; private set; } + public bool Is64Bits { get; private set; } + public byte MainThreadPriority { get; private set; } + public byte DefaultCpuId { get; private set; } + public int PersonalMmHeapSize { get; private set; } + public int ProcessCategory { get; private set; } + public int MainThreadStackSize { get; private set; } + public string TitleName { get; set; } + public byte[] ProductCode { get; private set; } + + public Aci0 Aci0 { get; private set; } + public Acid Acid { get; private set; } + + public Npdm(Stream stream) + { + BinaryReader reader = new BinaryReader(stream); + + if (reader.ReadInt32() != MetaMagic) + { + throw new InvalidNpdmException("NPDM Stream doesn't contain NPDM file!"); + } + + reader.ReadInt64(); + + MmuFlags = reader.ReadByte(); + + Is64Bits = (MmuFlags & 1) != 0; + + reader.ReadByte(); + + MainThreadPriority = reader.ReadByte(); + DefaultCpuId = reader.ReadByte(); + + reader.ReadInt32(); + + PersonalMmHeapSize = reader.ReadInt32(); + + ProcessCategory = reader.ReadInt32(); + + MainThreadStackSize = reader.ReadInt32(); + + byte[] tempTitleName = reader.ReadBytes(0x10); + + TitleName = Encoding.UTF8.GetString(tempTitleName, 0, tempTitleName.Length).Trim('\0'); + + ProductCode = reader.ReadBytes(0x10); + + stream.Seek(0x30, SeekOrigin.Current); + + int aci0Offset = reader.ReadInt32(); + int aci0Size = reader.ReadInt32(); + int acidOffset = reader.ReadInt32(); + int acidSize = reader.ReadInt32(); + + Aci0 = new Aci0(stream, aci0Offset); + Acid = new Acid(stream, acidOffset); + } + } +} diff --git a/Ryujinx.HLE/Loaders/Npdm/NpdmException.cs b/Ryujinx.HLE/Loaders/Npdm/NpdmException.cs deleted file mode 100644 index d87a6461da..0000000000 --- a/Ryujinx.HLE/Loaders/Npdm/NpdmException.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; - -namespace Ryujinx.HLE.Loaders.Npdm -{ - public class InvalidNpdmException : Exception - { - public InvalidNpdmException(string ExMsg) : base(ExMsg) { } - } -} diff --git a/Ryujinx.HLE/Loaders/Npdm/NpdmInfo.cs b/Ryujinx.HLE/Loaders/Npdm/NpdmInfo.cs deleted file mode 100644 index 72e6b3e254..0000000000 --- a/Ryujinx.HLE/Loaders/Npdm/NpdmInfo.cs +++ /dev/null @@ -1,215 +0,0 @@ -namespace Ryujinx.HLE.Loaders.Npdm -{ - enum FSPermissionRW : ulong - { - MountContentType2 = 0x8000000000000801, - MountContentType5 = 0x8000000000000801, - MountContentType3 = 0x8000000000000801, - MountContentType4 = 0x8000000000000801, - MountContentType6 = 0x8000000000000801, - MountContentType7 = 0x8000000000000801, - Unknown0x6 = 0x8000000000000000, - ContentStorageAccess = 0x8000000000000800, - ImageDirectoryAccess = 0x8000000000001000, - MountBisType28 = 0x8000000000000084, - MountBisType29 = 0x8000000000000080, - MountBisType30 = 0x8000000000008080, - MountBisType31 = 0x8000000000008080, - Unknown0xD = 0x8000000000000080, - SdCardAccess = 0xC000000000200000, - GameCardUser = 0x8000000000000010, - SaveDataAccess0 = 0x8000000000040020, - SystemSaveDataAccess0 = 0x8000000000000028, - SaveDataAccess1 = 0x8000000000000020, - SystemSaveDataAccess1 = 0x8000000000000020, - BisPartition0 = 0x8000000000010082, - BisPartition10 = 0x8000000000010080, - BisPartition20 = 0x8000000000010080, - BisPartition21 = 0x8000000000010080, - BisPartition22 = 0x8000000000010080, - BisPartition23 = 0x8000000000010080, - BisPartition24 = 0x8000000000010080, - BisPartition25 = 0x8000000000010080, - BisPartition26 = 0x8000000000000080, - BisPartition27 = 0x8000000000000084, - BisPartition28 = 0x8000000000000084, - BisPartition29 = 0x8000000000000080, - BisPartition30 = 0x8000000000000080, - BisPartition31 = 0x8000000000000080, - BisPartition32 = 0x8000000000000080, - Unknown0x23 = 0xC000000000200000, - GameCard_System = 0x8000000000000100, - MountContent_System = 0x8000000000100008, - HostAccess = 0xC000000000400000 - }; - - enum FSPermissionBool : ulong - { - BisCache = 0x8000000000000080, - EraseMmc = 0x8000000000000080, - GameCardCertificate = 0x8000000000000010, - GameCardIdSet = 0x8000000000000010, - GameCardDriver = 0x8000000000000200, - GameCardAsic = 0x8000000000000200, - SaveDataCreate = 0x8000000000002020, - SaveDataDelete0 = 0x8000000000000060, - SystemSaveDataCreate0 = 0x8000000000000028, - SystemSaveDataCreate1 = 0x8000000000000020, - SaveDataDelete1 = 0x8000000000004028, - SaveDataIterators0 = 0x8000000000000060, - SaveDataIterators1 = 0x8000000000004020, - SaveThumbnails = 0x8000000000020000, - PosixTime = 0x8000000000000400, - SaveDataExtraData = 0x8000000000004060, - GlobalMode = 0x8000000000080000, - SpeedEmulation = 0x8000000000080000, - NULL = 0, - PaddingFiles = 0xC000000000800000, - SaveData_Debug = 0xC000000001000000, - SaveData_SystemManagement = 0xC000000002000000, - Unknown0x16 = 0x8000000004000000, - Unknown0x17 = 0x8000000008000000, - Unknown0x18 = 0x8000000010000000, - Unknown0x19 = 0x8000000000000800, - Unknown0x1A = 0x8000000000004020 - } - - enum NpdmApplicationType - { - SystemModule, - Application, - Applet - } - - enum SvcName - { - svcUnknown0, - svcSetHeapSize, - svcSetMemoryPermission, - svcSetMemoryAttribute, - svcMapMemory, - svcUnmapMemory, - svcQueryMemory, - svcExitProcess, - svcCreateThread, - svcStartThread, - svcExitThread, - svcSleepThread, - svcGetThreadPriority, - svcSetThreadPriority, - svcGetThreadCoreMask, - svcSetThreadCoreMask, - svcGetCurrentProcessorNumber, - svcSignalEvent, - svcClearEvent, - svcMapSharedMemory, - svcUnmapSharedMemory, - svcCreateTransferMemory, - svcCloseHandle, - svcResetSignal, - svcWaitSynchronization, - svcCancelSynchronization, - svcArbitrateLock, - svcArbitrateUnlock, - svcWaitProcessWideKeyAtomic, - svcSignalProcessWideKey, - svcGetSystemTick, - svcConnectToNamedPort, - svcSendSyncRequestLight, - svcSendSyncRequest, - svcSendSyncRequestWithUserBuffer, - svcSendAsyncRequestWithUserBuffer, - svcGetProcessId, - svcGetThreadId, - svcBreak, - svcOutputDebugString, - svcReturnFromException, - svcGetInfo, - svcFlushEntireDataCache, - svcFlushDataCache, - svcMapPhysicalMemory, - svcUnmapPhysicalMemory, - svcGetFutureThreadInfo, - svcGetLastThreadInfo, - svcGetResourceLimitLimitValue, - svcGetResourceLimitCurrentValue, - svcSetThreadActivity, - svcGetThreadContext3, - svcWaitForAddress, - svcSignalToAddress, - svcUnknown1, - svcUnknown2, - svcUnknown3, - svcUnknown4, - svcUnknown5, - svcUnknown6, - svcDumpInfo, - svcDumpInfoNew, - svcUnknown7, - svcUnknown8, - svcCreateSession, - svcAcceptSession, - svcReplyAndReceiveLight, - svcReplyAndReceive, - svcReplyAndReceiveWithUserBuffer, - svcCreateEvent, - svcUnknown9, - svcUnknown10, - svcMapPhysicalMemoryUnsafe, - svcUnmapPhysicalMemoryUnsafe, - svcSetUnsafeLimit, - svcCreateCodeMemory, - svcControlCodeMemory, - svcSleepSystem, - svcReadWriteRegister, - svcSetProcessActivity, - svcCreateSharedMemory, - svcMapTransferMemory, - svcUnmapTransferMemory, - svcCreateInterruptEvent, - svcQueryPhysicalAddress, - svcQueryIoMapping, - svcCreateDeviceAddressSpace, - svcAttachDeviceAddressSpace, - svcDetachDeviceAddressSpace, - svcMapDeviceAddressSpaceByForce, - svcMapDeviceAddressSpaceAligned, - svcMapDeviceAddressSpace, - svcUnmapDeviceAddressSpace, - svcInvalidateProcessDataCache, - svcStoreProcessDataCache, - svcFlushProcessDataCache, - svcDebugActiveProcess, - svcBreakDebugProcess, - svcTerminateDebugProcess, - svcGetDebugEvent, - svcContinueDebugEvent, - svcGetProcessList, - svcGetThreadList, - svcGetDebugThreadContext, - svcSetDebugThreadContext, - svcQueryDebugProcessMemory, - svcReadDebugProcessMemory, - svcWriteDebugProcessMemory, - svcSetHardwareBreakPoint, - svcGetDebugThreadParam, - svcUnknown11, - svcGetSystemInfo, - svcCreatePort, - svcManageNamedPort, - svcConnectToPort, - svcSetProcessMemoryPermission, - svcMapProcessMemory, - svcUnmapProcessMemory, - svcQueryProcessMemory, - svcMapProcessCodeMemory, - svcUnmapProcessCodeMemory, - svcCreateProcess, - svcStartProcess, - svcTerminateProcess, - svcGetProcessInfo, - svcCreateResourceLimit, - svcSetResourceLimitLimitValue, - svcCallSecureMonitor - }; -} diff --git a/Ryujinx.HLE/Loaders/Npdm/ServiceAccessControl.cs b/Ryujinx.HLE/Loaders/Npdm/ServiceAccessControl.cs index ddd7d7ed2b..54012b8a97 100644 --- a/Ryujinx.HLE/Loaders/Npdm/ServiceAccessControl.cs +++ b/Ryujinx.HLE/Loaders/Npdm/ServiceAccessControl.cs @@ -1,34 +1,42 @@ -using System.Collections.Generic; -using System.IO; -using System.Text; - -namespace Ryujinx.HLE.Loaders.Npdm -{ - public class ServiceAccessControl - { - public List<(string, bool)> Services = new List<(string, bool)>(); - - public ServiceAccessControl(Stream ServiceAccessControlStream, int Offset, int Size) - { - ServiceAccessControlStream.Seek(Offset, SeekOrigin.Begin); - - BinaryReader Reader = new BinaryReader(ServiceAccessControlStream); - - int ByteReaded = 0; - - while (ByteReaded != Size) - { - byte ControlByte = Reader.ReadByte(); - - if (ControlByte == 0x00) break; - - int Length = ((ControlByte & 0x07)) + 1; - bool RegisterAllowed = ((ControlByte & 0x80) != 0); - - Services.Add((Encoding.ASCII.GetString(Reader.ReadBytes(Length), 0, Length), RegisterAllowed)); - - ByteReaded += Length + 1; - } - } - } -} +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Text; + +namespace Ryujinx.HLE.Loaders.Npdm +{ + public class ServiceAccessControl + { + public IReadOnlyDictionary Services { get; private set; } + + public ServiceAccessControl(Stream stream, int offset, int size) + { + stream.Seek(offset, SeekOrigin.Begin); + + BinaryReader reader = new BinaryReader(stream); + + int bytesRead = 0; + + Dictionary services = new Dictionary(); + + while (bytesRead != size) + { + byte controlByte = reader.ReadByte(); + + if (controlByte == 0) + { + break; + } + + int length = (controlByte & 0x07) + 1; + bool registerAllowed = (controlByte & 0x80) != 0; + + services[Encoding.ASCII.GetString(reader.ReadBytes(length))] = registerAllowed; + + bytesRead += length + 1; + } + + Services = new ReadOnlyDictionary(services); + } + } +} diff --git a/Ryujinx.HLE/Logging/LogEventArgs.cs b/Ryujinx.HLE/Logging/LogEventArgs.cs deleted file mode 100644 index 647cf71319..0000000000 --- a/Ryujinx.HLE/Logging/LogEventArgs.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; - -namespace Ryujinx.HLE.Logging -{ - public class LogEventArgs : EventArgs - { - public LogLevel Level { get; private set; } - public TimeSpan Time { get; private set; } - - public string Message { get; private set; } - - public LogEventArgs(LogLevel Level, TimeSpan Time, string Message) - { - this.Level = Level; - this.Time = Time; - this.Message = Message; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/Logging/Logger.cs b/Ryujinx.HLE/Logging/Logger.cs deleted file mode 100644 index 5376b253a3..0000000000 --- a/Ryujinx.HLE/Logging/Logger.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System; -using System.Diagnostics; -using System.Runtime.CompilerServices; - -namespace Ryujinx.HLE.Logging -{ - public class Logger - { - private bool[] EnabledLevels; - private bool[] EnabledClasses; - - public event EventHandler Updated; - - private Stopwatch Time; - - public Logger() - { - EnabledLevels = new bool[Enum.GetNames(typeof(LogLevel)).Length]; - EnabledClasses = new bool[Enum.GetNames(typeof(LogClass)).Length]; - - EnabledLevels[(int)LogLevel.Stub] = true; - EnabledLevels[(int)LogLevel.Info] = true; - EnabledLevels[(int)LogLevel.Warning] = true; - EnabledLevels[(int)LogLevel.Error] = true; - - for (int Index = 0; Index < EnabledClasses.Length; Index++) - { - EnabledClasses[Index] = true; - } - - Time = new Stopwatch(); - - Time.Start(); - } - - public void SetEnable(LogLevel Level, bool Enabled) - { - EnabledLevels[(int)Level] = Enabled; - } - - public void SetEnable(LogClass Class, bool Enabled) - { - EnabledClasses[(int)Class] = Enabled; - } - - internal void PrintDebug(LogClass Class, string Message, [CallerMemberName] string Caller = "") - { - Print(LogLevel.Debug, Class, GetFormattedMessage(Class, Message, Caller)); - } - - internal void PrintStub(LogClass Class, string Message, [CallerMemberName] string Caller = "") - { - Print(LogLevel.Stub, Class, GetFormattedMessage(Class, Message, Caller)); - } - - internal void PrintInfo(LogClass Class, string Message, [CallerMemberName] string Caller = "") - { - Print(LogLevel.Info, Class, GetFormattedMessage(Class, Message, Caller)); - } - - internal void PrintWarning(LogClass Class, string Message, [CallerMemberName] string Caller = "") - { - Print(LogLevel.Warning, Class, GetFormattedMessage(Class, Message, Caller)); - } - - internal void PrintError(LogClass Class, string Message, [CallerMemberName] string Caller = "") - { - Print(LogLevel.Error, Class, GetFormattedMessage(Class, Message, Caller)); - } - - private void Print(LogLevel Level, LogClass Class, string Message) - { - if (EnabledLevels[(int)Level] && EnabledClasses[(int)Class]) - { - Updated?.Invoke(this, new LogEventArgs(Level, Time.Elapsed, Message)); - } - } - - private string GetFormattedMessage(LogClass Class, string Message, string Caller) - { - return $"{Class} {Caller}: {Message}"; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/AppletStateMgr.cs b/Ryujinx.HLE/OsHle/AppletStateMgr.cs deleted file mode 100644 index 5fdb534339..0000000000 --- a/Ryujinx.HLE/OsHle/AppletStateMgr.cs +++ /dev/null @@ -1,62 +0,0 @@ -using Ryujinx.HLE.OsHle.Handles; -using Ryujinx.HLE.OsHle.Services.Am; -using System; -using System.Collections.Concurrent; - -namespace Ryujinx.HLE.OsHle -{ - class AppletStateMgr : IDisposable - { - private ConcurrentQueue Messages; - - public FocusState FocusState { get; private set; } - - public KEvent MessageEvent { get; private set; } - - public AppletStateMgr() - { - Messages = new ConcurrentQueue(); - - MessageEvent = new KEvent(); - } - - public void SetFocus(bool IsFocused) - { - FocusState = IsFocused - ? FocusState.InFocus - : FocusState.OutOfFocus; - - EnqueueMessage(MessageInfo.FocusStateChanged); - } - - public void EnqueueMessage(MessageInfo Message) - { - Messages.Enqueue(Message); - - MessageEvent.WaitEvent.Set(); - } - - public bool TryDequeueMessage(out MessageInfo Message) - { - if (Messages.Count < 2) - { - MessageEvent.WaitEvent.Reset(); - } - - return Messages.TryDequeue(out Message); - } - - public void Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool Disposing) - { - if (Disposing) - { - MessageEvent.Dispose(); - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Diagnostics/Demangler.cs b/Ryujinx.HLE/OsHle/Diagnostics/Demangler.cs deleted file mode 100644 index 6646dede6e..0000000000 --- a/Ryujinx.HLE/OsHle/Diagnostics/Demangler.cs +++ /dev/null @@ -1,416 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Ryujinx.HLE.OsHle.Diagnostics -{ - static class Demangler - { - private static readonly Dictionary BuiltinTypes = new Dictionary - { - { "v", "void" }, - { "w", "wchar_t" }, - { "b", "bool" }, - { "c", "char" }, - { "a", "signed char" }, - { "h", "unsigned char" }, - { "s", "short" }, - { "t", "unsigned short" }, - { "i", "int" }, - { "j", "unsigned int" }, - { "l", "long" }, - { "m", "unsigned long" }, - { "x", "long long" }, - { "y", "unsigned long long" }, - { "n", "__int128" }, - { "o", "unsigned __int128" }, - { "f", "float" }, - { "d", "double" }, - { "e", "long double" }, - { "g", "__float128" }, - { "z", "..." }, - { "Dd", "__iec559_double" }, - { "De", "__iec559_float128" }, - { "Df", "__iec559_float" }, - { "Dh", "__iec559_float16" }, - { "Di", "char32_t" }, - { "Ds", "char16_t" }, - { "Da", "decltype(auto)" }, - { "Dn", "std::nullptr_t" }, - }; - - private static readonly Dictionary SubstitutionExtra = new Dictionary - { - {"Sa", "std::allocator"}, - {"Sb", "std::basic_string"}, - {"Ss", "std::basic_string, ::std::allocator>"}, - {"Si", "std::basic_istream>"}, - {"So", "std::basic_ostream>"}, - {"Sd", "std::basic_iostream>"} - }; - - private static int FromBase36(string encoded) - { - string base36 = "0123456789abcdefghijklmnopqrstuvwxyz"; - char[] reversedEncoded = encoded.ToLower().ToCharArray().Reverse().ToArray(); - int result = 0; - for (int i = 0; i < reversedEncoded.Length; i++) - { - char c = reversedEncoded[i]; - int value = base36.IndexOf(c); - if (value == -1) - return -1; - result += value * (int)Math.Pow(36, i); - } - return result; - } - - private static string GetCompressedValue(string compression, List compressionData, out int pos) - { - string res = null; - bool canHaveUnqualifiedName = false; - pos = -1; - if (compressionData.Count == 0 || !compression.StartsWith("S")) - return null; - - if (compression.Length >= 2 && SubstitutionExtra.TryGetValue(compression.Substring(0, 2), out string substitutionValue)) - { - pos = 1; - res = substitutionValue; - compression = compression.Substring(2); - } - else if (compression.StartsWith("St")) - { - pos = 1; - canHaveUnqualifiedName = true; - res = "std"; - compression = compression.Substring(2); - } - else if (compression.StartsWith("S_")) - { - pos = 1; - res = compressionData[0]; - canHaveUnqualifiedName = true; - compression = compression.Substring(2); - } - else - { - int id = -1; - int underscorePos = compression.IndexOf('_'); - if (underscorePos == -1) - return null; - string partialId = compression.Substring(1, underscorePos - 1); - - id = FromBase36(partialId); - if (id == -1 || compressionData.Count <= (id + 1)) - { - return null; - } - res = compressionData[id + 1]; - pos = partialId.Length + 1; - canHaveUnqualifiedName= true; - compression = compression.Substring(pos); - } - if (res != null) - { - if (canHaveUnqualifiedName) - { - List type = ReadName(compression, compressionData, out int endOfNameType); - if (endOfNameType != -1 && type != null) - { - pos += endOfNameType; - res = res + "::" + type[type.Count - 1]; - } - } - } - return res; - } - - private static List ReadName(string mangled, List compressionData, out int pos, bool isNested = true) - { - List res = new List(); - string charCountString = null; - int charCount = 0; - int i; - - pos = -1; - for (i = 0; i < mangled.Length; i++) - { - char chr = mangled[i]; - if (charCountString == null) - { - if (ReadCVQualifiers(chr) != null) - { - continue; - } - if (chr == 'S') - { - string data = GetCompressedValue(mangled.Substring(i), compressionData, out pos); - if (pos == -1) - { - return null; - } - if (res.Count == 0) - res.Add(data); - else - res.Add(res[res.Count - 1] + "::" + data); - i += pos; - if (i < mangled.Length && mangled[i] == 'E') - { - break; - } - continue; - } - else if (chr == 'E') - { - break; - } - } - if (Char.IsDigit(chr)) - { - charCountString += chr; - } - else - { - if (!int.TryParse(charCountString, out charCount)) - { - return null; - } - string demangledPart = mangled.Substring(i, charCount); - if (res.Count == 0) - res.Add(demangledPart); - else - res.Add(res[res.Count - 1] + "::" + demangledPart); - i = i + charCount - 1; - charCount = 0; - charCountString = null; - if (!isNested) - break; - } - } - if (res.Count == 0) - { - return null; - } - pos = i; - return res; - } - - private static string ReadBuiltinType(string mangledType, out int pos) - { - string res = null; - string possibleBuiltinType; - pos = -1; - possibleBuiltinType = mangledType[0].ToString(); - if (!BuiltinTypes.TryGetValue(possibleBuiltinType, out res)) - { - if (mangledType.Length >= 2) - { - // Try to match the first 2 chars if the first call failed - possibleBuiltinType = mangledType.Substring(0, 2); - BuiltinTypes.TryGetValue(possibleBuiltinType, out res); - } - } - if (res != null) - pos = possibleBuiltinType.Length; - return res; - } - - private static string ReadCVQualifiers(char qualifier) - { - if (qualifier == 'r') - return "restricted"; - else if (qualifier == 'V') - return "volatile"; - else if (qualifier == 'K') - return "const"; - return null; - } - - private static string ReadRefQualifiers(char qualifier) - { - if (qualifier == 'R') - return "&"; - else if (qualifier == 'O') - return "&&"; - return null; - } - - private static string ReadSpecialQualifiers(char qualifier) - { - if (qualifier == 'P') - return "*"; - else if (qualifier == 'C') - return "complex"; - else if (qualifier == 'G') - return "imaginary"; - return null; - } - - private static List ReadParameters(string mangledParams, List compressionData, out int pos) - { - List res = new List(); - List refQualifiers = new List(); - string parsedTypePart = null; - string currentRefQualifiers = null; - string currentBuiltinType = null; - string currentSpecialQualifiers = null; - string currentCompressedValue = null; - int i = 0; - pos = -1; - - for (i = 0; i < mangledParams.Length; i++) - { - if (currentBuiltinType != null) - { - string currentCVQualifier = String.Join(" ", refQualifiers); - // Try to mimic the compression indexing - if (currentRefQualifiers != null) - { - compressionData.Add(currentBuiltinType + currentRefQualifiers); - } - if (refQualifiers.Count != 0) - { - compressionData.Add(currentBuiltinType + " " + currentCVQualifier + currentRefQualifiers); - } - if (currentSpecialQualifiers != null) - { - compressionData.Add(currentBuiltinType + " " + currentCVQualifier + currentRefQualifiers + currentSpecialQualifiers); - } - if (currentRefQualifiers == null && currentCVQualifier == null && currentSpecialQualifiers == null) - { - compressionData.Add(currentBuiltinType); - } - currentBuiltinType = null; - currentCompressedValue = null; - currentCVQualifier = null; - currentRefQualifiers = null; - refQualifiers.Clear(); - currentSpecialQualifiers = null; - } - char chr = mangledParams[i]; - string part = mangledParams.Substring(i); - - // Try to read qualifiers - parsedTypePart = ReadCVQualifiers(chr); - if (parsedTypePart != null) - { - refQualifiers.Add(parsedTypePart); - - // need more data - continue; - } - - parsedTypePart = ReadRefQualifiers(chr); - if (parsedTypePart != null) - { - currentRefQualifiers = parsedTypePart; - - // need more data - continue; - } - - parsedTypePart = ReadSpecialQualifiers(chr); - if (parsedTypePart != null) - { - currentSpecialQualifiers = parsedTypePart; - - // need more data - continue; - } - - // TODO: extended-qualifier? - - if (part.StartsWith("S")) - { - parsedTypePart = GetCompressedValue(part, compressionData, out pos); - if (pos != -1 && parsedTypePart != null) - { - currentCompressedValue = parsedTypePart; - i += pos; - res.Add(currentCompressedValue + " " + String.Join(" ", refQualifiers) + currentRefQualifiers + currentSpecialQualifiers); - currentBuiltinType = null; - currentCompressedValue = null; - currentRefQualifiers = null; - refQualifiers.Clear(); - currentSpecialQualifiers = null; - continue; - } - pos = -1; - return null; - } - else if (part.StartsWith("N")) - { - part = part.Substring(1); - List name = ReadName(part, compressionData, out pos); - if (pos != -1 && name != null) - { - i += pos + 1; - res.Add(name[name.Count - 1] + " " + String.Join(" ", refQualifiers) + currentRefQualifiers + currentSpecialQualifiers); - currentBuiltinType = null; - currentCompressedValue = null; - currentRefQualifiers = null; - refQualifiers.Clear(); - currentSpecialQualifiers = null; - continue; - } - } - - // Try builting - parsedTypePart = ReadBuiltinType(part, out pos); - if (pos == -1) - { - return null; - } - currentBuiltinType = parsedTypePart; - res.Add(currentBuiltinType + " " + String.Join(" ", refQualifiers) + currentRefQualifiers + currentSpecialQualifiers); - i = i + pos -1; - } - pos = i; - return res; - } - - private static string ParseFunctionName(string mangled) - { - List compressionData = new List(); - int pos = 0; - string res; - bool isNested = mangled.StartsWith("N"); - - // If it's start with "N" it must be a nested function name - if (isNested) - mangled = mangled.Substring(1); - compressionData = ReadName(mangled, compressionData, out pos, isNested); - if (pos == -1) - return null; - res = compressionData[compressionData.Count - 1]; - compressionData.Remove(res); - mangled = mangled.Substring(pos + 1); - - // more data? maybe not a data name so... - if (mangled != String.Empty) - { - List parameters = ReadParameters(mangled, compressionData, out pos); - // parameters parsing error, we return the original data to avoid information loss. - if (pos == -1) - return null; - parameters = parameters.Select(outer => outer.Trim()).ToList(); - res += "(" + String.Join(", ", parameters) + ")"; - } - return res; - } - - public static string Parse(string originalMangled) - { - if (originalMangled.StartsWith("_Z")) - { - // We assume that we have a name (TOOD: support special names) - string res = ParseFunctionName(originalMangled.Substring(2)); - if (res == null) - return originalMangled; - return res; - } - return originalMangled; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/ErrorCode.cs b/Ryujinx.HLE/OsHle/ErrorCode.cs deleted file mode 100644 index 1e07f9b249..0000000000 --- a/Ryujinx.HLE/OsHle/ErrorCode.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Ryujinx.HLE.OsHle -{ - static class ErrorCode - { - public static uint MakeError(ErrorModule Module, int Code) - { - return (uint)Module | ((uint)Code << 9); - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/ErrorModule.cs b/Ryujinx.HLE/OsHle/ErrorModule.cs deleted file mode 100644 index 924ee95160..0000000000 --- a/Ryujinx.HLE/OsHle/ErrorModule.cs +++ /dev/null @@ -1,101 +0,0 @@ -namespace Ryujinx.HLE.OsHle -{ - enum ErrorModule - { - Kernel = 1, - Fs = 2, - Os = 3, // (Memory, Thread, Mutex, NVIDIA) - Htcs = 4, - Ncm = 5, - Dd = 6, - Debug_Monitor = 7, - Lr = 8, - Loader = 9, - IPC_Command_Interface = 10, - IPC = 11, - Pm = 15, - Ns = 16, - Socket = 17, - Htc = 18, - Ncm_Content = 20, - Sm = 21, - RO_Userland = 22, - SdMmc = 24, - Ovln = 25, - Spl = 26, - Ethc = 100, - I2C = 101, - Gpio = 102, - Uart = 103, - Settings = 105, - Wlan = 107, - Xcd = 108, - Nifm = 110, - Hwopus = 111, - Bluetooth = 113, - Vi = 114, - Nfp = 115, - Time = 116, - Fgm = 117, - Oe = 118, - Pcie = 120, - Friends = 121, - Bcat = 122, - SSL = 123, - Account = 124, - News = 125, - Mii = 126, - Nfc = 127, - Am = 128, - Play_Report = 129, - Ahid = 130, - Qlaunch = 132, - Pcv = 133, - Omm = 134, - Bpc = 135, - Psm = 136, - Nim = 137, - Psc = 138, - Tc = 139, - Usb = 140, - Nsd = 141, - Pctl = 142, - Btm = 143, - Ec = 144, - ETicket = 145, - Ngc = 146, - Error_Report = 147, - Apm = 148, - Profiler = 150, - Error_Upload = 151, - Audio = 153, - Npns = 154, - Npns_Http_Stream = 155, - Arp = 157, - Swkbd = 158, - Boot = 159, - Nfc_Mifare = 161, - Userland_Assert = 162, - Fatal = 163, - Nim_Shop = 164, - Spsm = 165, - Bgtc = 167, - Userland_Crash = 168, - SRepo = 180, - Dauth = 181, - Hid = 202, - Ldn = 203, - Irsensor = 205, - Capture = 206, - Manu = 208, - Atk = 209, - Web = 210, - Grc = 212, - Migration = 216, - Migration_Ldc_Server = 217, - General_Web_Applet = 800, - Wifi_Web_Auth_Applet = 809, - Whitelisted_Applet = 810, - ShopN = 811 - } -} diff --git a/Ryujinx.HLE/OsHle/GlobalStateTable.cs b/Ryujinx.HLE/OsHle/GlobalStateTable.cs deleted file mode 100644 index fb71e46b9f..0000000000 --- a/Ryujinx.HLE/OsHle/GlobalStateTable.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System.Collections.Concurrent; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle -{ - class GlobalStateTable - { - private ConcurrentDictionary DictByProcess; - - public GlobalStateTable() - { - DictByProcess = new ConcurrentDictionary(); - } - - public bool Add(Process Process, int Id, object Data) - { - IdDictionary Dict = DictByProcess.GetOrAdd(Process, (Key) => new IdDictionary()); - - return Dict.Add(Id, Data); - } - - public int Add(Process Process, object Data) - { - IdDictionary Dict = DictByProcess.GetOrAdd(Process, (Key) => new IdDictionary()); - - return Dict.Add(Data); - } - - public object GetData(Process Process, int Id) - { - if (DictByProcess.TryGetValue(Process, out IdDictionary Dict)) - { - return Dict.GetData(Id); - } - - return null; - } - - public T GetData(Process Process, int Id) - { - if (DictByProcess.TryGetValue(Process, out IdDictionary Dict)) - { - return Dict.GetData(Id); - } - - return default(T); - } - - public object Delete(Process Process, int Id) - { - if (DictByProcess.TryGetValue(Process, out IdDictionary Dict)) - { - return Dict.Delete(Id); - } - - return null; - } - - public ICollection DeleteProcess(Process Process) - { - if (DictByProcess.TryRemove(Process, out IdDictionary Dict)) - { - return Dict.Clear(); - } - - return null; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Handles/HSharedMem.cs b/Ryujinx.HLE/OsHle/Handles/HSharedMem.cs deleted file mode 100644 index 6426e585ea..0000000000 --- a/Ryujinx.HLE/OsHle/Handles/HSharedMem.cs +++ /dev/null @@ -1,44 +0,0 @@ -using ChocolArm64.Memory; -using System; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Handles -{ - class HSharedMem - { - private List<(AMemory, long)> Positions; - - public EventHandler MemoryMapped; - public EventHandler MemoryUnmapped; - - public HSharedMem() - { - Positions = new List<(AMemory, long)>(); - } - - public void AddVirtualPosition(AMemory Memory, long Position) - { - lock (Positions) - { - Positions.Add((Memory, Position)); - - MemoryMapped?.Invoke(this, EventArgs.Empty); - } - } - - public void RemoveVirtualPosition(AMemory Memory, long Position) - { - lock (Positions) - { - Positions.Remove((Memory, Position)); - - MemoryUnmapped?.Invoke(this, EventArgs.Empty); - } - } - - public (AMemory, long)[] GetVirtualPositions() - { - return Positions.ToArray(); - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Handles/HTransferMem.cs b/Ryujinx.HLE/OsHle/Handles/HTransferMem.cs deleted file mode 100644 index 2969a2bae4..0000000000 --- a/Ryujinx.HLE/OsHle/Handles/HTransferMem.cs +++ /dev/null @@ -1,21 +0,0 @@ -using ChocolArm64.Memory; - -namespace Ryujinx.HLE.OsHle.Handles -{ - class HTransferMem - { - public AMemory Memory { get; private set; } - public AMemoryPerm Perm { get; private set; } - - public long Position { get; private set; } - public long Size { get; private set; } - - public HTransferMem(AMemory Memory, AMemoryPerm Perm, long Position, long Size) - { - this.Memory = Memory; - this.Perm = Perm; - this.Position = Position; - this.Size = Size; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Handles/KEvent.cs b/Ryujinx.HLE/OsHle/Handles/KEvent.cs deleted file mode 100644 index df5108f98a..0000000000 --- a/Ryujinx.HLE/OsHle/Handles/KEvent.cs +++ /dev/null @@ -1,4 +0,0 @@ -namespace Ryujinx.HLE.OsHle.Handles -{ - class KEvent : KSynchronizationObject { } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Handles/KProcessHandleTable.cs b/Ryujinx.HLE/OsHle/Handles/KProcessHandleTable.cs deleted file mode 100644 index d22b63c683..0000000000 --- a/Ryujinx.HLE/OsHle/Handles/KProcessHandleTable.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Handles -{ - class KProcessHandleTable - { - private IdDictionary Handles; - - public KProcessHandleTable() - { - Handles = new IdDictionary(); - } - - public int OpenHandle(object Obj) - { - return Handles.Add(Obj); - } - - public T GetData(int Handle) - { - return Handles.GetData(Handle); - } - - public object CloseHandle(int Handle) - { - return Handles.Delete(Handle); - } - - public ICollection Clear() - { - return Handles.Clear(); - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Handles/KProcessScheduler.cs b/Ryujinx.HLE/OsHle/Handles/KProcessScheduler.cs deleted file mode 100644 index 7d5e228460..0000000000 --- a/Ryujinx.HLE/OsHle/Handles/KProcessScheduler.cs +++ /dev/null @@ -1,360 +0,0 @@ -using Ryujinx.HLE.Logging; -using System; -using System.Collections.Concurrent; -using System.Threading; - -namespace Ryujinx.HLE.OsHle.Handles -{ - class KProcessScheduler : IDisposable - { - private ConcurrentDictionary AllThreads; - - private ThreadQueue WaitingToRun; - - private KThread[] CoreThreads; - - private bool[] CoreReschedule; - - private object SchedLock; - - private Logger Log; - - public KProcessScheduler(Logger Log) - { - this.Log = Log; - - AllThreads = new ConcurrentDictionary(); - - WaitingToRun = new ThreadQueue(); - - CoreThreads = new KThread[4]; - - CoreReschedule = new bool[4]; - - SchedLock = new object(); - } - - public void StartThread(KThread Thread) - { - lock (SchedLock) - { - SchedulerThread SchedThread = new SchedulerThread(Thread); - - if (!AllThreads.TryAdd(Thread, SchedThread)) - { - return; - } - - if (TryAddToCore(Thread)) - { - Thread.Thread.Execute(); - - PrintDbgThreadInfo(Thread, "running."); - } - else - { - WaitingToRun.Push(SchedThread); - - PrintDbgThreadInfo(Thread, "waiting to run."); - } - } - } - - public void RemoveThread(KThread Thread) - { - PrintDbgThreadInfo(Thread, "exited."); - - lock (SchedLock) - { - if (AllThreads.TryRemove(Thread, out SchedulerThread SchedThread)) - { - WaitingToRun.Remove(SchedThread); - - SchedThread.Dispose(); - } - - int ActualCore = Thread.ActualCore; - - SchedulerThread NewThread = WaitingToRun.Pop(ActualCore); - - if (NewThread == null) - { - Log.PrintDebug(LogClass.KernelScheduler, $"Nothing to run on core {ActualCore}!"); - - CoreThreads[ActualCore] = null; - - return; - } - - NewThread.Thread.ActualCore = ActualCore; - - RunThread(NewThread); - } - } - - public void SetThreadActivity(KThread Thread, bool Active) - { - SchedulerThread SchedThread = AllThreads[Thread]; - - SchedThread.IsActive = Active; - - if (Active) - { - SchedThread.WaitActivity.Set(); - } - else - { - SchedThread.WaitActivity.Reset(); - } - } - - public void EnterWait(KThread Thread, int TimeoutMs = Timeout.Infinite) - { - SchedulerThread SchedThread = AllThreads[Thread]; - - Suspend(Thread); - - SchedThread.WaitSync.WaitOne(TimeoutMs); - - TryResumingExecution(SchedThread); - } - - public void WakeUp(KThread Thread) - { - AllThreads[Thread].WaitSync.Set(); - } - - public void ChangeCore(KThread Thread, int IdealCore, int CoreMask) - { - lock (SchedLock) - { - if (IdealCore != -3) - { - Thread.IdealCore = IdealCore; - } - - Thread.CoreMask = CoreMask; - - if (AllThreads.ContainsKey(Thread)) - { - SetReschedule(Thread.ActualCore); - - SchedulerThread SchedThread = AllThreads[Thread]; - - //Note: Aways if the thread is on the queue first, and try - //adding to a new core later, to ensure that a thread that - //is already running won't be added to another core. - if (WaitingToRun.HasThread(SchedThread) && TryAddToCore(Thread)) - { - WaitingToRun.Remove(SchedThread); - - RunThread(SchedThread); - } - } - } - } - - public void Suspend(KThread Thread) - { - lock (SchedLock) - { - PrintDbgThreadInfo(Thread, "suspended."); - - int ActualCore = Thread.ActualCore; - - CoreReschedule[ActualCore] = false; - - SchedulerThread SchedThread = WaitingToRun.Pop(ActualCore); - - if (SchedThread != null) - { - SchedThread.Thread.ActualCore = ActualCore; - - CoreThreads[ActualCore] = SchedThread.Thread; - - RunThread(SchedThread); - } - else - { - Log.PrintDebug(LogClass.KernelScheduler, $"Nothing to run on core {Thread.ActualCore}!"); - - CoreThreads[ActualCore] = null; - } - } - } - - public void SetReschedule(int Core) - { - lock (SchedLock) - { - CoreReschedule[Core] = true; - } - } - - public void Reschedule(KThread Thread) - { - bool NeedsReschedule; - - lock (SchedLock) - { - int ActualCore = Thread.ActualCore; - - NeedsReschedule = CoreReschedule[ActualCore]; - - CoreReschedule[ActualCore] = false; - } - - if (NeedsReschedule) - { - Yield(Thread, Thread.ActualPriority - 1); - } - } - - public void Yield(KThread Thread) - { - Yield(Thread, Thread.ActualPriority); - } - - private void Yield(KThread Thread, int MinPriority) - { - PrintDbgThreadInfo(Thread, "yielded execution."); - - lock (SchedLock) - { - int ActualCore = Thread.ActualCore; - - SchedulerThread NewThread = WaitingToRun.Pop(ActualCore, MinPriority); - - if (NewThread != null) - { - NewThread.Thread.ActualCore = ActualCore; - - CoreThreads[ActualCore] = NewThread.Thread; - - RunThread(NewThread); - } - else - { - CoreThreads[ActualCore] = null; - } - } - - Resume(Thread); - } - - public void Resume(KThread Thread) - { - TryResumingExecution(AllThreads[Thread]); - } - - private void TryResumingExecution(SchedulerThread SchedThread) - { - KThread Thread = SchedThread.Thread; - - PrintDbgThreadInfo(Thread, "trying to resume..."); - - SchedThread.WaitActivity.WaitOne(); - - lock (SchedLock) - { - if (TryAddToCore(Thread)) - { - PrintDbgThreadInfo(Thread, "resuming execution..."); - - return; - } - - WaitingToRun.Push(SchedThread); - - SetReschedule(Thread.ProcessorId); - - PrintDbgThreadInfo(Thread, "entering wait state..."); - } - - SchedThread.WaitSched.WaitOne(); - - PrintDbgThreadInfo(Thread, "resuming execution..."); - } - - private void RunThread(SchedulerThread SchedThread) - { - if (!SchedThread.Thread.Thread.Execute()) - { - PrintDbgThreadInfo(SchedThread.Thread, "waked."); - - SchedThread.WaitSched.Set(); - } - else - { - PrintDbgThreadInfo(SchedThread.Thread, "running."); - } - } - - public void Resort(KThread Thread) - { - if (AllThreads.TryGetValue(Thread, out SchedulerThread SchedThread)) - { - WaitingToRun.Resort(SchedThread); - } - } - - private bool TryAddToCore(KThread Thread) - { - //First, try running it on Ideal Core. - int IdealCore = Thread.IdealCore; - - if (IdealCore != -1 && CoreThreads[IdealCore] == null) - { - Thread.ActualCore = IdealCore; - - CoreThreads[IdealCore] = Thread; - - return true; - } - - //If that fails, then try running on any core allowed by Core Mask. - int CoreMask = Thread.CoreMask; - - for (int Core = 0; Core < CoreThreads.Length; Core++, CoreMask >>= 1) - { - if ((CoreMask & 1) != 0 && CoreThreads[Core] == null) - { - Thread.ActualCore = Core; - - CoreThreads[Core] = Thread; - - return true; - } - } - - return false; - } - - private void PrintDbgThreadInfo(KThread Thread, string Message) - { - Log.PrintDebug(LogClass.KernelScheduler, "(" + - "ThreadId = " + Thread.ThreadId + ", " + - "CoreMask = 0x" + Thread.CoreMask.ToString("x1") + ", " + - "ActualCore = " + Thread.ActualCore + ", " + - "IdealCore = " + Thread.IdealCore + ", " + - "ActualPriority = " + Thread.ActualPriority + ", " + - "WantedPriority = " + Thread.WantedPriority + ") " + Message); - } - - public void Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool Disposing) - { - if (Disposing) - { - foreach (SchedulerThread SchedThread in AllThreads.Values) - { - SchedThread.Dispose(); - } - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Handles/KSession.cs b/Ryujinx.HLE/OsHle/Handles/KSession.cs deleted file mode 100644 index e85de36c8f..0000000000 --- a/Ryujinx.HLE/OsHle/Handles/KSession.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Ryujinx.HLE.OsHle.Services; -using System; - -namespace Ryujinx.HLE.OsHle.Handles -{ - class KSession : IDisposable - { - public IpcService Service { get; private set; } - - public string ServiceName { get; private set; } - - public KSession(IpcService Service, string ServiceName) - { - this.Service = Service; - this.ServiceName = ServiceName; - } - - public void Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool Disposing) - { - if (Disposing && Service is IDisposable DisposableService) - { - DisposableService.Dispose(); - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Handles/KSynchronizationObject.cs b/Ryujinx.HLE/OsHle/Handles/KSynchronizationObject.cs deleted file mode 100644 index 0e7d06f6d7..0000000000 --- a/Ryujinx.HLE/OsHle/Handles/KSynchronizationObject.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Threading; - -namespace Ryujinx.HLE.OsHle.Handles -{ - class KSynchronizationObject : IDisposable - { - public ManualResetEvent WaitEvent { get; private set; } - - public KSynchronizationObject() - { - WaitEvent = new ManualResetEvent(false); - } - - public void Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool Disposing) - { - if (Disposing) - { - WaitEvent.Dispose(); - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Handles/KThread.cs b/Ryujinx.HLE/OsHle/Handles/KThread.cs deleted file mode 100644 index 3db46f3d67..0000000000 --- a/Ryujinx.HLE/OsHle/Handles/KThread.cs +++ /dev/null @@ -1,94 +0,0 @@ -using ChocolArm64; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Handles -{ - class KThread : KSynchronizationObject - { - public AThread Thread { get; private set; } - - public int CoreMask { get; set; } - - public long MutexAddress { get; set; } - public long CondVarAddress { get; set; } - - public bool CondVarSignaled { get; set; } - - private Process Process; - - public List MutexWaiters { get; private set; } - - public KThread MutexOwner { get; set; } - - public int ActualPriority { get; private set; } - public int WantedPriority { get; private set; } - - public int ActualCore { get; set; } - public int ProcessorId { get; set; } - public int IdealCore { get; set; } - - public int WaitHandle { get; set; } - - public long LastPc { get; set; } - - public int ThreadId => Thread.ThreadId; - - public KThread( - AThread Thread, - Process Process, - int ProcessorId, - int Priority) - { - this.Thread = Thread; - this.Process = Process; - this.ProcessorId = ProcessorId; - this.IdealCore = ProcessorId; - - MutexWaiters = new List(); - - CoreMask = 1 << ProcessorId; - - ActualPriority = WantedPriority = Priority; - } - - public void SetPriority(int Priority) - { - WantedPriority = Priority; - - UpdatePriority(); - } - - public void UpdatePriority() - { - bool PriorityChanged; - - lock (Process.ThreadSyncLock) - { - int OldPriority = ActualPriority; - - int CurrPriority = WantedPriority; - - foreach (KThread Thread in MutexWaiters) - { - int WantedPriority = Thread.WantedPriority; - - if (CurrPriority > WantedPriority) - { - CurrPriority = WantedPriority; - } - } - - PriorityChanged = CurrPriority != OldPriority; - - ActualPriority = CurrPriority; - } - - if (PriorityChanged) - { - Process.Scheduler.Resort(this); - - MutexOwner?.UpdatePriority(); - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Handles/SchedulerThread.cs b/Ryujinx.HLE/OsHle/Handles/SchedulerThread.cs deleted file mode 100644 index 5bdefe74e9..0000000000 --- a/Ryujinx.HLE/OsHle/Handles/SchedulerThread.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; -using System.Threading; - -namespace Ryujinx.HLE.OsHle.Handles -{ - class SchedulerThread : IDisposable - { - public KThread Thread { get; private set; } - - public SchedulerThread Next { get; set; } - - public bool IsActive { get; set; } - - public AutoResetEvent WaitSync { get; private set; } - public ManualResetEvent WaitActivity { get; private set; } - public AutoResetEvent WaitSched { get; private set; } - - public SchedulerThread(KThread Thread) - { - this.Thread = Thread; - - IsActive = true; - - WaitSync = new AutoResetEvent(false); - - WaitActivity = new ManualResetEvent(true); - - WaitSched = new AutoResetEvent(false); - } - - public void Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool Disposing) - { - if (Disposing) - { - WaitSync.Dispose(); - - WaitActivity.Dispose(); - - WaitSched.Dispose(); - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Handles/ThreadQueue.cs b/Ryujinx.HLE/OsHle/Handles/ThreadQueue.cs deleted file mode 100644 index 09dcb3d088..0000000000 --- a/Ryujinx.HLE/OsHle/Handles/ThreadQueue.cs +++ /dev/null @@ -1,158 +0,0 @@ -namespace Ryujinx.HLE.OsHle.Handles -{ - class ThreadQueue - { - private const int LowestPriority = 0x3f; - - private SchedulerThread Head; - - private object ListLock; - - public ThreadQueue() - { - ListLock = new object(); - } - - public void Push(SchedulerThread Wait) - { - lock (ListLock) - { - //Ensure that we're not creating circular references - //by adding a thread that is already on the list. - if (HasThread(Wait)) - { - return; - } - - if (Head == null || Head.Thread.ActualPriority >= Wait.Thread.ActualPriority) - { - Wait.Next = Head; - - Head = Wait; - - return; - } - - SchedulerThread Curr = Head; - - while (Curr.Next != null) - { - if (Curr.Next.Thread.ActualPriority >= Wait.Thread.ActualPriority) - { - break; - } - - Curr = Curr.Next; - } - - Wait.Next = Curr.Next; - Curr.Next = Wait; - } - } - - public SchedulerThread Pop(int Core, int MinPriority = LowestPriority) - { - lock (ListLock) - { - int CoreMask = 1 << Core; - - SchedulerThread Prev = null; - SchedulerThread Curr = Head; - - while (Curr != null) - { - KThread Thread = Curr.Thread; - - if (Thread.ActualPriority <= MinPriority && (Thread.CoreMask & CoreMask) != 0) - { - if (Prev != null) - { - Prev.Next = Curr.Next; - } - else - { - Head = Head.Next; - } - - break; - } - - Prev = Curr; - Curr = Curr.Next; - } - - return Curr; - } - } - - public bool Remove(SchedulerThread Thread) - { - lock (ListLock) - { - if (Head == null) - { - return false; - } - else if (Head == Thread) - { - Head = Head.Next; - - return true; - } - - SchedulerThread Prev = Head; - SchedulerThread Curr = Head.Next; - - while (Curr != null) - { - if (Curr == Thread) - { - Prev.Next = Curr.Next; - - return true; - } - - Prev = Curr; - Curr = Curr.Next; - } - - return false; - } - } - - public bool Resort(SchedulerThread Thread) - { - lock (ListLock) - { - if (Remove(Thread)) - { - Push(Thread); - - return true; - } - - return false; - } - } - - public bool HasThread(SchedulerThread Thread) - { - lock (ListLock) - { - SchedulerThread Curr = Head; - - while (Curr != null) - { - if (Curr == Thread) - { - return true; - } - - Curr = Curr.Next; - } - - return false; - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Homebrew.cs b/Ryujinx.HLE/OsHle/Homebrew.cs deleted file mode 100644 index 4266c8db46..0000000000 --- a/Ryujinx.HLE/OsHle/Homebrew.cs +++ /dev/null @@ -1,69 +0,0 @@ -using ChocolArm64.Memory; - -namespace Ryujinx.HLE.OsHle -{ - static class Homebrew - { - //http://switchbrew.org/index.php?title=Homebrew_ABI - public static void WriteHbAbiData(AMemory Memory, long Position, int MainThreadHandle) - { - Memory.Manager.Map(Position, AMemoryMgr.PageSize, (int)MemoryType.Normal, AMemoryPerm.RW); - - //MainThreadHandle - WriteConfigEntry(Memory, ref Position, 1, 0, MainThreadHandle); - - //NextLoadPath - WriteConfigEntry(Memory, ref Position, 2, 0, Position + 0x200, Position + 0x400); - - //AppletType - WriteConfigEntry(Memory, ref Position, 7); - - //EndOfList - WriteConfigEntry(Memory, ref Position, 0); - } - - private static void WriteConfigEntry( - AMemory Memory, - ref long Position, - int Key, - int Flags = 0, - long Value0 = 0, - long Value1 = 0) - { - Memory.WriteInt32(Position + 0x00, Key); - Memory.WriteInt32(Position + 0x04, Flags); - Memory.WriteInt64(Position + 0x08, Value0); - Memory.WriteInt64(Position + 0x10, Value1); - - Position += 0x18; - } - - public static string ReadHbAbiNextLoadPath(AMemory Memory, long Position) - { - string FileName = null; - - while (true) - { - long Key = Memory.ReadInt64(Position); - - if (Key == 2) - { - long Value0 = Memory.ReadInt64(Position + 0x08); - long Value1 = Memory.ReadInt64(Position + 0x10); - - FileName = AMemoryHelper.ReadAsciiString(Memory, Value0, Value1 - Value0); - - break; - } - else if (Key == 0) - { - break; - } - - Position += 0x18; - } - - return FileName; - } - } -} diff --git a/Ryujinx.HLE/OsHle/Horizon.cs b/Ryujinx.HLE/OsHle/Horizon.cs deleted file mode 100644 index a5bf0616c6..0000000000 --- a/Ryujinx.HLE/OsHle/Horizon.cs +++ /dev/null @@ -1,200 +0,0 @@ -using Ryujinx.HLE.Loaders.Executables; -using Ryujinx.HLE.Logging; -using Ryujinx.HLE.OsHle.Handles; -using System; -using System.Collections.Concurrent; -using System.IO; - -namespace Ryujinx.HLE.OsHle -{ - public class Horizon : IDisposable - { - internal const int HidSize = 0x40000; - internal const int FontSize = 0x50; - - private Switch Ns; - - private KProcessScheduler Scheduler; - - private ConcurrentDictionary Processes; - - public SystemStateMgr SystemState { get; private set; } - - internal MemoryAllocator Allocator { get; private set; } - - internal HSharedMem HidSharedMem { get; private set; } - internal HSharedMem FontSharedMem { get; private set; } - - internal KEvent VsyncEvent { get; private set; } - - public Horizon(Switch Ns) - { - this.Ns = Ns; - - Scheduler = new KProcessScheduler(Ns.Log); - - Processes = new ConcurrentDictionary(); - - SystemState = new SystemStateMgr(); - - Allocator = new MemoryAllocator(); - - HidSharedMem = new HSharedMem(); - FontSharedMem = new HSharedMem(); - - VsyncEvent = new KEvent(); - } - - public void LoadCart(string ExeFsDir, string RomFsFile = null) - { - if (RomFsFile != null) - { - Ns.VFs.LoadRomFs(RomFsFile); - } - - Process MainProcess = MakeProcess(); - - void LoadNso(string FileName) - { - foreach (string File in Directory.GetFiles(ExeFsDir, FileName)) - { - if (Path.GetExtension(File) != string.Empty) - { - continue; - } - - Ns.Log.PrintInfo(LogClass.Loader, $"Loading {Path.GetFileNameWithoutExtension(File)}..."); - - using (FileStream Input = new FileStream(File, FileMode.Open)) - { - string Name = Path.GetFileNameWithoutExtension(File); - - Nso Program = new Nso(Input, Name); - - MainProcess.LoadProgram(Program); - } - } - } - - LoadNso("rtld"); - - MainProcess.SetEmptyArgs(); - - LoadNso("main"); - LoadNso("subsdk*"); - LoadNso("sdk"); - - MainProcess.Run(); - } - - public void LoadProgram(string FileName) - { - bool IsNro = Path.GetExtension(FileName).ToLower() == ".nro"; - - string Name = Path.GetFileNameWithoutExtension(FileName); - - Process MainProcess = MakeProcess(); - - using (FileStream Input = new FileStream(FileName, FileMode.Open)) - { - MainProcess.LoadProgram(IsNro - ? (IExecutable)new Nro(Input, Name) - : (IExecutable)new Nso(Input, Name)); - } - - MainProcess.SetEmptyArgs(); - MainProcess.Run(IsNro); - } - - public void SignalVsync() => VsyncEvent.WaitEvent.Set(); - - private Process MakeProcess() - { - Process Process; - - lock (Processes) - { - int ProcessId = 0; - - while (Processes.ContainsKey(ProcessId)) - { - ProcessId++; - } - - Process = new Process(Ns, Scheduler, ProcessId); - - Processes.TryAdd(ProcessId, Process); - } - - InitializeProcess(Process); - - return Process; - } - - private void InitializeProcess(Process Process) - { - Process.AppletState.SetFocus(true); - } - - internal void ExitProcess(int ProcessId) - { - if (Processes.TryGetValue(ProcessId, out Process Process) && Process.NeedsHbAbi) - { - string NextNro = Homebrew.ReadHbAbiNextLoadPath(Process.Memory, Process.HbAbiDataPosition); - - Ns.Log.PrintInfo(LogClass.Loader, $"HbAbi NextLoadPath {NextNro}"); - - if (NextNro == string.Empty) - { - NextNro = "sdmc:/hbmenu.nro"; - } - - NextNro = NextNro.Replace("sdmc:", string.Empty); - - NextNro = Ns.VFs.GetFullPath(Ns.VFs.GetSdCardPath(), NextNro); - - if (File.Exists(NextNro)) - { - LoadProgram(NextNro); - } - } - - if (Processes.TryRemove(ProcessId, out Process)) - { - Process.StopAllThreadsAsync(); - Process.Dispose(); - - if (Processes.Count == 0) - { - Ns.OnFinish(EventArgs.Empty); - } - } - } - - internal bool TryGetProcess(int ProcessId, out Process Process) - { - return Processes.TryGetValue(ProcessId, out Process); - } - - public void Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool Disposing) - { - if (Disposing) - { - foreach (Process Process in Processes.Values) - { - Process.StopAllThreadsAsync(); - Process.Dispose(); - } - - VsyncEvent.Dispose(); - - Scheduler.Dispose(); - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/IdDictionary.cs b/Ryujinx.HLE/OsHle/IdDictionary.cs deleted file mode 100644 index 7a93f63462..0000000000 --- a/Ryujinx.HLE/OsHle/IdDictionary.cs +++ /dev/null @@ -1,87 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle -{ - class IdDictionary - { - private ConcurrentDictionary Objs; - - private int FreeIdHint = 1; - - public IdDictionary() - { - Objs = new ConcurrentDictionary(); - } - - public bool Add(int Id, object Data) - { - return Objs.TryAdd(Id, Data); - } - - public int Add(object Data) - { - if (Objs.TryAdd(FreeIdHint, Data)) - { - return FreeIdHint++; - } - - return AddSlow(Data); - } - - private int AddSlow(object Data) - { - for (int Id = 1; Id < int.MaxValue; Id++) - { - if (Objs.TryAdd(Id, Data)) - { - return Id; - } - } - - throw new InvalidOperationException(); - } - - public object GetData(int Id) - { - if (Objs.TryGetValue(Id, out object Data)) - { - return Data; - } - - return null; - } - - public T GetData(int Id) - { - if (Objs.TryGetValue(Id, out object Data) && Data is T) - { - return (T)Data; - } - - return default(T); - } - - public object Delete(int Id) - { - if (Objs.TryRemove(Id, out object Obj)) - { - FreeIdHint = Id; - - return Obj; - } - - return null; - } - - public ICollection Clear() - { - ICollection Values = Objs.Values; - - Objs.Clear(); - - return Values; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Ipc/IpcBuffDesc.cs b/Ryujinx.HLE/OsHle/Ipc/IpcBuffDesc.cs deleted file mode 100644 index 12bff0fba4..0000000000 --- a/Ryujinx.HLE/OsHle/Ipc/IpcBuffDesc.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.IO; - -namespace Ryujinx.HLE.OsHle.Ipc -{ - struct IpcBuffDesc - { - public long Position { get; private set; } - public long Size { get; private set; } - public int Flags { get; private set; } - - public IpcBuffDesc(BinaryReader Reader) - { - long Word0 = Reader.ReadUInt32(); - long Word1 = Reader.ReadUInt32(); - long Word2 = Reader.ReadUInt32(); - - Position = Word1; - Position |= (Word2 << 4) & 0x0f00000000; - Position |= (Word2 << 34) & 0x7000000000; - - Size = Word0; - Size |= (Word2 << 8) & 0xf00000000; - - Flags = (int)Word2 & 3; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Ipc/IpcHandleDesc.cs b/Ryujinx.HLE/OsHle/Ipc/IpcHandleDesc.cs deleted file mode 100644 index 953cac7643..0000000000 --- a/Ryujinx.HLE/OsHle/Ipc/IpcHandleDesc.cs +++ /dev/null @@ -1,90 +0,0 @@ -using System; -using System.IO; - -namespace Ryujinx.HLE.OsHle.Ipc -{ - class IpcHandleDesc - { - public bool HasPId { get; private set; } - - public long PId { get; private set; } - - public int[] ToCopy { get; private set; } - public int[] ToMove { get; private set; } - - public IpcHandleDesc(BinaryReader Reader) - { - int Word = Reader.ReadInt32(); - - HasPId = (Word & 1) != 0; - - ToCopy = new int[(Word >> 1) & 0xf]; - ToMove = new int[(Word >> 5) & 0xf]; - - PId = HasPId ? Reader.ReadInt64() : 0; - - for (int Index = 0; Index < ToCopy.Length; Index++) - { - ToCopy[Index] = Reader.ReadInt32(); - } - - for (int Index = 0; Index < ToMove.Length; Index++) - { - ToMove[Index] = Reader.ReadInt32(); - } - } - - public IpcHandleDesc(int[] Copy, int[] Move) - { - ToCopy = Copy ?? throw new ArgumentNullException(nameof(Copy)); - ToMove = Move ?? throw new ArgumentNullException(nameof(Move)); - } - - public IpcHandleDesc(int[] Copy, int[] Move, long PId) : this(Copy, Move) - { - this.PId = PId; - - HasPId = true; - } - - public static IpcHandleDesc MakeCopy(int Handle) => new IpcHandleDesc( - new int[] { Handle }, - new int[0]); - - public static IpcHandleDesc MakeMove(int Handle) => new IpcHandleDesc( - new int[0], - new int[] { Handle }); - - public byte[] GetBytes() - { - using (MemoryStream MS = new MemoryStream()) - { - BinaryWriter Writer = new BinaryWriter(MS); - - int Word = HasPId ? 1 : 0; - - Word |= (ToCopy.Length & 0xf) << 1; - Word |= (ToMove.Length & 0xf) << 5; - - Writer.Write(Word); - - if (HasPId) - { - Writer.Write((long)PId); - } - - foreach (int Handle in ToCopy) - { - Writer.Write(Handle); - } - - foreach (int Handle in ToMove) - { - Writer.Write(Handle); - } - - return MS.ToArray(); - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Ipc/IpcHandler.cs b/Ryujinx.HLE/OsHle/Ipc/IpcHandler.cs deleted file mode 100644 index 9b46cf4bf1..0000000000 --- a/Ryujinx.HLE/OsHle/Ipc/IpcHandler.cs +++ /dev/null @@ -1,138 +0,0 @@ -using ChocolArm64.Memory; -using Ryujinx.HLE.OsHle.Handles; -using System; -using System.IO; - -namespace Ryujinx.HLE.OsHle.Ipc -{ - static class IpcHandler - { - public static long IpcCall( - Switch Ns, - Process Process, - AMemory Memory, - KSession Session, - IpcMessage Request, - long CmdPtr) - { - IpcMessage Response = new IpcMessage(); - - using (MemoryStream Raw = new MemoryStream(Request.RawData)) - { - BinaryReader ReqReader = new BinaryReader(Raw); - - if (Request.Type == IpcMessageType.Request) - { - Response.Type = IpcMessageType.Response; - - using (MemoryStream ResMS = new MemoryStream()) - { - BinaryWriter ResWriter = new BinaryWriter(ResMS); - - ServiceCtx Context = new ServiceCtx( - Ns, - Process, - Memory, - Session, - Request, - Response, - ReqReader, - ResWriter); - - Session.Service.CallMethod(Context); - - Response.RawData = ResMS.ToArray(); - } - } - else if (Request.Type == IpcMessageType.Control) - { - long Magic = ReqReader.ReadInt64(); - long CmdId = ReqReader.ReadInt64(); - - switch (CmdId) - { - case 0: - { - Request = FillResponse(Response, 0, Session.Service.ConvertToDomain()); - - break; - } - - case 3: - { - Request = FillResponse(Response, 0, 0x500); - - break; - } - - //TODO: Whats the difference between IpcDuplicateSession/Ex? - case 2: - case 4: - { - int Unknown = ReqReader.ReadInt32(); - - int Handle = Process.HandleTable.OpenHandle(Session); - - Response.HandleDesc = IpcHandleDesc.MakeMove(Handle); - - Request = FillResponse(Response, 0); - - break; - } - - default: throw new NotImplementedException(CmdId.ToString()); - } - } - else if (Request.Type == IpcMessageType.CloseSession) - { - //TODO - } - else - { - throw new NotImplementedException(Request.Type.ToString()); - } - - Memory.WriteBytes(CmdPtr, Response.GetBytes(CmdPtr)); - } - - return 0; - } - - private static IpcMessage FillResponse(IpcMessage Response, long Result, params int[] Values) - { - using (MemoryStream MS = new MemoryStream()) - { - BinaryWriter Writer = new BinaryWriter(MS); - - foreach (int Value in Values) - { - Writer.Write(Value); - } - - return FillResponse(Response, Result, MS.ToArray()); - } - } - - private static IpcMessage FillResponse(IpcMessage Response, long Result, byte[] Data = null) - { - Response.Type = IpcMessageType.Response; - - using (MemoryStream MS = new MemoryStream()) - { - BinaryWriter Writer = new BinaryWriter(MS); - - Writer.Write(IpcMagic.Sfco); - Writer.Write(Result); - - if (Data != null) - { - Writer.Write(Data); - } - - Response.RawData = MS.ToArray(); - } - - return Response; - } - } -} diff --git a/Ryujinx.HLE/OsHle/Ipc/IpcMessage.cs b/Ryujinx.HLE/OsHle/Ipc/IpcMessage.cs deleted file mode 100644 index 4e648aec97..0000000000 --- a/Ryujinx.HLE/OsHle/Ipc/IpcMessage.cs +++ /dev/null @@ -1,215 +0,0 @@ -using System.Collections.Generic; -using System.IO; - -namespace Ryujinx.HLE.OsHle.Ipc -{ - class IpcMessage - { - public IpcMessageType Type { get; set; } - - public IpcHandleDesc HandleDesc { get; set; } - - public List PtrBuff { get; private set; } - public List SendBuff { get; private set; } - public List ReceiveBuff { get; private set; } - public List ExchangeBuff { get; private set; } - public List RecvListBuff { get; private set; } - - public List ResponseObjIds { get; private set; } - - public byte[] RawData { get; set; } - - public IpcMessage() - { - PtrBuff = new List(); - SendBuff = new List(); - ReceiveBuff = new List(); - ExchangeBuff = new List(); - RecvListBuff = new List(); - - ResponseObjIds = new List(); - } - - public IpcMessage(byte[] Data, long CmdPtr) : this() - { - using (MemoryStream MS = new MemoryStream(Data)) - { - BinaryReader Reader = new BinaryReader(MS); - - Initialize(Reader, CmdPtr); - } - } - - private void Initialize(BinaryReader Reader, long CmdPtr) - { - int Word0 = Reader.ReadInt32(); - int Word1 = Reader.ReadInt32(); - - Type = (IpcMessageType)(Word0 & 0xffff); - - int PtrBuffCount = (Word0 >> 16) & 0xf; - int SendBuffCount = (Word0 >> 20) & 0xf; - int RecvBuffCount = (Word0 >> 24) & 0xf; - int XchgBuffCount = (Word0 >> 28) & 0xf; - - int RawDataSize = (Word1 >> 0) & 0x3ff; - int RecvListFlags = (Word1 >> 10) & 0xf; - bool HndDescEnable = ((Word1 >> 31) & 0x1) != 0; - - if (HndDescEnable) - { - HandleDesc = new IpcHandleDesc(Reader); - } - - for (int Index = 0; Index < PtrBuffCount; Index++) - { - PtrBuff.Add(new IpcPtrBuffDesc(Reader)); - } - - void ReadBuff(List Buff, int Count) - { - for (int Index = 0; Index < Count; Index++) - { - Buff.Add(new IpcBuffDesc(Reader)); - } - } - - ReadBuff(SendBuff, SendBuffCount); - ReadBuff(ReceiveBuff, RecvBuffCount); - ReadBuff(ExchangeBuff, XchgBuffCount); - - RawDataSize *= 4; - - long RecvListPos = Reader.BaseStream.Position + RawDataSize; - - long Pad0 = GetPadSize16(Reader.BaseStream.Position + CmdPtr); - - Reader.BaseStream.Seek(Pad0, SeekOrigin.Current); - - int RecvListCount = RecvListFlags - 2; - - if (RecvListCount == 0) - { - RecvListCount = 1; - } - else if (RecvListCount < 0) - { - RecvListCount = 0; - } - - RawData = Reader.ReadBytes(RawDataSize); - - Reader.BaseStream.Seek(RecvListPos, SeekOrigin.Begin); - - for (int Index = 0; Index < RecvListCount; Index++) - { - RecvListBuff.Add(new IpcRecvListBuffDesc(Reader)); - } - } - - public byte[] GetBytes(long CmdPtr) - { - using (MemoryStream MS = new MemoryStream()) - { - BinaryWriter Writer = new BinaryWriter(MS); - - int Word0; - int Word1; - - Word0 = (int)Type; - Word0 |= (PtrBuff.Count & 0xf) << 16; - Word0 |= (SendBuff.Count & 0xf) << 20; - Word0 |= (ReceiveBuff.Count & 0xf) << 24; - Word0 |= (ExchangeBuff.Count & 0xf) << 28; - - byte[] HandleData = new byte[0]; - - if (HandleDesc != null) - { - HandleData = HandleDesc.GetBytes(); - } - - int DataLength = RawData?.Length ?? 0; - - int Pad0 = (int)GetPadSize16(CmdPtr + 8 + HandleData.Length); - - //Apparently, padding after Raw Data is 16 bytes, however when there is - //padding before Raw Data too, we need to subtract the size of this padding. - //This is the weirdest padding I've seen so far... - int Pad1 = 0x10 - Pad0; - - DataLength = (DataLength + Pad0 + Pad1) / 4; - - Word1 = DataLength & 0x3ff; - - if (HandleDesc != null) - { - Word1 |= 1 << 31; - } - - Writer.Write(Word0); - Writer.Write(Word1); - Writer.Write(HandleData); - - MS.Seek(Pad0, SeekOrigin.Current); - - if (RawData != null) - { - Writer.Write(RawData); - } - - Writer.Write(new byte[Pad1]); - - return MS.ToArray(); - } - } - - private long GetPadSize16(long Position) - { - if ((Position & 0xf) != 0) - { - return 0x10 - (Position & 0xf); - } - - return 0; - } - - public (long Position, long Size) GetBufferType0x21() - { - if (PtrBuff.Count != 0 && - PtrBuff[0].Position != 0 && - PtrBuff[0].Size != 0) - { - return (PtrBuff[0].Position, PtrBuff[0].Size); - } - - if (SendBuff.Count != 0 && - SendBuff[0].Position != 0 && - SendBuff[0].Size != 0) - { - return (SendBuff[0].Position, SendBuff[0].Size); - } - - return (0, 0); - } - - public (long Position, long Size) GetBufferType0x22() - { - if (RecvListBuff.Count != 0 && - RecvListBuff[0].Position != 0 && - RecvListBuff[0].Size != 0) - { - return (RecvListBuff[0].Position, RecvListBuff[0].Size); - } - - if (ReceiveBuff.Count != 0 && - ReceiveBuff[0].Position != 0 && - ReceiveBuff[0].Size != 0) - { - return (ReceiveBuff[0].Position, ReceiveBuff[0].Size); - } - - return (0, 0); - } - } -} diff --git a/Ryujinx.HLE/OsHle/Ipc/IpcMessageType.cs b/Ryujinx.HLE/OsHle/Ipc/IpcMessageType.cs deleted file mode 100644 index f596fea461..0000000000 --- a/Ryujinx.HLE/OsHle/Ipc/IpcMessageType.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Ryujinx.HLE.OsHle.Ipc -{ - enum IpcMessageType - { - Response = 0, - CloseSession = 2, - Request = 4, - Control = 5 - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Ipc/IpcPtrBuffDesc.cs b/Ryujinx.HLE/OsHle/Ipc/IpcPtrBuffDesc.cs deleted file mode 100644 index f5a9f651e4..0000000000 --- a/Ryujinx.HLE/OsHle/Ipc/IpcPtrBuffDesc.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.IO; - -namespace Ryujinx.HLE.OsHle.Ipc -{ - struct IpcPtrBuffDesc - { - public long Position { get; private set; } - public int Index { get; private set; } - public long Size { get; private set; } - - public IpcPtrBuffDesc(BinaryReader Reader) - { - long Word0 = Reader.ReadUInt32(); - long Word1 = Reader.ReadUInt32(); - - Position = Word1; - Position |= (Word0 << 20) & 0x0f00000000; - Position |= (Word0 << 30) & 0x7000000000; - - Index = ((int)Word0 >> 0) & 0x03f; - Index |= ((int)Word0 >> 3) & 0x1c0; - - Size = (ushort)(Word0 >> 16); - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Ipc/IpcRecvListBuffDesc.cs b/Ryujinx.HLE/OsHle/Ipc/IpcRecvListBuffDesc.cs deleted file mode 100644 index 59191c1658..0000000000 --- a/Ryujinx.HLE/OsHle/Ipc/IpcRecvListBuffDesc.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.IO; - -namespace Ryujinx.HLE.OsHle.Ipc -{ - struct IpcRecvListBuffDesc - { - public long Position { get; private set; } - public long Size { get; private set; } - - public IpcRecvListBuffDesc(BinaryReader Reader) - { - long Value = Reader.ReadInt64(); - - Position = Value & 0xffffffffffff; - - Size = (ushort)(Value >> 48); - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Ipc/ServiceProcessRequest.cs b/Ryujinx.HLE/OsHle/Ipc/ServiceProcessRequest.cs deleted file mode 100644 index 47f72cb708..0000000000 --- a/Ryujinx.HLE/OsHle/Ipc/ServiceProcessRequest.cs +++ /dev/null @@ -1,4 +0,0 @@ -namespace Ryujinx.HLE.OsHle.Ipc -{ - delegate long ServiceProcessRequest(ServiceCtx Context); -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Kernel/KernelErr.cs b/Ryujinx.HLE/OsHle/Kernel/KernelErr.cs deleted file mode 100644 index ad4fdfb6bd..0000000000 --- a/Ryujinx.HLE/OsHle/Kernel/KernelErr.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace Ryujinx.HLE.OsHle.Kernel -{ - static class KernelErr - { - public const int InvalidAlignment = 102; - public const int InvalidAddress = 106; - public const int InvalidMemRange = 110; - public const int InvalidPriority = 112; - public const int InvalidCoreId = 113; - public const int InvalidHandle = 114; - public const int InvalidCoreMask = 116; - public const int Timeout = 117; - public const int Canceled = 118; - public const int CountOutOfRange = 119; - public const int InvalidInfo = 120; - public const int InvalidThread = 122; - public const int InvalidState = 125; - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Kernel/NsTimeConverter.cs b/Ryujinx.HLE/OsHle/Kernel/NsTimeConverter.cs deleted file mode 100644 index 966fdacae2..0000000000 --- a/Ryujinx.HLE/OsHle/Kernel/NsTimeConverter.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace Ryujinx.HLE.OsHle.Kernel -{ - static class NsTimeConverter - { - public static int GetTimeMs(ulong Ns) - { - ulong Ms = Ns / 1_000_000; - - if (Ms < int.MaxValue) - { - return (int)Ms; - } - else - { - return int.MaxValue; - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Kernel/SvcHandler.cs b/Ryujinx.HLE/OsHle/Kernel/SvcHandler.cs deleted file mode 100644 index e05073fda3..0000000000 --- a/Ryujinx.HLE/OsHle/Kernel/SvcHandler.cs +++ /dev/null @@ -1,150 +0,0 @@ -using ChocolArm64.Events; -using ChocolArm64.Memory; -using ChocolArm64.State; -using Ryujinx.HLE.Logging; -using Ryujinx.HLE.OsHle.Handles; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Threading; - -namespace Ryujinx.HLE.OsHle.Kernel -{ - partial class SvcHandler : IDisposable - { - private delegate void SvcFunc(AThreadState ThreadState); - - private Dictionary SvcFuncs; - - private Switch Ns; - private Process Process; - private AMemory Memory; - - private ConcurrentDictionary SyncWaits; - - private HashSet<(HSharedMem, long)> MappedSharedMems; - - private ulong CurrentHeapSize; - - private const uint SelfThreadHandle = 0xffff8000; - private const uint SelfProcessHandle = 0xffff8001; - - private static Random Rng; - - public SvcHandler(Switch Ns, Process Process) - { - SvcFuncs = new Dictionary() - { - { 0x01, SvcSetHeapSize }, - { 0x03, SvcSetMemoryAttribute }, - { 0x04, SvcMapMemory }, - { 0x05, SvcUnmapMemory }, - { 0x06, SvcQueryMemory }, - { 0x07, SvcExitProcess }, - { 0x08, SvcCreateThread }, - { 0x09, SvcStartThread }, - { 0x0a, SvcExitThread }, - { 0x0b, SvcSleepThread }, - { 0x0c, SvcGetThreadPriority }, - { 0x0d, SvcSetThreadPriority }, - { 0x0e, SvcGetThreadCoreMask }, - { 0x0f, SvcSetThreadCoreMask }, - { 0x10, SvcGetCurrentProcessorNumber }, - { 0x12, SvcClearEvent }, - { 0x13, SvcMapSharedMemory }, - { 0x14, SvcUnmapSharedMemory }, - { 0x15, SvcCreateTransferMemory }, - { 0x16, SvcCloseHandle }, - { 0x17, SvcResetSignal }, - { 0x18, SvcWaitSynchronization }, - { 0x19, SvcCancelSynchronization }, - { 0x1a, SvcArbitrateLock }, - { 0x1b, SvcArbitrateUnlock }, - { 0x1c, SvcWaitProcessWideKeyAtomic }, - { 0x1d, SvcSignalProcessWideKey }, - { 0x1e, SvcGetSystemTick }, - { 0x1f, SvcConnectToNamedPort }, - { 0x21, SvcSendSyncRequest }, - { 0x22, SvcSendSyncRequestWithUserBuffer }, - { 0x25, SvcGetThreadId }, - { 0x26, SvcBreak }, - { 0x27, SvcOutputDebugString }, - { 0x29, SvcGetInfo }, - { 0x2c, SvcMapPhysicalMemory }, - { 0x2d, SvcUnmapPhysicalMemory }, - { 0x32, SvcSetThreadActivity }, - { 0x33, SvcGetThreadContext3 } - }; - - this.Ns = Ns; - this.Process = Process; - this.Memory = Process.Memory; - - SyncWaits = new ConcurrentDictionary(); - - MappedSharedMems = new HashSet<(HSharedMem, long)>(); - } - - static SvcHandler() - { - Rng = new Random(); - } - - public void SvcCall(object sender, AInstExceptionEventArgs e) - { - AThreadState ThreadState = (AThreadState)sender; - - Process.GetThread(ThreadState.Tpidr).LastPc = e.Position; - - if (SvcFuncs.TryGetValue(e.Id, out SvcFunc Func)) - { - Ns.Log.PrintDebug(LogClass.KernelSvc, $"{Func.Method.Name} called."); - - Func(ThreadState); - - Process.Scheduler.Reschedule(Process.GetThread(ThreadState.Tpidr)); - - Ns.Log.PrintDebug(LogClass.KernelSvc, $"{Func.Method.Name} ended."); - } - else - { - Process.PrintStackTrace(ThreadState); - - throw new NotImplementedException(e.Id.ToString("x4")); - } - } - - private KThread GetThread(long Tpidr, int Handle) - { - if ((uint)Handle == SelfThreadHandle) - { - return Process.GetThread(Tpidr); - } - else - { - return Process.HandleTable.GetData(Handle); - } - } - - public void Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool Disposing) - { - if (Disposing) - { - lock (MappedSharedMems) - { - foreach ((HSharedMem SharedMem, long Position) in MappedSharedMems) - { - SharedMem.RemoveVirtualPosition(Memory, Position); - } - - MappedSharedMems.Clear(); - } - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Kernel/SvcMemory.cs b/Ryujinx.HLE/OsHle/Kernel/SvcMemory.cs deleted file mode 100644 index bb73f1ea17..0000000000 --- a/Ryujinx.HLE/OsHle/Kernel/SvcMemory.cs +++ /dev/null @@ -1,285 +0,0 @@ -using ChocolArm64.Memory; -using ChocolArm64.State; -using Ryujinx.HLE.Logging; -using Ryujinx.HLE.OsHle.Handles; - -using static Ryujinx.HLE.OsHle.ErrorCode; - -namespace Ryujinx.HLE.OsHle.Kernel -{ - partial class SvcHandler - { - private void SvcSetHeapSize(AThreadState ThreadState) - { - uint Size = (uint)ThreadState.X1; - - long Position = MemoryRegions.HeapRegionAddress; - - if (Size > CurrentHeapSize) - { - Memory.Manager.Map(Position, Size, (int)MemoryType.Heap, AMemoryPerm.RW); - } - else - { - Memory.Manager.Unmap(Position + Size, (long)CurrentHeapSize - Size); - } - - CurrentHeapSize = Size; - - ThreadState.X0 = 0; - ThreadState.X1 = (ulong)Position; - } - - private void SvcSetMemoryAttribute(AThreadState ThreadState) - { - long Position = (long)ThreadState.X0; - long Size = (long)ThreadState.X1; - int State0 = (int)ThreadState.X2; - int State1 = (int)ThreadState.X3; - - if ((State0 == 0 && State1 == 0) || - (State0 == 8 && State1 == 0)) - { - Memory.Manager.ClearAttrBit(Position, Size, 3); - } - else if (State0 == 8 && State1 == 8) - { - Memory.Manager.SetAttrBit(Position, Size, 3); - } - - ThreadState.X0 = 0; - } - - private void SvcMapMemory(AThreadState ThreadState) - { - long Dst = (long)ThreadState.X0; - long Src = (long)ThreadState.X1; - long Size = (long)ThreadState.X2; - - if (!IsValidPosition(Src)) - { - Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid src address {Src:x16}!"); - - ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMemRange); - - return; - } - - if (!IsValidMapPosition(Dst)) - { - Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid dst address {Dst:x16}!"); - - ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMemRange); - - return; - } - - AMemoryMapInfo SrcInfo = Memory.Manager.GetMapInfo(Src); - - Memory.Manager.Map(Dst, Size, (int)MemoryType.MappedMemory, SrcInfo.Perm); - - Memory.Manager.Reprotect(Src, Size, AMemoryPerm.None); - - Memory.Manager.SetAttrBit(Src, Size, 0); - - ThreadState.X0 = 0; - } - - private void SvcUnmapMemory(AThreadState ThreadState) - { - long Dst = (long)ThreadState.X0; - long Src = (long)ThreadState.X1; - long Size = (long)ThreadState.X2; - - if (!IsValidPosition(Src)) - { - Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid src address {Src:x16}!"); - - ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMemRange); - - return; - } - - if (!IsValidMapPosition(Dst)) - { - Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid dst address {Dst:x16}!"); - - ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMemRange); - - return; - } - - AMemoryMapInfo DstInfo = Memory.Manager.GetMapInfo(Dst); - - Memory.Manager.Unmap(Dst, Size, (int)MemoryType.MappedMemory); - - Memory.Manager.Reprotect(Src, Size, DstInfo.Perm); - - Memory.Manager.ClearAttrBit(Src, Size, 0); - - ThreadState.X0 = 0; - } - - private void SvcQueryMemory(AThreadState ThreadState) - { - long InfoPtr = (long)ThreadState.X0; - long Position = (long)ThreadState.X2; - - AMemoryMapInfo MapInfo = Memory.Manager.GetMapInfo(Position); - - if (MapInfo == null) - { - long AddrSpaceEnd = MemoryRegions.AddrSpaceStart + MemoryRegions.AddrSpaceSize; - - long ReservedSize = (long)(ulong.MaxValue - (ulong)AddrSpaceEnd) + 1; - - MapInfo = new AMemoryMapInfo(AddrSpaceEnd, ReservedSize, (int)MemoryType.Reserved, 0, AMemoryPerm.None); - } - - Memory.WriteInt64(InfoPtr + 0x00, MapInfo.Position); - Memory.WriteInt64(InfoPtr + 0x08, MapInfo.Size); - Memory.WriteInt32(InfoPtr + 0x10, MapInfo.Type); - Memory.WriteInt32(InfoPtr + 0x14, MapInfo.Attr); - Memory.WriteInt32(InfoPtr + 0x18, (int)MapInfo.Perm); - Memory.WriteInt32(InfoPtr + 0x1c, 0); - Memory.WriteInt32(InfoPtr + 0x20, 0); - Memory.WriteInt32(InfoPtr + 0x24, 0); - //TODO: X1. - - ThreadState.X0 = 0; - ThreadState.X1 = 0; - } - - private void SvcMapSharedMemory(AThreadState ThreadState) - { - int Handle = (int)ThreadState.X0; - long Src = (long)ThreadState.X1; - long Size = (long)ThreadState.X2; - int Perm = (int)ThreadState.X3; - - if (!IsValidPosition(Src)) - { - Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid address {Src:x16}!"); - - ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMemRange); - - return; - } - - HSharedMem SharedMem = Process.HandleTable.GetData(Handle); - - if (SharedMem != null) - { - Memory.Manager.Map(Src, Size, (int)MemoryType.SharedMemory, AMemoryPerm.Write); - - AMemoryHelper.FillWithZeros(Memory, Src, (int)Size); - - Memory.Manager.Reprotect(Src, Size, (AMemoryPerm)Perm); - - lock (MappedSharedMems) - { - MappedSharedMems.Add((SharedMem, Src)); - } - - SharedMem.AddVirtualPosition(Memory, Src); - - ThreadState.X0 = 0; - } - - //TODO: Error codes. - } - - private void SvcUnmapSharedMemory(AThreadState ThreadState) - { - int Handle = (int)ThreadState.X0; - long Src = (long)ThreadState.X1; - long Size = (long)ThreadState.X2; - - if (!IsValidPosition(Src)) - { - Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid address {Src:x16}!"); - - ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMemRange); - - return; - } - - HSharedMem SharedMem = Process.HandleTable.GetData(Handle); - - if (SharedMem != null) - { - Memory.Manager.Unmap(Src, Size, (int)MemoryType.SharedMemory); - - SharedMem.RemoveVirtualPosition(Memory, Src); - - lock (MappedSharedMems) - { - MappedSharedMems.Remove((SharedMem, Src)); - } - - ThreadState.X0 = 0; - } - - //TODO: Error codes. - } - - private void SvcCreateTransferMemory(AThreadState ThreadState) - { - long Src = (long)ThreadState.X1; - long Size = (long)ThreadState.X2; - int Perm = (int)ThreadState.X3; - - if (!IsValidPosition(Src)) - { - Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid address {Src:x16}!"); - - ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMemRange); - - return; - } - - AMemoryMapInfo MapInfo = Memory.Manager.GetMapInfo(Src); - - Memory.Manager.Reprotect(Src, Size, (AMemoryPerm)Perm); - - HTransferMem TMem = new HTransferMem(Memory, MapInfo.Perm, Src, Size); - - ulong Handle = (ulong)Process.HandleTable.OpenHandle(TMem); - - ThreadState.X0 = 0; - ThreadState.X1 = Handle; - } - - private void SvcMapPhysicalMemory(AThreadState ThreadState) - { - long Position = (long)ThreadState.X0; - uint Size = (uint)ThreadState.X1; - - Memory.Manager.Map(Position, Size, (int)MemoryType.Heap, AMemoryPerm.RW); - - ThreadState.X0 = 0; - } - - private void SvcUnmapPhysicalMemory(AThreadState ThreadState) - { - long Position = (long)ThreadState.X0; - uint Size = (uint)ThreadState.X1; - - Memory.Manager.Unmap(Position, Size); - - ThreadState.X0 = 0; - } - - private static bool IsValidPosition(long Position) - { - return Position >= MemoryRegions.AddrSpaceStart && - Position < MemoryRegions.AddrSpaceStart + MemoryRegions.AddrSpaceSize; - } - - private static bool IsValidMapPosition(long Position) - { - return Position >= MemoryRegions.MapRegionAddress && - Position < MemoryRegions.MapRegionAddress + MemoryRegions.MapRegionSize; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Kernel/SvcSystem.cs b/Ryujinx.HLE/OsHle/Kernel/SvcSystem.cs deleted file mode 100644 index a32b2d86fd..0000000000 --- a/Ryujinx.HLE/OsHle/Kernel/SvcSystem.cs +++ /dev/null @@ -1,369 +0,0 @@ -using ChocolArm64.Memory; -using ChocolArm64.State; -using Ryujinx.HLE.Logging; -using Ryujinx.HLE.OsHle.Exceptions; -using Ryujinx.HLE.OsHle.Handles; -using Ryujinx.HLE.OsHle.Ipc; -using Ryujinx.HLE.OsHle.Services; -using System; -using System.Threading; - -using static Ryujinx.HLE.OsHle.ErrorCode; - -namespace Ryujinx.HLE.OsHle.Kernel -{ - partial class SvcHandler - { - private const int AllowedCpuIdBitmask = 0b1111; - - private const bool EnableProcessDebugging = false; - - private const bool IsVirtualMemoryEnabled = true; //This is always true(?) - - private void SvcExitProcess(AThreadState ThreadState) - { - Ns.Os.ExitProcess(ThreadState.ProcessId); - } - - private void SvcClearEvent(AThreadState ThreadState) - { - int Handle = (int)ThreadState.X0; - - //TODO: Implement events. - - ThreadState.X0 = 0; - } - - private void SvcCloseHandle(AThreadState ThreadState) - { - int Handle = (int)ThreadState.X0; - - object Obj = Process.HandleTable.CloseHandle(Handle); - - if (Obj == null) - { - Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid handle 0x{Handle:x8}!"); - - ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); - - return; - } - - if (Obj is KSession Session) - { - Session.Dispose(); - } - else if (Obj is HTransferMem TMem) - { - TMem.Memory.Manager.Reprotect( - TMem.Position, - TMem.Size, - TMem.Perm); - } - - ThreadState.X0 = 0; - } - - private void SvcResetSignal(AThreadState ThreadState) - { - int Handle = (int)ThreadState.X0; - - KEvent Event = Process.HandleTable.GetData(Handle); - - if (Event != null) - { - Event.WaitEvent.Reset(); - - ThreadState.X0 = 0; - } - else - { - Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid event handle 0x{Handle:x8}!"); - - ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); - } - } - - private void SvcWaitSynchronization(AThreadState ThreadState) - { - long HandlesPtr = (long)ThreadState.X1; - int HandlesCount = (int)ThreadState.X2; - ulong Timeout = ThreadState.X3; - - Ns.Log.PrintDebug(LogClass.KernelSvc, - "HandlesPtr = " + HandlesPtr .ToString("x16") + ", " + - "HandlesCount = " + HandlesCount.ToString("x8") + ", " + - "Timeout = " + Timeout .ToString("x16")); - - if ((uint)HandlesCount > 0x40) - { - ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.CountOutOfRange); - - return; - } - - KThread CurrThread = Process.GetThread(ThreadState.Tpidr); - - WaitHandle[] Handles = new WaitHandle[HandlesCount + 1]; - - for (int Index = 0; Index < HandlesCount; Index++) - { - int Handle = Memory.ReadInt32(HandlesPtr + Index * 4); - - KSynchronizationObject SyncObj = Process.HandleTable.GetData(Handle); - - if (SyncObj == null) - { - Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid handle 0x{Handle:x8}!"); - - ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); - - return; - } - - Handles[Index] = SyncObj.WaitEvent; - } - - using (AutoResetEvent WaitEvent = new AutoResetEvent(false)) - { - if (!SyncWaits.TryAdd(CurrThread, WaitEvent)) - { - throw new InvalidOperationException(); - } - - Handles[HandlesCount] = WaitEvent; - - Process.Scheduler.Suspend(CurrThread); - - int HandleIndex; - - ulong Result = 0; - - if (Timeout != ulong.MaxValue) - { - HandleIndex = WaitHandle.WaitAny(Handles, NsTimeConverter.GetTimeMs(Timeout)); - } - else - { - HandleIndex = WaitHandle.WaitAny(Handles); - } - - if (HandleIndex == WaitHandle.WaitTimeout) - { - Result = MakeError(ErrorModule.Kernel, KernelErr.Timeout); - } - else if (HandleIndex == HandlesCount) - { - Result = MakeError(ErrorModule.Kernel, KernelErr.Canceled); - } - - SyncWaits.TryRemove(CurrThread, out _); - - Process.Scheduler.Resume(CurrThread); - - ThreadState.X0 = Result; - - if (Result == 0) - { - ThreadState.X1 = (ulong)HandleIndex; - } - } - } - - private void SvcCancelSynchronization(AThreadState ThreadState) - { - int ThreadHandle = (int)ThreadState.X0; - - KThread Thread = GetThread(ThreadState.Tpidr, ThreadHandle); - - if (Thread == null) - { - Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{ThreadHandle:x8}!"); - - ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); - - return; - } - - if (SyncWaits.TryRemove(Thread, out AutoResetEvent WaitEvent)) - { - WaitEvent.Set(); - } - - ThreadState.X0 = 0; - } - - private void SvcGetSystemTick(AThreadState ThreadState) - { - ThreadState.X0 = ThreadState.CntpctEl0; - } - - private void SvcConnectToNamedPort(AThreadState ThreadState) - { - long StackPtr = (long)ThreadState.X0; - long NamePtr = (long)ThreadState.X1; - - string Name = AMemoryHelper.ReadAsciiString(Memory, NamePtr, 8); - - //TODO: Validate that app has perms to access the service, and that the service - //actually exists, return error codes otherwise. - KSession Session = new KSession(ServiceFactory.MakeService(Name), Name); - - ulong Handle = (ulong)Process.HandleTable.OpenHandle(Session); - - ThreadState.X0 = 0; - ThreadState.X1 = Handle; - } - - private void SvcSendSyncRequest(AThreadState ThreadState) - { - SendSyncRequest(ThreadState, ThreadState.Tpidr, 0x100, (int)ThreadState.X0); - } - - private void SvcSendSyncRequestWithUserBuffer(AThreadState ThreadState) - { - SendSyncRequest( - ThreadState, - (long)ThreadState.X0, - (long)ThreadState.X1, - (int)ThreadState.X2); - } - - private void SendSyncRequest(AThreadState ThreadState, long CmdPtr, long Size, int Handle) - { - KThread CurrThread = Process.GetThread(ThreadState.Tpidr); - - byte[] CmdData = Memory.ReadBytes(CmdPtr, Size); - - KSession Session = Process.HandleTable.GetData(Handle); - - if (Session != null) - { - Process.Scheduler.Suspend(CurrThread); - - IpcMessage Cmd = new IpcMessage(CmdData, CmdPtr); - - long Result = IpcHandler.IpcCall(Ns, Process, Memory, Session, Cmd, CmdPtr); - - Thread.Yield(); - - Process.Scheduler.Resume(CurrThread); - - ThreadState.X0 = (ulong)Result; - } - else - { - Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid session handle 0x{Handle:x8}!"); - - ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); - } - } - - private void SvcBreak(AThreadState ThreadState) - { - long Reason = (long)ThreadState.X0; - long Unknown = (long)ThreadState.X1; - long Info = (long)ThreadState.X2; - - Process.PrintStackTrace(ThreadState); - - throw new GuestBrokeExecutionException(); - } - - private void SvcOutputDebugString(AThreadState ThreadState) - { - long Position = (long)ThreadState.X0; - long Size = (long)ThreadState.X1; - - string Str = AMemoryHelper.ReadAsciiString(Memory, Position, Size); - - Ns.Log.PrintWarning(LogClass.KernelSvc, Str); - - ThreadState.X0 = 0; - } - - private void SvcGetInfo(AThreadState ThreadState) - { - long StackPtr = (long)ThreadState.X0; - int InfoType = (int)ThreadState.X1; - long Handle = (long)ThreadState.X2; - int InfoId = (int)ThreadState.X3; - - //Fail for info not available on older Kernel versions. - if (InfoType == 18 || - InfoType == 19 || - InfoType == 20) - { - ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidInfo); - - return; - } - - switch (InfoType) - { - case 0: - ThreadState.X1 = AllowedCpuIdBitmask; - break; - - case 2: - ThreadState.X1 = MemoryRegions.MapRegionAddress; - break; - - case 3: - ThreadState.X1 = MemoryRegions.MapRegionSize; - break; - - case 4: - ThreadState.X1 = MemoryRegions.HeapRegionAddress; - break; - - case 5: - ThreadState.X1 = MemoryRegions.HeapRegionSize; - break; - - case 6: - ThreadState.X1 = MemoryRegions.TotalMemoryAvailable; - break; - - case 7: - ThreadState.X1 = MemoryRegions.TotalMemoryUsed + CurrentHeapSize; - break; - - case 8: - ThreadState.X1 = EnableProcessDebugging ? 1 : 0; - break; - - case 11: - ThreadState.X1 = (ulong)Rng.Next() + ((ulong)Rng.Next() << 32); - break; - - case 12: - ThreadState.X1 = MemoryRegions.AddrSpaceStart; - break; - - case 13: - ThreadState.X1 = MemoryRegions.AddrSpaceSize; - break; - - case 14: - ThreadState.X1 = MemoryRegions.MapRegionAddress; - break; - - case 15: - ThreadState.X1 = MemoryRegions.MapRegionSize; - break; - - case 16: - ThreadState.X1 = IsVirtualMemoryEnabled ? 1 : 0; - break; - - default: - Process.PrintStackTrace(ThreadState); - - throw new NotImplementedException($"SvcGetInfo: {InfoType} {Handle:x8} {InfoId}"); - } - - ThreadState.X0 = 0; - } - } -} diff --git a/Ryujinx.HLE/OsHle/Kernel/SvcThread.cs b/Ryujinx.HLE/OsHle/Kernel/SvcThread.cs deleted file mode 100644 index b0a7490a39..0000000000 --- a/Ryujinx.HLE/OsHle/Kernel/SvcThread.cs +++ /dev/null @@ -1,386 +0,0 @@ -using ChocolArm64.State; -using Ryujinx.HLE.Logging; -using Ryujinx.HLE.OsHle.Handles; -using System.Threading; - -using static Ryujinx.HLE.OsHle.ErrorCode; - -namespace Ryujinx.HLE.OsHle.Kernel -{ - partial class SvcHandler - { - private void SvcCreateThread(AThreadState ThreadState) - { - long EntryPoint = (long)ThreadState.X1; - long ArgsPtr = (long)ThreadState.X2; - long StackTop = (long)ThreadState.X3; - int Priority = (int)ThreadState.X4; - int ProcessorId = (int)ThreadState.X5; - - if ((uint)Priority > 0x3f) - { - Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid priority 0x{Priority:x8}!"); - - ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidPriority); - - return; - } - - if (ProcessorId == -2) - { - //TODO: Get this value from the NPDM file. - ProcessorId = 0; - } - else if ((uint)ProcessorId > 3) - { - Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid core id 0x{ProcessorId:x8}!"); - - ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidCoreId); - - return; - } - - int Handle = Process.MakeThread( - EntryPoint, - StackTop, - ArgsPtr, - Priority, - ProcessorId); - - ThreadState.X0 = 0; - ThreadState.X1 = (ulong)Handle; - } - - private void SvcStartThread(AThreadState ThreadState) - { - int Handle = (int)ThreadState.X0; - - KThread NewThread = Process.HandleTable.GetData(Handle); - - if (NewThread != null) - { - Process.Scheduler.StartThread(NewThread); - Process.Scheduler.SetReschedule(NewThread.ProcessorId); - - ThreadState.X0 = 0; - } - else - { - Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!"); - - ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); - } - } - - private void SvcExitThread(AThreadState ThreadState) - { - KThread CurrThread = Process.GetThread(ThreadState.Tpidr); - - CurrThread.Thread.StopExecution(); - } - - private void SvcSleepThread(AThreadState ThreadState) - { - ulong TimeoutNs = ThreadState.X0; - - Ns.Log.PrintDebug(LogClass.KernelSvc, "Timeout = " + TimeoutNs.ToString("x16")); - - KThread CurrThread = Process.GetThread(ThreadState.Tpidr); - - if (TimeoutNs == 0) - { - Process.Scheduler.Yield(CurrThread); - } - else - { - Process.Scheduler.Suspend(CurrThread); - - Thread.Sleep(NsTimeConverter.GetTimeMs(TimeoutNs)); - - Process.Scheduler.Resume(CurrThread); - } - } - - private void SvcGetThreadPriority(AThreadState ThreadState) - { - int Handle = (int)ThreadState.X1; - - KThread Thread = GetThread(ThreadState.Tpidr, Handle); - - if (Thread != null) - { - ThreadState.X0 = 0; - ThreadState.X1 = (ulong)Thread.ActualPriority; - } - else - { - Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!"); - - ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); - } - } - - private void SvcSetThreadPriority(AThreadState ThreadState) - { - int Handle = (int)ThreadState.X0; - int Priority = (int)ThreadState.X1; - - Ns.Log.PrintDebug(LogClass.KernelSvc, - "Handle = " + Handle .ToString("x8") + ", " + - "Priority = " + Priority.ToString("x8")); - - KThread Thread = GetThread(ThreadState.Tpidr, Handle); - - if (Thread != null) - { - Thread.SetPriority(Priority); - - ThreadState.X0 = 0; - } - else - { - Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!"); - - ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); - } - } - - private void SvcGetThreadCoreMask(AThreadState ThreadState) - { - int Handle = (int)ThreadState.X2; - - Ns.Log.PrintDebug(LogClass.KernelSvc, "Handle = " + Handle.ToString("x8")); - - KThread Thread = GetThread(ThreadState.Tpidr, Handle); - - if (Thread != null) - { - ThreadState.X0 = 0; - ThreadState.X1 = (ulong)Thread.IdealCore; - ThreadState.X2 = (ulong)Thread.CoreMask; - } - else - { - Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!"); - - ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); - } - } - - private void SvcSetThreadCoreMask(AThreadState ThreadState) - { - int Handle = (int)ThreadState.X0; - int IdealCore = (int)ThreadState.X1; - long CoreMask = (long)ThreadState.X2; - - Ns.Log.PrintDebug(LogClass.KernelSvc, - "Handle = " + Handle .ToString("x8") + ", " + - "IdealCore = " + IdealCore.ToString("x8") + ", " + - "CoreMask = " + CoreMask .ToString("x16")); - - KThread Thread = GetThread(ThreadState.Tpidr, Handle); - - if (IdealCore == -2) - { - //TODO: Get this value from the NPDM file. - IdealCore = 0; - - CoreMask = 1 << IdealCore; - } - else - { - if ((uint)IdealCore > 3) - { - if ((IdealCore | 2) != -1) - { - Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid core id 0x{IdealCore:x8}!"); - - ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidCoreId); - - return; - } - } - else if ((CoreMask & (1 << IdealCore)) == 0) - { - Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid core mask 0x{CoreMask:x8}!"); - - ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidCoreMask); - - return; - } - } - - if (Thread == null) - { - Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!"); - - ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); - - return; - } - - //-1 is used as "don't care", so the IdealCore value is ignored. - //-2 is used as "use NPDM default core id" (handled above). - //-3 is used as "don't update", the old IdealCore value is kept. - if (IdealCore == -3 && (CoreMask & (1 << Thread.IdealCore)) == 0) - { - Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid core mask 0x{CoreMask:x8}!"); - - ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidCoreMask); - - return; - } - - Process.Scheduler.ChangeCore(Thread, IdealCore, (int)CoreMask); - - ThreadState.X0 = 0; - } - - private void SvcGetCurrentProcessorNumber(AThreadState ThreadState) - { - ThreadState.X0 = (ulong)Process.GetThread(ThreadState.Tpidr).ActualCore; - } - - private void SvcGetThreadId(AThreadState ThreadState) - { - int Handle = (int)ThreadState.X1; - - KThread Thread = GetThread(ThreadState.Tpidr, Handle); - - if (Thread != null) - { - ThreadState.X0 = 0; - ThreadState.X1 = (ulong)Thread.ThreadId; - } - else - { - Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!"); - - ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); - } - } - - private void SvcSetThreadActivity(AThreadState ThreadState) - { - int Handle = (int)ThreadState.X0; - bool Active = (int)ThreadState.X1 == 0; - - KThread Thread = Process.HandleTable.GetData(Handle); - - if (Thread != null) - { - Process.Scheduler.SetThreadActivity(Thread, Active); - - ThreadState.X0 = 0; - } - else - { - Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!"); - - ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); - } - } - - private void SvcGetThreadContext3(AThreadState ThreadState) - { - long Position = (long)ThreadState.X0; - int Handle = (int)ThreadState.X1; - - KThread Thread = Process.HandleTable.GetData(Handle); - - if (Thread == null) - { - Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!"); - - ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); - - return; - } - - if (Process.GetThread(ThreadState.Tpidr) == Thread) - { - Ns.Log.PrintWarning(LogClass.KernelSvc, $"Thread handle 0x{Handle:x8} is current thread!"); - - ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidThread); - - return; - } - - Memory.WriteUInt64(Position + 0x0, ThreadState.X0); - Memory.WriteUInt64(Position + 0x8, ThreadState.X1); - Memory.WriteUInt64(Position + 0x10, ThreadState.X2); - Memory.WriteUInt64(Position + 0x18, ThreadState.X3); - Memory.WriteUInt64(Position + 0x20, ThreadState.X4); - Memory.WriteUInt64(Position + 0x28, ThreadState.X5); - Memory.WriteUInt64(Position + 0x30, ThreadState.X6); - Memory.WriteUInt64(Position + 0x38, ThreadState.X7); - Memory.WriteUInt64(Position + 0x40, ThreadState.X8); - Memory.WriteUInt64(Position + 0x48, ThreadState.X9); - Memory.WriteUInt64(Position + 0x50, ThreadState.X10); - Memory.WriteUInt64(Position + 0x58, ThreadState.X11); - Memory.WriteUInt64(Position + 0x60, ThreadState.X12); - Memory.WriteUInt64(Position + 0x68, ThreadState.X13); - Memory.WriteUInt64(Position + 0x70, ThreadState.X14); - Memory.WriteUInt64(Position + 0x78, ThreadState.X15); - Memory.WriteUInt64(Position + 0x80, ThreadState.X16); - Memory.WriteUInt64(Position + 0x88, ThreadState.X17); - Memory.WriteUInt64(Position + 0x90, ThreadState.X18); - Memory.WriteUInt64(Position + 0x98, ThreadState.X19); - Memory.WriteUInt64(Position + 0xa0, ThreadState.X20); - Memory.WriteUInt64(Position + 0xa8, ThreadState.X21); - Memory.WriteUInt64(Position + 0xb0, ThreadState.X22); - Memory.WriteUInt64(Position + 0xb8, ThreadState.X23); - Memory.WriteUInt64(Position + 0xc0, ThreadState.X24); - Memory.WriteUInt64(Position + 0xc8, ThreadState.X25); - Memory.WriteUInt64(Position + 0xd0, ThreadState.X26); - Memory.WriteUInt64(Position + 0xd8, ThreadState.X27); - Memory.WriteUInt64(Position + 0xe0, ThreadState.X28); - Memory.WriteUInt64(Position + 0xe8, ThreadState.X29); - Memory.WriteUInt64(Position + 0xf0, ThreadState.X30); - Memory.WriteUInt64(Position + 0xf8, ThreadState.X31); - - Memory.WriteInt64(Position + 0x100, Thread.LastPc); - - Memory.WriteUInt64(Position + 0x108, (ulong)ThreadState.Psr); - - Memory.WriteVector128(Position + 0x110, ThreadState.V0); - Memory.WriteVector128(Position + 0x120, ThreadState.V1); - Memory.WriteVector128(Position + 0x130, ThreadState.V2); - Memory.WriteVector128(Position + 0x140, ThreadState.V3); - Memory.WriteVector128(Position + 0x150, ThreadState.V4); - Memory.WriteVector128(Position + 0x160, ThreadState.V5); - Memory.WriteVector128(Position + 0x170, ThreadState.V6); - Memory.WriteVector128(Position + 0x180, ThreadState.V7); - Memory.WriteVector128(Position + 0x190, ThreadState.V8); - Memory.WriteVector128(Position + 0x1a0, ThreadState.V9); - Memory.WriteVector128(Position + 0x1b0, ThreadState.V10); - Memory.WriteVector128(Position + 0x1c0, ThreadState.V11); - Memory.WriteVector128(Position + 0x1d0, ThreadState.V12); - Memory.WriteVector128(Position + 0x1e0, ThreadState.V13); - Memory.WriteVector128(Position + 0x1f0, ThreadState.V14); - Memory.WriteVector128(Position + 0x200, ThreadState.V15); - Memory.WriteVector128(Position + 0x210, ThreadState.V16); - Memory.WriteVector128(Position + 0x220, ThreadState.V17); - Memory.WriteVector128(Position + 0x230, ThreadState.V18); - Memory.WriteVector128(Position + 0x240, ThreadState.V19); - Memory.WriteVector128(Position + 0x250, ThreadState.V20); - Memory.WriteVector128(Position + 0x260, ThreadState.V21); - Memory.WriteVector128(Position + 0x270, ThreadState.V22); - Memory.WriteVector128(Position + 0x280, ThreadState.V23); - Memory.WriteVector128(Position + 0x290, ThreadState.V24); - Memory.WriteVector128(Position + 0x2a0, ThreadState.V25); - Memory.WriteVector128(Position + 0x2b0, ThreadState.V26); - Memory.WriteVector128(Position + 0x2c0, ThreadState.V27); - Memory.WriteVector128(Position + 0x2d0, ThreadState.V28); - Memory.WriteVector128(Position + 0x2e0, ThreadState.V29); - Memory.WriteVector128(Position + 0x2f0, ThreadState.V30); - Memory.WriteVector128(Position + 0x300, ThreadState.V31); - - Memory.WriteInt32(Position + 0x310, ThreadState.Fpcr); - Memory.WriteInt32(Position + 0x314, ThreadState.Fpsr); - Memory.WriteInt64(Position + 0x318, ThreadState.Tpidr); - - ThreadState.X0 = 0; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Kernel/SvcThreadSync.cs b/Ryujinx.HLE/OsHle/Kernel/SvcThreadSync.cs deleted file mode 100644 index ec9c40e08b..0000000000 --- a/Ryujinx.HLE/OsHle/Kernel/SvcThreadSync.cs +++ /dev/null @@ -1,473 +0,0 @@ -using ChocolArm64.State; -using Ryujinx.HLE.Logging; -using Ryujinx.HLE.OsHle.Handles; -using System; - -using static Ryujinx.HLE.OsHle.ErrorCode; - -namespace Ryujinx.HLE.OsHle.Kernel -{ - partial class SvcHandler - { - private const int MutexHasListenersMask = 0x40000000; - - private void SvcArbitrateLock(AThreadState ThreadState) - { - int OwnerThreadHandle = (int)ThreadState.X0; - long MutexAddress = (long)ThreadState.X1; - int WaitThreadHandle = (int)ThreadState.X2; - - Ns.Log.PrintDebug(LogClass.KernelSvc, - "OwnerThreadHandle = " + OwnerThreadHandle.ToString("x8") + ", " + - "MutexAddress = " + MutexAddress .ToString("x16") + ", " + - "WaitThreadHandle = " + WaitThreadHandle .ToString("x8")); - - if (IsPointingInsideKernel(MutexAddress)) - { - Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid mutex address 0x{MutexAddress:x16}!"); - - ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress); - - return; - } - - if (IsWordAddressUnaligned(MutexAddress)) - { - Ns.Log.PrintWarning(LogClass.KernelSvc, $"Unaligned mutex address 0x{MutexAddress:x16}!"); - - ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAlignment); - - return; - } - - KThread OwnerThread = Process.HandleTable.GetData(OwnerThreadHandle); - - if (OwnerThread == null) - { - Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid owner thread handle 0x{OwnerThreadHandle:x8}!"); - - ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); - - return; - } - - KThread WaitThread = Process.HandleTable.GetData(WaitThreadHandle); - - if (WaitThread == null) - { - Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid requesting thread handle 0x{WaitThreadHandle:x8}!"); - - ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); - - return; - } - - KThread CurrThread = Process.GetThread(ThreadState.Tpidr); - - MutexLock(CurrThread, WaitThread, OwnerThreadHandle, WaitThreadHandle, MutexAddress); - - ThreadState.X0 = 0; - } - - private void SvcArbitrateUnlock(AThreadState ThreadState) - { - long MutexAddress = (long)ThreadState.X0; - - Ns.Log.PrintDebug(LogClass.KernelSvc, "MutexAddress = " + MutexAddress.ToString("x16")); - - if (IsPointingInsideKernel(MutexAddress)) - { - Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid mutex address 0x{MutexAddress:x16}!"); - - ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress); - - return; - } - - if (IsWordAddressUnaligned(MutexAddress)) - { - Ns.Log.PrintWarning(LogClass.KernelSvc, $"Unaligned mutex address 0x{MutexAddress:x16}!"); - - ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAlignment); - - return; - } - - MutexUnlock(Process.GetThread(ThreadState.Tpidr), MutexAddress); - - ThreadState.X0 = 0; - } - - private void SvcWaitProcessWideKeyAtomic(AThreadState ThreadState) - { - long MutexAddress = (long)ThreadState.X0; - long CondVarAddress = (long)ThreadState.X1; - int ThreadHandle = (int)ThreadState.X2; - ulong Timeout = ThreadState.X3; - - Ns.Log.PrintDebug(LogClass.KernelSvc, - "MutexAddress = " + MutexAddress .ToString("x16") + ", " + - "CondVarAddress = " + CondVarAddress.ToString("x16") + ", " + - "ThreadHandle = " + ThreadHandle .ToString("x8") + ", " + - "Timeout = " + Timeout .ToString("x16")); - - if (IsPointingInsideKernel(MutexAddress)) - { - Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid mutex address 0x{MutexAddress:x16}!"); - - ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress); - - return; - } - - if (IsWordAddressUnaligned(MutexAddress)) - { - Ns.Log.PrintWarning(LogClass.KernelSvc, $"Unaligned mutex address 0x{MutexAddress:x16}!"); - - ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAlignment); - - return; - } - - KThread Thread = Process.HandleTable.GetData(ThreadHandle); - - if (Thread == null) - { - Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{ThreadHandle:x8}!"); - - ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); - - return; - } - - KThread WaitThread = Process.GetThread(ThreadState.Tpidr); - - if (!CondVarWait(WaitThread, ThreadHandle, MutexAddress, CondVarAddress, Timeout)) - { - ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.Timeout); - - return; - } - - ThreadState.X0 = 0; - } - - private void SvcSignalProcessWideKey(AThreadState ThreadState) - { - long CondVarAddress = (long)ThreadState.X0; - int Count = (int)ThreadState.X1; - - Ns.Log.PrintDebug(LogClass.KernelSvc, - "CondVarAddress = " + CondVarAddress.ToString("x16") + ", " + - "Count = " + Count .ToString("x8")); - - KThread CurrThread = Process.GetThread(ThreadState.Tpidr); - - CondVarSignal(ThreadState, CurrThread, CondVarAddress, Count); - - ThreadState.X0 = 0; - } - - private void MutexLock( - KThread CurrThread, - KThread WaitThread, - int OwnerThreadHandle, - int WaitThreadHandle, - long MutexAddress) - { - lock (Process.ThreadSyncLock) - { - int MutexValue = Memory.ReadInt32(MutexAddress); - - Ns.Log.PrintDebug(LogClass.KernelSvc, "MutexValue = " + MutexValue.ToString("x8")); - - if (MutexValue != (OwnerThreadHandle | MutexHasListenersMask)) - { - return; - } - - CurrThread.WaitHandle = WaitThreadHandle; - CurrThread.MutexAddress = MutexAddress; - - InsertWaitingMutexThreadUnsafe(OwnerThreadHandle, WaitThread); - } - - Ns.Log.PrintDebug(LogClass.KernelSvc, "Entering wait state..."); - - Process.Scheduler.EnterWait(CurrThread); - } - - private void MutexUnlock(KThread CurrThread, long MutexAddress) - { - lock (Process.ThreadSyncLock) - { - //This is the new thread that will now own the mutex. - //If no threads are waiting for the lock, then it should be null. - (KThread OwnerThread, int Count) = PopMutexThreadUnsafe(CurrThread, MutexAddress); - - if (OwnerThread == CurrThread) - { - throw new InvalidOperationException(); - } - - if (OwnerThread != null) - { - //Remove all waiting mutex from the old owner, - //and insert then on the new owner. - UpdateMutexOwnerUnsafe(CurrThread, OwnerThread, MutexAddress); - - CurrThread.UpdatePriority(); - - int HasListeners = Count >= 2 ? MutexHasListenersMask : 0; - - Memory.WriteInt32ToSharedAddr(MutexAddress, HasListeners | OwnerThread.WaitHandle); - - OwnerThread.WaitHandle = 0; - OwnerThread.MutexAddress = 0; - OwnerThread.CondVarAddress = 0; - OwnerThread.MutexOwner = null; - - OwnerThread.UpdatePriority(); - - Process.Scheduler.WakeUp(OwnerThread); - - Ns.Log.PrintDebug(LogClass.KernelSvc, "Gave mutex to thread id " + OwnerThread.ThreadId + "!"); - } - else - { - Memory.WriteInt32ToSharedAddr(MutexAddress, 0); - - Ns.Log.PrintDebug(LogClass.KernelSvc, "No threads waiting mutex!"); - } - } - } - - private bool CondVarWait( - KThread WaitThread, - int WaitThreadHandle, - long MutexAddress, - long CondVarAddress, - ulong Timeout) - { - WaitThread.WaitHandle = WaitThreadHandle; - WaitThread.MutexAddress = MutexAddress; - WaitThread.CondVarAddress = CondVarAddress; - - lock (Process.ThreadSyncLock) - { - MutexUnlock(WaitThread, MutexAddress); - - WaitThread.CondVarSignaled = false; - - Process.ThreadArbiterList.Add(WaitThread); - } - - Ns.Log.PrintDebug(LogClass.KernelSvc, "Entering wait state..."); - - if (Timeout != ulong.MaxValue) - { - Process.Scheduler.EnterWait(WaitThread, NsTimeConverter.GetTimeMs(Timeout)); - - lock (Process.ThreadSyncLock) - { - if (!WaitThread.CondVarSignaled || WaitThread.MutexOwner != null) - { - if (WaitThread.MutexOwner != null) - { - WaitThread.MutexOwner.MutexWaiters.Remove(WaitThread); - WaitThread.MutexOwner.UpdatePriority(); - - WaitThread.MutexOwner = null; - } - - Process.ThreadArbiterList.Remove(WaitThread); - - Ns.Log.PrintDebug(LogClass.KernelSvc, "Timed out..."); - - return false; - } - } - } - else - { - Process.Scheduler.EnterWait(WaitThread); - } - - return true; - } - - private void CondVarSignal( - AThreadState ThreadState, - KThread CurrThread, - long CondVarAddress, - int Count) - { - lock (Process.ThreadSyncLock) - { - while (Count == -1 || Count-- > 0) - { - KThread WaitThread = PopCondVarThreadUnsafe(CondVarAddress); - - if (WaitThread == null) - { - Ns.Log.PrintDebug(LogClass.KernelSvc, "No more threads to wake up!"); - - break; - } - - WaitThread.CondVarSignaled = true; - - long MutexAddress = WaitThread.MutexAddress; - - Memory.SetExclusive(ThreadState, MutexAddress); - - int MutexValue = Memory.ReadInt32(MutexAddress); - - while (MutexValue != 0) - { - if (Memory.TestExclusive(ThreadState, MutexAddress)) - { - //Wait until the lock is released. - InsertWaitingMutexThreadUnsafe(MutexValue & ~MutexHasListenersMask, WaitThread); - - Memory.WriteInt32(MutexAddress, MutexValue | MutexHasListenersMask); - - Memory.ClearExclusiveForStore(ThreadState); - - break; - } - - Memory.SetExclusive(ThreadState, MutexAddress); - - MutexValue = Memory.ReadInt32(MutexAddress); - } - - Ns.Log.PrintDebug(LogClass.KernelSvc, "MutexValue = " + MutexValue.ToString("x8")); - - if (MutexValue == 0) - { - //Give the lock to this thread. - Memory.WriteInt32ToSharedAddr(MutexAddress, WaitThread.WaitHandle); - - WaitThread.WaitHandle = 0; - WaitThread.MutexAddress = 0; - WaitThread.CondVarAddress = 0; - - WaitThread.MutexOwner?.UpdatePriority(); - - WaitThread.MutexOwner = null; - - Process.Scheduler.WakeUp(WaitThread); - } - } - } - } - - private void UpdateMutexOwnerUnsafe(KThread CurrThread, KThread NewOwner, long MutexAddress) - { - //Go through all threads waiting for the mutex, - //and update the MutexOwner field to point to the new owner. - for (int Index = 0; Index < CurrThread.MutexWaiters.Count; Index++) - { - KThread Thread = CurrThread.MutexWaiters[Index]; - - if (Thread.MutexAddress == MutexAddress) - { - CurrThread.MutexWaiters.RemoveAt(Index--); - - InsertWaitingMutexThreadUnsafe(NewOwner, Thread); - } - } - } - - private void InsertWaitingMutexThreadUnsafe(int OwnerThreadHandle, KThread WaitThread) - { - KThread OwnerThread = Process.HandleTable.GetData(OwnerThreadHandle); - - if (OwnerThread == null) - { - Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{OwnerThreadHandle:x8}!"); - - return; - } - - InsertWaitingMutexThreadUnsafe(OwnerThread, WaitThread); - } - - private void InsertWaitingMutexThreadUnsafe(KThread OwnerThread, KThread WaitThread) - { - WaitThread.MutexOwner = OwnerThread; - - if (!OwnerThread.MutexWaiters.Contains(WaitThread)) - { - OwnerThread.MutexWaiters.Add(WaitThread); - - OwnerThread.UpdatePriority(); - } - } - - private (KThread, int) PopMutexThreadUnsafe(KThread OwnerThread, long MutexAddress) - { - int Count = 0; - - KThread WakeThread = null; - - foreach (KThread Thread in OwnerThread.MutexWaiters) - { - if (Thread.MutexAddress != MutexAddress) - { - continue; - } - - if (WakeThread == null || Thread.ActualPriority < WakeThread.ActualPriority) - { - WakeThread = Thread; - } - - Count++; - } - - if (WakeThread != null) - { - OwnerThread.MutexWaiters.Remove(WakeThread); - } - - return (WakeThread, Count); - } - - private KThread PopCondVarThreadUnsafe(long CondVarAddress) - { - KThread WakeThread = null; - - foreach (KThread Thread in Process.ThreadArbiterList) - { - if (Thread.CondVarAddress != CondVarAddress) - { - continue; - } - - if (WakeThread == null || Thread.ActualPriority < WakeThread.ActualPriority) - { - WakeThread = Thread; - } - } - - if (WakeThread != null) - { - Process.ThreadArbiterList.Remove(WakeThread); - } - - return WakeThread; - } - - private bool IsPointingInsideKernel(long Address) - { - return ((ulong)Address + 0x1000000000) < 0xffffff000; - } - - private bool IsWordAddressUnaligned(long Address) - { - return (Address & 3) != 0; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/MemoryAllocator.cs b/Ryujinx.HLE/OsHle/MemoryAllocator.cs deleted file mode 100644 index 8696aa4d11..0000000000 --- a/Ryujinx.HLE/OsHle/MemoryAllocator.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace Ryujinx.HLE.OsHle -{ - class MemoryAllocator - { - public bool TryAllocate(long Size, out long Address) - { - throw new NotImplementedException(); - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/MemoryRegions.cs b/Ryujinx.HLE/OsHle/MemoryRegions.cs deleted file mode 100644 index 198c621ccc..0000000000 --- a/Ryujinx.HLE/OsHle/MemoryRegions.cs +++ /dev/null @@ -1,29 +0,0 @@ -using ChocolArm64.Memory; - -namespace Ryujinx.HLE.OsHle -{ - static class MemoryRegions - { - public const long AddrSpaceStart = 0x08000000; - - public const long MapRegionAddress = 0x10000000; - public const long MapRegionSize = 0x20000000; - - public const long HeapRegionAddress = MapRegionAddress + MapRegionSize; - public const long HeapRegionSize = TlsPagesAddress - HeapRegionAddress; - - public const long MainStackSize = 0x100000; - - public const long MainStackAddress = AMemoryMgr.AddrSize - MainStackSize; - - public const long TlsPagesSize = 0x20000; - - public const long TlsPagesAddress = MainStackAddress - TlsPagesSize; - - public const long TotalMemoryUsed = HeapRegionAddress + TlsPagesSize + MainStackSize; - - public const long TotalMemoryAvailable = AMemoryMgr.RamSize - AddrSpaceStart; - - public const long AddrSpaceSize = AMemoryMgr.AddrSize - AddrSpaceStart; - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/MemoryType.cs b/Ryujinx.HLE/OsHle/MemoryType.cs deleted file mode 100644 index 64b0794729..0000000000 --- a/Ryujinx.HLE/OsHle/MemoryType.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace Ryujinx.HLE.OsHle -{ - enum MemoryType - { - Unmapped = 0, - Io = 1, - Normal = 2, - CodeStatic = 3, - CodeMutable = 4, - Heap = 5, - SharedMemory = 6, - ModCodeStatic = 8, - ModCodeMutable = 9, - IpcBuffer0 = 10, - MappedMemory = 11, - ThreadLocal = 12, - TransferMemoryIsolated = 13, - TransferMemory = 14, - ProcessMemory = 15, - Reserved = 16, - IpcBuffer1 = 17, - IpcBuffer3 = 18, - KernelStack = 19 - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Process.cs b/Ryujinx.HLE/OsHle/Process.cs deleted file mode 100644 index 53e357ab9a..0000000000 --- a/Ryujinx.HLE/OsHle/Process.cs +++ /dev/null @@ -1,438 +0,0 @@ -using ChocolArm64; -using ChocolArm64.Events; -using ChocolArm64.Memory; -using ChocolArm64.State; -using Ryujinx.HLE.Loaders; -using Ryujinx.HLE.Loaders.Executables; -using Ryujinx.HLE.Logging; -using Ryujinx.HLE.OsHle.Diagnostics; -using Ryujinx.HLE.OsHle.Exceptions; -using Ryujinx.HLE.OsHle.Handles; -using Ryujinx.HLE.OsHle.Kernel; -using Ryujinx.HLE.OsHle.Services.Nv; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Text; - -namespace Ryujinx.HLE.OsHle -{ - class Process : IDisposable - { - private const int TlsSize = 0x200; - - private const int TotalTlsSlots = (int)MemoryRegions.TlsPagesSize / TlsSize; - - private const int TickFreq = 19_200_000; - - private Switch Ns; - - public bool NeedsHbAbi { get; private set; } - - public long HbAbiDataPosition { get; private set; } - - public int ProcessId { get; private set; } - - private ATranslator Translator; - - public AMemory Memory { get; private set; } - - public KProcessScheduler Scheduler { get; private set; } - - public List ThreadArbiterList { get; private set; } - - public object ThreadSyncLock { get; private set; } - - public KProcessHandleTable HandleTable { get; private set; } - - public AppletStateMgr AppletState { get; private set; } - - private SvcHandler SvcHandler; - - private ConcurrentDictionary TlsSlots; - - private ConcurrentDictionary Threads; - - private KThread MainThread; - - private List Executables; - - private Dictionary SymbolTable; - - private long ImageBase; - - private bool ShouldDispose; - - private bool Disposed; - - public Process(Switch Ns, KProcessScheduler Scheduler, int ProcessId) - { - this.Ns = Ns; - this.Scheduler = Scheduler; - this.ProcessId = ProcessId; - - Memory = new AMemory(); - - ThreadArbiterList = new List(); - - ThreadSyncLock = new object(); - - HandleTable = new KProcessHandleTable(); - - AppletState = new AppletStateMgr(); - - SvcHandler = new SvcHandler(Ns, this); - - TlsSlots = new ConcurrentDictionary(); - - Threads = new ConcurrentDictionary(); - - Executables = new List(); - - ImageBase = MemoryRegions.AddrSpaceStart; - - MapRWMemRegion( - MemoryRegions.TlsPagesAddress, - MemoryRegions.TlsPagesSize, - MemoryType.ThreadLocal); - } - - public void LoadProgram(IExecutable Program) - { - if (Disposed) - { - throw new ObjectDisposedException(nameof(Process)); - } - - Ns.Log.PrintInfo(LogClass.Loader, $"Image base at 0x{ImageBase:x16}."); - - Executable Executable = new Executable(Program, Memory, ImageBase); - - Executables.Add(Executable); - - ImageBase = AMemoryHelper.PageRoundUp(Executable.ImageEnd); - } - - public void SetEmptyArgs() - { - //TODO: This should be part of Run. - ImageBase += AMemoryMgr.PageSize; - } - - public bool Run(bool NeedsHbAbi = false) - { - if (Disposed) - { - throw new ObjectDisposedException(nameof(Process)); - } - - this.NeedsHbAbi = NeedsHbAbi; - - if (Executables.Count == 0) - { - return false; - } - - MakeSymbolTable(); - - MapRWMemRegion( - MemoryRegions.MainStackAddress, - MemoryRegions.MainStackSize, - MemoryType.Normal); - - long StackTop = MemoryRegions.MainStackAddress + MemoryRegions.MainStackSize; - - int Handle = MakeThread(Executables[0].ImageBase, StackTop, 0, 44, 0); - - if (Handle == -1) - { - return false; - } - - MainThread = HandleTable.GetData(Handle); - - if (NeedsHbAbi) - { - HbAbiDataPosition = AMemoryHelper.PageRoundUp(Executables[0].ImageEnd); - - Homebrew.WriteHbAbiData(Memory, HbAbiDataPosition, Handle); - - MainThread.Thread.ThreadState.X0 = (ulong)HbAbiDataPosition; - MainThread.Thread.ThreadState.X1 = ulong.MaxValue; - } - - Scheduler.StartThread(MainThread); - - return true; - } - - private void MapRWMemRegion(long Position, long Size, MemoryType Type) - { - Memory.Manager.Map(Position, Size, (int)Type, AMemoryPerm.RW); - } - - public void StopAllThreadsAsync() - { - if (Disposed) - { - throw new ObjectDisposedException(nameof(Process)); - } - - if (MainThread != null) - { - MainThread.Thread.StopExecution(); - } - - foreach (AThread Thread in TlsSlots.Values) - { - Thread.StopExecution(); - } - } - - public int MakeThread( - long EntryPoint, - long StackTop, - long ArgsPtr, - int Priority, - int ProcessorId) - { - if (Disposed) - { - throw new ObjectDisposedException(nameof(Process)); - } - - AThread CpuThread = new AThread(GetTranslator(), Memory, EntryPoint); - - KThread Thread = new KThread(CpuThread, this, ProcessorId, Priority); - - Thread.LastPc = EntryPoint; - - int Handle = HandleTable.OpenHandle(Thread); - - int ThreadId = GetFreeTlsSlot(CpuThread); - - long Tpidr = MemoryRegions.TlsPagesAddress + ThreadId * TlsSize; - - CpuThread.ThreadState.ProcessId = ProcessId; - CpuThread.ThreadState.ThreadId = ThreadId; - CpuThread.ThreadState.CntfrqEl0 = TickFreq; - CpuThread.ThreadState.Tpidr = Tpidr; - - CpuThread.ThreadState.X0 = (ulong)ArgsPtr; - CpuThread.ThreadState.X1 = (ulong)Handle; - CpuThread.ThreadState.X31 = (ulong)StackTop; - - CpuThread.ThreadState.Break += BreakHandler; - CpuThread.ThreadState.SvcCall += SvcHandler.SvcCall; - CpuThread.ThreadState.Undefined += UndefinedHandler; - - CpuThread.WorkFinished += ThreadFinished; - - Threads.TryAdd(CpuThread.ThreadState.Tpidr, Thread); - - return Handle; - } - - private void BreakHandler(object sender, AInstExceptionEventArgs e) - { - throw new GuestBrokeExecutionException(); - } - - private void UndefinedHandler(object sender, AInstUndefinedEventArgs e) - { - throw new UndefinedInstructionException(e.Position, e.RawOpCode); - } - - private void MakeSymbolTable() - { - SymbolTable = new Dictionary(); - - foreach (Executable Exe in Executables) - { - foreach (KeyValuePair KV in Exe.SymbolTable) - { - SymbolTable.TryAdd(Exe.ImageBase + KV.Key, KV.Value); - } - } - } - - private ATranslator GetTranslator() - { - if (Translator == null) - { - Translator = new ATranslator(SymbolTable); - - Translator.CpuTrace += CpuTraceHandler; - } - - return Translator; - } - - public void EnableCpuTracing() - { - Translator.EnableCpuTrace = true; - } - - public void DisableCpuTracing() - { - Translator.EnableCpuTrace = false; - } - - private void CpuTraceHandler(object sender, ACpuTraceEventArgs e) - { - string NsoName = string.Empty; - - for (int Index = Executables.Count - 1; Index >= 0; Index--) - { - if (e.Position >= Executables[Index].ImageBase) - { - NsoName = $"{(e.Position - Executables[Index].ImageBase):x16}"; - - break; - } - } - - Ns.Log.PrintDebug(LogClass.Cpu, $"Executing at 0x{e.Position:x16} {e.SubName} {NsoName}"); - } - - public void PrintStackTrace(AThreadState ThreadState) - { - long[] Positions = ThreadState.GetCallStack(); - - StringBuilder Trace = new StringBuilder(); - - Trace.AppendLine("Guest stack trace:"); - - foreach (long Position in Positions) - { - if (!SymbolTable.TryGetValue(Position, out string SubName)) - { - SubName = $"Sub{Position:x16}"; - } - else if (SubName.StartsWith("_Z")) - { - SubName = Demangler.Parse(SubName); - } - - Trace.AppendLine(" " + SubName + " (" + GetNsoNameAndAddress(Position) + ")"); - } - - Ns.Log.PrintInfo(LogClass.Cpu, Trace.ToString()); - } - - private string GetNsoNameAndAddress(long Position) - { - string Name = string.Empty; - - for (int Index = Executables.Count - 1; Index >= 0; Index--) - { - if (Position >= Executables[Index].ImageBase) - { - long Offset = Position - Executables[Index].ImageBase; - - Name = $"{Executables[Index].Name}:{Offset:x8}"; - - break; - } - } - - return Name; - } - - private int GetFreeTlsSlot(AThread Thread) - { - for (int Index = 1; Index < TotalTlsSlots; Index++) - { - if (TlsSlots.TryAdd(Index, Thread)) - { - return Index; - } - } - - throw new InvalidOperationException(); - } - - private void ThreadFinished(object sender, EventArgs e) - { - if (sender is AThread Thread) - { - TlsSlots.TryRemove(GetTlsSlot(Thread.ThreadState.Tpidr), out _); - - Threads.TryRemove(Thread.ThreadState.Tpidr, out KThread KernelThread); - - Scheduler.RemoveThread(KernelThread); - - KernelThread.WaitEvent.Set(); - } - - if (TlsSlots.Count == 0) - { - if (ShouldDispose) - { - Dispose(); - } - - Ns.Os.ExitProcess(ProcessId); - } - } - - private int GetTlsSlot(long Position) - { - return (int)((Position - MemoryRegions.TlsPagesAddress) / TlsSize); - } - - public KThread GetThread(long Tpidr) - { - if (!Threads.TryGetValue(Tpidr, out KThread Thread)) - { - throw new InvalidOperationException(); - } - - return Thread; - } - - public void Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool Disposing) - { - if (Disposing && !Disposed) - { - //If there is still some thread running, disposing the objects is not - //safe as the thread may try to access those resources. Instead, we set - //the flag to have the Process disposed when all threads finishes. - //Note: This may not happen if the guest code gets stuck on a infinite loop. - if (TlsSlots.Count > 0) - { - ShouldDispose = true; - - Ns.Log.PrintInfo(LogClass.Loader, $"Process {ProcessId} waiting all threads terminate..."); - - return; - } - - Disposed = true; - - foreach (object Obj in HandleTable.Clear()) - { - if (Obj is KSession Session) - { - Session.Dispose(); - } - } - - INvDrvServices.UnloadProcess(this); - - AppletState.Dispose(); - - SvcHandler.Dispose(); - - Memory.Dispose(); - - Ns.Log.PrintInfo(LogClass.Loader, $"Process {ProcessId} exiting..."); - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/ServiceCtx.cs b/Ryujinx.HLE/OsHle/ServiceCtx.cs deleted file mode 100644 index eb9ff5fd88..0000000000 --- a/Ryujinx.HLE/OsHle/ServiceCtx.cs +++ /dev/null @@ -1,39 +0,0 @@ -using ChocolArm64.Memory; -using Ryujinx.HLE.OsHle.Handles; -using Ryujinx.HLE.OsHle.Ipc; -using System.IO; - -namespace Ryujinx.HLE.OsHle -{ - class ServiceCtx - { - public Switch Ns { get; private set; } - public Process Process { get; private set; } - public AMemory Memory { get; private set; } - public KSession Session { get; private set; } - public IpcMessage Request { get; private set; } - public IpcMessage Response { get; private set; } - public BinaryReader RequestData { get; private set; } - public BinaryWriter ResponseData { get; private set; } - - public ServiceCtx( - Switch Ns, - Process Process, - AMemory Memory, - KSession Session, - IpcMessage Request, - IpcMessage Response, - BinaryReader RequestData, - BinaryWriter ResponseData) - { - this.Ns = Ns; - this.Process = Process; - this.Memory = Memory; - this.Session = Session; - this.Request = Request; - this.Response = Response; - this.RequestData = RequestData; - this.ResponseData = ResponseData; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Acc/IAccountServiceForApplication.cs b/Ryujinx.HLE/OsHle/Services/Acc/IAccountServiceForApplication.cs deleted file mode 100644 index 35c4cd8280..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Acc/IAccountServiceForApplication.cs +++ /dev/null @@ -1,91 +0,0 @@ -using Ryujinx.HLE.Logging; -using Ryujinx.HLE.OsHle.Ipc; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Services.Acc -{ - class IAccountServiceForApplication : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - public IAccountServiceForApplication() - { - m_Commands = new Dictionary() - { - { 0, GetUserCount }, - { 1, GetUserExistence }, - { 2, ListAllUsers }, - { 3, ListOpenUsers }, - { 4, GetLastOpenedUser }, - { 5, GetProfile }, - { 100, InitializeApplicationInfo }, - { 101, GetBaasAccountManagerForApplication } - }; - } - - public long GetUserCount(ServiceCtx Context) - { - Context.ResponseData.Write(0); - - Context.Ns.Log.PrintStub(LogClass.ServiceAcc, "Stubbed."); - - return 0; - } - - public long GetUserExistence(ServiceCtx Context) - { - Context.ResponseData.Write(1); - - Context.Ns.Log.PrintStub(LogClass.ServiceAcc, "Stubbed."); - - return 0; - } - - public long ListAllUsers(ServiceCtx Context) - { - Context.Ns.Log.PrintStub(LogClass.ServiceAcc, "Stubbed."); - - return 0; - } - - public long ListOpenUsers(ServiceCtx Context) - { - Context.Ns.Log.PrintStub(LogClass.ServiceAcc, "Stubbed."); - - return 0; - } - - public long GetLastOpenedUser(ServiceCtx Context) - { - Context.ResponseData.Write(1L); - Context.ResponseData.Write(0L); - - Context.Ns.Log.PrintStub(LogClass.ServiceAcc, "Stubbed."); - - return 0; - } - - public long GetProfile(ServiceCtx Context) - { - MakeObject(Context, new IProfile()); - - return 0; - } - - public long InitializeApplicationInfo(ServiceCtx Context) - { - Context.Ns.Log.PrintStub(LogClass.ServiceAcc, "Stubbed."); - - return 0; - } - - public long GetBaasAccountManagerForApplication(ServiceCtx Context) - { - MakeObject(Context, new IManagerForApplication()); - - return 0; - } - } -} diff --git a/Ryujinx.HLE/OsHle/Services/Acc/IManagerForApplication.cs b/Ryujinx.HLE/OsHle/Services/Acc/IManagerForApplication.cs deleted file mode 100644 index ce3865f485..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Acc/IManagerForApplication.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Ryujinx.HLE.Logging; -using Ryujinx.HLE.OsHle.Ipc; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Services.Acc -{ - class IManagerForApplication : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - public IManagerForApplication() - { - m_Commands = new Dictionary() - { - { 0, CheckAvailability }, - { 1, GetAccountId } - }; - } - - public long CheckAvailability(ServiceCtx Context) - { - Context.Ns.Log.PrintStub(LogClass.ServiceAcc, "Stubbed."); - - return 0; - } - - public long GetAccountId(ServiceCtx Context) - { - Context.Ns.Log.PrintStub(LogClass.ServiceAcc, "Stubbed."); - - Context.ResponseData.Write(0xcafeL); - - return 0; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Acc/IProfile.cs b/Ryujinx.HLE/OsHle/Services/Acc/IProfile.cs deleted file mode 100644 index 24daa3d570..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Acc/IProfile.cs +++ /dev/null @@ -1,36 +0,0 @@ -using Ryujinx.HLE.Logging; -using Ryujinx.HLE.OsHle.Ipc; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Services.Acc -{ - class IProfile : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - public IProfile() - { - m_Commands = new Dictionary() - { - { 1, GetBase } - }; - } - - public long GetBase(ServiceCtx Context) - { - Context.Ns.Log.PrintStub(LogClass.ServiceAcc, "Stubbed."); - - Context.ResponseData.Write(0L); - Context.ResponseData.Write(0L); - Context.ResponseData.Write(0L); - Context.ResponseData.Write(0L); - Context.ResponseData.Write(0L); - Context.ResponseData.Write(0L); - Context.ResponseData.Write(0L); - - return 0; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Am/AmErr.cs b/Ryujinx.HLE/OsHle/Services/Am/AmErr.cs deleted file mode 100644 index 6622463927..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Am/AmErr.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Ryujinx.HLE.OsHle.Services.Am -{ - static class AmErr - { - public const int NoMessages = 3; - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Am/FocusState.cs b/Ryujinx.HLE/OsHle/Services/Am/FocusState.cs deleted file mode 100644 index 074f159eb3..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Am/FocusState.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Ryujinx.HLE.OsHle.Services.Am -{ - enum FocusState - { - InFocus = 1, - OutOfFocus = 2 - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Am/IAllSystemAppletProxiesService.cs b/Ryujinx.HLE/OsHle/Services/Am/IAllSystemAppletProxiesService.cs deleted file mode 100644 index 8dc17cecf6..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Am/IAllSystemAppletProxiesService.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Ryujinx.HLE.OsHle.Ipc; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Services.Am -{ - class IAllSystemAppletProxiesService : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - public IAllSystemAppletProxiesService() - { - m_Commands = new Dictionary() - { - { 100, OpenSystemAppletProxy } - }; - } - - public long OpenSystemAppletProxy(ServiceCtx Context) - { - MakeObject(Context, new ISystemAppletProxy()); - - return 0; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Am/IApplicationCreator.cs b/Ryujinx.HLE/OsHle/Services/Am/IApplicationCreator.cs deleted file mode 100644 index 6ee5b5c2e5..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Am/IApplicationCreator.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Ryujinx.HLE.OsHle.Ipc; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Services.Am -{ - class IApplicationCreator : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - public IApplicationCreator() - { - m_Commands = new Dictionary() - { - //... - }; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Am/IApplicationFunctions.cs b/Ryujinx.HLE/OsHle/Services/Am/IApplicationFunctions.cs deleted file mode 100644 index e25b524abb..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Am/IApplicationFunctions.cs +++ /dev/null @@ -1,117 +0,0 @@ -using Ryujinx.HLE.Logging; -using Ryujinx.HLE.OsHle.Ipc; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Services.Am -{ - class IApplicationFunctions : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - public IApplicationFunctions() - { - m_Commands = new Dictionary() - { - { 1, PopLaunchParameter }, - { 20, EnsureSaveData }, - { 21, GetDesiredLanguage }, - { 22, SetTerminateResult }, - { 23, GetDisplayVersion }, - { 40, NotifyRunning }, - { 50, GetPseudoDeviceId }, - { 66, InitializeGamePlayRecording }, - { 67, SetGamePlayRecordingState } - }; - } - - public long PopLaunchParameter(ServiceCtx Context) - { - //Only the first 0x18 bytes of the Data seems to be actually used. - MakeObject(Context, new IStorage(StorageHelper.MakeLaunchParams())); - - return 0; - } - - public long EnsureSaveData(ServiceCtx Context) - { - long UIdLow = Context.RequestData.ReadInt64(); - long UIdHigh = Context.RequestData.ReadInt64(); - - Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); - - Context.ResponseData.Write(0L); - - return 0; - } - - public long GetDesiredLanguage(ServiceCtx Context) - { - Context.ResponseData.Write(Context.Ns.Os.SystemState.DesiredLanguageCode); - - return 0; - } - - public long SetTerminateResult(ServiceCtx Context) - { - int ErrorCode = Context.RequestData.ReadInt32(); - - string Result = GetFormattedErrorCode(ErrorCode); - - Context.Ns.Log.PrintInfo(LogClass.ServiceAm, $"Result = 0x{ErrorCode:x8} ({Result})."); - - return 0; - } - - private string GetFormattedErrorCode(int ErrorCode) - { - int Module = (ErrorCode >> 0) & 0x1ff; - int Description = (ErrorCode >> 9) & 0x1fff; - - return $"{(2000 + Module):d4}-{Description:d4}"; - } - - public long GetDisplayVersion(ServiceCtx Context) - { - //FIXME: Need to check correct version on a switch. - Context.ResponseData.Write(1L); - Context.ResponseData.Write(0L); - - return 0; - } - - public long NotifyRunning(ServiceCtx Context) - { - Context.ResponseData.Write(1); - - return 0; - } - - public long GetPseudoDeviceId(ServiceCtx Context) - { - Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); - - Context.ResponseData.Write(0L); - Context.ResponseData.Write(0L); - - return 0; - } - - public long InitializeGamePlayRecording(ServiceCtx Context) - { - Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); - - return 0; - } - - public long SetGamePlayRecordingState(ServiceCtx Context) - { - int State = Context.RequestData.ReadInt32(); - - Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); - - return 0; - } - } -} diff --git a/Ryujinx.HLE/OsHle/Services/Am/IApplicationProxy.cs b/Ryujinx.HLE/OsHle/Services/Am/IApplicationProxy.cs deleted file mode 100644 index ec0285025b..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Am/IApplicationProxy.cs +++ /dev/null @@ -1,83 +0,0 @@ -using Ryujinx.HLE.OsHle.Ipc; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Services.Am -{ - class IApplicationProxy : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - public IApplicationProxy() - { - m_Commands = new Dictionary() - { - { 0, GetCommonStateGetter }, - { 1, GetSelfController }, - { 2, GetWindowController }, - { 3, GetAudioController }, - { 4, GetDisplayController }, - { 11, GetLibraryAppletCreator }, - { 20, GetApplicationFunctions }, - { 1000, GetDebugFunctions } - }; - } - - public long GetCommonStateGetter(ServiceCtx Context) - { - MakeObject(Context, new ICommonStateGetter()); - - return 0; - } - - public long GetSelfController(ServiceCtx Context) - { - MakeObject(Context, new ISelfController()); - - return 0; - } - - public long GetWindowController(ServiceCtx Context) - { - MakeObject(Context, new IWindowController()); - - return 0; - } - - public long GetAudioController(ServiceCtx Context) - { - MakeObject(Context, new IAudioController()); - - return 0; - } - - public long GetDisplayController(ServiceCtx Context) - { - MakeObject(Context, new IDisplayController()); - - return 0; - } - - public long GetLibraryAppletCreator(ServiceCtx Context) - { - MakeObject(Context, new ILibraryAppletCreator()); - - return 0; - } - - public long GetApplicationFunctions(ServiceCtx Context) - { - MakeObject(Context, new IApplicationFunctions()); - - return 0; - } - - public long GetDebugFunctions(ServiceCtx Context) - { - MakeObject(Context, new IDebugFunctions()); - - return 0; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Am/IApplicationProxyService.cs b/Ryujinx.HLE/OsHle/Services/Am/IApplicationProxyService.cs deleted file mode 100644 index 6b39b265db..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Am/IApplicationProxyService.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Ryujinx.HLE.OsHle.Ipc; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Services.Am -{ - class IApplicationProxyService : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - public IApplicationProxyService() - { - m_Commands = new Dictionary() - { - { 0, OpenApplicationProxy } - }; - } - - public long OpenApplicationProxy(ServiceCtx Context) - { - MakeObject(Context, new IApplicationProxy()); - - return 0; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Am/IAudioController.cs b/Ryujinx.HLE/OsHle/Services/Am/IAudioController.cs deleted file mode 100644 index 3cb6318169..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Am/IAudioController.cs +++ /dev/null @@ -1,72 +0,0 @@ -using Ryujinx.HLE.Logging; -using Ryujinx.HLE.OsHle.Ipc; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Services.Am -{ - class IAudioController : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - public IAudioController() - { - m_Commands = new Dictionary() - { - { 0, SetExpectedMasterVolume }, - { 1, GetMainAppletExpectedMasterVolume }, - { 2, GetLibraryAppletExpectedMasterVolume }, - { 3, ChangeMainAppletMasterVolume }, - { 4, SetTransparentVolumeRate } - }; - } - - public long SetExpectedMasterVolume(ServiceCtx Context) - { - float AppletVolume = Context.RequestData.ReadSingle(); - float LibraryAppletVolume = Context.RequestData.ReadSingle(); - - Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); - - return 0; - } - - public long GetMainAppletExpectedMasterVolume(ServiceCtx Context) - { - Context.ResponseData.Write(1f); - - Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); - - return 0; - } - - public long GetLibraryAppletExpectedMasterVolume(ServiceCtx Context) - { - Context.ResponseData.Write(1f); - - Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); - - return 0; - } - - public long ChangeMainAppletMasterVolume(ServiceCtx Context) - { - float Unknown0 = Context.RequestData.ReadSingle(); - long Unknown1 = Context.RequestData.ReadInt64(); - - Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); - - return 0; - } - - public long SetTransparentVolumeRate(ServiceCtx Context) - { - float Unknown0 = Context.RequestData.ReadSingle(); - - Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); - - return 0; - } - } -} diff --git a/Ryujinx.HLE/OsHle/Services/Am/ICommonStateGetter.cs b/Ryujinx.HLE/OsHle/Services/Am/ICommonStateGetter.cs deleted file mode 100644 index e1a13c5979..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Am/ICommonStateGetter.cs +++ /dev/null @@ -1,107 +0,0 @@ -using Ryujinx.HLE.Logging; -using Ryujinx.HLE.OsHle.Handles; -using Ryujinx.HLE.OsHle.Ipc; -using System.Collections.Generic; - -using static Ryujinx.HLE.OsHle.ErrorCode; - -namespace Ryujinx.HLE.OsHle.Services.Am -{ - class ICommonStateGetter : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - private KEvent DisplayResolutionChangeEvent; - - public ICommonStateGetter() - { - m_Commands = new Dictionary() - { - { 0, GetEventHandle }, - { 1, ReceiveMessage }, - { 5, GetOperationMode }, - { 6, GetPerformanceMode }, - { 8, GetBootMode }, - { 9, GetCurrentFocusState }, - { 60, GetDefaultDisplayResolution }, - { 61, GetDefaultDisplayResolutionChangeEvent } - }; - - DisplayResolutionChangeEvent = new KEvent(); - } - - public long GetEventHandle(ServiceCtx Context) - { - KEvent Event = Context.Process.AppletState.MessageEvent; - - int Handle = Context.Process.HandleTable.OpenHandle(Event); - - Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); - - return 0; - } - - public long ReceiveMessage(ServiceCtx Context) - { - if (!Context.Process.AppletState.TryDequeueMessage(out MessageInfo Message)) - { - return MakeError(ErrorModule.Am, AmErr.NoMessages); - } - - Context.ResponseData.Write((int)Message); - - return 0; - } - - public long GetOperationMode(ServiceCtx Context) - { - Context.ResponseData.Write((byte)OperationMode.Handheld); - - return 0; - } - - public long GetPerformanceMode(ServiceCtx Context) - { - Context.ResponseData.Write((byte)Apm.PerformanceMode.Handheld); - - return 0; - } - - public long GetBootMode(ServiceCtx Context) - { - Context.ResponseData.Write((byte)0); //Unknown value. - - Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); - - return 0; - } - - public long GetCurrentFocusState(ServiceCtx Context) - { - Context.ResponseData.Write((byte)Context.Process.AppletState.FocusState); - - return 0; - } - - public long GetDefaultDisplayResolution(ServiceCtx Context) - { - Context.ResponseData.Write(1280); - Context.ResponseData.Write(720); - - return 0; - } - - public long GetDefaultDisplayResolutionChangeEvent(ServiceCtx Context) - { - int Handle = Context.Process.HandleTable.OpenHandle(DisplayResolutionChangeEvent); - - Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); - - Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); - - return 0; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Am/IDebugFunctions.cs b/Ryujinx.HLE/OsHle/Services/Am/IDebugFunctions.cs deleted file mode 100644 index b07c68dd84..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Am/IDebugFunctions.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Ryujinx.HLE.OsHle.Ipc; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Services.Am -{ - class IDebugFunctions : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - public IDebugFunctions() - { - m_Commands = new Dictionary() - { - //... - }; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Am/IDisplayController.cs b/Ryujinx.HLE/OsHle/Services/Am/IDisplayController.cs deleted file mode 100644 index 8785f07199..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Am/IDisplayController.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Ryujinx.HLE.OsHle.Ipc; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Services.Am -{ - class IDisplayController : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - public IDisplayController() - { - m_Commands = new Dictionary() - { - //... - }; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Am/IGlobalStateController.cs b/Ryujinx.HLE/OsHle/Services/Am/IGlobalStateController.cs deleted file mode 100644 index 0fbcb28407..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Am/IGlobalStateController.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Ryujinx.HLE.OsHle.Ipc; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Services.Am -{ - class IGlobalStateController : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - public IGlobalStateController() - { - m_Commands = new Dictionary() - { - //... - }; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Am/IHomeMenuFunctions.cs b/Ryujinx.HLE/OsHle/Services/Am/IHomeMenuFunctions.cs deleted file mode 100644 index 1005fe0c5e..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Am/IHomeMenuFunctions.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Ryujinx.HLE.Logging; -using Ryujinx.HLE.OsHle.Handles; -using Ryujinx.HLE.OsHle.Ipc; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Services.Am -{ - class IHomeMenuFunctions : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - private KEvent ChannelEvent; - - public IHomeMenuFunctions() - { - m_Commands = new Dictionary() - { - { 10, RequestToGetForeground }, - { 21, GetPopFromGeneralChannelEvent } - }; - - //ToDo: Signal this Event somewhere in future. - ChannelEvent = new KEvent(); - } - - public long RequestToGetForeground(ServiceCtx Context) - { - Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); - - return 0; - } - - public long GetPopFromGeneralChannelEvent(ServiceCtx Context) - { - int Handle = Context.Process.HandleTable.OpenHandle(ChannelEvent); - - Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); - - Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); - - return 0; - } - } -} diff --git a/Ryujinx.HLE/OsHle/Services/Am/ILibraryAppletAccessor.cs b/Ryujinx.HLE/OsHle/Services/Am/ILibraryAppletAccessor.cs deleted file mode 100644 index b1955cc6d6..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Am/ILibraryAppletAccessor.cs +++ /dev/null @@ -1,71 +0,0 @@ -using Ryujinx.HLE.Logging; -using Ryujinx.HLE.OsHle.Handles; -using Ryujinx.HLE.OsHle.Ipc; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Services.Am -{ - class ILibraryAppletAccessor : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - private KEvent StateChangedEvent; - - public ILibraryAppletAccessor() - { - m_Commands = new Dictionary() - { - { 0, GetAppletStateChangedEvent }, - { 10, Start }, - { 30, GetResult }, - { 100, PushInData }, - { 101, PopOutData } - }; - - StateChangedEvent = new KEvent(); - } - - public long GetAppletStateChangedEvent(ServiceCtx Context) - { - StateChangedEvent.WaitEvent.Set(); - - int Handle = Context.Process.HandleTable.OpenHandle(StateChangedEvent); - - Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); - - Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); - - return 0; - } - - public long Start(ServiceCtx Context) - { - Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); - - return 0; - } - - public long GetResult(ServiceCtx Context) - { - Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); - - return 0; - } - - public long PushInData(ServiceCtx Context) - { - Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); - - return 0; - } - - public long PopOutData(ServiceCtx Context) - { - MakeObject(Context, new IStorage(StorageHelper.MakeLaunchParams())); - - return 0; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Am/ILibraryAppletCreator.cs b/Ryujinx.HLE/OsHle/Services/Am/ILibraryAppletCreator.cs deleted file mode 100644 index 66973fc6ce..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Am/ILibraryAppletCreator.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Ryujinx.HLE.OsHle.Ipc; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Services.Am -{ - class ILibraryAppletCreator : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - public ILibraryAppletCreator() - { - m_Commands = new Dictionary() - { - { 0, CreateLibraryApplet }, - { 10, CreateStorage } - }; - } - - public long CreateLibraryApplet(ServiceCtx Context) - { - MakeObject(Context, new ILibraryAppletAccessor()); - - return 0; - } - - public long CreateStorage(ServiceCtx Context) - { - long Size = Context.RequestData.ReadInt64(); - - MakeObject(Context, new IStorage(new byte[Size])); - - return 0; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Am/ISelfController.cs b/Ryujinx.HLE/OsHle/Services/Am/ISelfController.cs deleted file mode 100644 index ee0fb91569..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Am/ISelfController.cs +++ /dev/null @@ -1,117 +0,0 @@ -using Ryujinx.HLE.Logging; -using Ryujinx.HLE.OsHle.Handles; -using Ryujinx.HLE.OsHle.Ipc; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Services.Am -{ - class ISelfController : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - private KEvent LaunchableEvent; - - public ISelfController() - { - m_Commands = new Dictionary() - { - { 1, LockExit }, - { 9, GetLibraryAppletLaunchableEvent }, - { 10, SetScreenShotPermission }, - { 11, SetOperationModeChangedNotification }, - { 12, SetPerformanceModeChangedNotification }, - { 13, SetFocusHandlingMode }, - { 14, SetRestartMessageEnabled }, - { 16, SetOutOfFocusSuspendingEnabled }, - { 50, SetHandlesRequestToDisplay } - }; - - LaunchableEvent = new KEvent(); - } - - public long LockExit(ServiceCtx Context) - { - return 0; - } - - public long GetLibraryAppletLaunchableEvent(ServiceCtx Context) - { - LaunchableEvent.WaitEvent.Set(); - - int Handle = Context.Process.HandleTable.OpenHandle(LaunchableEvent); - - Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); - - Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); - - return 0; - } - - public long SetScreenShotPermission(ServiceCtx Context) - { - bool Enable = Context.RequestData.ReadByte() != 0 ? true : false; - - Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); - - return 0; - } - - public long SetOperationModeChangedNotification(ServiceCtx Context) - { - bool Enable = Context.RequestData.ReadByte() != 0 ? true : false; - - Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); - - return 0; - } - - public long SetPerformanceModeChangedNotification(ServiceCtx Context) - { - bool Enable = Context.RequestData.ReadByte() != 0 ? true : false; - - Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); - - return 0; - } - - public long SetFocusHandlingMode(ServiceCtx Context) - { - bool Flag1 = Context.RequestData.ReadByte() != 0 ? true : false; - bool Flag2 = Context.RequestData.ReadByte() != 0 ? true : false; - bool Flag3 = Context.RequestData.ReadByte() != 0 ? true : false; - - Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); - - return 0; - } - - public long SetRestartMessageEnabled(ServiceCtx Context) - { - bool Enable = Context.RequestData.ReadByte() != 0 ? true : false; - - Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); - - return 0; - } - - public long SetOutOfFocusSuspendingEnabled(ServiceCtx Context) - { - bool Enable = Context.RequestData.ReadByte() != 0 ? true : false; - - Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); - - return 0; - } - - public long SetHandlesRequestToDisplay(ServiceCtx Context) - { - bool Enable = Context.RequestData.ReadByte() != 0 ? true : false; - - Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); - - return 0; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Am/IStorage.cs b/Ryujinx.HLE/OsHle/Services/Am/IStorage.cs deleted file mode 100644 index 0aa1f571be..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Am/IStorage.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Ryujinx.HLE.OsHle.Ipc; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Services.Am -{ - class IStorage : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - public byte[] Data { get; private set; } - - public IStorage(byte[] Data) - { - m_Commands = new Dictionary() - { - { 0, Open } - }; - - this.Data = Data; - } - - public long Open(ServiceCtx Context) - { - MakeObject(Context, new IStorageAccessor(this)); - - return 0; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Am/IStorageAccessor.cs b/Ryujinx.HLE/OsHle/Services/Am/IStorageAccessor.cs deleted file mode 100644 index c2a8c11e23..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Am/IStorageAccessor.cs +++ /dev/null @@ -1,83 +0,0 @@ -using Ryujinx.HLE.OsHle.Ipc; -using System; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Services.Am -{ - class IStorageAccessor : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - private IStorage Storage; - - public IStorageAccessor(IStorage Storage) - { - m_Commands = new Dictionary() - { - { 0, GetSize }, - { 10, Write }, - { 11, Read } - }; - - this.Storage = Storage; - } - - public long GetSize(ServiceCtx Context) - { - Context.ResponseData.Write((long)Storage.Data.Length); - - return 0; - } - - public long Write(ServiceCtx Context) - { - //TODO: Error conditions. - long WritePosition = Context.RequestData.ReadInt64(); - - (long Position, long Size) = Context.Request.GetBufferType0x21(); - - if (Size > 0) - { - long MaxSize = Storage.Data.Length - WritePosition; - - if (Size > MaxSize) - { - Size = MaxSize; - } - - byte[] Data = Context.Memory.ReadBytes(Position, Size); - - Buffer.BlockCopy(Data, 0, Storage.Data, (int)WritePosition, (int)Size); - } - - return 0; - } - - public long Read(ServiceCtx Context) - { - //TODO: Error conditions. - long ReadPosition = Context.RequestData.ReadInt64(); - - (long Position, long Size) = Context.Request.GetBufferType0x22(); - - byte[] Data; - - if (Storage.Data.Length > Size) - { - Data = new byte[Size]; - - Buffer.BlockCopy(Storage.Data, 0, Data, 0, (int)Size); - } - else - { - Data = Storage.Data; - } - - Context.Memory.WriteBytes(Position, Data); - - return 0; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Am/ISystemAppletProxy.cs b/Ryujinx.HLE/OsHle/Services/Am/ISystemAppletProxy.cs deleted file mode 100644 index e0d78e34a6..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Am/ISystemAppletProxy.cs +++ /dev/null @@ -1,99 +0,0 @@ -using Ryujinx.HLE.OsHle.Ipc; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Services.Am -{ - class ISystemAppletProxy : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - public ISystemAppletProxy() - { - m_Commands = new Dictionary() - { - { 0, GetCommonStateGetter }, - { 1, GetSelfController }, - { 2, GetWindowController }, - { 3, GetAudioController }, - { 4, GetDisplayController }, - { 11, GetLibraryAppletCreator }, - { 20, GetHomeMenuFunctions }, - { 21, GetGlobalStateController }, - { 22, GetApplicationCreator }, - { 1000, GetDebugFunctions } - }; - } - - public long GetCommonStateGetter(ServiceCtx Context) - { - MakeObject(Context, new ICommonStateGetter()); - - return 0; - } - - public long GetSelfController(ServiceCtx Context) - { - MakeObject(Context, new ISelfController()); - - return 0; - } - - public long GetWindowController(ServiceCtx Context) - { - MakeObject(Context, new IWindowController()); - - return 0; - } - - public long GetAudioController(ServiceCtx Context) - { - MakeObject(Context, new IAudioController()); - - return 0; - } - - public long GetDisplayController(ServiceCtx Context) - { - MakeObject(Context, new IDisplayController()); - - return 0; - } - - public long GetLibraryAppletCreator(ServiceCtx Context) - { - MakeObject(Context, new ILibraryAppletCreator()); - - return 0; - } - - public long GetHomeMenuFunctions(ServiceCtx Context) - { - MakeObject(Context, new IHomeMenuFunctions()); - - return 0; - } - - public long GetGlobalStateController(ServiceCtx Context) - { - MakeObject(Context, new IGlobalStateController()); - - return 0; - } - - public long GetApplicationCreator(ServiceCtx Context) - { - MakeObject(Context, new IApplicationCreator()); - - return 0; - } - - public long GetDebugFunctions(ServiceCtx Context) - { - MakeObject(Context, new IDebugFunctions()); - - return 0; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Am/IWindowController.cs b/Ryujinx.HLE/OsHle/Services/Am/IWindowController.cs deleted file mode 100644 index d9ab5db3c5..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Am/IWindowController.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Ryujinx.HLE.Logging; -using Ryujinx.HLE.OsHle.Ipc; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Services.Am -{ - class IWindowController : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - public IWindowController() - { - m_Commands = new Dictionary() - { - { 1, GetAppletResourceUserId }, - { 10, AcquireForegroundRights } - }; - } - - public long GetAppletResourceUserId(ServiceCtx Context) - { - Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); - - Context.ResponseData.Write(0L); - - return 0; - } - - public long AcquireForegroundRights(ServiceCtx Context) - { - Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); - - return 0; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Am/OperationMode.cs b/Ryujinx.HLE/OsHle/Services/Am/OperationMode.cs deleted file mode 100644 index 632ce9316c..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Am/OperationMode.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Ryujinx.HLE.OsHle.Services.Am -{ - enum OperationMode - { - Handheld = 0, - Docked = 1 - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Am/StorageHelper.cs b/Ryujinx.HLE/OsHle/Services/Am/StorageHelper.cs deleted file mode 100644 index 56e2a6529e..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Am/StorageHelper.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.IO; - -namespace Ryujinx.HLE.OsHle.Services.Am -{ - class StorageHelper - { - private const uint LaunchParamsMagic = 0xc79497ca; - - public static byte[] MakeLaunchParams() - { - //Size needs to be at least 0x88 bytes otherwise application errors. - using (MemoryStream MS = new MemoryStream()) - { - BinaryWriter Writer = new BinaryWriter(MS); - - MS.SetLength(0x88); - - Writer.Write(LaunchParamsMagic); - Writer.Write(1); //IsAccountSelected? Only lower 8 bits actually used. - Writer.Write(1L); //User Id Low (note: User Id needs to be != 0) - Writer.Write(0L); //User Id High - - return MS.ToArray(); - } - } - } -} diff --git a/Ryujinx.HLE/OsHle/Services/Apm/IManager.cs b/Ryujinx.HLE/OsHle/Services/Apm/IManager.cs deleted file mode 100644 index 22150d6e0a..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Apm/IManager.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Ryujinx.HLE.OsHle.Ipc; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Services.Apm -{ - class IManager : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - public IManager() - { - m_Commands = new Dictionary() - { - { 0, OpenSession } - }; - } - - public long OpenSession(ServiceCtx Context) - { - MakeObject(Context, new ISession()); - - return 0; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Apm/ISession.cs b/Ryujinx.HLE/OsHle/Services/Apm/ISession.cs deleted file mode 100644 index 3c9bf07c4d..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Apm/ISession.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Ryujinx.HLE.Logging; -using Ryujinx.HLE.OsHle.Ipc; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Services.Apm -{ - class ISession : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - public ISession() - { - m_Commands = new Dictionary() - { - { 0, SetPerformanceConfiguration }, - { 1, GetPerformanceConfiguration } - }; - } - - public long SetPerformanceConfiguration(ServiceCtx Context) - { - PerformanceMode PerfMode = (PerformanceMode)Context.RequestData.ReadInt32(); - PerformanceConfiguration PerfConfig = (PerformanceConfiguration)Context.RequestData.ReadInt32(); - - return 0; - } - - public long GetPerformanceConfiguration(ServiceCtx Context) - { - PerformanceMode PerfMode = (PerformanceMode)Context.RequestData.ReadInt32(); - - Context.ResponseData.Write((uint)PerformanceConfiguration.PerformanceConfiguration1); - - Context.Ns.Log.PrintStub(LogClass.ServiceApm, "Stubbed."); - - return 0; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Apm/PerformanceConfiguration.cs b/Ryujinx.HLE/OsHle/Services/Apm/PerformanceConfiguration.cs deleted file mode 100644 index 07d59285b1..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Apm/PerformanceConfiguration.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Ryujinx.HLE.OsHle.Services.Apm -{ - enum PerformanceConfiguration : uint - { - PerformanceConfiguration1 = 0x00010000, - PerformanceConfiguration2 = 0x00010001, - PerformanceConfiguration3 = 0x00010002, - PerformanceConfiguration4 = 0x00020000, - PerformanceConfiguration5 = 0x00020001, - PerformanceConfiguration6 = 0x00020002, - PerformanceConfiguration7 = 0x00020003, - PerformanceConfiguration8 = 0x00020004, - PerformanceConfiguration9 = 0x00020005, - PerformanceConfiguration10 = 0x00020006, - PerformanceConfiguration11 = 0x92220007, - PerformanceConfiguration12 = 0x92220008 - } -} diff --git a/Ryujinx.HLE/OsHle/Services/Aud/AudErr.cs b/Ryujinx.HLE/OsHle/Services/Aud/AudErr.cs deleted file mode 100644 index fa201d8cdf..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Aud/AudErr.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Ryujinx.HLE.OsHle.Services.Aud -{ - static class AudErr - { - public const int DeviceNotFound = 1; - public const int UnsupportedSampleRate = 3; - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Aud/IAudioDevice.cs b/Ryujinx.HLE/OsHle/Services/Aud/IAudioDevice.cs deleted file mode 100644 index 67c0d837e9..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Aud/IAudioDevice.cs +++ /dev/null @@ -1,222 +0,0 @@ -using Ryujinx.HLE.Logging; -using Ryujinx.HLE.OsHle.Handles; -using Ryujinx.HLE.OsHle.Ipc; -using System.Collections.Generic; -using System.Text; - -namespace Ryujinx.HLE.OsHle.Services.Aud -{ - class IAudioDevice : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - private KEvent SystemEvent; - - public IAudioDevice() - { - m_Commands = new Dictionary() - { - { 0, ListAudioDeviceName }, - { 1, SetAudioDeviceOutputVolume }, - { 3, GetActiveAudioDeviceName }, - { 4, QueryAudioDeviceSystemEvent }, - { 5, GetActiveChannelCount }, - { 6, ListAudioDeviceNameAuto }, - { 7, SetAudioDeviceOutputVolumeAuto }, - { 8, GetAudioDeviceOutputVolumeAuto }, - { 10, GetActiveAudioDeviceNameAuto }, - { 11, QueryAudioDeviceInputEvent }, - { 12, QueryAudioDeviceOutputEvent } - }; - - SystemEvent = new KEvent(); - - //TODO: We shouldn't be signaling this here. - SystemEvent.WaitEvent.Set(); - } - - public long ListAudioDeviceName(ServiceCtx Context) - { - string[] DeviceNames = SystemStateMgr.AudioOutputs; - - Context.ResponseData.Write(DeviceNames.Length); - - long Position = Context.Request.ReceiveBuff[0].Position; - long Size = Context.Request.ReceiveBuff[0].Size; - - long BasePosition = Position; - - foreach (string Name in DeviceNames) - { - byte[] Buffer = Encoding.ASCII.GetBytes(Name + "\0"); - - if ((Position - BasePosition) + Buffer.Length > Size) - { - Context.Ns.Log.PrintError(LogClass.ServiceAudio, $"Output buffer size {Size} too small!"); - - break; - } - - Context.Memory.WriteBytes(Position, Buffer); - - Position += Buffer.Length; - } - - return 0; - } - - public long SetAudioDeviceOutputVolume(ServiceCtx Context) - { - float Volume = Context.RequestData.ReadSingle(); - - long Position = Context.Request.SendBuff[0].Position; - long Size = Context.Request.SendBuff[0].Size; - - byte[] DeviceNameBuffer = Context.Memory.ReadBytes(Position, Size); - - string DeviceName = Encoding.ASCII.GetString(DeviceNameBuffer); - - Context.Ns.Log.PrintStub(LogClass.ServiceAudio, "Stubbed."); - - return 0; - } - - public long GetActiveAudioDeviceName(ServiceCtx Context) - { - string Name = Context.Ns.Os.SystemState.ActiveAudioOutput; - - long Position = Context.Request.ReceiveBuff[0].Position; - long Size = Context.Request.ReceiveBuff[0].Size; - - byte[] DeviceNameBuffer = Encoding.ASCII.GetBytes(Name + "\0"); - - if ((ulong)DeviceNameBuffer.Length <= (ulong)Size) - { - Context.Memory.WriteBytes(Position, DeviceNameBuffer); - } - else - { - Context.Ns.Log.PrintError(LogClass.ServiceAudio, $"Output buffer size {Size} too small!"); - } - - return 0; - } - - public long QueryAudioDeviceSystemEvent(ServiceCtx Context) - { - int Handle = Context.Process.HandleTable.OpenHandle(SystemEvent); - - Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); - - Context.Ns.Log.PrintStub(LogClass.ServiceAudio, "Stubbed."); - - return 0; - } - - public long GetActiveChannelCount(ServiceCtx Context) - { - Context.ResponseData.Write(2); - - Context.Ns.Log.PrintStub(LogClass.ServiceAudio, "Stubbed."); - - return 0; - } - - public long ListAudioDeviceNameAuto(ServiceCtx Context) - { - string[] DeviceNames = SystemStateMgr.AudioOutputs; - - Context.ResponseData.Write(DeviceNames.Length); - - (long Position, long Size) = Context.Request.GetBufferType0x22(); - - long BasePosition = Position; - - foreach (string Name in DeviceNames) - { - byte[] Buffer = Encoding.UTF8.GetBytes(Name + '\0'); - - if ((Position - BasePosition) + Buffer.Length > Size) - { - Context.Ns.Log.PrintError(LogClass.ServiceAudio, $"Output buffer size {Size} too small!"); - - break; - } - - Context.Memory.WriteBytes(Position, Buffer); - - Position += Buffer.Length; - } - - return 0; - } - - public long SetAudioDeviceOutputVolumeAuto(ServiceCtx Context) - { - float Volume = Context.RequestData.ReadSingle(); - - (long Position, long Size) = Context.Request.GetBufferType0x21(); - - byte[] DeviceNameBuffer = Context.Memory.ReadBytes(Position, Size); - - string DeviceName = Encoding.UTF8.GetString(DeviceNameBuffer); - - Context.Ns.Log.PrintStub(LogClass.ServiceAudio, "Stubbed."); - - return 0; - } - - public long GetAudioDeviceOutputVolumeAuto(ServiceCtx Context) - { - Context.ResponseData.Write(1f); - - Context.Ns.Log.PrintStub(LogClass.ServiceAudio, "Stubbed."); - - return 0; - } - - public long GetActiveAudioDeviceNameAuto(ServiceCtx Context) - { - string Name = Context.Ns.Os.SystemState.ActiveAudioOutput; - - (long Position, long Size) = Context.Request.GetBufferType0x22(); - - byte[] DeviceNameBuffer = Encoding.UTF8.GetBytes(Name + '\0'); - - if ((ulong)DeviceNameBuffer.Length <= (ulong)Size) - { - Context.Memory.WriteBytes(Position, DeviceNameBuffer); - } - else - { - Context.Ns.Log.PrintError(LogClass.ServiceAudio, $"Output buffer size {Size} too small!"); - } - - return 0; - } - - public long QueryAudioDeviceInputEvent(ServiceCtx Context) - { - int Handle = Context.Process.HandleTable.OpenHandle(SystemEvent); - - Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); - - Context.Ns.Log.PrintStub(LogClass.ServiceAudio, "Stubbed."); - - return 0; - } - - public long QueryAudioDeviceOutputEvent(ServiceCtx Context) - { - int Handle = Context.Process.HandleTable.OpenHandle(SystemEvent); - - Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); - - Context.Ns.Log.PrintStub(LogClass.ServiceAudio, "Stubbed."); - - return 0; - } - } -} diff --git a/Ryujinx.HLE/OsHle/Services/Aud/IAudioOut.cs b/Ryujinx.HLE/OsHle/Services/Aud/IAudioOut.cs deleted file mode 100644 index 49c87a5616..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Aud/IAudioOut.cs +++ /dev/null @@ -1,164 +0,0 @@ -using ChocolArm64.Memory; -using Ryujinx.Audio; -using Ryujinx.HLE.Logging; -using Ryujinx.HLE.OsHle.Handles; -using Ryujinx.HLE.OsHle.Ipc; -using System; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Services.Aud -{ - class IAudioOut : IpcService, IDisposable - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - private IAalOutput AudioOut; - - private KEvent ReleaseEvent; - - private int Track; - - public IAudioOut(IAalOutput AudioOut, KEvent ReleaseEvent, int Track) - { - m_Commands = new Dictionary() - { - { 0, GetAudioOutState }, - { 1, StartAudioOut }, - { 2, StopAudioOut }, - { 3, AppendAudioOutBuffer }, - { 4, RegisterBufferEvent }, - { 5, GetReleasedAudioOutBuffer }, - { 6, ContainsAudioOutBuffer }, - { 7, AppendAudioOutBufferAuto }, - { 8, GetReleasedAudioOutBufferAuto } - }; - - this.AudioOut = AudioOut; - this.ReleaseEvent = ReleaseEvent; - this.Track = Track; - } - - public long GetAudioOutState(ServiceCtx Context) - { - Context.ResponseData.Write((int)AudioOut.GetState(Track)); - - return 0; - } - - public long StartAudioOut(ServiceCtx Context) - { - AudioOut.Start(Track); - - return 0; - } - - public long StopAudioOut(ServiceCtx Context) - { - AudioOut.Stop(Track); - - return 0; - } - - public long AppendAudioOutBuffer(ServiceCtx Context) - { - return AppendAudioOutBufferImpl(Context, Context.Request.SendBuff[0].Position); - } - - public long RegisterBufferEvent(ServiceCtx Context) - { - int Handle = Context.Process.HandleTable.OpenHandle(ReleaseEvent); - - Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); - - return 0; - } - - public long GetReleasedAudioOutBuffer(ServiceCtx Context) - { - long Position = Context.Request.ReceiveBuff[0].Position; - long Size = Context.Request.ReceiveBuff[0].Size; - - return GetReleasedAudioOutBufferImpl(Context, Position, Size); - } - - public long ContainsAudioOutBuffer(ServiceCtx Context) - { - long Tag = Context.RequestData.ReadInt64(); - - Context.ResponseData.Write(AudioOut.ContainsBuffer(Track, Tag) ? 1 : 0); - - return 0; - } - - public long AppendAudioOutBufferAuto(ServiceCtx Context) - { - (long Position, long Size) = Context.Request.GetBufferType0x21(); - - return AppendAudioOutBufferImpl(Context, Position); - } - - public long AppendAudioOutBufferImpl(ServiceCtx Context, long Position) - { - long Tag = Context.RequestData.ReadInt64(); - - AudioOutData Data = AMemoryHelper.Read( - Context.Memory, - Position); - - byte[] Buffer = Context.Memory.ReadBytes( - Data.SampleBufferPtr, - Data.SampleBufferSize); - - AudioOut.AppendBuffer(Track, Tag, Buffer); - - return 0; - } - - public long GetReleasedAudioOutBufferAuto(ServiceCtx Context) - { - (long Position, long Size) = Context.Request.GetBufferType0x22(); - - return GetReleasedAudioOutBufferImpl(Context, Position, Size); - } - - public long GetReleasedAudioOutBufferImpl(ServiceCtx Context, long Position, long Size) - { - uint Count = (uint)((ulong)Size >> 3); - - long[] ReleasedBuffers = AudioOut.GetReleasedBuffers(Track, (int)Count); - - for (uint Index = 0; Index < Count; Index++) - { - long Tag = 0; - - if (Index < ReleasedBuffers.Length) - { - Tag = ReleasedBuffers[Index]; - } - - Context.Memory.WriteInt64(Position + Index * 8, Tag); - } - - Context.ResponseData.Write(ReleasedBuffers.Length); - - return 0; - } - - public void Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool Disposing) - { - if (Disposing) - { - AudioOut.CloseTrack(Track); - - ReleaseEvent.Dispose(); - } - } - } -} diff --git a/Ryujinx.HLE/OsHle/Services/Aud/IAudioOutManager.cs b/Ryujinx.HLE/OsHle/Services/Aud/IAudioOutManager.cs deleted file mode 100644 index 8c78d1d493..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Aud/IAudioOutManager.cs +++ /dev/null @@ -1,169 +0,0 @@ -using ChocolArm64.Memory; -using Ryujinx.Audio; -using Ryujinx.HLE.Logging; -using Ryujinx.HLE.OsHle.Handles; -using Ryujinx.HLE.OsHle.Ipc; -using System.Collections.Generic; -using System.Text; - -using static Ryujinx.HLE.OsHle.ErrorCode; - -namespace Ryujinx.HLE.OsHle.Services.Aud -{ - class IAudioOutManager : IpcService - { - private const string DefaultAudioOutput = "DeviceOut"; - - private const int DefaultSampleRate = 48000; - - private const int DefaultChannelsCount = 2; - - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - public IAudioOutManager() - { - m_Commands = new Dictionary() - { - { 0, ListAudioOuts }, - { 1, OpenAudioOut }, - { 2, ListAudioOutsAuto }, - { 3, OpenAudioOutAuto } - }; - } - - public long ListAudioOuts(ServiceCtx Context) - { - return ListAudioOutsImpl( - Context, - Context.Request.ReceiveBuff[0].Position, - Context.Request.ReceiveBuff[0].Size); - } - - public long OpenAudioOut(ServiceCtx Context) - { - return OpenAudioOutImpl( - Context, - Context.Request.SendBuff[0].Position, - Context.Request.SendBuff[0].Size, - Context.Request.ReceiveBuff[0].Position, - Context.Request.ReceiveBuff[0].Size); - } - - public long ListAudioOutsAuto(ServiceCtx Context) - { - (long RecvPosition, long RecvSize) = Context.Request.GetBufferType0x22(); - - return ListAudioOutsImpl(Context, RecvPosition, RecvSize); - } - - public long OpenAudioOutAuto(ServiceCtx Context) - { - (long SendPosition, long SendSize) = Context.Request.GetBufferType0x21(); - (long RecvPosition, long RecvSize) = Context.Request.GetBufferType0x22(); - - return OpenAudioOutImpl( - Context, - SendPosition, - SendSize, - RecvPosition, - RecvSize); - } - - private long ListAudioOutsImpl(ServiceCtx Context, long Position, long Size) - { - int NameCount = 0; - - byte[] DeviceNameBuffer = Encoding.ASCII.GetBytes(DefaultAudioOutput + "\0"); - - if ((ulong)DeviceNameBuffer.Length <= (ulong)Size) - { - Context.Memory.WriteBytes(Position, DeviceNameBuffer); - - NameCount++; - } - else - { - Context.Ns.Log.PrintError(LogClass.ServiceAudio, $"Output buffer size {Size} too small!"); - } - - Context.ResponseData.Write(NameCount); - - return 0; - } - - private long OpenAudioOutImpl(ServiceCtx Context, long SendPosition, long SendSize, long ReceivePosition, long ReceiveSize) - { - string DeviceName = AMemoryHelper.ReadAsciiString( - Context.Memory, - SendPosition, - SendSize); - - if (DeviceName == string.Empty) - { - DeviceName = DefaultAudioOutput; - } - - if (DeviceName != DefaultAudioOutput) - { - Context.Ns.Log.PrintWarning(LogClass.Audio, "Invalid device name!"); - - return MakeError(ErrorModule.Audio, AudErr.DeviceNotFound); - } - - byte[] DeviceNameBuffer = Encoding.ASCII.GetBytes(DeviceName + "\0"); - - if ((ulong)DeviceNameBuffer.Length <= (ulong)ReceiveSize) - { - Context.Memory.WriteBytes(ReceivePosition, DeviceNameBuffer); - } - else - { - Context.Ns.Log.PrintError(LogClass.ServiceAudio, $"Output buffer size {ReceiveSize} too small!"); - } - - int SampleRate = Context.RequestData.ReadInt32(); - int Channels = Context.RequestData.ReadInt32(); - - if (SampleRate == 0) - { - SampleRate = DefaultSampleRate; - } - - if (SampleRate != DefaultSampleRate) - { - Context.Ns.Log.PrintWarning(LogClass.Audio, "Invalid sample rate!"); - - return MakeError(ErrorModule.Audio, AudErr.UnsupportedSampleRate); - } - - Channels = (ushort)Channels; - - if (Channels == 0) - { - Channels = DefaultChannelsCount; - } - - KEvent ReleaseEvent = new KEvent(); - - ReleaseCallback Callback = () => - { - ReleaseEvent.WaitEvent.Set(); - }; - - IAalOutput AudioOut = Context.Ns.AudioOut; - - int Track = AudioOut.OpenTrack(SampleRate, Channels, Callback, out AudioFormat Format); - - MakeObject(Context, new IAudioOut(AudioOut, ReleaseEvent, Track)); - - Context.ResponseData.Write(SampleRate); - Context.ResponseData.Write(Channels); - Context.ResponseData.Write((int)Format); - Context.ResponseData.Write((int)PlaybackState.Stopped); - - return 0; - } - } -} diff --git a/Ryujinx.HLE/OsHle/Services/Aud/IAudioRenderer.cs b/Ryujinx.HLE/OsHle/Services/Aud/IAudioRenderer.cs deleted file mode 100644 index bd9188c341..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Aud/IAudioRenderer.cs +++ /dev/null @@ -1,136 +0,0 @@ -using ChocolArm64.Memory; -using Ryujinx.HLE.Logging; -using Ryujinx.HLE.OsHle.Handles; -using Ryujinx.HLE.OsHle.Ipc; -using System; -using System.Collections.Generic; -using System.Runtime.InteropServices; - -namespace Ryujinx.HLE.OsHle.Services.Aud -{ - class IAudioRenderer : IpcService, IDisposable - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - private KEvent UpdateEvent; - - private AudioRendererParameter Params; - - public IAudioRenderer(AudioRendererParameter Params) - { - m_Commands = new Dictionary() - { - { 4, RequestUpdateAudioRenderer }, - { 5, StartAudioRenderer }, - { 6, StopAudioRenderer }, - { 7, QuerySystemEvent } - }; - - UpdateEvent = new KEvent(); - - this.Params = Params; - } - - public long RequestUpdateAudioRenderer(ServiceCtx Context) - { - long OutputPosition = Context.Request.ReceiveBuff[0].Position; - long OutputSize = Context.Request.ReceiveBuff[0].Size; - - AMemoryHelper.FillWithZeros(Context.Memory, OutputPosition, (int)OutputSize); - - long InputPosition = Context.Request.SendBuff[0].Position; - - UpdateDataHeader InputDataHeader = AMemoryHelper.Read(Context.Memory, InputPosition); - - UpdateDataHeader OutputDataHeader = new UpdateDataHeader(); - - int UpdateHeaderSize = Marshal.SizeOf(); - - OutputDataHeader.Revision = Params.Revision; - OutputDataHeader.BehaviorSize = 0xb0; - OutputDataHeader.MemoryPoolsSize = (Params.EffectCount + Params.VoiceCount * 4) * 0x10; - OutputDataHeader.VoicesSize = Params.VoiceCount * 0x10; - OutputDataHeader.EffectsSize = Params.EffectCount * 0x10; - OutputDataHeader.SinksSize = Params.SinkCount * 0x20; - OutputDataHeader.PerformanceManagerSize = 0x10; - OutputDataHeader.TotalSize = UpdateHeaderSize + - OutputDataHeader.BehaviorSize + - OutputDataHeader.MemoryPoolsSize + - OutputDataHeader.VoicesSize + - OutputDataHeader.EffectsSize + - OutputDataHeader.SinksSize + - OutputDataHeader.PerformanceManagerSize; - - AMemoryHelper.Write(Context.Memory, OutputPosition, OutputDataHeader); - - int InMemoryPoolOffset = UpdateHeaderSize + InputDataHeader.BehaviorSize; - - int OutMemoryPoolOffset = UpdateHeaderSize; - - for (int Offset = 0; Offset < OutputDataHeader.MemoryPoolsSize; Offset += 0x10, InMemoryPoolOffset += 0x20) - { - MemoryPoolState PoolState = (MemoryPoolState)Context.Memory.ReadInt32(InputPosition + InMemoryPoolOffset + 0x10); - - //TODO: Figure out what the other values does. - if (PoolState == MemoryPoolState.RequestAttach) - { - Context.Memory.WriteInt32(OutputPosition + OutMemoryPoolOffset + Offset, (int)MemoryPoolState.Attached); - } - else if (PoolState == MemoryPoolState.RequestDetach) - { - Context.Memory.WriteInt32(OutputPosition + OutMemoryPoolOffset + Offset, (int)MemoryPoolState.Detached); - } - } - - int OutVoicesOffset = OutMemoryPoolOffset + OutputDataHeader.MemoryPoolsSize; - - for (int Offset = 0; Offset < OutputDataHeader.VoicesSize; Offset += 0x10) - { - Context.Memory.WriteInt32(OutputPosition + OutVoicesOffset + Offset + 8, (int)VoicePlaybackState.Finished); - } - - //TODO: We shouldn't be signaling this here. - UpdateEvent.WaitEvent.Set(); - - return 0; - } - - public long StartAudioRenderer(ServiceCtx Context) - { - Context.Ns.Log.PrintStub(LogClass.ServiceAudio, "Stubbed."); - - return 0; - } - - public long StopAudioRenderer(ServiceCtx Context) - { - Context.Ns.Log.PrintStub(LogClass.ServiceAudio, "Stubbed."); - - return 0; - } - - public long QuerySystemEvent(ServiceCtx Context) - { - int Handle = Context.Process.HandleTable.OpenHandle(UpdateEvent); - - Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); - - return 0; - } - - public void Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool Disposing) - { - if (Disposing) - { - UpdateEvent.Dispose(); - } - } - } -} diff --git a/Ryujinx.HLE/OsHle/Services/Aud/IAudioRendererManager.cs b/Ryujinx.HLE/OsHle/Services/Aud/IAudioRendererManager.cs deleted file mode 100644 index a7daeedd58..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Aud/IAudioRendererManager.cs +++ /dev/null @@ -1,152 +0,0 @@ -using Ryujinx.HLE.Logging; -using Ryujinx.HLE.OsHle.Ipc; -using System.Collections.Generic; -using System.Runtime.InteropServices; - -namespace Ryujinx.HLE.OsHle.Services.Aud -{ - class IAudioRendererManager : IpcService - { - private const int Rev0Magic = ('R' << 0) | - ('E' << 8) | - ('V' << 16) | - ('0' << 24); - - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - public IAudioRendererManager() - { - m_Commands = new Dictionary() - { - { 0, OpenAudioRenderer }, - { 1, GetAudioRendererWorkBufferSize }, - { 2, GetAudioDevice } - }; - } - - public long OpenAudioRenderer(ServiceCtx Context) - { - //Same buffer as GetAudioRendererWorkBufferSize is receive here. - - AudioRendererParameter Params = new AudioRendererParameter(); - - Params.SampleRate = Context.RequestData.ReadInt32(); - Params.SampleCount = Context.RequestData.ReadInt32(); - Params.Unknown8 = Context.RequestData.ReadInt32(); - Params.UnknownC = Context.RequestData.ReadInt32(); - Params.VoiceCount = Context.RequestData.ReadInt32(); - Params.SinkCount = Context.RequestData.ReadInt32(); - Params.EffectCount = Context.RequestData.ReadInt32(); - Params.Unknown1C = Context.RequestData.ReadInt32(); - Params.Unknown20 = Context.RequestData.ReadInt32(); - Params.SplitterCount = Context.RequestData.ReadInt32(); - Params.Unknown28 = Context.RequestData.ReadInt32(); - Params.Unknown2C = Context.RequestData.ReadInt32(); - Params.Revision = Context.RequestData.ReadInt32(); - - MakeObject(Context, new IAudioRenderer(Params)); - - return 0; - } - - public long GetAudioRendererWorkBufferSize(ServiceCtx Context) - { - long SampleRate = Context.RequestData.ReadUInt32(); - long Unknown4 = Context.RequestData.ReadUInt32(); - long Unknown8 = Context.RequestData.ReadUInt32(); - long UnknownC = Context.RequestData.ReadUInt32(); - long Unknown10 = Context.RequestData.ReadUInt32(); //VoiceCount - long Unknown14 = Context.RequestData.ReadUInt32(); //SinkCount - long Unknown18 = Context.RequestData.ReadUInt32(); //EffectCount - long Unknown1c = Context.RequestData.ReadUInt32(); //Boolean - long Unknown20 = Context.RequestData.ReadUInt32(); //Not used here in FW3.0.1 - Boolean - long Unknown24 = Context.RequestData.ReadUInt32(); - long Unknown28 = Context.RequestData.ReadUInt32(); //SplitterCount - long Unknown2c = Context.RequestData.ReadUInt32(); //Not used here in FW3.0.1 - int RevMagic = Context.RequestData.ReadInt32(); - - int Version = (RevMagic - Rev0Magic) >> 24; - - if (Version <= 3) //REV3 Max is supported - { - long Size = RoundUp(Unknown8 * 4, 64); - Size += (UnknownC << 10); - Size += (UnknownC + 1) * 0x940; - Size += Unknown10 * 0x3F0; - Size += RoundUp((UnknownC + 1) * 8, 16); - Size += RoundUp(Unknown10 * 8, 16); - Size += RoundUp((0x3C0 * (Unknown14 + UnknownC) + 4 * Unknown4) * (Unknown8 + 6), 64); - Size += 0x2C0 * (Unknown14 + UnknownC) + 0x30 * (Unknown18 + (4 * Unknown10)) + 0x50; - - if (Version >= 3) //IsSplitterSupported - { - Size += RoundUp((NodeStatesGetWorkBufferSize((int)UnknownC + 1) + EdgeMatrixGetWorkBufferSize((int)UnknownC + 1)), 16); - Size += 0xE0 * Unknown28 + 0x20 * Unknown24 + RoundUp(Unknown28 * 4, 16); - } - - Size = 0x4C0 * Unknown18 + RoundUp(Size, 64) + 0x170 * Unknown14 + ((Unknown10 << 8) | 0x40); - - if (Unknown1c >= 1) - { - Size += ((((Unknown18 + Unknown14 + Unknown10 + UnknownC + 1) * 16) + 0x658) * (Unknown1c + 1) + 0x13F) & ~0x3FL; - } - - long WorkBufferSize = (Size + 0x1907D) & ~0xFFFL; - - Context.ResponseData.Write(WorkBufferSize); - - Context.Ns.Log.PrintDebug(LogClass.ServiceAudio, $"WorkBufferSize is 0x{WorkBufferSize:x16}."); - - return 0; - } - else - { - Context.ResponseData.Write(0L); - - Context.Ns.Log.PrintError(LogClass.ServiceAudio, $"Library Revision 0x{RevMagic:x8} is not supported!"); - - return 0x499; - } - } - - private static long RoundUp(long Value, int Size) - { - return (Value + (Size - 1)) & ~((long)Size - 1); - } - - private static int NodeStatesGetWorkBufferSize(int Value) - { - int Result = (int)RoundUp(Value, 64); - - if (Result < 0) - { - Result |= 7; - } - - return 4 * (Value * Value) + 0x12 * Value + 2 * (Result / 8); - } - - private static int EdgeMatrixGetWorkBufferSize(int Value) - { - int Result = (int)RoundUp(Value * Value, 64); - - if (Result < 0) - { - Result |= 7; - } - - return Result / 8; - } - - public long GetAudioDevice(ServiceCtx Context) - { - long UserId = Context.RequestData.ReadInt64(); - - MakeObject(Context, new IAudioDevice()); - - return 0; - } - } -} diff --git a/Ryujinx.HLE/OsHle/Services/Aud/VoiceState.cs b/Ryujinx.HLE/OsHle/Services/Aud/VoiceState.cs deleted file mode 100644 index 8b34332392..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Aud/VoiceState.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Ryujinx.HLE.OsHle.Services.Aud -{ - enum VoicePlaybackState : int - { - Playing = 0, - Finished = 1, - Paused = 2 - } -} diff --git a/Ryujinx.HLE/OsHle/Services/Bsd/BsdError.cs b/Ryujinx.HLE/OsHle/Services/Bsd/BsdError.cs deleted file mode 100644 index 114130dcf5..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Bsd/BsdError.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Ryujinx.HLE.OsHle.Services.Bsd -{ - //bsd_errno == (SocketException.ErrorCode - 10000) - public enum BsdError - { - Timeout = 60 - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Bsd/IClient.cs b/Ryujinx.HLE/OsHle/Services/Bsd/IClient.cs deleted file mode 100644 index 15ce92a163..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Bsd/IClient.cs +++ /dev/null @@ -1,445 +0,0 @@ -using Ryujinx.HLE.OsHle.Ipc; -using Ryujinx.HLE.OsHle.Utilities; -using System.Collections.Generic; -using System.IO; -using System.Net; -using System.Net.Sockets; -using System.Threading.Tasks; - -namespace Ryujinx.HLE.OsHle.Services.Bsd -{ - class IClient : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - private List Sockets = new List(); - - public IClient() - { - m_Commands = new Dictionary() - { - { 0, Initialize }, - { 1, StartMonitoring }, - { 2, Socket }, - { 6, Poll }, - { 8, Recv }, - { 10, Send }, - { 11, SendTo }, - { 12, Accept }, - { 13, Bind }, - { 14, Connect }, - { 18, Listen }, - { 21, SetSockOpt }, - { 26, Close } - }; - } - - //(u32, u32, u32, u32, u32, u32, u32, u32, u64 pid, u64 transferMemorySize, pid, KObject) -> u32 bsd_errno - public long Initialize(ServiceCtx Context) - { - /* - typedef struct { - u32 version; // Observed 1 on 2.0 LibAppletWeb, 2 on 3.0. - u32 tcp_tx_buf_size; // Size of the TCP transfer (send) buffer (initial or fixed). - u32 tcp_rx_buf_size; // Size of the TCP recieve buffer (initial or fixed). - u32 tcp_tx_buf_max_size; // Maximum size of the TCP transfer (send) buffer. If it is 0, the size of the buffer is fixed to its initial value. - u32 tcp_rx_buf_max_size; // Maximum size of the TCP receive buffer. If it is 0, the size of the buffer is fixed to its initial value. - u32 udp_tx_buf_size; // Size of the UDP transfer (send) buffer (typically 0x2400 bytes). - u32 udp_rx_buf_size; // Size of the UDP receive buffer (typically 0xA500 bytes). - u32 sb_efficiency; // Number of buffers for each socket (standard values range from 1 to 8). - } BsdBufferConfig; - */ - - Context.ResponseData.Write(0); - - //Todo: Stub - - return 0; - } - - //(u64, pid) - public long StartMonitoring(ServiceCtx Context) - { - //Todo: Stub - - return 0; - } - - //(u32 domain, u32 type, u32 protocol) -> (i32 ret, u32 bsd_errno) - public long Socket(ServiceCtx Context) - { - BsdSocket NewBsdSocket = new BsdSocket - { - Family = Context.RequestData.ReadInt32(), - Type = Context.RequestData.ReadInt32(), - Protocol = Context.RequestData.ReadInt32() - }; - - Sockets.Add(NewBsdSocket); - - NewBsdSocket.Handle = new Socket((AddressFamily)NewBsdSocket.Family, - (SocketType)NewBsdSocket.Type, - (ProtocolType)NewBsdSocket.Protocol); - - Context.ResponseData.Write(Sockets.Count - 1); - Context.ResponseData.Write(0); - - return 0; - } - - //(u32, u32, buffer) -> (i32 ret, u32 bsd_errno, buffer) - public long Poll(ServiceCtx Context) - { - int PollCount = Context.RequestData.ReadInt32(); - int TimeOut = Context.RequestData.ReadInt32(); - - //https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/poll.h - //https://msdn.microsoft.com/fr-fr/library/system.net.sockets.socket.poll(v=vs.110).aspx - //https://github.com/switchbrew/libnx/blob/e0457c4534b3c37426d83e1a620f82cb28c3b528/nx/source/services/bsd.c#L343 - //https://github.com/TuxSH/ftpd/blob/switch_pr/source/ftp.c#L1634 - //https://linux.die.net/man/2/poll - - byte[] SentBuffer = Context.Memory.ReadBytes(Context.Request.SendBuff[0].Position, - Context.Request.SendBuff[0].Size); - - int SocketId = Get32(SentBuffer, 0); - int RequestedEvents = Get16(SentBuffer, 4); - int ReturnedEvents = Get16(SentBuffer, 6); - - //Todo: Stub - Need to implemented the Type-22 buffer. - - Context.ResponseData.Write(1); - Context.ResponseData.Write(0); - - return 0; - } - - //(u32 socket, u32 flags) -> (i32 ret, u32 bsd_errno, buffer message) - public long Recv(ServiceCtx Context) - { - int SocketId = Context.RequestData.ReadInt32(); - int SocketFlags = Context.RequestData.ReadInt32(); - - byte[] ReceivedBuffer = new byte[Context.Request.ReceiveBuff[0].Size]; - - try - { - int BytesRead = Sockets[SocketId].Handle.Receive(ReceivedBuffer); - - //Logging.Debug("Received Buffer:" + Environment.NewLine + Logging.HexDump(ReceivedBuffer)); - - Context.Memory.WriteBytes(Context.Request.ReceiveBuff[0].Position, ReceivedBuffer); - - Context.ResponseData.Write(BytesRead); - Context.ResponseData.Write(0); - } - catch (SocketException Ex) - { - Context.ResponseData.Write(-1); - Context.ResponseData.Write(Ex.ErrorCode - 10000); - } - - return 0; - } - - //(u32 socket, u32 flags, buffer) -> (i32 ret, u32 bsd_errno) - public long Send(ServiceCtx Context) - { - int SocketId = Context.RequestData.ReadInt32(); - int SocketFlags = Context.RequestData.ReadInt32(); - - byte[] SentBuffer = Context.Memory.ReadBytes(Context.Request.SendBuff[0].Position, - Context.Request.SendBuff[0].Size); - - try - { - //Logging.Debug("Sent Buffer:" + Environment.NewLine + Logging.HexDump(SentBuffer)); - - int BytesSent = Sockets[SocketId].Handle.Send(SentBuffer); - - Context.ResponseData.Write(BytesSent); - Context.ResponseData.Write(0); - } - catch (SocketException Ex) - { - Context.ResponseData.Write(-1); - Context.ResponseData.Write(Ex.ErrorCode - 10000); - } - - return 0; - } - - //(u32 socket, u32 flags, buffer, buffer) -> (i32 ret, u32 bsd_errno) - public long SendTo(ServiceCtx Context) - { - int SocketId = Context.RequestData.ReadInt32(); - int SocketFlags = Context.RequestData.ReadInt32(); - - byte[] SentBuffer = Context.Memory.ReadBytes(Context.Request.SendBuff[0].Position, - Context.Request.SendBuff[0].Size); - - byte[] AddressBuffer = Context.Memory.ReadBytes(Context.Request.SendBuff[1].Position, - Context.Request.SendBuff[1].Size); - - if (!Sockets[SocketId].Handle.Connected) - { - try - { - ParseAddrBuffer(SocketId, AddressBuffer); - - Sockets[SocketId].Handle.Connect(Sockets[SocketId].RemoteEP); - } - catch (SocketException Ex) - { - Context.ResponseData.Write(-1); - Context.ResponseData.Write(Ex.ErrorCode - 10000); - } - } - - try - { - //Logging.Debug("Sent Buffer:" + Environment.NewLine + Logging.HexDump(SentBuffer)); - - int BytesSent = Sockets[SocketId].Handle.Send(SentBuffer); - - Context.ResponseData.Write(BytesSent); - Context.ResponseData.Write(0); - } - catch (SocketException Ex) - { - Context.ResponseData.Write(-1); - Context.ResponseData.Write(Ex.ErrorCode - 10000); - } - - return 0; - } - - //(u32 socket) -> (i32 ret, u32 bsd_errno, u32 addrlen, buffer addr) - public long Accept(ServiceCtx Context) - { - int SocketId = Context.RequestData.ReadInt32(); - - long AddrBufferPtr = Context.Request.ReceiveBuff[0].Position; - - Socket HandleAccept = null; - - Task TimeOut = Task.Factory.StartNew(() => - { - try - { - HandleAccept = Sockets[SocketId].Handle.Accept(); - } - catch (SocketException Ex) - { - Context.ResponseData.Write(-1); - Context.ResponseData.Write(Ex.ErrorCode - 10000); - } - }); - - TimeOut.Wait(10000); - - if (HandleAccept != null) - { - BsdSocket NewBsdSocket = new BsdSocket - { - IpAddress = ((IPEndPoint)Sockets[SocketId].Handle.LocalEndPoint).Address, - RemoteEP = ((IPEndPoint)Sockets[SocketId].Handle.LocalEndPoint), - Handle = HandleAccept - }; - - Sockets.Add(NewBsdSocket); - - using (MemoryStream MS = new MemoryStream()) - { - BinaryWriter Writer = new BinaryWriter(MS); - - Writer.Write((byte)0); - - Writer.Write((byte)NewBsdSocket.Handle.AddressFamily); - - Writer.Write((short)((IPEndPoint)NewBsdSocket.Handle.LocalEndPoint).Port); - - byte[] IpAddress = NewBsdSocket.IpAddress.GetAddressBytes(); - - Writer.Write(IpAddress); - - Context.Memory.WriteBytes(AddrBufferPtr, MS.ToArray()); - - Context.ResponseData.Write(Sockets.Count - 1); - Context.ResponseData.Write(0); - Context.ResponseData.Write(MS.Length); - } - } - else - { - Context.ResponseData.Write(-1); - Context.ResponseData.Write((int)BsdError.Timeout); - } - - return 0; - } - - //(u32 socket, buffer) -> (i32 ret, u32 bsd_errno) - public long Bind(ServiceCtx Context) - { - int SocketId = Context.RequestData.ReadInt32(); - - byte[] AddressBuffer = Context.Memory.ReadBytes(Context.Request.SendBuff[0].Position, - Context.Request.SendBuff[0].Size); - - try - { - ParseAddrBuffer(SocketId, AddressBuffer); - - Context.ResponseData.Write(0); - Context.ResponseData.Write(0); - } - catch (SocketException Ex) - { - Context.ResponseData.Write(-1); - Context.ResponseData.Write(Ex.ErrorCode - 10000); - } - - return 0; - } - - //(u32 socket, buffer) -> (i32 ret, u32 bsd_errno) - public long Connect(ServiceCtx Context) - { - int SocketId = Context.RequestData.ReadInt32(); - - byte[] AddressBuffer = Context.Memory.ReadBytes(Context.Request.SendBuff[0].Position, - Context.Request.SendBuff[0].Size); - - try - { - ParseAddrBuffer(SocketId, AddressBuffer); - - Sockets[SocketId].Handle.Connect(Sockets[SocketId].RemoteEP); - - Context.ResponseData.Write(0); - Context.ResponseData.Write(0); - } - catch (SocketException Ex) - { - Context.ResponseData.Write(-1); - Context.ResponseData.Write(Ex.ErrorCode - 10000); - } - - return 0; - } - - //(u32 socket, u32 backlog) -> (i32 ret, u32 bsd_errno) - public long Listen(ServiceCtx Context) - { - int SocketId = Context.RequestData.ReadInt32(); - int BackLog = Context.RequestData.ReadInt32(); - - try - { - Sockets[SocketId].Handle.Bind(Sockets[SocketId].RemoteEP); - Sockets[SocketId].Handle.Listen(BackLog); - - Context.ResponseData.Write(0); - Context.ResponseData.Write(0); - } - catch (SocketException Ex) - { - Context.ResponseData.Write(-1); - Context.ResponseData.Write(Ex.ErrorCode - 10000); - } - - return 0; - } - - //(u32 socket, u32 level, u32 option_name, buffer) -> (i32 ret, u32 bsd_errno) - public long SetSockOpt(ServiceCtx Context) - { - int SocketId = Context.RequestData.ReadInt32(); - - SocketOptionLevel SocketLevel = (SocketOptionLevel)Context.RequestData.ReadInt32(); - SocketOptionName SocketOptionName = (SocketOptionName)Context.RequestData.ReadInt32(); - - byte[] SocketOptionValue = Context.Memory.ReadBytes(Context.Request.PtrBuff[0].Position, - Context.Request.PtrBuff[0].Size); - - int OptionValue = Get32(SocketOptionValue, 0); - - try - { - Sockets[SocketId].Handle.SetSocketOption(SocketLevel, SocketOptionName, OptionValue); - - Context.ResponseData.Write(0); - Context.ResponseData.Write(0); - } - catch (SocketException Ex) - { - Context.ResponseData.Write(-1); - Context.ResponseData.Write(Ex.ErrorCode - 10000); - } - - return 0; - } - - //(u32 socket) -> (i32 ret, u32 bsd_errno) - public long Close(ServiceCtx Context) - { - int SocketId = Context.RequestData.ReadInt32(); - - try - { - Sockets[SocketId].Handle.Close(); - Sockets[SocketId] = null; - - Context.ResponseData.Write(0); - Context.ResponseData.Write(0); - } - catch (SocketException Ex) - { - Context.ResponseData.Write(-1); - Context.ResponseData.Write(Ex.ErrorCode - 10000); - } - - return 0; - } - - public void ParseAddrBuffer(int SocketId, byte[] AddrBuffer) - { - using (MemoryStream MS = new MemoryStream(AddrBuffer)) - { - BinaryReader Reader = new BinaryReader(MS); - - int Size = Reader.ReadByte(); - int Family = Reader.ReadByte(); - int Port = EndianSwap.Swap16(Reader.ReadInt16()); - - string IpAddress = Reader.ReadByte().ToString() + "." + - Reader.ReadByte().ToString() + "." + - Reader.ReadByte().ToString() + "." + - Reader.ReadByte().ToString(); - - Sockets[SocketId].IpAddress = IPAddress.Parse(IpAddress); - - Sockets[SocketId].RemoteEP = new IPEndPoint(Sockets[SocketId].IpAddress, Port); - } - } - - private int Get16(byte[] Data, int Address) - { - return - Data[Address + 0] << 0 | - Data[Address + 1] << 8; - } - - private int Get32(byte[] Data, int Address) - { - return - Data[Address + 0] << 0 | - Data[Address + 1] << 8 | - Data[Address + 2] << 16 | - Data[Address + 3] << 24; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Caps/IAlbumAccessorService.cs b/Ryujinx.HLE/OsHle/Services/Caps/IAlbumAccessorService.cs deleted file mode 100644 index 04a81f903a..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Caps/IAlbumAccessorService.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Ryujinx.HLE.OsHle.Ipc; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Services.Caps -{ - class IAlbumAccessorService : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - public IAlbumAccessorService() - { - m_Commands = new Dictionary() - { - //... - }; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Caps/IScreenshotService.cs b/Ryujinx.HLE/OsHle/Services/Caps/IScreenshotService.cs deleted file mode 100644 index 9b1005edc2..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Caps/IScreenshotService.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Ryujinx.HLE.OsHle.Ipc; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Services.Caps -{ - class IScreenshotService : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - public IScreenshotService() - { - m_Commands = new Dictionary() - { - //... - }; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Friend/IFriendService.cs b/Ryujinx.HLE/OsHle/Services/Friend/IFriendService.cs deleted file mode 100644 index d5843ffbd2..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Friend/IFriendService.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Ryujinx.HLE.OsHle.Ipc; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Services.Friend -{ - class IFriendService : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - public IFriendService() - { - m_Commands = new Dictionary() - { - //... - }; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Friend/IServiceCreator.cs b/Ryujinx.HLE/OsHle/Services/Friend/IServiceCreator.cs deleted file mode 100644 index 6b9a265f6c..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Friend/IServiceCreator.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Ryujinx.HLE.OsHle.Ipc; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Services.Friend -{ - class IServiceCreator : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - public IServiceCreator() - { - m_Commands = new Dictionary() - { - { 0, CreateFriendService } - }; - } - - public static long CreateFriendService(ServiceCtx Context) - { - MakeObject(Context, new IFriendService()); - - return 0; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/FspSrv/FsErr.cs b/Ryujinx.HLE/OsHle/Services/FspSrv/FsErr.cs deleted file mode 100644 index bdc70959fa..0000000000 --- a/Ryujinx.HLE/OsHle/Services/FspSrv/FsErr.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Ryujinx.HLE.OsHle.Services.FspSrv -{ - static class FsErr - { - public const int PathDoesNotExist = 1; - public const int PathAlreadyExists = 2; - public const int PathAlreadyInUse = 7; - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/FspSrv/IDirectory.cs b/Ryujinx.HLE/OsHle/Services/FspSrv/IDirectory.cs deleted file mode 100644 index bb4b7a030b..0000000000 --- a/Ryujinx.HLE/OsHle/Services/FspSrv/IDirectory.cs +++ /dev/null @@ -1,116 +0,0 @@ -using Ryujinx.HLE.OsHle.Ipc; -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; - -namespace Ryujinx.HLE.OsHle.Services.FspSrv -{ - class IDirectory : IpcService, IDisposable - { - private const int DirectoryEntrySize = 0x310; - - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - private List DirectoryEntries; - - private int CurrentItemIndex; - - public event EventHandler Disposed; - - public string HostPath { get; private set; } - - public IDirectory(string HostPath, int Flags) - { - m_Commands = new Dictionary() - { - { 0, Read }, - { 1, GetEntryCount } - }; - - this.HostPath = HostPath; - - DirectoryEntries = new List(); - - if ((Flags & 1) != 0) - { - DirectoryEntries.AddRange(Directory.GetDirectories(HostPath)); - } - - if ((Flags & 2) != 0) - { - DirectoryEntries.AddRange(Directory.GetFiles(HostPath)); - } - - CurrentItemIndex = 0; - } - - public long Read(ServiceCtx Context) - { - long BufferPosition = Context.Request.ReceiveBuff[0].Position; - long BufferLen = Context.Request.ReceiveBuff[0].Size; - - int MaxReadCount = (int)(BufferLen / DirectoryEntrySize); - - int Count = Math.Min(DirectoryEntries.Count - CurrentItemIndex, MaxReadCount); - - for (int Index = 0; Index < Count; Index++) - { - long Position = BufferPosition + Index * DirectoryEntrySize; - - WriteDirectoryEntry(Context, Position, DirectoryEntries[CurrentItemIndex++]); - } - - Context.ResponseData.Write((long)Count); - - return 0; - } - - private void WriteDirectoryEntry(ServiceCtx Context, long Position, string FullPath) - { - for (int Offset = 0; Offset < 0x300; Offset += 8) - { - Context.Memory.WriteInt64(Position + Offset, 0); - } - - byte[] NameBuffer = Encoding.UTF8.GetBytes(Path.GetFileName(FullPath)); - - Context.Memory.WriteBytes(Position, NameBuffer); - - int Type = 0; - long Size = 0; - - if (File.Exists(FullPath)) - { - Type = 1; - Size = new FileInfo(FullPath).Length; - } - - Context.Memory.WriteInt32(Position + 0x300, 0); //Padding? - Context.Memory.WriteInt32(Position + 0x304, Type); - Context.Memory.WriteInt64(Position + 0x308, Size); - } - - public long GetEntryCount(ServiceCtx Context) - { - Context.ResponseData.Write((long)DirectoryEntries.Count); - - return 0; - } - - public void Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - Disposed?.Invoke(this, EventArgs.Empty); - } - } - } -} diff --git a/Ryujinx.HLE/OsHle/Services/FspSrv/IFile.cs b/Ryujinx.HLE/OsHle/Services/FspSrv/IFile.cs deleted file mode 100644 index a610a3ab88..0000000000 --- a/Ryujinx.HLE/OsHle/Services/FspSrv/IFile.cs +++ /dev/null @@ -1,110 +0,0 @@ -using Ryujinx.HLE.OsHle.Ipc; -using System; -using System.Collections.Generic; -using System.IO; - -namespace Ryujinx.HLE.OsHle.Services.FspSrv -{ - class IFile : IpcService, IDisposable - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - private Stream BaseStream; - - public event EventHandler Disposed; - - public string HostPath { get; private set; } - - public IFile(Stream BaseStream, string HostPath) - { - m_Commands = new Dictionary() - { - { 0, Read }, - { 1, Write }, - { 2, Flush }, - { 3, SetSize }, - { 4, GetSize } - }; - - this.BaseStream = BaseStream; - this.HostPath = HostPath; - } - - public long Read(ServiceCtx Context) - { - long Position = Context.Request.ReceiveBuff[0].Position; - - long Zero = Context.RequestData.ReadInt64(); - long Offset = Context.RequestData.ReadInt64(); - long Size = Context.RequestData.ReadInt64(); - - byte[] Data = new byte[Size]; - - BaseStream.Seek(Offset, SeekOrigin.Begin); - - int ReadSize = BaseStream.Read(Data, 0, (int)Size); - - Context.Memory.WriteBytes(Position, Data); - - Context.ResponseData.Write((long)ReadSize); - - return 0; - } - - public long Write(ServiceCtx Context) - { - long Position = Context.Request.SendBuff[0].Position; - - long Zero = Context.RequestData.ReadInt64(); - long Offset = Context.RequestData.ReadInt64(); - long Size = Context.RequestData.ReadInt64(); - - byte[] Data = Context.Memory.ReadBytes(Position, Size); - - BaseStream.Seek(Offset, SeekOrigin.Begin); - BaseStream.Write(Data, 0, (int)Size); - - return 0; - } - - public long Flush(ServiceCtx Context) - { - BaseStream.Flush(); - - return 0; - } - - public long SetSize(ServiceCtx Context) - { - long Size = Context.RequestData.ReadInt64(); - - BaseStream.SetLength(Size); - - return 0; - } - - public long GetSize(ServiceCtx Context) - { - Context.ResponseData.Write(BaseStream.Length); - - return 0; - } - - public void Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool disposing) - { - if (disposing && BaseStream != null) - { - BaseStream.Dispose(); - - Disposed?.Invoke(this, EventArgs.Empty); - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/FspSrv/IFileSystem.cs b/Ryujinx.HLE/OsHle/Services/FspSrv/IFileSystem.cs deleted file mode 100644 index 441b7e8add..0000000000 --- a/Ryujinx.HLE/OsHle/Services/FspSrv/IFileSystem.cs +++ /dev/null @@ -1,399 +0,0 @@ -using Ryujinx.HLE.OsHle.Ipc; -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; - -using static Ryujinx.HLE.OsHle.ErrorCode; - -namespace Ryujinx.HLE.OsHle.Services.FspSrv -{ - class IFileSystem : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - private HashSet OpenPaths; - - private string Path; - - public IFileSystem(string Path) - { - m_Commands = new Dictionary() - { - { 0, CreateFile }, - { 1, DeleteFile }, - { 2, CreateDirectory }, - { 3, DeleteDirectory }, - { 4, DeleteDirectoryRecursively }, - { 5, RenameFile }, - { 6, RenameDirectory }, - { 7, GetEntryType }, - { 8, OpenFile }, - { 9, OpenDirectory }, - { 10, Commit }, - { 11, GetFreeSpaceSize }, - { 12, GetTotalSpaceSize }, - //{ 13, CleanDirectoryRecursively }, - //{ 14, GetFileTimeStampRaw } - }; - - OpenPaths = new HashSet(); - - this.Path = Path; - } - - public long CreateFile(ServiceCtx Context) - { - long Position = Context.Request.PtrBuff[0].Position; - - string Name = ReadUtf8String(Context); - - long Mode = Context.RequestData.ReadInt64(); - int Size = Context.RequestData.ReadInt32(); - - string FileName = Context.Ns.VFs.GetFullPath(Path, Name); - - if (FileName == null) - { - return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist); - } - - if (File.Exists(FileName)) - { - return MakeError(ErrorModule.Fs, FsErr.PathAlreadyExists); - } - - if (IsPathAlreadyInUse(FileName)) - { - return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse); - } - - using (FileStream NewFile = File.Create(FileName)) - { - NewFile.SetLength(Size); - } - - return 0; - } - - public long DeleteFile(ServiceCtx Context) - { - long Position = Context.Request.PtrBuff[0].Position; - - string Name = ReadUtf8String(Context); - - string FileName = Context.Ns.VFs.GetFullPath(Path, Name); - - if (!File.Exists(FileName)) - { - return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist); - } - - if (IsPathAlreadyInUse(FileName)) - { - return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse); - } - - File.Delete(FileName); - - return 0; - } - - public long CreateDirectory(ServiceCtx Context) - { - long Position = Context.Request.PtrBuff[0].Position; - - string Name = ReadUtf8String(Context); - - string DirName = Context.Ns.VFs.GetFullPath(Path, Name); - - if (DirName == null) - { - return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist); - } - - if (Directory.Exists(DirName)) - { - return MakeError(ErrorModule.Fs, FsErr.PathAlreadyExists); - } - - if (IsPathAlreadyInUse(DirName)) - { - return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse); - } - - Directory.CreateDirectory(DirName); - - return 0; - } - - public long DeleteDirectory(ServiceCtx Context) - { - return DeleteDirectory(Context, false); - } - - public long DeleteDirectoryRecursively(ServiceCtx Context) - { - return DeleteDirectory(Context, true); - } - - private long DeleteDirectory(ServiceCtx Context, bool Recursive) - { - long Position = Context.Request.PtrBuff[0].Position; - - string Name = ReadUtf8String(Context); - - string DirName = Context.Ns.VFs.GetFullPath(Path, Name); - - if (!Directory.Exists(DirName)) - { - return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist); - } - - if (IsPathAlreadyInUse(DirName)) - { - return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse); - } - - Directory.Delete(DirName, Recursive); - - return 0; - } - - public long RenameFile(ServiceCtx Context) - { - string OldName = ReadUtf8String(Context, 0); - string NewName = ReadUtf8String(Context, 1); - - string OldFileName = Context.Ns.VFs.GetFullPath(Path, OldName); - string NewFileName = Context.Ns.VFs.GetFullPath(Path, NewName); - - if (!File.Exists(OldFileName)) - { - return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist); - } - - if (File.Exists(NewFileName)) - { - return MakeError(ErrorModule.Fs, FsErr.PathAlreadyExists); - } - - if (IsPathAlreadyInUse(OldFileName)) - { - return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse); - } - - File.Move(OldFileName, NewFileName); - - return 0; - } - - public long RenameDirectory(ServiceCtx Context) - { - string OldName = ReadUtf8String(Context, 0); - string NewName = ReadUtf8String(Context, 1); - - string OldDirName = Context.Ns.VFs.GetFullPath(Path, OldName); - string NewDirName = Context.Ns.VFs.GetFullPath(Path, NewName); - - if (!Directory.Exists(OldDirName)) - { - return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist); - } - - if (Directory.Exists(NewDirName)) - { - return MakeError(ErrorModule.Fs, FsErr.PathAlreadyExists); - } - - if (IsPathAlreadyInUse(OldDirName)) - { - return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse); - } - - Directory.Move(OldDirName, NewDirName); - - return 0; - } - - public long GetEntryType(ServiceCtx Context) - { - long Position = Context.Request.PtrBuff[0].Position; - - string Name = ReadUtf8String(Context); - - string FileName = Context.Ns.VFs.GetFullPath(Path, Name); - - if (File.Exists(FileName)) - { - Context.ResponseData.Write(1); - } - else if (Directory.Exists(FileName)) - { - Context.ResponseData.Write(0); - } - else - { - Context.ResponseData.Write(0); - - return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist); - } - - return 0; - } - - public long OpenFile(ServiceCtx Context) - { - long Position = Context.Request.PtrBuff[0].Position; - - int FilterFlags = Context.RequestData.ReadInt32(); - - string Name = ReadUtf8String(Context); - - string FileName = Context.Ns.VFs.GetFullPath(Path, Name); - - if (!File.Exists(FileName)) - { - return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist); - } - - if (IsPathAlreadyInUse(FileName)) - { - return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse); - } - - FileStream Stream = new FileStream(FileName, FileMode.Open); - - IFile FileInterface = new IFile(Stream, FileName); - - FileInterface.Disposed += RemoveFileInUse; - - lock (OpenPaths) - { - OpenPaths.Add(FileName); - } - - MakeObject(Context, FileInterface); - - return 0; - } - - public long OpenDirectory(ServiceCtx Context) - { - long Position = Context.Request.PtrBuff[0].Position; - - int FilterFlags = Context.RequestData.ReadInt32(); - - string Name = ReadUtf8String(Context); - - string DirName = Context.Ns.VFs.GetFullPath(Path, Name); - - if (!Directory.Exists(DirName)) - { - return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist); - } - - if (IsPathAlreadyInUse(DirName)) - { - return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse); - } - - IDirectory DirInterface = new IDirectory(DirName, FilterFlags); - - DirInterface.Disposed += RemoveDirectoryInUse; - - lock (OpenPaths) - { - OpenPaths.Add(DirName); - } - - MakeObject(Context, DirInterface); - - return 0; - } - - public long Commit(ServiceCtx Context) - { - return 0; - } - - public long GetFreeSpaceSize(ServiceCtx Context) - { - long Position = Context.Request.PtrBuff[0].Position; - - string Name = ReadUtf8String(Context); - - Context.ResponseData.Write(Context.Ns.VFs.GetDrive().AvailableFreeSpace); - - return 0; - } - - public long GetTotalSpaceSize(ServiceCtx Context) - { - long Position = Context.Request.PtrBuff[0].Position; - - string Name = ReadUtf8String(Context); - - Context.ResponseData.Write(Context.Ns.VFs.GetDrive().TotalSize); - - return 0; - } - - private bool IsPathAlreadyInUse(string Path) - { - lock (OpenPaths) - { - return OpenPaths.Contains(Path); - } - } - - private void RemoveFileInUse(object sender, EventArgs e) - { - IFile FileInterface = (IFile)sender; - - lock (OpenPaths) - { - FileInterface.Disposed -= RemoveFileInUse; - - OpenPaths.Remove(FileInterface.HostPath); - } - } - - private void RemoveDirectoryInUse(object sender, EventArgs e) - { - IDirectory DirInterface = (IDirectory)sender; - - lock (OpenPaths) - { - DirInterface.Disposed -= RemoveDirectoryInUse; - - OpenPaths.Remove(DirInterface.HostPath); - } - } - - private string ReadUtf8String(ServiceCtx Context, int Index = 0) - { - long Position = Context.Request.PtrBuff[Index].Position; - long Size = Context.Request.PtrBuff[Index].Size; - - using (MemoryStream MS = new MemoryStream()) - { - while (Size-- > 0) - { - byte Value = Context.Memory.ReadByte(Position++); - - if (Value == 0) - { - break; - } - - MS.WriteByte(Value); - } - - return Encoding.UTF8.GetString(MS.ToArray()); - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/FspSrv/IFileSystemProxy.cs b/Ryujinx.HLE/OsHle/Services/FspSrv/IFileSystemProxy.cs deleted file mode 100644 index 84a0bc3de0..0000000000 --- a/Ryujinx.HLE/OsHle/Services/FspSrv/IFileSystemProxy.cs +++ /dev/null @@ -1,74 +0,0 @@ -using Ryujinx.HLE.Logging; -using Ryujinx.HLE.OsHle.Ipc; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Services.FspSrv -{ - class IFileSystemProxy : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - public IFileSystemProxy() - { - m_Commands = new Dictionary() - { - { 1, SetCurrentProcess }, - { 18, OpenSdCardFileSystem }, - { 22, CreateSaveDataFileSystem }, - { 51, OpenSaveDataFileSystem }, - { 200, OpenDataStorageByCurrentProcess }, - { 203, OpenPatchDataStorageByCurrentProcess }, - { 1005, GetGlobalAccessLogMode } - }; - } - - public long SetCurrentProcess(ServiceCtx Context) - { - return 0; - } - - public long OpenSdCardFileSystem(ServiceCtx Context) - { - MakeObject(Context, new IFileSystem(Context.Ns.VFs.GetSdCardPath())); - - return 0; - } - - public long CreateSaveDataFileSystem(ServiceCtx Context) - { - Context.Ns.Log.PrintStub(LogClass.ServiceFs, "Stubbed."); - - return 0; - } - - public long OpenSaveDataFileSystem(ServiceCtx Context) - { - MakeObject(Context, new IFileSystem(Context.Ns.VFs.GetGameSavesPath())); - - return 0; - } - - public long OpenDataStorageByCurrentProcess(ServiceCtx Context) - { - MakeObject(Context, new IStorage(Context.Ns.VFs.RomFs)); - - return 0; - } - - public long OpenPatchDataStorageByCurrentProcess(ServiceCtx Context) - { - MakeObject(Context, new IStorage(Context.Ns.VFs.RomFs)); - - return 0; - } - - public long GetGlobalAccessLogMode(ServiceCtx Context) - { - Context.ResponseData.Write(0); - - return 0; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/FspSrv/IStorage.cs b/Ryujinx.HLE/OsHle/Services/FspSrv/IStorage.cs deleted file mode 100644 index 56c27d03db..0000000000 --- a/Ryujinx.HLE/OsHle/Services/FspSrv/IStorage.cs +++ /dev/null @@ -1,51 +0,0 @@ -using Ryujinx.HLE.OsHle.Ipc; -using System.Collections.Generic; -using System.IO; - -namespace Ryujinx.HLE.OsHle.Services.FspSrv -{ - class IStorage : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - private Stream BaseStream; - - public IStorage(Stream BaseStream) - { - m_Commands = new Dictionary() - { - { 0, Read } - }; - - this.BaseStream = BaseStream; - } - - public long Read(ServiceCtx Context) - { - long Offset = Context.RequestData.ReadInt64(); - long Size = Context.RequestData.ReadInt64(); - - if (Context.Request.ReceiveBuff.Count > 0) - { - IpcBuffDesc BuffDesc = Context.Request.ReceiveBuff[0]; - - //Use smaller length to avoid overflows. - if (Size > BuffDesc.Size) - { - Size = BuffDesc.Size; - } - - byte[] Data = new byte[Size]; - - BaseStream.Seek(Offset, SeekOrigin.Begin); - BaseStream.Read(Data, 0, Data.Length); - - Context.Memory.WriteBytes(BuffDesc.Position, Data); - } - - return 0; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Hid/IActiveVibrationDeviceList.cs b/Ryujinx.HLE/OsHle/Services/Hid/IActiveVibrationDeviceList.cs deleted file mode 100644 index 12eaf7063b..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Hid/IActiveVibrationDeviceList.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Ryujinx.HLE.OsHle.Ipc; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Services.Hid -{ - class IActiveApplicationDeviceList : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - public IActiveApplicationDeviceList() - { - m_Commands = new Dictionary() - { - { 0, ActivateVibrationDevice } - }; - } - - public long ActivateVibrationDevice(ServiceCtx Context) - { - int VibrationDeviceHandle = Context.RequestData.ReadInt32(); - - return 0; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Hid/IAppletResource.cs b/Ryujinx.HLE/OsHle/Services/Hid/IAppletResource.cs deleted file mode 100644 index 2ef67cc3a7..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Hid/IAppletResource.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Ryujinx.HLE.OsHle.Handles; -using Ryujinx.HLE.OsHle.Ipc; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Services.Hid -{ - class IAppletResource : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - private HSharedMem HidSharedMem; - - public IAppletResource(HSharedMem HidSharedMem) - { - m_Commands = new Dictionary() - { - { 0, GetSharedMemoryHandle } - }; - - this.HidSharedMem = HidSharedMem; - } - - public long GetSharedMemoryHandle(ServiceCtx Context) - { - int Handle = Context.Process.HandleTable.OpenHandle(HidSharedMem); - - Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); - - return 0; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Hid/IHidServer.cs b/Ryujinx.HLE/OsHle/Services/Hid/IHidServer.cs deleted file mode 100644 index 79d37fd4c9..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Hid/IHidServer.cs +++ /dev/null @@ -1,270 +0,0 @@ -using Ryujinx.HLE.Input; -using Ryujinx.HLE.Logging; -using Ryujinx.HLE.OsHle.Ipc; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Services.Hid -{ - class IHidServer : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - public IHidServer() - { - m_Commands = new Dictionary() - { - { 0, CreateAppletResource }, - { 1, ActivateDebugPad }, - { 11, ActivateTouchScreen }, - { 21, ActivateMouse }, - { 31, ActivateKeyboard }, - { 66, StartSixAxisSensor }, - { 79, SetGyroscopeZeroDriftMode }, - { 100, SetSupportedNpadStyleSet }, - { 101, GetSupportedNpadStyleSet }, - { 102, SetSupportedNpadIdType }, - { 103, ActivateNpad }, - { 108, GetPlayerLedPattern }, - { 120, SetNpadJoyHoldType }, - { 121, GetNpadJoyHoldType }, - { 122, SetNpadJoyAssignmentModeSingleByDefault }, - { 123, SetNpadJoyAssignmentModeSingle }, - { 124, SetNpadJoyAssignmentModeDual }, - { 125, MergeSingleJoyAsDualJoy }, - { 128, SetNpadHandheldActivationMode }, - { 200, GetVibrationDeviceInfo }, - { 201, SendVibrationValue }, - { 203, CreateActiveVibrationDeviceList }, - { 206, SendVibrationValues } - }; - } - - public long CreateAppletResource(ServiceCtx Context) - { - MakeObject(Context, new IAppletResource(Context.Ns.Os.HidSharedMem)); - - return 0; - } - - public long ActivateDebugPad(ServiceCtx Context) - { - Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); - - return 0; - } - - public long ActivateTouchScreen(ServiceCtx Context) - { - long AppletResourceUserId = Context.RequestData.ReadInt64(); - - Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); - - return 0; - } - - public long ActivateMouse(ServiceCtx Context) - { - long AppletResourceUserId = Context.RequestData.ReadInt64(); - - Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); - - return 0; - } - - public long ActivateKeyboard(ServiceCtx Context) - { - long AppletResourceUserId = Context.RequestData.ReadInt64(); - - Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); - - return 0; - } - - public long StartSixAxisSensor(ServiceCtx Context) - { - int Handle = Context.RequestData.ReadInt32(); - - long AppletResourceUserId = Context.RequestData.ReadInt64(); - - Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); - - return 0; - } - - public long SetGyroscopeZeroDriftMode(ServiceCtx Context) - { - int Handle = Context.RequestData.ReadInt32(); - int Unknown = Context.RequestData.ReadInt32(); - long AppletResourceUserId = Context.RequestData.ReadInt64(); - - Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); - - return 0; - } - - public long GetSupportedNpadStyleSet(ServiceCtx Context) - { - Context.ResponseData.Write(0); - - Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); - - return 0; - } - - public long SetSupportedNpadStyleSet(ServiceCtx Context) - { - long Unknown0 = Context.RequestData.ReadInt64(); - long Unknown8 = Context.RequestData.ReadInt64(); - - Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); - - return 0; - } - - public long SetSupportedNpadIdType(ServiceCtx Context) - { - long Unknown = Context.RequestData.ReadInt64(); - - Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); - - return 0; - } - - public long ActivateNpad(ServiceCtx Context) - { - long Unknown = Context.RequestData.ReadInt64(); - - Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); - - return 0; - } - - public long GetPlayerLedPattern(ServiceCtx Context) - { - long Unknown = Context.RequestData.ReadInt32(); - - Context.ResponseData.Write(0L); - - Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); - - return 0; - } - - public long SetNpadJoyHoldType(ServiceCtx Context) - { - long Unknown0 = Context.RequestData.ReadInt64(); - long Unknown8 = Context.RequestData.ReadInt64(); - - Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); - - return 0; - } - - public long GetNpadJoyHoldType(ServiceCtx Context) - { - Context.ResponseData.Write(0L); - - Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); - - return 0; - } - - public long SetNpadJoyAssignmentModeSingleByDefault(ServiceCtx Context) - { - HidControllerId HidControllerId = (HidControllerId)Context.RequestData.ReadInt32(); - - long AppletUserResourceId = Context.RequestData.ReadInt64(); - - Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); - - return 0; - } - - public long SetNpadJoyAssignmentModeSingle(ServiceCtx Context) - { - HidControllerId HidControllerId = (HidControllerId)Context.RequestData.ReadInt32(); - - long AppletUserResourceId = Context.RequestData.ReadInt64(); - long NpadJoyDeviceType = Context.RequestData.ReadInt64(); - - Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); - - return 0; - } - - public long SetNpadJoyAssignmentModeDual(ServiceCtx Context) - { - HidControllerId HidControllerId = (HidControllerId)Context.RequestData.ReadInt32(); - - long AppletUserResourceId = Context.RequestData.ReadInt64(); - - Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); - - return 0; - } - - public long MergeSingleJoyAsDualJoy(ServiceCtx Context) - { - long Unknown0 = Context.RequestData.ReadInt32(); - long Unknown8 = Context.RequestData.ReadInt32(); - long AppletUserResourceId = Context.RequestData.ReadInt64(); - - Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); - - return 0; - } - - public long SetNpadHandheldActivationMode(ServiceCtx Context) - { - long AppletUserResourceId = Context.RequestData.ReadInt64(); - long Unknown = Context.RequestData.ReadInt64(); - - Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); - - return 0; - } - - public long GetVibrationDeviceInfo(ServiceCtx Context) - { - int VibrationDeviceHandle = Context.RequestData.ReadInt32(); - - Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); - - Context.ResponseData.Write(0L); //VibrationDeviceInfoForIpc - - return 0; - } - - public long SendVibrationValue(ServiceCtx Context) - { - int VibrationDeviceHandle = Context.RequestData.ReadInt32(); - - int VibrationValue1 = Context.RequestData.ReadInt32(); - int VibrationValue2 = Context.RequestData.ReadInt32(); - int VibrationValue3 = Context.RequestData.ReadInt32(); - int VibrationValue4 = Context.RequestData.ReadInt32(); - - long AppletUserResourceId = Context.RequestData.ReadInt64(); - - Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); - - return 0; - } - - public long CreateActiveVibrationDeviceList(ServiceCtx Context) - { - MakeObject(Context, new IActiveApplicationDeviceList()); - - return 0; - } - - public long SendVibrationValues(ServiceCtx Context) - { - Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); - - return 0; - } - } -} diff --git a/Ryujinx.HLE/OsHle/Services/IIpcService.cs b/Ryujinx.HLE/OsHle/Services/IIpcService.cs deleted file mode 100644 index 6067538047..0000000000 --- a/Ryujinx.HLE/OsHle/Services/IIpcService.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Ryujinx.HLE.OsHle.Ipc; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Services -{ - interface IIpcService - { - IReadOnlyDictionary Commands { get; } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/IpcService.cs b/Ryujinx.HLE/OsHle/Services/IpcService.cs deleted file mode 100644 index 25fd56fec0..0000000000 --- a/Ryujinx.HLE/OsHle/Services/IpcService.cs +++ /dev/null @@ -1,154 +0,0 @@ -using Ryujinx.HLE.Logging; -using Ryujinx.HLE.OsHle.Handles; -using Ryujinx.HLE.OsHle.Ipc; -using System; -using System.Collections.Generic; -using System.IO; - -namespace Ryujinx.HLE.OsHle.Services -{ - abstract class IpcService : IIpcService - { - public abstract IReadOnlyDictionary Commands { get; } - - private IdDictionary DomainObjects; - - private int SelfId; - - private bool IsDomain; - - public IpcService() - { - DomainObjects = new IdDictionary(); - - SelfId = -1; - } - - public int ConvertToDomain() - { - if (SelfId == -1) - { - SelfId = DomainObjects.Add(this); - } - - IsDomain = true; - - return SelfId; - } - - public void ConvertToSession() - { - IsDomain = false; - } - - public void CallMethod(ServiceCtx Context) - { - IIpcService Service = this; - - if (IsDomain) - { - int DomainWord0 = Context.RequestData.ReadInt32(); - int DomainObjId = Context.RequestData.ReadInt32(); - - long Padding = Context.RequestData.ReadInt64(); - - int DomainCmd = DomainWord0 & 0xff; - - if (DomainCmd == 1) - { - Service = GetObject(DomainObjId); - - Context.ResponseData.Write(0L); - Context.ResponseData.Write(0L); - } - else if (DomainCmd == 2) - { - Delete(DomainObjId); - - Context.ResponseData.Write(0L); - - return; - } - else - { - throw new NotImplementedException($"Domain command: {DomainCmd}"); - } - } - - long SfciMagic = Context.RequestData.ReadInt64(); - int CommandId = (int)Context.RequestData.ReadInt64(); - - if (Service.Commands.TryGetValue(CommandId, out ServiceProcessRequest ProcessRequest)) - { - Context.ResponseData.BaseStream.Seek(IsDomain ? 0x20 : 0x10, SeekOrigin.Begin); - - Context.Ns.Log.PrintDebug(LogClass.KernelIpc, $"{Service.GetType().Name}: {ProcessRequest.Method.Name}"); - - long Result = ProcessRequest(Context); - - if (IsDomain) - { - foreach (int Id in Context.Response.ResponseObjIds) - { - Context.ResponseData.Write(Id); - } - - Context.ResponseData.BaseStream.Seek(0, SeekOrigin.Begin); - - Context.ResponseData.Write(Context.Response.ResponseObjIds.Count); - } - - Context.ResponseData.BaseStream.Seek(IsDomain ? 0x10 : 0, SeekOrigin.Begin); - - Context.ResponseData.Write(IpcMagic.Sfco); - Context.ResponseData.Write(Result); - } - else - { - string DbgMessage = $"{Context.Session.ServiceName} {Service.GetType().Name}: {CommandId}"; - - throw new NotImplementedException(DbgMessage); - } - } - - protected static void MakeObject(ServiceCtx Context, IpcService Obj) - { - IpcService Service = Context.Session.Service; - - if (Service.IsDomain) - { - Context.Response.ResponseObjIds.Add(Service.Add(Obj)); - } - else - { - KSession Session = new KSession(Obj, Context.Session.ServiceName); - - int Handle = Context.Process.HandleTable.OpenHandle(Session); - - Context.Response.HandleDesc = IpcHandleDesc.MakeMove(Handle); - } - } - - private int Add(IIpcService Obj) - { - return DomainObjects.Add(Obj); - } - - private bool Delete(int Id) - { - object Obj = DomainObjects.Delete(Id); - - if (Obj is IDisposable DisposableObj) - { - DisposableObj.Dispose(); - } - - return Obj != null; - } - - private IIpcService GetObject(int Id) - { - return DomainObjects.GetData(Id); - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Lm/ILogService.cs b/Ryujinx.HLE/OsHle/Services/Lm/ILogService.cs deleted file mode 100644 index c3aeb1844b..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Lm/ILogService.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Ryujinx.HLE.OsHle.Ipc; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Services.Lm -{ - class ILogService : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - public ILogService() - { - m_Commands = new Dictionary() - { - { 0, Initialize } - }; - } - - public long Initialize(ServiceCtx Context) - { - MakeObject(Context, new ILogger()); - - return 0; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Lm/ILogger.cs b/Ryujinx.HLE/OsHle/Services/Lm/ILogger.cs deleted file mode 100644 index 90edf2ad39..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Lm/ILogger.cs +++ /dev/null @@ -1,86 +0,0 @@ -using Ryujinx.HLE.Logging; -using Ryujinx.HLE.OsHle.Ipc; -using System.Collections.Generic; -using System.IO; -using System.Text; - -namespace Ryujinx.HLE.OsHle.Services.Lm -{ - class ILogger : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - public ILogger() - { - m_Commands = new Dictionary() - { - { 0, Log } - }; - } - - public long Log(ServiceCtx Context) - { - byte[] LogBuffer = Context.Memory.ReadBytes( - Context.Request.PtrBuff[0].Position, - Context.Request.PtrBuff[0].Size); - - using (MemoryStream MS = new MemoryStream(LogBuffer)) - { - BinaryReader Reader = new BinaryReader(MS); - - long Pid = Reader.ReadInt64(); - long ThreadContext = Reader.ReadInt64(); - short Flags = Reader.ReadInt16(); - byte Level = Reader.ReadByte(); - byte Verbosity = Reader.ReadByte(); - int PayloadLength = Reader.ReadInt32(); - - StringBuilder SB = new StringBuilder(); - - SB.AppendLine("Guest log:"); - - while (MS.Position < MS.Length) - { - byte Type = Reader.ReadByte(); - byte Size = Reader.ReadByte(); - - LmLogField Field = (LmLogField)Type; - - string FieldStr = string.Empty; - - if (Field == LmLogField.Skip) - { - Reader.ReadByte(); - - continue; - } - else if (Field == LmLogField.Line) - { - FieldStr = Field + ": " + Reader.ReadInt32(); - } - else - { - FieldStr = Field + ": \"" + Encoding.UTF8.GetString(Reader.ReadBytes(Size)) + "\""; - } - - SB.AppendLine(" " + FieldStr); - } - - string Text = SB.ToString(); - - switch((LmLogLevel)Level) - { - case LmLogLevel.Trace: Context.Ns.Log.PrintDebug (LogClass.ServiceLm, Text); break; - case LmLogLevel.Info: Context.Ns.Log.PrintInfo (LogClass.ServiceLm, Text); break; - case LmLogLevel.Warning: Context.Ns.Log.PrintWarning(LogClass.ServiceLm, Text); break; - case LmLogLevel.Error: Context.Ns.Log.PrintError (LogClass.ServiceLm, Text); break; - case LmLogLevel.Critical: Context.Ns.Log.PrintError (LogClass.ServiceLm, Text); break; - } - } - - return 0; - } - } -} diff --git a/Ryujinx.HLE/OsHle/Services/Lm/LmLogField.cs b/Ryujinx.HLE/OsHle/Services/Lm/LmLogField.cs deleted file mode 100644 index 33593103ba..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Lm/LmLogField.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Ryujinx.HLE.OsHle.Services.Lm -{ - enum LmLogField - { - Skip = 1, - Message = 2, - Line = 3, - Filename = 4, - Function = 5, - Module = 6, - Thread = 7 - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Mm/IRequest.cs b/Ryujinx.HLE/OsHle/Services/Mm/IRequest.cs deleted file mode 100644 index c60b7f5231..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Mm/IRequest.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Ryujinx.HLE.Logging; -using Ryujinx.HLE.OsHle.Ipc; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Services.Mm -{ - class IRequest : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - public IRequest() - { - m_Commands = new Dictionary() - { - { 4, Initialize }, - { 6, SetAndWait }, - { 7, Get } - }; - } - - public long Initialize(ServiceCtx Context) - { - Context.Ns.Log.PrintStub(LogClass.ServiceMm, "Stubbed."); - - return 0; - } - - public long SetAndWait(ServiceCtx Context) - { - Context.Ns.Log.PrintStub(LogClass.ServiceMm, "Stubbed."); - - return 0; - } - - public long Get(ServiceCtx Context) - { - Context.ResponseData.Write(0); - - Context.Ns.Log.PrintStub(LogClass.ServiceMm, "Stubbed."); - - return 0; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nfp/DeviceState.cs b/Ryujinx.HLE/OsHle/Services/Nfp/DeviceState.cs deleted file mode 100644 index 1863e0d920..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Nfp/DeviceState.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Ryujinx.HLE.OsHle.Services.Nfp -{ - enum DeviceState - { - Initialized = 0 - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nfp/IUser.cs b/Ryujinx.HLE/OsHle/Services/Nfp/IUser.cs deleted file mode 100644 index 4b423ba715..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Nfp/IUser.cs +++ /dev/null @@ -1,114 +0,0 @@ -using Ryujinx.HLE.Input; -using Ryujinx.HLE.Logging; -using Ryujinx.HLE.OsHle.Handles; -using Ryujinx.HLE.OsHle.Ipc; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Services.Nfp -{ - class IUser : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - private const HidControllerId NpadId = HidControllerId.CONTROLLER_PLAYER_1; - - private State State = State.NonInitialized; - - private DeviceState DeviceState = DeviceState.Initialized; - - private KEvent ActivateEvent; - - private KEvent DeactivateEvent; - - private KEvent AvailabilityChangeEvent; - - public IUser() - { - m_Commands = new Dictionary() - { - { 0, Initialize }, - { 17, AttachActivateEvent }, - { 18, AttachDeactivateEvent }, - { 19, GetState }, - { 20, GetDeviceState }, - { 21, GetNpadId }, - { 23, AttachAvailabilityChangeEvent } - }; - - ActivateEvent = new KEvent(); - DeactivateEvent = new KEvent(); - AvailabilityChangeEvent = new KEvent(); - } - - public long Initialize(ServiceCtx Context) - { - Context.Ns.Log.PrintStub(LogClass.ServiceNfp, "Stubbed."); - - State = State.Initialized; - - return 0; - } - - public long AttachActivateEvent(ServiceCtx Context) - { - Context.Ns.Log.PrintStub(LogClass.ServiceNfp, "Stubbed."); - - int Handle = Context.Process.HandleTable.OpenHandle(ActivateEvent); - - Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle);; - - return 0; - } - - public long AttachDeactivateEvent(ServiceCtx Context) - { - Context.Ns.Log.PrintStub(LogClass.ServiceNfp, "Stubbed."); - - int Handle = Context.Process.HandleTable.OpenHandle(DeactivateEvent); - - Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); - - return 0; - } - - public long GetState(ServiceCtx Context) - { - Context.ResponseData.Write((int)State); - - Context.Ns.Log.PrintStub(LogClass.ServiceNfp, "Stubbed."); - - return 0; - } - - public long GetDeviceState(ServiceCtx Context) - { - Context.ResponseData.Write((int)DeviceState); - - Context.Ns.Log.PrintStub(LogClass.ServiceNfp, "Stubbed."); - - return 0; - } - - public long GetNpadId(ServiceCtx Context) - { - Context.ResponseData.Write((int)NpadId); - - Context.Ns.Log.PrintStub(LogClass.ServiceNfp, "Stubbed."); - - return 0; - } - - public long AttachAvailabilityChangeEvent(ServiceCtx Context) - { - Context.Ns.Log.PrintStub(LogClass.ServiceNfp, "Stubbed."); - - int Handle = Context.Process.HandleTable.OpenHandle(AvailabilityChangeEvent); - - Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); - - return 0; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nfp/IUserManager.cs b/Ryujinx.HLE/OsHle/Services/Nfp/IUserManager.cs deleted file mode 100644 index 845ce7cf0a..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Nfp/IUserManager.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Ryujinx.HLE.OsHle.Ipc; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Services.Nfp -{ - class IUserManager : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - public IUserManager() - { - m_Commands = new Dictionary() - { - { 0, GetUserInterface } - }; - } - - public long GetUserInterface(ServiceCtx Context) - { - MakeObject(Context, new IUser()); - - return 0; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nfp/State.cs b/Ryujinx.HLE/OsHle/Services/Nfp/State.cs deleted file mode 100644 index c1f0bb1a49..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Nfp/State.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Ryujinx.HLE.OsHle.Services.Nfp -{ - enum State - { - NonInitialized = 0, - Initialized = 1 - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nifm/IGeneralService.cs b/Ryujinx.HLE/OsHle/Services/Nifm/IGeneralService.cs deleted file mode 100644 index e289a8db8f..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Nifm/IGeneralService.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Ryujinx.HLE.Logging; -using Ryujinx.HLE.OsHle.Ipc; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Services.Nifm -{ - class IGeneralService : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - public IGeneralService() - { - m_Commands = new Dictionary() - { - { 4, CreateRequest } - }; - } - - //CreateRequest(i32) - public long CreateRequest(ServiceCtx Context) - { - int Unknown = Context.RequestData.ReadInt32(); - - MakeObject(Context, new IRequest()); - - Context.Ns.Log.PrintStub(LogClass.ServiceNifm, "Stubbed."); - - return 0; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nifm/IRequest.cs b/Ryujinx.HLE/OsHle/Services/Nifm/IRequest.cs deleted file mode 100644 index c8c679c4c5..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Nifm/IRequest.cs +++ /dev/null @@ -1,93 +0,0 @@ -using Ryujinx.HLE.Logging; -using Ryujinx.HLE.OsHle.Handles; -using Ryujinx.HLE.OsHle.Ipc; -using System; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Services.Nifm -{ - class IRequest : IpcService, IDisposable - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - private KEvent Event; - - public IRequest() - { - m_Commands = new Dictionary() - { - { 0, GetRequestState }, - { 1, GetResult }, - { 2, GetSystemEventReadableHandles }, - { 3, Cancel }, - { 4, Submit }, - { 11, SetConnectionConfirmationOption } - }; - - Event = new KEvent(); - } - - public long GetRequestState(ServiceCtx Context) - { - Context.ResponseData.Write(0); - - Context.Ns.Log.PrintStub(LogClass.ServiceNifm, "Stubbed."); - - return 0; - } - - public long GetResult(ServiceCtx Context) - { - Context.Ns.Log.PrintStub(LogClass.ServiceNifm, "Stubbed."); - - return 0; - } - - //GetSystemEventReadableHandles() -> (KObject, KObject) - public long GetSystemEventReadableHandles(ServiceCtx Context) - { - //FIXME: Is this supposed to return 2 events? - int Handle = Context.Process.HandleTable.OpenHandle(Event); - - Context.Response.HandleDesc = IpcHandleDesc.MakeMove(Handle); - - return 0; - } - - public long Cancel(ServiceCtx Context) - { - Context.Ns.Log.PrintStub(LogClass.ServiceNifm, "Stubbed."); - - return 0; - } - - public long Submit(ServiceCtx Context) - { - Context.Ns.Log.PrintStub(LogClass.ServiceNifm, "Stubbed."); - - return 0; - } - - public long SetConnectionConfirmationOption(ServiceCtx Context) - { - Context.Ns.Log.PrintStub(LogClass.ServiceNifm, "Stubbed."); - - return 0; - } - - public void Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool Disposing) - { - if (Disposing) - { - Event.Dispose(); - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nifm/IStaticService.cs b/Ryujinx.HLE/OsHle/Services/Nifm/IStaticService.cs deleted file mode 100644 index c6d773f58e..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Nifm/IStaticService.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Ryujinx.HLE.OsHle.Ipc; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Services.Nifm -{ - class IStaticService : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - public IStaticService() - { - m_Commands = new Dictionary() - { - { 4, CreateGeneralServiceOld }, - { 5, CreateGeneralService } - }; - } - - public long CreateGeneralServiceOld(ServiceCtx Context) - { - MakeObject(Context, new IGeneralService()); - - return 0; - } - - public long CreateGeneralService(ServiceCtx Context) - { - MakeObject(Context, new IGeneralService()); - - return 0; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Ns/IAddOnContentManager.cs b/Ryujinx.HLE/OsHle/Services/Ns/IAddOnContentManager.cs deleted file mode 100644 index f3e7146e5e..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Ns/IAddOnContentManager.cs +++ /dev/null @@ -1,42 +0,0 @@ -using Ryujinx.HLE.Logging; -using Ryujinx.HLE.OsHle.Ipc; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Services.Ns -{ - class IAddOnContentManager : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - public IAddOnContentManager() - { - m_Commands = new Dictionary() - { - { 2, CountAddOnContent }, - { 3, ListAddOnContent } - }; - } - - public static long CountAddOnContent(ServiceCtx Context) - { - Context.ResponseData.Write(0); - - Context.Ns.Log.PrintStub(LogClass.ServiceNs, "Stubbed."); - - return 0; - } - - public static long ListAddOnContent(ServiceCtx Context) - { - Context.Ns.Log.PrintStub(LogClass.ServiceNs, "Stubbed."); - - //TODO: This is supposed to write a u32 array aswell. - //It's unknown what it contains. - Context.ResponseData.Write(0); - - return 0; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Ns/IServiceGetterInterface.cs b/Ryujinx.HLE/OsHle/Services/Ns/IServiceGetterInterface.cs deleted file mode 100644 index 3650f8a42a..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Ns/IServiceGetterInterface.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Ryujinx.HLE.OsHle.Ipc; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Services.Ns -{ - class IServiceGetterInterface : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - public IServiceGetterInterface() - { - m_Commands = new Dictionary() - { - //... - }; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Ns/ISystemUpdateInterface.cs b/Ryujinx.HLE/OsHle/Services/Ns/ISystemUpdateInterface.cs deleted file mode 100644 index adb6add9c4..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Ns/ISystemUpdateInterface.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Ryujinx.HLE.OsHle.Ipc; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Services.Ns -{ - class ISystemUpdateInterface : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - public ISystemUpdateInterface() - { - m_Commands = new Dictionary() - { - //... - }; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Ns/IVulnerabilityManagerInterface.cs b/Ryujinx.HLE/OsHle/Services/Ns/IVulnerabilityManagerInterface.cs deleted file mode 100644 index 6a2c3d3b5f..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Ns/IVulnerabilityManagerInterface.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Ryujinx.HLE.OsHle.Ipc; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Services.Ns -{ - class IVulnerabilityManagerInterface : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - public IVulnerabilityManagerInterface() - { - m_Commands = new Dictionary() - { - //... - }; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nv/INvDrvServices.cs b/Ryujinx.HLE/OsHle/Services/Nv/INvDrvServices.cs deleted file mode 100644 index 5c1748bdb4..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Nv/INvDrvServices.cs +++ /dev/null @@ -1,228 +0,0 @@ -using ChocolArm64.Memory; -using Ryujinx.HLE.Logging; -using Ryujinx.HLE.OsHle.Handles; -using Ryujinx.HLE.OsHle.Ipc; -using Ryujinx.HLE.OsHle.Services.Nv.NvGpuAS; -using Ryujinx.HLE.OsHle.Services.Nv.NvGpuGpu; -using Ryujinx.HLE.OsHle.Services.Nv.NvHostChannel; -using Ryujinx.HLE.OsHle.Services.Nv.NvHostCtrl; -using Ryujinx.HLE.OsHle.Services.Nv.NvMap; -using System; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Services.Nv -{ - class INvDrvServices : IpcService, IDisposable - { - private delegate int IoctlProcessor(ServiceCtx Context, int Cmd); - - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - private static Dictionary IoctlProcessors = - new Dictionary() - { - { "/dev/nvhost-as-gpu", ProcessIoctlNvGpuAS }, - { "/dev/nvhost-ctrl", ProcessIoctlNvHostCtrl }, - { "/dev/nvhost-ctrl-gpu", ProcessIoctlNvGpuGpu }, - { "/dev/nvhost-gpu", ProcessIoctlNvHostChannel }, - { "/dev/nvmap", ProcessIoctlNvMap } - }; - - public static GlobalStateTable Fds { get; private set; } - - private KEvent Event; - - public INvDrvServices() - { - m_Commands = new Dictionary() - { - { 0, Open }, - { 1, Ioctl }, - { 2, Close }, - { 3, Initialize }, - { 4, QueryEvent }, - { 8, SetClientPid }, - { 13, FinishInitialize } - }; - - Event = new KEvent(); - } - - static INvDrvServices() - { - Fds = new GlobalStateTable(); - } - - public long Open(ServiceCtx Context) - { - long NamePtr = Context.Request.SendBuff[0].Position; - - string Name = AMemoryHelper.ReadAsciiString(Context.Memory, NamePtr); - - int Fd = Fds.Add(Context.Process, new NvFd(Name)); - - Context.ResponseData.Write(Fd); - Context.ResponseData.Write(0); - - return 0; - } - - public long Ioctl(ServiceCtx Context) - { - int Fd = Context.RequestData.ReadInt32(); - int Cmd = Context.RequestData.ReadInt32(); - - NvFd FdData = Fds.GetData(Context.Process, Fd); - - int Result; - - if (IoctlProcessors.TryGetValue(FdData.Name, out IoctlProcessor Process)) - { - Result = Process(Context, Cmd); - } - else - { - throw new NotImplementedException($"{FdData.Name} {Cmd:x4}"); - } - - //TODO: Verify if the error codes needs to be translated. - Context.ResponseData.Write(Result); - - return 0; - } - - public long Close(ServiceCtx Context) - { - int Fd = Context.RequestData.ReadInt32(); - - Fds.Delete(Context.Process, Fd); - - Context.ResponseData.Write(0); - - return 0; - } - - public long Initialize(ServiceCtx Context) - { - long TransferMemSize = Context.RequestData.ReadInt64(); - int TransferMemHandle = Context.Request.HandleDesc.ToCopy[0]; - - NvMapIoctl.InitializeNvMap(Context); - - Context.ResponseData.Write(0); - - return 0; - } - - public long QueryEvent(ServiceCtx Context) - { - int Fd = Context.RequestData.ReadInt32(); - int EventId = Context.RequestData.ReadInt32(); - - //TODO: Use Fd/EventId, different channels have different events. - int Handle = Context.Process.HandleTable.OpenHandle(Event); - - Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); - - Context.ResponseData.Write(0); - - return 0; - } - - public long SetClientPid(ServiceCtx Context) - { - long Pid = Context.RequestData.ReadInt64(); - - Context.ResponseData.Write(0); - - return 0; - } - - public long FinishInitialize(ServiceCtx Context) - { - Context.Ns.Log.PrintStub(LogClass.ServiceNv, "Stubbed."); - - return 0; - } - - private static int ProcessIoctlNvGpuAS(ServiceCtx Context, int Cmd) - { - return ProcessIoctl(Context, Cmd, NvGpuASIoctl.ProcessIoctl); - } - - private static int ProcessIoctlNvHostCtrl(ServiceCtx Context, int Cmd) - { - return ProcessIoctl(Context, Cmd, NvHostCtrlIoctl.ProcessIoctl); - } - - private static int ProcessIoctlNvGpuGpu(ServiceCtx Context, int Cmd) - { - return ProcessIoctl(Context, Cmd, NvGpuGpuIoctl.ProcessIoctl); - } - - private static int ProcessIoctlNvHostChannel(ServiceCtx Context, int Cmd) - { - return ProcessIoctl(Context, Cmd, NvHostChannelIoctl.ProcessIoctl); - } - - private static int ProcessIoctlNvMap(ServiceCtx Context, int Cmd) - { - return ProcessIoctl(Context, Cmd, NvMapIoctl.ProcessIoctl); - } - - private static int ProcessIoctl(ServiceCtx Context, int Cmd, IoctlProcessor Processor) - { - if (CmdIn(Cmd) && Context.Request.GetBufferType0x21().Position == 0) - { - Context.Ns.Log.PrintError(LogClass.ServiceNv, "Input buffer is null!"); - - return NvResult.InvalidInput; - } - - if (CmdOut(Cmd) && Context.Request.GetBufferType0x22().Position == 0) - { - Context.Ns.Log.PrintError(LogClass.ServiceNv, "Output buffer is null!"); - - return NvResult.InvalidInput; - } - - return Processor(Context, Cmd); - } - - private static bool CmdIn(int Cmd) - { - return ((Cmd >> 30) & 1) != 0; - } - - private static bool CmdOut(int Cmd) - { - return ((Cmd >> 31) & 1) != 0; - } - - public static void UnloadProcess(Process Process) - { - Fds.DeleteProcess(Process); - - NvGpuASIoctl.UnloadProcess(Process); - - NvHostCtrlIoctl.UnloadProcess(Process); - - NvMapIoctl.UnloadProcess(Process); - } - - public void Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool Disposing) - { - if (Disposing) - { - Event.Dispose(); - } - } - } -} diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvFd.cs b/Ryujinx.HLE/OsHle/Services/Nv/NvFd.cs deleted file mode 100644 index 96fce80a7a..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Nv/NvFd.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Ryujinx.HLE.OsHle.Services.Nv -{ - class NvFd - { - public string Name { get; private set; } - - public NvFd(string Name) - { - this.Name = Name; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvGpuAS/NvGpuASAllocSpace.cs b/Ryujinx.HLE/OsHle/Services/Nv/NvGpuAS/NvGpuASAllocSpace.cs deleted file mode 100644 index 9d955d6253..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Nv/NvGpuAS/NvGpuASAllocSpace.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Ryujinx.HLE.OsHle.Services.Nv.NvGpuAS -{ - struct NvGpuASAllocSpace - { - public int Pages; - public int PageSize; - public int Flags; - public int Padding; - public long Offset; - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvGpuAS/NvGpuASIoctl.cs b/Ryujinx.HLE/OsHle/Services/Nv/NvGpuAS/NvGpuASIoctl.cs deleted file mode 100644 index fcc478a417..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Nv/NvGpuAS/NvGpuASIoctl.cs +++ /dev/null @@ -1,243 +0,0 @@ -using ChocolArm64.Memory; -using Ryujinx.HLE.Gpu.Memory; -using Ryujinx.HLE.Logging; -using Ryujinx.HLE.OsHle.Services.Nv.NvMap; -using System; -using System.Collections.Concurrent; - -namespace Ryujinx.HLE.OsHle.Services.Nv.NvGpuAS -{ - class NvGpuASIoctl - { - private const int FlagFixedOffset = 1; - - private static ConcurrentDictionary Vmms; - - static NvGpuASIoctl() - { - Vmms = new ConcurrentDictionary(); - } - - public static int ProcessIoctl(ServiceCtx Context, int Cmd) - { - switch (Cmd & 0xffff) - { - case 0x4101: return BindChannel (Context); - case 0x4102: return AllocSpace (Context); - case 0x4103: return FreeSpace (Context); - case 0x4105: return UnmapBuffer (Context); - case 0x4106: return MapBufferEx (Context); - case 0x4108: return GetVaRegions(Context); - case 0x4109: return InitializeEx(Context); - case 0x4114: return Remap (Context); - } - - throw new NotImplementedException(Cmd.ToString("x8")); - } - - private static int BindChannel(ServiceCtx Context) - { - long InputPosition = Context.Request.GetBufferType0x21().Position; - long OutputPosition = Context.Request.GetBufferType0x22().Position; - - Context.Ns.Log.PrintStub(LogClass.ServiceNv, "Stubbed."); - - return NvResult.Success; - } - - private static int AllocSpace(ServiceCtx Context) - { - long InputPosition = Context.Request.GetBufferType0x21().Position; - long OutputPosition = Context.Request.GetBufferType0x22().Position; - - NvGpuASAllocSpace Args = AMemoryHelper.Read(Context.Memory, InputPosition); - - NvGpuVmm Vmm = GetVmm(Context); - - ulong Size = (ulong)Args.Pages * - (ulong)Args.PageSize; - - if ((Args.Flags & FlagFixedOffset) != 0) - { - Args.Offset = Vmm.Reserve(Args.Offset, (long)Size, 1); - } - else - { - Args.Offset = Vmm.Reserve((long)Size, 1); - } - - int Result = NvResult.Success; - - if (Args.Offset < 0) - { - Args.Offset = 0; - - Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"No memory to allocate size {Size:x16}!"); - - Result = NvResult.OutOfMemory; - } - - AMemoryHelper.Write(Context.Memory, OutputPosition, Args); - - return Result; - } - - private static int FreeSpace(ServiceCtx Context) - { - long InputPosition = Context.Request.GetBufferType0x21().Position; - long OutputPosition = Context.Request.GetBufferType0x22().Position; - - NvGpuASAllocSpace Args = AMemoryHelper.Read(Context.Memory, InputPosition); - - NvGpuVmm Vmm = GetVmm(Context); - - ulong Size = (ulong)Args.Pages * - (ulong)Args.PageSize; - - Vmm.Free(Args.Offset, (long)Size); - - return NvResult.Success; - } - - private static int UnmapBuffer(ServiceCtx Context) - { - long InputPosition = Context.Request.GetBufferType0x21().Position; - long OutputPosition = Context.Request.GetBufferType0x22().Position; - - NvGpuASUnmapBuffer Args = AMemoryHelper.Read(Context.Memory, InputPosition); - - NvGpuVmm Vmm = GetVmm(Context); - - if (!Vmm.Unmap(Args.Offset)) - { - Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"Invalid buffer offset {Args.Offset:x16}!"); - } - - return NvResult.Success; - } - - private static int MapBufferEx(ServiceCtx Context) - { - long InputPosition = Context.Request.GetBufferType0x21().Position; - long OutputPosition = Context.Request.GetBufferType0x22().Position; - - NvGpuASMapBufferEx Args = AMemoryHelper.Read(Context.Memory, InputPosition); - - NvGpuVmm Vmm = GetVmm(Context); - - NvMapHandle Map = NvMapIoctl.GetNvMapWithFb(Context, Args.NvMapHandle); - - if (Map == null) - { - Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"Invalid NvMap handle 0x{Args.NvMapHandle:x8}!"); - - return NvResult.InvalidInput; - } - - long PA = Map.Address + Args.BufferOffset; - - long Size = Args.MappingSize; - - if (Size == 0) - { - Size = (uint)Map.Size; - } - - int Result = NvResult.Success; - - //Note: When the fixed offset flag is not set, - //the Offset field holds the alignment size instead. - if ((Args.Flags & FlagFixedOffset) != 0) - { - long MapEnd = Args.Offset + Args.MappingSize; - - if ((ulong)MapEnd <= (ulong)Args.Offset) - { - Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"Offset 0x{Args.Offset:x16} and size 0x{Args.MappingSize:x16} results in a overflow!"); - - return NvResult.InvalidInput; - } - - if ((Args.Offset & NvGpuVmm.PageMask) != 0) - { - Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"Offset 0x{Args.Offset:x16} is not page aligned!"); - - return NvResult.InvalidInput; - } - - Args.Offset = Vmm.Map(PA, Args.Offset, Size); - } - else - { - Args.Offset = Vmm.Map(PA, Size); - - if (Args.Offset < 0) - { - Args.Offset = 0; - - Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"No memory to map size {Args.MappingSize:x16}!"); - - Result = NvResult.InvalidInput; - } - } - - AMemoryHelper.Write(Context.Memory, OutputPosition, Args); - - return Result; - } - - private static int GetVaRegions(ServiceCtx Context) - { - long InputPosition = Context.Request.GetBufferType0x21().Position; - long OutputPosition = Context.Request.GetBufferType0x22().Position; - - Context.Ns.Log.PrintStub(LogClass.ServiceNv, "Stubbed."); - - return NvResult.Success; - } - - private static int InitializeEx(ServiceCtx Context) - { - long InputPosition = Context.Request.GetBufferType0x21().Position; - long OutputPosition = Context.Request.GetBufferType0x22().Position; - - Context.Ns.Log.PrintStub(LogClass.ServiceNv, "Stubbed."); - - return NvResult.Success; - } - - private static int Remap(ServiceCtx Context) - { - long InputPosition = Context.Request.GetBufferType0x21().Position; - - NvGpuASRemap Args = AMemoryHelper.Read(Context.Memory, InputPosition); - - NvGpuVmm Vmm = GetVmm(Context); - - NvMapHandle Map = NvMapIoctl.GetNvMapWithFb(Context, Args.NvMapHandle); - - if (Map == null) - { - Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"Invalid NvMap handle 0x{Args.NvMapHandle:x8}!"); - - return NvResult.InvalidInput; - } - - //FIXME: This is most likely wrong... - Vmm.Map(Map.Address, (long)(uint)Args.Offset << 16, - (long)(uint)Args.Pages << 16); - - return NvResult.Success; - } - - public static NvGpuVmm GetVmm(ServiceCtx Context) - { - return Vmms.GetOrAdd(Context.Process, (Key) => new NvGpuVmm(Context.Memory)); - } - - public static void UnloadProcess(Process Process) - { - Vmms.TryRemove(Process, out _); - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvGpuAS/NvGpuASMapBufferEx.cs b/Ryujinx.HLE/OsHle/Services/Nv/NvGpuAS/NvGpuASMapBufferEx.cs deleted file mode 100644 index f3ee40b666..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Nv/NvGpuAS/NvGpuASMapBufferEx.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Ryujinx.HLE.OsHle.Services.Nv.NvGpuAS -{ - struct NvGpuASMapBufferEx - { - public int Flags; - public int Kind; - public int NvMapHandle; - public int PageSize; - public long BufferOffset; - public long MappingSize; - public long Offset; - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvGpuAS/NvGpuASRemap.cs b/Ryujinx.HLE/OsHle/Services/Nv/NvGpuAS/NvGpuASRemap.cs deleted file mode 100644 index e0ccb113b4..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Nv/NvGpuAS/NvGpuASRemap.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Ryujinx.HLE.OsHle.Services.Nv.NvGpuAS -{ - struct NvGpuASRemap - { - public short Flags; - public short Kind; - public int NvMapHandle; - public int Padding; - public int Offset; - public int Pages; - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvGpuAS/NvGpuASUnmapBuffer.cs b/Ryujinx.HLE/OsHle/Services/Nv/NvGpuAS/NvGpuASUnmapBuffer.cs deleted file mode 100644 index 790da3c261..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Nv/NvGpuAS/NvGpuASUnmapBuffer.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Ryujinx.HLE.OsHle.Services.Nv.NvGpuAS -{ - struct NvGpuASUnmapBuffer - { - public long Offset; - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvGpuGpu/NvGpuGpuGetActiveSlotMask.cs b/Ryujinx.HLE/OsHle/Services/Nv/NvGpuGpu/NvGpuGpuGetActiveSlotMask.cs deleted file mode 100644 index 71edebbb94..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Nv/NvGpuGpu/NvGpuGpuGetActiveSlotMask.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Ryujinx.HLE.OsHle.Services.Nv.NvGpuGpu -{ - struct NvGpuGpuGetActiveSlotMask - { - public int Slot; - public int Mask; - } -} diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvGpuGpu/NvGpuGpuGetTpcMasks.cs b/Ryujinx.HLE/OsHle/Services/Nv/NvGpuGpu/NvGpuGpuGetTpcMasks.cs deleted file mode 100644 index 17a7da6218..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Nv/NvGpuGpu/NvGpuGpuGetTpcMasks.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Ryujinx.HLE.OsHle.Services.Nv.NvGpuGpu -{ - struct NvGpuGpuGetTpcMasks - { - public int MaskBufferSize; - public int Reserved; - public long MaskBufferAddress; - public int TpcMask; - public int Padding; - } -} diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvGpuGpu/NvGpuGpuIoctl.cs b/Ryujinx.HLE/OsHle/Services/Nv/NvGpuGpu/NvGpuGpuIoctl.cs deleted file mode 100644 index c034994c69..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Nv/NvGpuGpu/NvGpuGpuIoctl.cs +++ /dev/null @@ -1,187 +0,0 @@ -using ChocolArm64.Memory; -using Ryujinx.HLE.Logging; -using System; -using System.Diagnostics; - -namespace Ryujinx.HLE.OsHle.Services.Nv.NvGpuGpu -{ - class NvGpuGpuIoctl - { - private static Stopwatch PTimer; - - private static double TicksToNs; - - static NvGpuGpuIoctl() - { - PTimer = new Stopwatch(); - - PTimer.Start(); - - TicksToNs = (1.0 / Stopwatch.Frequency) * 1_000_000_000; - } - - public static int ProcessIoctl(ServiceCtx Context, int Cmd) - { - switch (Cmd & 0xffff) - { - case 0x4701: return ZcullGetCtxSize (Context); - case 0x4702: return ZcullGetInfo (Context); - case 0x4703: return ZbcSetTable (Context); - case 0x4705: return GetCharacteristics(Context); - case 0x4706: return GetTpcMasks (Context); - case 0x4714: return GetActiveSlotMask (Context); - case 0x471c: return GetGpuTime (Context); - } - - throw new NotImplementedException(Cmd.ToString("x8")); - } - - private static int ZcullGetCtxSize(ServiceCtx Context) - { - long OutputPosition = Context.Request.GetBufferType0x22().Position; - - NvGpuGpuZcullGetCtxSize Args = new NvGpuGpuZcullGetCtxSize(); - - Args.Size = 1; - - AMemoryHelper.Write(Context.Memory, OutputPosition, Args); - - Context.Ns.Log.PrintStub(LogClass.ServiceNv, "Stubbed."); - - return NvResult.Success; - } - - private static int ZcullGetInfo(ServiceCtx Context) - { - long OutputPosition = Context.Request.GetBufferType0x22().Position; - - NvGpuGpuZcullGetInfo Args = new NvGpuGpuZcullGetInfo(); - - Args.WidthAlignPixels = 0x20; - Args.HeightAlignPixels = 0x20; - Args.PixelSquaresByAliquots = 0x400; - Args.AliquotTotal = 0x800; - Args.RegionByteMultiplier = 0x20; - Args.RegionHeaderSize = 0x20; - Args.SubregionHeaderSize = 0xc0; - Args.SubregionWidthAlignPixels = 0x20; - Args.SubregionHeightAlignPixels = 0x40; - Args.SubregionCount = 0x10; - - AMemoryHelper.Write(Context.Memory, OutputPosition, Args); - - Context.Ns.Log.PrintStub(LogClass.ServiceNv, "Stubbed."); - - return NvResult.Success; - } - - private static int ZbcSetTable(ServiceCtx Context) - { - long InputPosition = Context.Request.GetBufferType0x21().Position; - long OutputPosition = Context.Request.GetBufferType0x22().Position; - - Context.Ns.Log.PrintStub(LogClass.ServiceNv, "Stubbed."); - - return NvResult.Success; - } - - private static int GetCharacteristics(ServiceCtx Context) - { - long InputPosition = Context.Request.GetBufferType0x21().Position; - long OutputPosition = Context.Request.GetBufferType0x22().Position; - - NvGpuGpuGetCharacteristics Args = AMemoryHelper.Read(Context.Memory, InputPosition); - - Args.BufferSize = 0xa0; - - Args.Arch = 0x120; - Args.Impl = 0xb; - Args.Rev = 0xa1; - Args.NumGpc = 0x1; - Args.L2CacheSize = 0x40000; - Args.OnBoardVideoMemorySize = 0x0; - Args.NumTpcPerGpc = 0x2; - Args.BusType = 0x20; - Args.BigPageSize = 0x20000; - Args.CompressionPageSize = 0x20000; - Args.PdeCoverageBitCount = 0x1b; - Args.AvailableBigPageSizes = 0x30000; - Args.GpcMask = 0x1; - Args.SmArchSmVersion = 0x503; - Args.SmArchSpaVersion = 0x503; - Args.SmArchWarpCount = 0x80; - Args.GpuVaBitCount = 0x28; - Args.Reserved = 0x0; - Args.Flags = 0x55; - Args.TwodClass = 0x902d; - Args.ThreedClass = 0xb197; - Args.ComputeClass = 0xb1c0; - Args.GpfifoClass = 0xb06f; - Args.InlineToMemoryClass = 0xa140; - Args.DmaCopyClass = 0xb0b5; - Args.MaxFbpsCount = 0x1; - Args.FbpEnMask = 0x0; - Args.MaxLtcPerFbp = 0x2; - Args.MaxLtsPerLtc = 0x1; - Args.MaxTexPerTpc = 0x0; - Args.MaxGpcCount = 0x1; - Args.RopL2EnMask0 = 0x21d70; - Args.RopL2EnMask1 = 0x0; - Args.ChipName = 0x6230326d67; - Args.GrCompbitStoreBaseHw = 0x0; - - AMemoryHelper.Write(Context.Memory, OutputPosition, Args); - - return NvResult.Success; - } - - private static int GetTpcMasks(ServiceCtx Context) - { - long InputPosition = Context.Request.GetBufferType0x21().Position; - long OutputPosition = Context.Request.GetBufferType0x22().Position; - - NvGpuGpuGetTpcMasks Args = AMemoryHelper.Read(Context.Memory, InputPosition); - - if (Args.MaskBufferSize != 0) - { - Args.TpcMask = 3; - } - - AMemoryHelper.Write(Context.Memory, OutputPosition, Args); - - return NvResult.Success; - } - - private static int GetActiveSlotMask(ServiceCtx Context) - { - long OutputPosition = Context.Request.GetBufferType0x22().Position; - - NvGpuGpuGetActiveSlotMask Args = new NvGpuGpuGetActiveSlotMask(); - - Args.Slot = 0x07; - Args.Mask = 0x01; - - AMemoryHelper.Write(Context.Memory, OutputPosition, Args); - - Context.Ns.Log.PrintStub(LogClass.ServiceNv, "Stubbed."); - - return NvResult.Success; - } - - private static int GetGpuTime(ServiceCtx Context) - { - long OutputPosition = Context.Request.GetBufferType0x22().Position; - - Context.Memory.WriteInt64(OutputPosition, GetPTimerNanoSeconds()); - - return NvResult.Success; - } - - private static long GetPTimerNanoSeconds() - { - double Ticks = PTimer.ElapsedTicks; - - return (long)(Ticks * TicksToNs) & 0xff_ffff_ffff_ffff; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvGpuGpu/NvGpuGpuZcullGetCtxSize.cs b/Ryujinx.HLE/OsHle/Services/Nv/NvGpuGpu/NvGpuGpuZcullGetCtxSize.cs deleted file mode 100644 index 21bcacebf3..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Nv/NvGpuGpu/NvGpuGpuZcullGetCtxSize.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Ryujinx.HLE.OsHle.Services.Nv.NvGpuGpu -{ - struct NvGpuGpuZcullGetCtxSize - { - public int Size; - } -} diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvHelper.cs b/Ryujinx.HLE/OsHle/Services/Nv/NvHelper.cs deleted file mode 100644 index 22f1feccef..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Nv/NvHelper.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Ryujinx.HLE.OsHle.Services.Nv -{ - static class NvHelper - { - public static void Crash() - { - - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvHostChannel/NvHostChannelIoctl.cs b/Ryujinx.HLE/OsHle/Services/Nv/NvHostChannel/NvHostChannelIoctl.cs deleted file mode 100644 index 8f3d3cd7d6..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Nv/NvHostChannel/NvHostChannelIoctl.cs +++ /dev/null @@ -1,130 +0,0 @@ -using ChocolArm64.Memory; -using Ryujinx.HLE.Gpu.Memory; -using Ryujinx.HLE.Logging; -using Ryujinx.HLE.OsHle.Services.Nv.NvGpuAS; -using System; - -namespace Ryujinx.HLE.OsHle.Services.Nv.NvHostChannel -{ - class NvHostChannelIoctl - { - public static int ProcessIoctl(ServiceCtx Context, int Cmd) - { - switch (Cmd & 0xffff) - { - case 0x4714: return SetUserData (Context); - case 0x4801: return SetNvMap (Context); - case 0x4808: return SubmitGpfifo (Context); - case 0x4809: return AllocObjCtx (Context); - case 0x480b: return ZcullBind (Context); - case 0x480c: return SetErrorNotifier(Context); - case 0x480d: return SetPriority (Context); - case 0x481a: return AllocGpfifoEx2 (Context); - } - - throw new NotImplementedException(Cmd.ToString("x8")); - } - - private static int SetUserData(ServiceCtx Context) - { - long InputPosition = Context.Request.GetBufferType0x21().Position; - long OutputPosition = Context.Request.GetBufferType0x22().Position; - - Context.Ns.Log.PrintStub(LogClass.ServiceNv, "Stubbed."); - - return NvResult.Success; - } - - private static int SetNvMap(ServiceCtx Context) - { - long InputPosition = Context.Request.GetBufferType0x21().Position; - long OutputPosition = Context.Request.GetBufferType0x22().Position; - - Context.Ns.Log.PrintStub(LogClass.ServiceNv, "Stubbed."); - - return NvResult.Success; - } - - private static int SubmitGpfifo(ServiceCtx Context) - { - long InputPosition = Context.Request.GetBufferType0x21().Position; - long OutputPosition = Context.Request.GetBufferType0x22().Position; - - NvHostChannelSubmitGpfifo Args = AMemoryHelper.Read(Context.Memory, InputPosition); - - NvGpuVmm Vmm = NvGpuASIoctl.GetVmm(Context); - - for (int Index = 0; Index < Args.NumEntries; Index++) - { - long Gpfifo = Context.Memory.ReadInt64(InputPosition + 0x18 + Index * 8); - - long VA = Gpfifo & 0xff_ffff_ffff; - - int Size = (int)(Gpfifo >> 40) & 0x7ffffc; - - byte[] Data = Vmm.ReadBytes(VA, Size); - - NvGpuPBEntry[] PushBuffer = NvGpuPushBuffer.Decode(Data); - - Context.Ns.Gpu.Fifo.PushBuffer(Vmm, PushBuffer); - } - - Args.SyncptId = 0; - Args.SyncptValue = 0; - - AMemoryHelper.Write(Context.Memory, OutputPosition, Args); - - return NvResult.Success; - } - - private static int AllocObjCtx(ServiceCtx Context) - { - long InputPosition = Context.Request.GetBufferType0x21().Position; - long OutputPosition = Context.Request.GetBufferType0x22().Position; - - Context.Ns.Log.PrintStub(LogClass.ServiceNv, "Stubbed."); - - return NvResult.Success; - } - - private static int ZcullBind(ServiceCtx Context) - { - long InputPosition = Context.Request.GetBufferType0x21().Position; - long OutputPosition = Context.Request.GetBufferType0x22().Position; - - Context.Ns.Log.PrintStub(LogClass.ServiceNv, "Stubbed."); - - return NvResult.Success; - } - - private static int SetErrorNotifier(ServiceCtx Context) - { - long InputPosition = Context.Request.GetBufferType0x21().Position; - long OutputPosition = Context.Request.GetBufferType0x22().Position; - - Context.Ns.Log.PrintStub(LogClass.ServiceNv, "Stubbed."); - - return NvResult.Success; - } - - private static int SetPriority(ServiceCtx Context) - { - long InputPosition = Context.Request.GetBufferType0x21().Position; - long OutputPosition = Context.Request.GetBufferType0x22().Position; - - Context.Ns.Log.PrintStub(LogClass.ServiceNv, "Stubbed."); - - return NvResult.Success; - } - - private static int AllocGpfifoEx2(ServiceCtx Context) - { - long InputPosition = Context.Request.GetBufferType0x21().Position; - long OutputPosition = Context.Request.GetBufferType0x22().Position; - - Context.Ns.Log.PrintStub(LogClass.ServiceNv, "Stubbed."); - - return NvResult.Success; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvHostChannel/NvHostChannelSubmitGpfifo.cs b/Ryujinx.HLE/OsHle/Services/Nv/NvHostChannel/NvHostChannelSubmitGpfifo.cs deleted file mode 100644 index 9541f7018c..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Nv/NvHostChannel/NvHostChannelSubmitGpfifo.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Ryujinx.HLE.OsHle.Services.Nv.NvHostChannel -{ - struct NvHostChannelSubmitGpfifo - { - public long Gpfifo; - public int NumEntries; - public int Flags; - public int SyncptId; - public int SyncptValue; - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvHostCtrl/NvHostCtrlIoctl.cs b/Ryujinx.HLE/OsHle/Services/Nv/NvHostCtrl/NvHostCtrlIoctl.cs deleted file mode 100644 index a9fd9d3abd..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Nv/NvHostCtrl/NvHostCtrlIoctl.cs +++ /dev/null @@ -1,355 +0,0 @@ -using ChocolArm64.Memory; -using Ryujinx.HLE.Logging; -using System; -using System.Collections.Concurrent; -using System.Threading; - -namespace Ryujinx.HLE.OsHle.Services.Nv.NvHostCtrl -{ - class NvHostCtrlIoctl - { - private static ConcurrentDictionary UserCtxs; - - static NvHostCtrlIoctl() - { - UserCtxs = new ConcurrentDictionary(); - } - - public static int ProcessIoctl(ServiceCtx Context, int Cmd) - { - switch (Cmd & 0xffff) - { - case 0x0014: return SyncptRead (Context); - case 0x0015: return SyncptIncr (Context); - case 0x0016: return SyncptWait (Context); - case 0x0019: return SyncptWaitEx (Context); - case 0x001a: return SyncptReadMax (Context); - case 0x001b: return GetConfig (Context); - case 0x001d: return EventWait (Context); - case 0x001e: return EventWaitAsync(Context); - case 0x001f: return EventRegister (Context); - } - - throw new NotImplementedException(Cmd.ToString("x8")); - } - - private static int SyncptRead(ServiceCtx Context) - { - return SyncptReadMinOrMax(Context, Max: false); - } - - private static int SyncptIncr(ServiceCtx Context) - { - long InputPosition = Context.Request.GetBufferType0x21().Position; - - int Id = Context.Memory.ReadInt32(InputPosition); - - if ((uint)Id >= NvHostSyncpt.SyncptsCount) - { - return NvResult.InvalidInput; - } - - GetUserCtx(Context).Syncpt.Increment(Id); - - return NvResult.Success; - } - - private static int SyncptWait(ServiceCtx Context) - { - return SyncptWait(Context, Extended: false); - } - - private static int SyncptWaitEx(ServiceCtx Context) - { - return SyncptWait(Context, Extended: true); - } - - private static int SyncptReadMax(ServiceCtx Context) - { - return SyncptReadMinOrMax(Context, Max: true); - } - - private static int GetConfig(ServiceCtx Context) - { - long InputPosition = Context.Request.GetBufferType0x21().Position; - long OutputPosition = Context.Request.GetBufferType0x22().Position; - - string Nv = AMemoryHelper.ReadAsciiString(Context.Memory, InputPosition + 0, 0x41); - string Name = AMemoryHelper.ReadAsciiString(Context.Memory, InputPosition + 0x41, 0x41); - - Context.Memory.WriteByte(OutputPosition + 0x82, 0); - - Context.Ns.Log.PrintStub(LogClass.ServiceNv, "Stubbed."); - - return NvResult.Success; - } - - private static int EventWait(ServiceCtx Context) - { - return EventWait(Context, Async: false); - } - - private static int EventWaitAsync(ServiceCtx Context) - { - return EventWait(Context, Async: true); - } - - private static int EventRegister(ServiceCtx Context) - { - long InputPosition = Context.Request.GetBufferType0x21().Position; - long OutputPosition = Context.Request.GetBufferType0x22().Position; - - int EventId = Context.Memory.ReadInt32(InputPosition); - - Context.Ns.Log.PrintStub(LogClass.ServiceNv, "Stubbed."); - - return NvResult.Success; - } - - private static int SyncptReadMinOrMax(ServiceCtx Context, bool Max) - { - long InputPosition = Context.Request.GetBufferType0x21().Position; - long OutputPosition = Context.Request.GetBufferType0x22().Position; - - NvHostCtrlSyncptRead Args = AMemoryHelper.Read(Context.Memory, InputPosition); - - if ((uint)Args.Id >= NvHostSyncpt.SyncptsCount) - { - return NvResult.InvalidInput; - } - - if (Max) - { - Args.Value = GetUserCtx(Context).Syncpt.GetMax(Args.Id); - } - else - { - Args.Value = GetUserCtx(Context).Syncpt.GetMin(Args.Id); - } - - AMemoryHelper.Write(Context.Memory, OutputPosition, Args); - - return NvResult.Success; - } - - private static int SyncptWait(ServiceCtx Context, bool Extended) - { - long InputPosition = Context.Request.GetBufferType0x21().Position; - long OutputPosition = Context.Request.GetBufferType0x22().Position; - - NvHostCtrlSyncptWait Args = AMemoryHelper.Read(Context.Memory, InputPosition); - - NvHostSyncpt Syncpt = GetUserCtx(Context).Syncpt; - - if ((uint)Args.Id >= NvHostSyncpt.SyncptsCount) - { - return NvResult.InvalidInput; - } - - int Result; - - if (Syncpt.MinCompare(Args.Id, Args.Thresh)) - { - Result = NvResult.Success; - } - else if (Args.Timeout == 0) - { - Result = NvResult.TryAgain; - } - else - { - Context.Ns.Log.PrintDebug(LogClass.ServiceNv, "Waiting syncpt with timeout of " + Args.Timeout + "ms..."); - - using (ManualResetEvent WaitEvent = new ManualResetEvent(false)) - { - Syncpt.AddWaiter(Args.Thresh, WaitEvent); - - //Note: Negative (> INT_MAX) timeouts aren't valid on .NET, - //in this case we just use the maximum timeout possible. - int Timeout = Args.Timeout; - - if (Timeout < -1) - { - Timeout = int.MaxValue; - } - - if (Timeout == -1) - { - WaitEvent.WaitOne(); - - Result = NvResult.Success; - } - else if (WaitEvent.WaitOne(Timeout)) - { - Result = NvResult.Success; - } - else - { - Result = NvResult.TimedOut; - } - } - - Context.Ns.Log.PrintDebug(LogClass.ServiceNv, "Resuming..."); - } - - if (Extended) - { - Context.Memory.WriteInt32(OutputPosition + 0xc, Syncpt.GetMin(Args.Id)); - } - - return Result; - } - - private static int EventWait(ServiceCtx Context, bool Async) - { - long InputPosition = Context.Request.GetBufferType0x21().Position; - long OutputPosition = Context.Request.GetBufferType0x22().Position; - - NvHostCtrlSyncptWaitEx Args = AMemoryHelper.Read(Context.Memory, InputPosition); - - if ((uint)Args.Id >= NvHostSyncpt.SyncptsCount) - { - return NvResult.InvalidInput; - } - - void WriteArgs() - { - AMemoryHelper.Write(Context.Memory, OutputPosition, Args); - } - - NvHostSyncpt Syncpt = GetUserCtx(Context).Syncpt; - - if (Syncpt.MinCompare(Args.Id, Args.Thresh)) - { - Args.Value = Syncpt.GetMin(Args.Id); - - WriteArgs(); - - return NvResult.Success; - } - - if (!Async) - { - Args.Value = 0; - } - - if (Args.Timeout == 0) - { - WriteArgs(); - - return NvResult.TryAgain; - } - - NvHostEvent Event; - - int Result, EventIndex; - - if (Async) - { - EventIndex = Args.Value; - - if ((uint)EventIndex >= NvHostCtrlUserCtx.EventsCount) - { - return NvResult.InvalidInput; - } - - Event = GetUserCtx(Context).Events[EventIndex]; - } - else - { - Event = GetFreeEvent(Context, Syncpt, Args.Id, out EventIndex); - } - - if (Event != null && - (Event.State == NvHostEventState.Registered || - Event.State == NvHostEventState.Free)) - { - Event.Id = Args.Id; - Event.Thresh = Args.Thresh; - - Event.State = NvHostEventState.Waiting; - - if (!Async) - { - Args.Value = ((Args.Id & 0xfff) << 16) | 0x10000000; - } - else - { - Args.Value = Args.Id << 4; - } - - Args.Value |= EventIndex; - - Result = NvResult.TryAgain; - } - else - { - Result = NvResult.InvalidInput; - } - - WriteArgs(); - - return Result; - } - - private static NvHostEvent GetFreeEvent( - ServiceCtx Context, - NvHostSyncpt Syncpt, - int Id, - out int EventIndex) - { - NvHostEvent[] Events = GetUserCtx(Context).Events; - - EventIndex = NvHostCtrlUserCtx.EventsCount; - - int NullIndex = NvHostCtrlUserCtx.EventsCount; - - for (int Index = 0; Index < NvHostCtrlUserCtx.EventsCount; Index++) - { - NvHostEvent Event = Events[Index]; - - if (Event != null) - { - if (Event.State == NvHostEventState.Registered || - Event.State == NvHostEventState.Free) - { - EventIndex = Index; - - if (Event.Id == Id) - { - return Event; - } - } - } - else if (NullIndex == NvHostCtrlUserCtx.EventsCount) - { - NullIndex = Index; - } - } - - if (NullIndex < NvHostCtrlUserCtx.EventsCount) - { - EventIndex = NullIndex; - - return Events[NullIndex] = new NvHostEvent(); - } - - if (EventIndex < NvHostCtrlUserCtx.EventsCount) - { - return Events[EventIndex]; - } - - return null; - } - - public static NvHostCtrlUserCtx GetUserCtx(ServiceCtx Context) - { - return UserCtxs.GetOrAdd(Context.Process, (Key) => new NvHostCtrlUserCtx()); - } - - public static void UnloadProcess(Process Process) - { - UserCtxs.TryRemove(Process, out _); - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvHostCtrl/NvHostCtrlSyncPtRead.cs b/Ryujinx.HLE/OsHle/Services/Nv/NvHostCtrl/NvHostCtrlSyncPtRead.cs deleted file mode 100644 index 7801f467a9..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Nv/NvHostCtrl/NvHostCtrlSyncPtRead.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Ryujinx.HLE.OsHle.Services.Nv.NvHostCtrl -{ - struct NvHostCtrlSyncptRead - { - public int Id; - public int Value; - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvHostCtrl/NvHostCtrlSyncPtWait.cs b/Ryujinx.HLE/OsHle/Services/Nv/NvHostCtrl/NvHostCtrlSyncPtWait.cs deleted file mode 100644 index 29a75dd7d8..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Nv/NvHostCtrl/NvHostCtrlSyncPtWait.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Ryujinx.HLE.OsHle.Services.Nv.NvHostCtrl -{ - struct NvHostCtrlSyncptWait - { - public int Id; - public int Thresh; - public int Timeout; - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvHostCtrl/NvHostCtrlSyncPtWaitEx.cs b/Ryujinx.HLE/OsHle/Services/Nv/NvHostCtrl/NvHostCtrlSyncPtWaitEx.cs deleted file mode 100644 index 79f84214bf..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Nv/NvHostCtrl/NvHostCtrlSyncPtWaitEx.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Ryujinx.HLE.OsHle.Services.Nv.NvHostCtrl -{ - struct NvHostCtrlSyncptWaitEx - { - public int Id; - public int Thresh; - public int Timeout; - public int Value; - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvHostCtrl/NvHostCtrlUserCtx.cs b/Ryujinx.HLE/OsHle/Services/Nv/NvHostCtrl/NvHostCtrlUserCtx.cs deleted file mode 100644 index 5d414a2e5f..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Nv/NvHostCtrl/NvHostCtrlUserCtx.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace Ryujinx.HLE.OsHle.Services.Nv.NvHostCtrl -{ - class NvHostCtrlUserCtx - { - public const int LocksCount = 16; - public const int EventsCount = 64; - - public NvHostSyncpt Syncpt { get; private set; } - - public NvHostEvent[] Events { get; private set; } - - public NvHostCtrlUserCtx() - { - Syncpt = new NvHostSyncpt(); - - Events = new NvHostEvent[EventsCount]; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvHostCtrl/NvHostSyncPt.cs b/Ryujinx.HLE/OsHle/Services/Nv/NvHostCtrl/NvHostSyncPt.cs deleted file mode 100644 index 47d63f794b..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Nv/NvHostCtrl/NvHostSyncPt.cs +++ /dev/null @@ -1,107 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Threading; - -namespace Ryujinx.HLE.OsHle.Services.Nv.NvHostCtrl -{ - class NvHostSyncpt - { - public const int SyncptsCount = 192; - - private int[] CounterMin; - private int[] CounterMax; - - private long EventMask; - - private ConcurrentDictionary Waiters; - - public NvHostSyncpt() - { - CounterMin = new int[SyncptsCount]; - CounterMax = new int[SyncptsCount]; - - Waiters = new ConcurrentDictionary(); - } - - public int GetMin(int Id) - { - return CounterMin[Id]; - } - - public int GetMax(int Id) - { - return CounterMax[Id]; - } - - public int Increment(int Id) - { - if (((EventMask >> Id) & 1) != 0) - { - Interlocked.Increment(ref CounterMax[Id]); - } - - return IncrementMin(Id); - } - - public int IncrementMin(int Id) - { - int Value = Interlocked.Increment(ref CounterMin[Id]); - - WakeUpWaiters(Id, Value); - - return Value; - } - - public int IncrementMax(int Id) - { - return Interlocked.Increment(ref CounterMax[Id]); - } - - public void AddWaiter(int Threshold, EventWaitHandle WaitEvent) - { - if (!Waiters.TryAdd(WaitEvent, Threshold)) - { - throw new InvalidOperationException(); - } - } - - public bool RemoveWaiter(EventWaitHandle WaitEvent) - { - return Waiters.TryRemove(WaitEvent, out _); - } - - private void WakeUpWaiters(int Id, int NewValue) - { - foreach (KeyValuePair KV in Waiters) - { - if (MinCompare(Id, NewValue, CounterMax[Id], KV.Value)) - { - KV.Key.Set(); - - Waiters.TryRemove(KV.Key, out _); - } - } - } - - public bool MinCompare(int Id, int Threshold) - { - return MinCompare(Id, CounterMin[Id], CounterMax[Id], Threshold); - } - - private bool MinCompare(int Id, int Min, int Max, int Threshold) - { - int MinDiff = Min - Threshold; - int MaxDiff = Max - Threshold; - - if (((EventMask >> Id) & 1) != 0) - { - return MinDiff >= 0; - } - else - { - return (uint)MaxDiff >= (uint)MinDiff; - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvMap/NvMapCreate.cs b/Ryujinx.HLE/OsHle/Services/Nv/NvMap/NvMapCreate.cs deleted file mode 100644 index 8f3be79d02..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Nv/NvMap/NvMapCreate.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Ryujinx.HLE.OsHle.Services.Nv.NvMap -{ - struct NvMapCreate - { - public int Size; - public int Handle; - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvMap/NvMapFree.cs b/Ryujinx.HLE/OsHle/Services/Nv/NvMap/NvMapFree.cs deleted file mode 100644 index 1e13f19746..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Nv/NvMap/NvMapFree.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Ryujinx.HLE.OsHle.Services.Nv.NvMap -{ - struct NvMapFree - { - public int Handle; - public int Padding; - public long RefCount; - public int Size; - public int Flags; - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvMap/NvMapFromId.cs b/Ryujinx.HLE/OsHle/Services/Nv/NvMap/NvMapFromId.cs deleted file mode 100644 index 31c751317e..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Nv/NvMap/NvMapFromId.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Ryujinx.HLE.OsHle.Services.Nv.NvMap -{ - struct NvMapFromId - { - public int Id; - public int Handle; - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvMap/NvMapGetId.cs b/Ryujinx.HLE/OsHle/Services/Nv/NvMap/NvMapGetId.cs deleted file mode 100644 index 780815d27f..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Nv/NvMap/NvMapGetId.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Ryujinx.HLE.OsHle.Services.Nv.NvMap -{ - struct NvMapGetId - { - public int Id; - public int Handle; - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvMap/NvMapIoctl.cs b/Ryujinx.HLE/OsHle/Services/Nv/NvMap/NvMapIoctl.cs deleted file mode 100644 index ec10a37588..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Nv/NvMap/NvMapIoctl.cs +++ /dev/null @@ -1,302 +0,0 @@ -using ChocolArm64.Memory; -using Ryujinx.HLE.Gpu.Memory; -using Ryujinx.HLE.Logging; -using Ryujinx.HLE.OsHle.Utilities; -using System.Collections.Concurrent; - -namespace Ryujinx.HLE.OsHle.Services.Nv.NvMap -{ - class NvMapIoctl - { - private const int FlagNotFreedYet = 1; - - private static ConcurrentDictionary Maps; - - static NvMapIoctl() - { - Maps = new ConcurrentDictionary(); - } - - public static int ProcessIoctl(ServiceCtx Context, int Cmd) - { - switch (Cmd & 0xffff) - { - case 0x0101: return Create(Context); - case 0x0103: return FromId(Context); - case 0x0104: return Alloc (Context); - case 0x0105: return Free (Context); - case 0x0109: return Param (Context); - case 0x010e: return GetId (Context); - } - - Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"Unsupported Ioctl command 0x{Cmd:x8}!"); - - return NvResult.NotSupported; - } - - private static int Create(ServiceCtx Context) - { - long InputPosition = Context.Request.GetBufferType0x21().Position; - long OutputPosition = Context.Request.GetBufferType0x22().Position; - - NvMapCreate Args = AMemoryHelper.Read(Context.Memory, InputPosition); - - if (Args.Size == 0) - { - Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"Invalid size 0x{Args.Size:x8}!"); - - return NvResult.InvalidInput; - } - - int Size = IntUtils.RoundUp(Args.Size, NvGpuVmm.PageSize); - - Args.Handle = AddNvMap(Context, new NvMapHandle(Size)); - - Context.Ns.Log.PrintInfo(LogClass.ServiceNv, $"Created map {Args.Handle} with size 0x{Size:x8}!"); - - AMemoryHelper.Write(Context.Memory, OutputPosition, Args); - - return NvResult.Success; - } - - private static int FromId(ServiceCtx Context) - { - long InputPosition = Context.Request.GetBufferType0x21().Position; - long OutputPosition = Context.Request.GetBufferType0x22().Position; - - NvMapFromId Args = AMemoryHelper.Read(Context.Memory, InputPosition); - - NvMapHandle Map = GetNvMap(Context, Args.Id); - - if (Map == null) - { - Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"Invalid handle 0x{Args.Handle:x8}!"); - - return NvResult.InvalidInput; - } - - Map.IncrementRefCount(); - - Args.Handle = Args.Id; - - AMemoryHelper.Write(Context.Memory, OutputPosition, Args); - - return NvResult.Success; - } - - private static int Alloc(ServiceCtx Context) - { - long InputPosition = Context.Request.GetBufferType0x21().Position; - long OutputPosition = Context.Request.GetBufferType0x22().Position; - - NvMapAlloc Args = AMemoryHelper.Read(Context.Memory, InputPosition); - - NvMapHandle Map = GetNvMap(Context, Args.Handle); - - if (Map == null) - { - Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"Invalid handle 0x{Args.Handle:x8}!"); - - return NvResult.InvalidInput; - } - - if ((Args.Align & (Args.Align - 1)) != 0) - { - Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"Invalid alignment 0x{Args.Align:x8}!"); - - return NvResult.InvalidInput; - } - - if ((uint)Args.Align < NvGpuVmm.PageSize) - { - Args.Align = NvGpuVmm.PageSize; - } - - int Result = NvResult.Success; - - if (!Map.Allocated) - { - Map.Allocated = true; - - Map.Align = Args.Align; - Map.Kind = (byte)Args.Kind; - - int Size = IntUtils.RoundUp(Map.Size, NvGpuVmm.PageSize); - - long Address = Args.Address; - - if (Address == 0) - { - //When the address is zero, we need to allocate - //our own backing memory for the NvMap. - if (!Context.Ns.Os.Allocator.TryAllocate((uint)Size, out Address)) - { - Result = NvResult.OutOfMemory; - } - } - - if (Result == NvResult.Success) - { - Map.Size = Size; - Map.Address = Address; - } - } - - AMemoryHelper.Write(Context.Memory, OutputPosition, Args); - - return Result; - } - - private static int Free(ServiceCtx Context) - { - long InputPosition = Context.Request.GetBufferType0x21().Position; - long OutputPosition = Context.Request.GetBufferType0x22().Position; - - NvMapFree Args = AMemoryHelper.Read(Context.Memory, InputPosition); - - NvMapHandle Map = GetNvMap(Context, Args.Handle); - - if (Map == null) - { - Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"Invalid handle 0x{Args.Handle:x8}!"); - - return NvResult.InvalidInput; - } - - long OldRefCount = Map.DecrementRefCount(); - - if (OldRefCount <= 1) - { - DeleteNvMap(Context, Args.Handle); - - Context.Ns.Log.PrintInfo(LogClass.ServiceNv, $"Deleted map {Args.Handle}!"); - - Args.Flags = 0; - } - else - { - Args.Flags = FlagNotFreedYet; - } - - Args.RefCount = OldRefCount; - Args.Size = Map.Size; - - AMemoryHelper.Write(Context.Memory, OutputPosition, Args); - - return NvResult.Success; - } - - private static int Param(ServiceCtx Context) - { - long InputPosition = Context.Request.GetBufferType0x21().Position; - long OutputPosition = Context.Request.GetBufferType0x22().Position; - - NvMapParam Args = AMemoryHelper.Read(Context.Memory, InputPosition); - - NvMapHandle Map = GetNvMap(Context, Args.Handle); - - if (Map == null) - { - Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"Invalid handle 0x{Args.Handle:x8}!"); - - return NvResult.InvalidInput; - } - - switch ((NvMapHandleParam)Args.Param) - { - case NvMapHandleParam.Size: Args.Result = Map.Size; break; - case NvMapHandleParam.Align: Args.Result = Map.Align; break; - case NvMapHandleParam.Heap: Args.Result = 0x40000000; break; - case NvMapHandleParam.Kind: Args.Result = Map.Kind; break; - case NvMapHandleParam.Compr: Args.Result = 0; break; - - //Note: Base is not supported and returns an error. - //Any other value also returns an error. - default: return NvResult.InvalidInput; - } - - AMemoryHelper.Write(Context.Memory, OutputPosition, Args); - - return NvResult.Success; - } - - private static int GetId(ServiceCtx Context) - { - long InputPosition = Context.Request.GetBufferType0x21().Position; - long OutputPosition = Context.Request.GetBufferType0x22().Position; - - NvMapGetId Args = AMemoryHelper.Read(Context.Memory, InputPosition); - - NvMapHandle Map = GetNvMap(Context, Args.Handle); - - if (Map == null) - { - Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"Invalid handle 0x{Args.Handle:x8}!"); - - return NvResult.InvalidInput; - } - - Args.Id = Args.Handle; - - AMemoryHelper.Write(Context.Memory, OutputPosition, Args); - - return NvResult.Success; - } - - private static int AddNvMap(ServiceCtx Context, NvMapHandle Map) - { - IdDictionary Dict = Maps.GetOrAdd(Context.Process, (Key) => - { - IdDictionary NewDict = new IdDictionary(); - - NewDict.Add(0, new NvMapHandle()); - - return NewDict; - }); - - return Dict.Add(Map); - } - - private static bool DeleteNvMap(ServiceCtx Context, int Handle) - { - if (Maps.TryGetValue(Context.Process, out IdDictionary Dict)) - { - return Dict.Delete(Handle) != null; - } - - return false; - } - - public static void InitializeNvMap(ServiceCtx Context) - { - IdDictionary Dict = Maps.GetOrAdd(Context.Process, (Key) =>new IdDictionary()); - - Dict.Add(0, new NvMapHandle()); - } - - public static NvMapHandle GetNvMapWithFb(ServiceCtx Context, int Handle) - { - if (Maps.TryGetValue(Context.Process, out IdDictionary Dict)) - { - return Dict.GetData(Handle); - } - - return null; - } - - public static NvMapHandle GetNvMap(ServiceCtx Context, int Handle) - { - if (Handle != 0 && Maps.TryGetValue(Context.Process, out IdDictionary Dict)) - { - return Dict.GetData(Handle); - } - - return null; - } - - public static void UnloadProcess(Process Process) - { - Maps.TryRemove(Process, out _); - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvMap/NvMapParam.cs b/Ryujinx.HLE/OsHle/Services/Nv/NvMap/NvMapParam.cs deleted file mode 100644 index 218cb700a7..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Nv/NvMap/NvMapParam.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Ryujinx.HLE.OsHle.Services.Nv.NvMap -{ - struct NvMapParam - { - public int Handle; - public int Param; - public int Result; - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvResult.cs b/Ryujinx.HLE/OsHle/Services/Nv/NvResult.cs deleted file mode 100644 index 720f5ccf2c..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Nv/NvResult.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Ryujinx.HLE.OsHle.Services.Nv -{ - static class NvResult - { - public const int Success = 0; - public const int TryAgain = -11; - public const int OutOfMemory = -12; - public const int InvalidInput = -22; - public const int NotSupported = -25; - public const int Restart = -85; - public const int TimedOut = -110; - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Pctl/IParentalControlService.cs b/Ryujinx.HLE/OsHle/Services/Pctl/IParentalControlService.cs deleted file mode 100644 index 60a69f58c9..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Pctl/IParentalControlService.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Ryujinx.HLE.Logging; -using Ryujinx.HLE.OsHle.Ipc; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Services.Pctl -{ - class IParentalControlService : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - private bool Initialized = false; - - private bool NeedInitialize; - - public IParentalControlService(bool NeedInitialize = true) - { - m_Commands = new Dictionary() - { - { 1, Initialize } - }; - - this.NeedInitialize = NeedInitialize; - } - - public long Initialize(ServiceCtx Context) - { - if (NeedInitialize && !Initialized) - { - Initialized = true; - } - else - { - Context.Ns.Log.PrintWarning(LogClass.ServicePctl, "Service is already initialized!"); - } - - return 0; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Pctl/IParentalControlServiceFactory.cs b/Ryujinx.HLE/OsHle/Services/Pctl/IParentalControlServiceFactory.cs deleted file mode 100644 index 7ef91d7fb6..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Pctl/IParentalControlServiceFactory.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Ryujinx.HLE.OsHle.Ipc; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Services.Pctl -{ - class IParentalControlServiceFactory : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - public IParentalControlServiceFactory() - { - m_Commands = new Dictionary() - { - { 0, CreateService }, - { 1, CreateServiceWithoutInitialize } - }; - } - - public long CreateService(ServiceCtx Context) - { - MakeObject(Context, new IParentalControlService()); - - return 0; - } - - public long CreateServiceWithoutInitialize(ServiceCtx Context) - { - MakeObject(Context, new IParentalControlService(false)); - - return 0; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Pl/ISharedFontManager.cs b/Ryujinx.HLE/OsHle/Services/Pl/ISharedFontManager.cs deleted file mode 100644 index 9f85f3d102..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Pl/ISharedFontManager.cs +++ /dev/null @@ -1,61 +0,0 @@ -using Ryujinx.HLE.OsHle.Ipc; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Services.Pl -{ - class ISharedFontManager : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - public ISharedFontManager() - { - m_Commands = new Dictionary() - { - { 0, RequestLoad }, - { 1, GetLoadState }, - { 2, GetFontSize }, - { 3, GetSharedMemoryAddressOffset }, - { 4, GetSharedMemoryNativeHandle } - }; - } - - public long RequestLoad(ServiceCtx Context) - { - SharedFontType FontType = (SharedFontType)Context.RequestData.ReadInt32(); - - return 0; - } - - public long GetLoadState(ServiceCtx Context) - { - Context.ResponseData.Write(1); //Loaded - - return 0; - } - - public long GetFontSize(ServiceCtx Context) - { - Context.ResponseData.Write(Horizon.FontSize); - - return 0; - } - - public long GetSharedMemoryAddressOffset(ServiceCtx Context) - { - Context.ResponseData.Write(0); - - return 0; - } - - public long GetSharedMemoryNativeHandle(ServiceCtx Context) - { - int Handle = Context.Process.HandleTable.OpenHandle(Context.Ns.Os.FontSharedMem); - - Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); - - return 0; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Prepo/IPrepoService.cs b/Ryujinx.HLE/OsHle/Services/Prepo/IPrepoService.cs deleted file mode 100644 index f313921a78..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Prepo/IPrepoService.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Ryujinx.HLE.Logging; -using Ryujinx.HLE.OsHle.Ipc; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Services.Prepo -{ - class IPrepoService : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - public IPrepoService() - { - m_Commands = new Dictionary() - { - { 10101, SaveReportWithUser } - }; - } - - public static long SaveReportWithUser(ServiceCtx Context) - { - Context.Ns.Log.PrintStub(LogClass.ServicePrepo, "Stubbed."); - - return 0; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/ServiceFactory.cs b/Ryujinx.HLE/OsHle/Services/ServiceFactory.cs deleted file mode 100644 index 914c84490d..0000000000 --- a/Ryujinx.HLE/OsHle/Services/ServiceFactory.cs +++ /dev/null @@ -1,166 +0,0 @@ -using Ryujinx.HLE.OsHle.Services.Acc; -using Ryujinx.HLE.OsHle.Services.Am; -using Ryujinx.HLE.OsHle.Services.Apm; -using Ryujinx.HLE.OsHle.Services.Aud; -using Ryujinx.HLE.OsHle.Services.Bsd; -using Ryujinx.HLE.OsHle.Services.Caps; -using Ryujinx.HLE.OsHle.Services.Friend; -using Ryujinx.HLE.OsHle.Services.FspSrv; -using Ryujinx.HLE.OsHle.Services.Hid; -using Ryujinx.HLE.OsHle.Services.Lm; -using Ryujinx.HLE.OsHle.Services.Mm; -using Ryujinx.HLE.OsHle.Services.Nfp; -using Ryujinx.HLE.OsHle.Services.Ns; -using Ryujinx.HLE.OsHle.Services.Nv; -using Ryujinx.HLE.OsHle.Services.Pctl; -using Ryujinx.HLE.OsHle.Services.Pl; -using Ryujinx.HLE.OsHle.Services.Prepo; -using Ryujinx.HLE.OsHle.Services.Set; -using Ryujinx.HLE.OsHle.Services.Sfdnsres; -using Ryujinx.HLE.OsHle.Services.Sm; -using Ryujinx.HLE.OsHle.Services.Ssl; -using Ryujinx.HLE.OsHle.Services.Vi; -using System; - -namespace Ryujinx.HLE.OsHle.Services -{ - static class ServiceFactory - { - public static IpcService MakeService(string Name) - { - switch (Name) - { - case "acc:u0": - return new IAccountServiceForApplication(); - - case "aoc:u": - return new IAddOnContentManager(); - - case "apm": - return new IManager(); - - case "apm:p": - return new IManager(); - - case "appletAE": - return new IAllSystemAppletProxiesService(); - - case "appletOE": - return new IApplicationProxyService(); - - case "audout:u": - return new IAudioOutManager(); - - case "audren:u": - return new IAudioRendererManager(); - - case "bsd:s": - return new IClient(); - - case "bsd:u": - return new IClient(); - - case "caps:a": - return new IAlbumAccessorService(); - - case "caps:ss": - return new IScreenshotService(); - - case "friend:a": - return new IServiceCreator(); - - case "friend:u": - return new IServiceCreator(); - - case "fsp-srv": - return new IFileSystemProxy(); - - case "hid": - return new IHidServer(); - - case "lm": - return new ILogService(); - - case "mm:u": - return new IRequest(); - - case "nfp:user": - return new IUserManager(); - - case "nifm:u": - return new Nifm.IStaticService(); - - case "ns:ec": - return new IServiceGetterInterface(); - - case "ns:su": - return new ISystemUpdateInterface(); - - case "ns:vm": - return new IVulnerabilityManagerInterface(); - - case "nvdrv": - return new INvDrvServices(); - - case "nvdrv:a": - return new INvDrvServices(); - - case "pctl:s": - return new IParentalControlServiceFactory(); - - case "pctl:r": - return new IParentalControlServiceFactory(); - - case "pctl:a": - return new IParentalControlServiceFactory(); - - case "pctl": - return new IParentalControlServiceFactory(); - - case "pl:u": - return new ISharedFontManager(); - - case "prepo:a": - return new IPrepoService(); - - case "prepo:u": - return new IPrepoService(); - - case "set": - return new ISettingsServer(); - - case "set:sys": - return new ISystemSettingsServer(); - - case "sfdnsres": - return new IResolver(); - - case "sm:": - return new IUserInterface(); - - case "ssl": - return new ISslService(); - - case "time:a": - return new Time.IStaticService(); - - case "time:s": - return new Time.IStaticService(); - - case "time:u": - return new Time.IStaticService(); - - case "vi:m": - return new IManagerRootService(); - - case "vi:s": - return new ISystemRootService(); - - case "vi:u": - return new IApplicationRootService(); - } - - throw new NotImplementedException(Name); - } - } -} diff --git a/Ryujinx.HLE/OsHle/Services/Set/ISettingsServer.cs b/Ryujinx.HLE/OsHle/Services/Set/ISettingsServer.cs deleted file mode 100644 index 5c4f826767..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Set/ISettingsServer.cs +++ /dev/null @@ -1,78 +0,0 @@ -using Ryujinx.HLE.OsHle.Ipc; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Services.Set -{ - class ISettingsServer : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - public ISettingsServer() - { - m_Commands = new Dictionary() - { - { 0, GetLanguageCode }, - { 1, GetAvailableLanguageCodes }, - { 3, GetAvailableLanguageCodeCount }, - { 5, GetAvailableLanguageCodes2 } - }; - } - - public static long GetLanguageCode(ServiceCtx Context) - { - Context.ResponseData.Write(Context.Ns.Os.SystemState.DesiredLanguageCode); - - return 0; - } - - public static long GetAvailableLanguageCodes(ServiceCtx Context) - { - GetAvailableLanguagesCodesImpl( - Context, - Context.Request.RecvListBuff[0].Position, - Context.Request.RecvListBuff[0].Size); - - return 0; - } - - public static long GetAvailableLanguageCodeCount(ServiceCtx Context) - { - Context.ResponseData.Write(SystemStateMgr.LanguageCodes.Length); - - return 0; - } - - public static long GetAvailableLanguageCodes2(ServiceCtx Context) - { - GetAvailableLanguagesCodesImpl( - Context, - Context.Request.ReceiveBuff[0].Position, - Context.Request.ReceiveBuff[0].Size); - - return 0; - } - - public static long GetAvailableLanguagesCodesImpl(ServiceCtx Context, long Position, long Size) - { - int Count = (int)(Size / 8); - - if (Count > SystemStateMgr.LanguageCodes.Length) - { - Count = SystemStateMgr.LanguageCodes.Length; - } - - for (int Index = 0; Index < Count; Index++) - { - Context.Memory.WriteInt64(Position, SystemStateMgr.GetLanguageCode(Index)); - - Position += 8; - } - - Context.ResponseData.Write(Count); - - return 0; - } - } -} diff --git a/Ryujinx.HLE/OsHle/Services/Set/ISystemSettingsServer.cs b/Ryujinx.HLE/OsHle/Services/Set/ISystemSettingsServer.cs deleted file mode 100644 index 6a3ea53782..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Set/ISystemSettingsServer.cs +++ /dev/null @@ -1,149 +0,0 @@ -using Ryujinx.HLE.OsHle.Ipc; -using Ryujinx.HLE.Settings; -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; - -namespace Ryujinx.HLE.OsHle.Services.Set -{ - class ISystemSettingsServer : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - public ISystemSettingsServer() - { - m_Commands = new Dictionary() - { - { 4, GetFirmwareVersion2 }, - { 23, GetColorSetId }, - { 24, SetColorSetId }, - { 38, GetSettingsItemValue } - }; - } - - public static long GetFirmwareVersion2(ServiceCtx Context) - { - long ReplyPos = Context.Request.RecvListBuff[0].Position; - long ReplySize = Context.Request.RecvListBuff[0].Size; - - const byte MajorFWVersion = 0x03; - const byte MinorFWVersion = 0x00; - const byte MicroFWVersion = 0x00; - const byte Unknown = 0x00; //Build? - - const int RevisionNumber = 0x0A; - - const string Platform = "NX"; - const string UnknownHex = "7fbde2b0bba4d14107bf836e4643043d9f6c8e47"; - const string Version = "3.0.0"; - const string Build = "NintendoSDK Firmware for NX 3.0.0-10.0"; - - //http://switchbrew.org/index.php?title=System_Version_Title - using (MemoryStream MS = new MemoryStream(0x100)) - { - BinaryWriter Writer = new BinaryWriter(MS); - - Writer.Write(MajorFWVersion); - Writer.Write(MinorFWVersion); - Writer.Write(MicroFWVersion); - Writer.Write(Unknown); - - Writer.Write(RevisionNumber); - - Writer.Write(Encoding.ASCII.GetBytes(Platform)); - - MS.Seek(0x28, SeekOrigin.Begin); - - Writer.Write(Encoding.ASCII.GetBytes(UnknownHex)); - - MS.Seek(0x68, SeekOrigin.Begin); - - Writer.Write(Encoding.ASCII.GetBytes(Version)); - - MS.Seek(0x80, SeekOrigin.Begin); - - Writer.Write(Encoding.ASCII.GetBytes(Build)); - - Context.Memory.WriteBytes(ReplyPos, MS.ToArray()); - } - - return 0; - } - - public static long GetColorSetId(ServiceCtx Context) - { - Context.ResponseData.Write((int)Context.Ns.Settings.ThemeColor); - - return 0; - } - - public static long SetColorSetId(ServiceCtx Context) - { - int ColorSetId = Context.RequestData.ReadInt32(); - - Context.Ns.Settings.ThemeColor = (ColorSet)ColorSetId; - return 0; - } - - public static long GetSettingsItemValue(ServiceCtx Context) - { - long ClassPos = Context.Request.PtrBuff[0].Position; - long ClassSize = Context.Request.PtrBuff[0].Size; - - long NamePos = Context.Request.PtrBuff[1].Position; - long NameSize = Context.Request.PtrBuff[1].Size; - - long ReplyPos = Context.Request.ReceiveBuff[0].Position; - long ReplySize = Context.Request.ReceiveBuff[0].Size; - - byte[] Class = Context.Memory.ReadBytes(ClassPos, ClassSize); - byte[] Name = Context.Memory.ReadBytes(NamePos, NameSize); - - string AskedSetting = Encoding.ASCII.GetString(Class).Trim('\0') + "!" + Encoding.ASCII.GetString(Name).Trim('\0'); - - NxSettings.Settings.TryGetValue(AskedSetting, out object NxSetting); - - if (NxSetting != null) - { - byte[] SettingBuffer = new byte[ReplySize]; - - if (NxSetting is string StringValue) - { - if (StringValue.Length + 1 > ReplySize) - { - Context.Ns.Log.PrintError(Logging.LogClass.ServiceSet, $"{AskedSetting} String value size is too big!"); - } - else - { - SettingBuffer = Encoding.ASCII.GetBytes(StringValue + "\0"); - } - } - if (NxSetting is int IntValue) - { - SettingBuffer = BitConverter.GetBytes(IntValue); - } - else if (NxSetting is bool BoolValue) - { - SettingBuffer[0] = BoolValue ? (byte)1 : (byte)0; - } - else - { - throw new NotImplementedException(NxSetting.GetType().Name); - } - - Context.Memory.WriteBytes(ReplyPos, SettingBuffer); - - Context.Ns.Log.PrintDebug(Logging.LogClass.ServiceSet, $"{AskedSetting} set value: {NxSetting} as {NxSetting.GetType()}"); - } - else - { - Context.Ns.Log.PrintError(Logging.LogClass.ServiceSet, $"{AskedSetting} not found!"); - } - - return 0; - } - } -} diff --git a/Ryujinx.HLE/OsHle/Services/Sfdnsres/IResolver.cs b/Ryujinx.HLE/OsHle/Services/Sfdnsres/IResolver.cs deleted file mode 100644 index 2fa81eb9b3..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Sfdnsres/IResolver.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Ryujinx.HLE.OsHle.Ipc; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Services.Sfdnsres -{ - class IResolver : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - public IResolver() - { - m_Commands = new Dictionary() - { - //... - }; - } - } -} diff --git a/Ryujinx.HLE/OsHle/Services/Sm/IUserInterface.cs b/Ryujinx.HLE/OsHle/Services/Sm/IUserInterface.cs deleted file mode 100644 index a0a174f5e6..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Sm/IUserInterface.cs +++ /dev/null @@ -1,69 +0,0 @@ -using Ryujinx.HLE.OsHle.Handles; -using Ryujinx.HLE.OsHle.Ipc; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Services.Sm -{ - class IUserInterface : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - private bool IsInitialized; - - public IUserInterface() - { - m_Commands = new Dictionary() - { - { 0, Initialize }, - { 1, GetService } - }; - } - - private const int SmNotInitialized = 0x415; - - public long Initialize(ServiceCtx Context) - { - IsInitialized = true; - - return 0; - } - - public long GetService(ServiceCtx Context) - { - //Only for kernel version > 3.0.0. - if (!IsInitialized) - { - //return SmNotInitialized; - } - - string Name = string.Empty; - - for (int Index = 0; Index < 8 && - Context.RequestData.BaseStream.Position < - Context.RequestData.BaseStream.Length; Index++) - { - byte Chr = Context.RequestData.ReadByte(); - - if (Chr >= 0x20 && Chr < 0x7f) - { - Name += (char)Chr; - } - } - - if (Name == string.Empty) - { - return 0; - } - - KSession Session = new KSession(ServiceFactory.MakeService(Name), Name); - - int Handle = Context.Process.HandleTable.OpenHandle(Session); - - Context.Response.HandleDesc = IpcHandleDesc.MakeMove(Handle); - - return 0; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Ssl/ISslService.cs b/Ryujinx.HLE/OsHle/Services/Ssl/ISslService.cs deleted file mode 100644 index 0bf4c14465..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Ssl/ISslService.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Ryujinx.HLE.Logging; -using Ryujinx.HLE.OsHle.Ipc; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Services.Ssl -{ - class ISslService : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - public ISslService() - { - m_Commands = new Dictionary() - { - { 5, SetInterfaceVersion } - }; - } - - public long SetInterfaceVersion(ServiceCtx Context) - { - int Version = Context.RequestData.ReadInt32(); - - Context.Ns.Log.PrintStub(LogClass.ServiceSsl, "Stubbed."); - - return 0; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Time/IStaticService.cs b/Ryujinx.HLE/OsHle/Services/Time/IStaticService.cs deleted file mode 100644 index 55601a8947..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Time/IStaticService.cs +++ /dev/null @@ -1,74 +0,0 @@ -using Ryujinx.HLE.OsHle.Ipc; -using System; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Services.Time -{ - class IStaticService : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - private static readonly DateTime StartupDate = DateTime.UtcNow; - - public IStaticService() - { - m_Commands = new Dictionary() - { - { 0, GetStandardUserSystemClock }, - { 1, GetStandardNetworkSystemClock }, - { 2, GetStandardSteadyClock }, - { 3, GetTimeZoneService }, - { 4, GetStandardLocalSystemClock }, - { 300, CalculateMonotonicSystemClockBaseTimePoint } - }; - } - - public long GetStandardUserSystemClock(ServiceCtx Context) - { - MakeObject(Context, new ISystemClock(SystemClockType.User)); - - return 0; - } - - public long GetStandardNetworkSystemClock(ServiceCtx Context) - { - MakeObject(Context, new ISystemClock(SystemClockType.Network)); - - return 0; - } - - public long GetStandardSteadyClock(ServiceCtx Context) - { - MakeObject(Context, new ISteadyClock()); - - return 0; - } - - public long GetTimeZoneService(ServiceCtx Context) - { - MakeObject(Context, new ITimeZoneService()); - - return 0; - } - - public long GetStandardLocalSystemClock(ServiceCtx Context) - { - MakeObject(Context, new ISystemClock(SystemClockType.Local)); - - return 0; - } - - public long CalculateMonotonicSystemClockBaseTimePoint(ServiceCtx Context) - { - long TimeOffset = (long)(DateTime.UtcNow - StartupDate).TotalSeconds; - long SystemClockContextEpoch = Context.RequestData.ReadInt64(); - - Context.ResponseData.Write(TimeOffset + SystemClockContextEpoch); - - return 0; - } - - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Time/ISteadyClock.cs b/Ryujinx.HLE/OsHle/Services/Time/ISteadyClock.cs deleted file mode 100644 index e2cb34e3c3..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Time/ISteadyClock.cs +++ /dev/null @@ -1,53 +0,0 @@ -using Ryujinx.HLE.OsHle.Ipc; -using System; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Services.Time -{ - class ISteadyClock : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - private ulong TestOffset; - - public ISteadyClock() - { - m_Commands = new Dictionary() - { - { 0, GetCurrentTimePoint }, - { 1, GetTestOffset }, - { 2, SetTestOffset } - }; - - TestOffset = 0; - } - - public long GetCurrentTimePoint(ServiceCtx Context) - { - Context.ResponseData.Write((long)(System.Diagnostics.Process.GetCurrentProcess().StartTime - DateTime.Now).TotalSeconds); - - for (int i = 0; i < 0x10; i++) - { - Context.ResponseData.Write((byte)0); - } - - return 0; - } - - public long GetTestOffset(ServiceCtx Context) - { - Context.ResponseData.Write(TestOffset); - - return 0; - } - - public long SetTestOffset(ServiceCtx Context) - { - TestOffset = Context.RequestData.ReadUInt64(); - - return 0; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Time/ISystemClock.cs b/Ryujinx.HLE/OsHle/Services/Time/ISystemClock.cs deleted file mode 100644 index 27b65c3c6d..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Time/ISystemClock.cs +++ /dev/null @@ -1,107 +0,0 @@ -using Ryujinx.HLE.OsHle.Ipc; -using System; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Services.Time -{ - class ISystemClock : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); - - private SystemClockType ClockType; - - private DateTime SystemClockContextEpoch; - - private long SystemClockTimePoint; - - private byte[] SystemClockContextEnding; - - private long TimeOffset; - - public ISystemClock(SystemClockType ClockType) - { - m_Commands = new Dictionary() - { - { 0, GetCurrentTime }, - { 1, SetCurrentTime }, - { 2, GetSystemClockContext }, - { 3, SetSystemClockContext } - }; - - this.ClockType = ClockType; - SystemClockContextEpoch = System.Diagnostics.Process.GetCurrentProcess().StartTime; - SystemClockContextEnding = new byte[0x10]; - TimeOffset = 0; - - if (ClockType == SystemClockType.User || - ClockType == SystemClockType.Network) - { - SystemClockContextEpoch = SystemClockContextEpoch.ToUniversalTime(); - } - - SystemClockTimePoint = (long)(SystemClockContextEpoch - Epoch).TotalSeconds; - } - - public long GetCurrentTime(ServiceCtx Context) - { - DateTime CurrentTime = DateTime.Now; - - if (ClockType == SystemClockType.User || - ClockType == SystemClockType.Network) - { - CurrentTime = CurrentTime.ToUniversalTime(); - } - - Context.ResponseData.Write((long)((CurrentTime - Epoch).TotalSeconds) + TimeOffset); - - return 0; - } - - public long SetCurrentTime(ServiceCtx Context) - { - DateTime CurrentTime = DateTime.Now; - - if (ClockType == SystemClockType.User || - ClockType == SystemClockType.Network) - { - CurrentTime = CurrentTime.ToUniversalTime(); - } - - TimeOffset = (Context.RequestData.ReadInt64() - (long)(CurrentTime - Epoch).TotalSeconds); - - return 0; - } - - public long GetSystemClockContext(ServiceCtx Context) - { - Context.ResponseData.Write((long)(SystemClockContextEpoch - Epoch).TotalSeconds); - - // The point in time, TODO: is there a link between epoch and this? - Context.ResponseData.Write(SystemClockTimePoint); - - // This seems to be some kind of identifier? - for (int i = 0; i < 0x10; i++) - { - Context.ResponseData.Write(SystemClockContextEnding[i]); - } - - return 0; - } - - public long SetSystemClockContext(ServiceCtx Context) - { - long NewSystemClockEpoch = Context.RequestData.ReadInt64(); - long NewSystemClockTimePoint = Context.RequestData.ReadInt64(); - - SystemClockContextEpoch = Epoch.Add(TimeSpan.FromSeconds(NewSystemClockEpoch)); - SystemClockTimePoint = NewSystemClockTimePoint; - SystemClockContextEnding = Context.RequestData.ReadBytes(0x10); - - return 0; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Time/ITimeZoneService.cs b/Ryujinx.HLE/OsHle/Services/Time/ITimeZoneService.cs deleted file mode 100644 index 39454d4335..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Time/ITimeZoneService.cs +++ /dev/null @@ -1,76 +0,0 @@ -using Ryujinx.HLE.Logging; -using Ryujinx.HLE.OsHle.Ipc; -using System; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Services.Time -{ - class ITimeZoneService : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); - - public ITimeZoneService() - { - m_Commands = new Dictionary() - { - { 0, GetDeviceLocationName }, - { 101, ToCalendarTimeWithMyRule } - }; - } - - public long GetDeviceLocationName(ServiceCtx Context) - { - Context.Ns.Log.PrintStub(LogClass.ServiceTime, "Stubbed."); - - for (int Index = 0; Index < 0x24; Index++) - { - Context.ResponseData.Write((byte)0); - } - - return 0; - } - - public long ToCalendarTimeWithMyRule(ServiceCtx Context) - { - long PosixTime = Context.RequestData.ReadInt64(); - - DateTime CurrentTime = Epoch.AddSeconds(PosixTime).ToLocalTime(); - - Context.ResponseData.Write((ushort)CurrentTime.Year); - Context.ResponseData.Write((byte)CurrentTime.Month); - Context.ResponseData.Write((byte)CurrentTime.Day); - Context.ResponseData.Write((byte)CurrentTime.Hour); - Context.ResponseData.Write((byte)CurrentTime.Minute); - Context.ResponseData.Write((byte)CurrentTime.Second); - Context.ResponseData.Write((byte)0); - - /* Thanks to TuxSH - struct CalendarAdditionalInfo { - u32 tm_wday; //day of week [0,6] (Sunday = 0) - s32 tm_yday; //day of year [0,365] - struct timezone { - char[8] tz_name; - bool isDaylightSavingTime; - s32 utcOffsetSeconds; - }; - }; - */ - Context.ResponseData.Write((int)CurrentTime.DayOfWeek); - - Context.ResponseData.Write(CurrentTime.DayOfYear - 1); - - //TODO: Find out the names used. - Context.ResponseData.Write(new byte[8]); - - Context.ResponseData.Write((byte)(CurrentTime.IsDaylightSavingTime() ? 1 : 0)); - - Context.ResponseData.Write((int)TimeZoneInfo.Local.GetUtcOffset(CurrentTime).TotalSeconds); - - return 0; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Time/SystemClockType.cs b/Ryujinx.HLE/OsHle/Services/Time/SystemClockType.cs deleted file mode 100644 index d581d9ca5f..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Time/SystemClockType.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Ryujinx.HLE.OsHle.Services.Time -{ - enum SystemClockType - { - User, - Network, - Local, - EphemeralNetwork - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Vi/Display.cs b/Ryujinx.HLE/OsHle/Services/Vi/Display.cs deleted file mode 100644 index 3da51c478f..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Vi/Display.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Ryujinx.HLE.OsHle.Services.Vi -{ - class Display - { - public string Name { get; private set; } - - public Display(string Name) - { - this.Name = Name; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Vi/GbpBuffer.cs b/Ryujinx.HLE/OsHle/Services/Vi/GbpBuffer.cs deleted file mode 100644 index b9e9054ba8..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Vi/GbpBuffer.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System.IO; - -namespace Ryujinx.HLE.OsHle.Services.Android -{ - struct GbpBuffer - { - public int Magic { get; private set; } - public int Width { get; private set; } - public int Height { get; private set; } - public int Stride { get; private set; } - public int Format { get; private set; } - public int Usage { get; private set; } - - public int Pid { get; private set; } - public int RefCount { get; private set; } - - public int FdsCount { get; private set; } - public int IntsCount { get; private set; } - - public byte[] RawData { get; private set; } - - public int Size => RawData.Length + 10 * 4; - - public GbpBuffer(BinaryReader Reader) - { - Magic = Reader.ReadInt32(); - Width = Reader.ReadInt32(); - Height = Reader.ReadInt32(); - Stride = Reader.ReadInt32(); - Format = Reader.ReadInt32(); - Usage = Reader.ReadInt32(); - - Pid = Reader.ReadInt32(); - RefCount = Reader.ReadInt32(); - - FdsCount = Reader.ReadInt32(); - IntsCount = Reader.ReadInt32(); - - RawData = Reader.ReadBytes((FdsCount + IntsCount) * 4); - } - - public void Write(BinaryWriter Writer) - { - Writer.Write(Magic); - Writer.Write(Width); - Writer.Write(Height); - Writer.Write(Stride); - Writer.Write(Format); - Writer.Write(Usage); - - Writer.Write(Pid); - Writer.Write(RefCount); - - Writer.Write(FdsCount); - Writer.Write(IntsCount); - - Writer.Write(RawData); - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Vi/IApplicationDisplayService.cs b/Ryujinx.HLE/OsHle/Services/Vi/IApplicationDisplayService.cs deleted file mode 100644 index 57848319f3..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Vi/IApplicationDisplayService.cs +++ /dev/null @@ -1,212 +0,0 @@ -using Ryujinx.HLE.OsHle.Ipc; -using System.Collections.Generic; -using System.IO; - -using static Ryujinx.HLE.OsHle.Services.Android.Parcel; - -namespace Ryujinx.HLE.OsHle.Services.Vi -{ - class IApplicationDisplayService : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - private IdDictionary Displays; - - public IApplicationDisplayService() - { - m_Commands = new Dictionary() - { - { 100, GetRelayService }, - { 101, GetSystemDisplayService }, - { 102, GetManagerDisplayService }, - { 103, GetIndirectDisplayTransactionService }, - { 1010, OpenDisplay }, - { 1020, CloseDisplay }, - { 1102, GetDisplayResolution }, - { 2020, OpenLayer }, - { 2021, CloseLayer }, - { 2030, CreateStrayLayer }, - { 2031, DestroyStrayLayer }, - { 2101, SetLayerScalingMode }, - { 5202, GetDisplayVSyncEvent } - }; - - Displays = new IdDictionary(); - } - - public long GetRelayService(ServiceCtx Context) - { - MakeObject(Context, new IHOSBinderDriver(Context.Ns.Gpu.Renderer)); - - return 0; - } - - public long GetSystemDisplayService(ServiceCtx Context) - { - MakeObject(Context, new ISystemDisplayService()); - - return 0; - } - - public long GetManagerDisplayService(ServiceCtx Context) - { - MakeObject(Context, new IManagerDisplayService()); - - return 0; - } - - public long GetIndirectDisplayTransactionService(ServiceCtx Context) - { - MakeObject(Context, new IHOSBinderDriver(Context.Ns.Gpu.Renderer)); - - return 0; - } - - public long OpenDisplay(ServiceCtx Context) - { - string Name = GetDisplayName(Context); - - long DisplayId = Displays.Add(new Display(Name)); - - Context.ResponseData.Write(DisplayId); - - return 0; - } - - public long CloseDisplay(ServiceCtx Context) - { - int DisplayId = Context.RequestData.ReadInt32(); - - Displays.Delete(DisplayId); - - return 0; - } - - public long GetDisplayResolution(ServiceCtx Context) - { - long DisplayId = Context.RequestData.ReadInt32(); - - Context.ResponseData.Write(1280); - Context.ResponseData.Write(720); - - return 0; - } - - public long OpenLayer(ServiceCtx Context) - { - long LayerId = Context.RequestData.ReadInt64(); - long UserId = Context.RequestData.ReadInt64(); - - long ParcelPtr = Context.Request.ReceiveBuff[0].Position; - - byte[] Parcel = MakeIGraphicsBufferProducer(ParcelPtr); - - Context.Memory.WriteBytes(ParcelPtr, Parcel); - - Context.ResponseData.Write((long)Parcel.Length); - - return 0; - } - - public long CloseLayer(ServiceCtx Context) - { - long LayerId = Context.RequestData.ReadInt64(); - - return 0; - } - - public long CreateStrayLayer(ServiceCtx Context) - { - long LayerFlags = Context.RequestData.ReadInt64(); - long DisplayId = Context.RequestData.ReadInt64(); - - long ParcelPtr = Context.Request.ReceiveBuff[0].Position; - - Display Disp = Displays.GetData((int)DisplayId); - - byte[] Parcel = MakeIGraphicsBufferProducer(ParcelPtr); - - Context.Memory.WriteBytes(ParcelPtr, Parcel); - - Context.ResponseData.Write(0L); - Context.ResponseData.Write((long)Parcel.Length); - - return 0; - } - - public long DestroyStrayLayer(ServiceCtx Context) - { - return 0; - } - - public long SetLayerScalingMode(ServiceCtx Context) - { - int ScalingMode = Context.RequestData.ReadInt32(); - long Unknown = Context.RequestData.ReadInt64(); - - return 0; - } - - public long GetDisplayVSyncEvent(ServiceCtx Context) - { - string Name = GetDisplayName(Context); - - int Handle = Context.Process.HandleTable.OpenHandle(Context.Ns.Os.VsyncEvent); - - Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); - - return 0; - } - - private byte[] MakeIGraphicsBufferProducer(long BasePtr) - { - long Id = 0x20; - long CookiePtr = 0L; - - using (MemoryStream MS = new MemoryStream()) - { - BinaryWriter Writer = new BinaryWriter(MS); - - //flat_binder_object (size is 0x28) - Writer.Write(2); //Type (BINDER_TYPE_WEAK_BINDER) - Writer.Write(0); //Flags - Writer.Write((int)(Id >> 0)); - Writer.Write((int)(Id >> 32)); - Writer.Write((int)(CookiePtr >> 0)); - Writer.Write((int)(CookiePtr >> 32)); - Writer.Write((byte)'d'); - Writer.Write((byte)'i'); - Writer.Write((byte)'s'); - Writer.Write((byte)'p'); - Writer.Write((byte)'d'); - Writer.Write((byte)'r'); - Writer.Write((byte)'v'); - Writer.Write((byte)'\0'); - Writer.Write(0L); //Pad - - return MakeParcel(MS.ToArray(), new byte[] { 0, 0, 0, 0 }); - } - } - - private string GetDisplayName(ServiceCtx Context) - { - string Name = string.Empty; - - for (int Index = 0; Index < 8 && - Context.RequestData.BaseStream.Position < - Context.RequestData.BaseStream.Length; Index++) - { - byte Chr = Context.RequestData.ReadByte(); - - if (Chr >= 0x20 && Chr < 0x7f) - { - Name += (char)Chr; - } - } - - return Name; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Vi/IApplicationRootService.cs b/Ryujinx.HLE/OsHle/Services/Vi/IApplicationRootService.cs deleted file mode 100644 index 93b05156da..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Vi/IApplicationRootService.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Ryujinx.HLE.OsHle.Ipc; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Services.Vi -{ - class IApplicationRootService : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - public IApplicationRootService() - { - m_Commands = new Dictionary() - { - { 0, GetDisplayService } - }; - } - - public long GetDisplayService(ServiceCtx Context) - { - int ServiceType = Context.RequestData.ReadInt32(); - - MakeObject(Context, new IApplicationDisplayService()); - - return 0; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Vi/IHOSBinderDriver.cs b/Ryujinx.HLE/OsHle/Services/Vi/IHOSBinderDriver.cs deleted file mode 100644 index 85283b75c1..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Vi/IHOSBinderDriver.cs +++ /dev/null @@ -1,100 +0,0 @@ -using Ryujinx.Graphics.Gal; -using Ryujinx.HLE.OsHle.Handles; -using Ryujinx.HLE.OsHle.Ipc; -using Ryujinx.HLE.OsHle.Services.Android; -using System; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Services.Vi -{ - class IHOSBinderDriver : IpcService, IDisposable - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - private KEvent ReleaseEvent; - - private NvFlinger Flinger; - - public IHOSBinderDriver(IGalRenderer Renderer) - { - m_Commands = new Dictionary() - { - { 0, TransactParcel }, - { 1, AdjustRefcount }, - { 2, GetNativeHandle }, - { 3, TransactParcelAuto } - }; - - ReleaseEvent = new KEvent(); - - Flinger = new NvFlinger(Renderer, ReleaseEvent); - } - - public long TransactParcel(ServiceCtx Context) - { - int Id = Context.RequestData.ReadInt32(); - int Code = Context.RequestData.ReadInt32(); - - long DataPos = Context.Request.SendBuff[0].Position; - long DataSize = Context.Request.SendBuff[0].Size; - - byte[] Data = Context.Memory.ReadBytes(DataPos, DataSize); - - Data = Parcel.GetParcelData(Data); - - return Flinger.ProcessParcelRequest(Context, Data, Code); - } - - public long TransactParcelAuto(ServiceCtx Context) - { - int Id = Context.RequestData.ReadInt32(); - int Code = Context.RequestData.ReadInt32(); - - (long DataPos, long DataSize) = Context.Request.GetBufferType0x21(); - - byte[] Data = Context.Memory.ReadBytes(DataPos, DataSize); - - Data = Parcel.GetParcelData(Data); - - return Flinger.ProcessParcelRequest(Context, Data, Code); - } - - public long AdjustRefcount(ServiceCtx Context) - { - int Id = Context.RequestData.ReadInt32(); - int AddVal = Context.RequestData.ReadInt32(); - int Type = Context.RequestData.ReadInt32(); - - return 0; - } - - public long GetNativeHandle(ServiceCtx Context) - { - int Id = Context.RequestData.ReadInt32(); - uint Unk = Context.RequestData.ReadUInt32(); - - int Handle = Context.Process.HandleTable.OpenHandle(ReleaseEvent); - - Context.Response.HandleDesc = IpcHandleDesc.MakeMove(Handle); - - return 0; - } - - public void Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool Disposing) - { - if (Disposing) - { - ReleaseEvent.Dispose(); - - Flinger.Dispose(); - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Vi/IManagerDisplayService.cs b/Ryujinx.HLE/OsHle/Services/Vi/IManagerDisplayService.cs deleted file mode 100644 index d7a51b0e83..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Vi/IManagerDisplayService.cs +++ /dev/null @@ -1,49 +0,0 @@ -using Ryujinx.HLE.Logging; -using Ryujinx.HLE.OsHle.Ipc; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Services.Vi -{ - class IManagerDisplayService : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - public IManagerDisplayService() - { - m_Commands = new Dictionary() - { - { 2010, CreateManagedLayer }, - { 2011, DestroyManagedLayer }, - { 6000, AddToLayerStack }, - { 6002, SetLayerVisibility } - }; - } - - public static long CreateManagedLayer(ServiceCtx Context) - { - Context.Ns.Log.PrintStub(LogClass.ServiceVi, "Stubbed."); - Context.ResponseData.Write(0L); //LayerId - return 0; - } - - public long DestroyManagedLayer(ServiceCtx Context) - { - Context.Ns.Log.PrintStub(LogClass.ServiceVi, "Stubbed."); - return 0; - } - - public static long AddToLayerStack(ServiceCtx Context) - { - Context.Ns.Log.PrintStub(LogClass.ServiceVi, "Stubbed."); - return 0; - } - - public static long SetLayerVisibility(ServiceCtx Context) - { - Context.Ns.Log.PrintStub(LogClass.ServiceVi, "Stubbed."); - return 0; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Vi/IManagerRootService.cs b/Ryujinx.HLE/OsHle/Services/Vi/IManagerRootService.cs deleted file mode 100644 index 7c131dac87..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Vi/IManagerRootService.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Ryujinx.HLE.OsHle.Ipc; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Services.Vi -{ - class IManagerRootService : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - public IManagerRootService() - { - m_Commands = new Dictionary() - { - { 2, GetDisplayService } - }; - } - - public long GetDisplayService(ServiceCtx Context) - { - int ServiceType = Context.RequestData.ReadInt32(); - - MakeObject(Context, new IApplicationDisplayService()); - - return 0; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Vi/ISystemDisplayService.cs b/Ryujinx.HLE/OsHle/Services/Vi/ISystemDisplayService.cs deleted file mode 100644 index 360268b9be..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Vi/ISystemDisplayService.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Ryujinx.HLE.Logging; -using Ryujinx.HLE.OsHle.Ipc; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Services.Vi -{ - class ISystemDisplayService : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - public ISystemDisplayService() - { - m_Commands = new Dictionary() - { - { 2205, SetLayerZ }, - { 2207, SetLayerVisibility }, - { 3200, GetDisplayMode } - }; - } - - public static long SetLayerZ(ServiceCtx Context) - { - Context.Ns.Log.PrintStub(LogClass.ServiceVi, "Stubbed."); - return 0; - } - - public static long SetLayerVisibility(ServiceCtx Context) - { - Context.Ns.Log.PrintStub(LogClass.ServiceVi, "Stubbed."); - return 0; - } - - public static long GetDisplayMode(ServiceCtx Context) - { - Context.ResponseData.Write(1280); - Context.ResponseData.Write(720); - Context.ResponseData.Write(60.0f); - Context.ResponseData.Write(0); - return 0; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Vi/ISystemRootService.cs b/Ryujinx.HLE/OsHle/Services/Vi/ISystemRootService.cs deleted file mode 100644 index 21581baa08..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Vi/ISystemRootService.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Ryujinx.HLE.OsHle.Ipc; -using System.Collections.Generic; - -namespace Ryujinx.HLE.OsHle.Services.Vi -{ - class ISystemRootService : IpcService - { - private Dictionary m_Commands; - - public override IReadOnlyDictionary Commands => m_Commands; - - public ISystemRootService() - { - m_Commands = new Dictionary() - { - { 1, GetDisplayService } - }; - } - - public long GetDisplayService(ServiceCtx Context) - { - int ServiceType = Context.RequestData.ReadInt32(); - - MakeObject(Context, new IApplicationDisplayService()); - - return 0; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Vi/NvFlinger.cs b/Ryujinx.HLE/OsHle/Services/Vi/NvFlinger.cs deleted file mode 100644 index a3ed3ab51c..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Vi/NvFlinger.cs +++ /dev/null @@ -1,453 +0,0 @@ -using Ryujinx.Graphics.Gal; -using Ryujinx.HLE.Gpu.Texture; -using Ryujinx.HLE.Logging; -using Ryujinx.HLE.OsHle.Handles; -using Ryujinx.HLE.OsHle.Services.Nv.NvMap; -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; -using System.Threading; - -using static Ryujinx.HLE.OsHle.Services.Android.Parcel; - -namespace Ryujinx.HLE.OsHle.Services.Android -{ - class NvFlinger : IDisposable - { - private delegate long ServiceProcessParcel(ServiceCtx Context, BinaryReader ParcelReader); - - private Dictionary<(string, int), ServiceProcessParcel> Commands; - - private KEvent ReleaseEvent; - - private IGalRenderer Renderer; - - private const int BufferQueueCount = 0x40; - private const int BufferQueueMask = BufferQueueCount - 1; - - [Flags] - private enum HalTransform - { - FlipX = 1 << 0, - FlipY = 1 << 1, - Rotate90 = 1 << 2 - } - - private enum BufferState - { - Free, - Dequeued, - Queued, - Acquired - } - - private struct Rect - { - public int Top; - public int Left; - public int Right; - public int Bottom; - } - - private struct BufferEntry - { - public BufferState State; - - public HalTransform Transform; - - public Rect Crop; - - public GbpBuffer Data; - } - - private BufferEntry[] BufferQueue; - - private ManualResetEvent WaitBufferFree; - - private bool Disposed; - - public NvFlinger(IGalRenderer Renderer, KEvent ReleaseEvent) - { - Commands = new Dictionary<(string, int), ServiceProcessParcel>() - { - { ("android.gui.IGraphicBufferProducer", 0x1), GbpRequestBuffer }, - { ("android.gui.IGraphicBufferProducer", 0x3), GbpDequeueBuffer }, - { ("android.gui.IGraphicBufferProducer", 0x4), GbpDetachBuffer }, - { ("android.gui.IGraphicBufferProducer", 0x7), GbpQueueBuffer }, - { ("android.gui.IGraphicBufferProducer", 0x8), GbpCancelBuffer }, - { ("android.gui.IGraphicBufferProducer", 0x9), GbpQuery }, - { ("android.gui.IGraphicBufferProducer", 0xa), GbpConnect }, - { ("android.gui.IGraphicBufferProducer", 0xb), GbpDisconnect }, - { ("android.gui.IGraphicBufferProducer", 0xe), GbpPreallocBuffer } - }; - - this.Renderer = Renderer; - this.ReleaseEvent = ReleaseEvent; - - BufferQueue = new BufferEntry[0x40]; - - WaitBufferFree = new ManualResetEvent(false); - } - - public long ProcessParcelRequest(ServiceCtx Context, byte[] ParcelData, int Code) - { - using (MemoryStream MS = new MemoryStream(ParcelData)) - { - BinaryReader Reader = new BinaryReader(MS); - - MS.Seek(4, SeekOrigin.Current); - - int StrSize = Reader.ReadInt32(); - - string InterfaceName = Encoding.Unicode.GetString(Reader.ReadBytes(StrSize * 2)); - - long Remainder = MS.Position & 0xf; - - if (Remainder != 0) - { - MS.Seek(0x10 - Remainder, SeekOrigin.Current); - } - - MS.Seek(0x50, SeekOrigin.Begin); - - if (Commands.TryGetValue((InterfaceName, Code), out ServiceProcessParcel ProcReq)) - { - Context.Ns.Log.PrintDebug(LogClass.ServiceVi, $"{InterfaceName} {ProcReq.Method.Name}"); - - return ProcReq(Context, Reader); - } - else - { - throw new NotImplementedException($"{InterfaceName} {Code}"); - } - } - } - - private long GbpRequestBuffer(ServiceCtx Context, BinaryReader ParcelReader) - { - int Slot = ParcelReader.ReadInt32(); - - using (MemoryStream MS = new MemoryStream()) - { - BinaryWriter Writer = new BinaryWriter(MS); - - BufferEntry Entry = BufferQueue[Slot]; - - int BufferCount = 1; //? - long BufferSize = Entry.Data.Size; - - Writer.Write(BufferCount); - Writer.Write(BufferSize); - - Entry.Data.Write(Writer); - - Writer.Write(0); - - return MakeReplyParcel(Context, MS.ToArray()); - } - } - - private long GbpDequeueBuffer(ServiceCtx Context, BinaryReader ParcelReader) - { - //TODO: Errors. - int Format = ParcelReader.ReadInt32(); - int Width = ParcelReader.ReadInt32(); - int Height = ParcelReader.ReadInt32(); - int GetTimestamps = ParcelReader.ReadInt32(); - int Usage = ParcelReader.ReadInt32(); - - int Slot = GetFreeSlotBlocking(Width, Height); - - return MakeReplyParcel(Context, Slot, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); - } - - private long GbpQueueBuffer(ServiceCtx Context, BinaryReader ParcelReader) - { - Context.Ns.Statistics.RecordGameFrameTime(); - - //TODO: Errors. - int Slot = ParcelReader.ReadInt32(); - int Unknown4 = ParcelReader.ReadInt32(); - int Unknown8 = ParcelReader.ReadInt32(); - int Unknownc = ParcelReader.ReadInt32(); - int Timestamp = ParcelReader.ReadInt32(); - int IsAutoTimestamp = ParcelReader.ReadInt32(); - int CropTop = ParcelReader.ReadInt32(); - int CropLeft = ParcelReader.ReadInt32(); - int CropRight = ParcelReader.ReadInt32(); - int CropBottom = ParcelReader.ReadInt32(); - int ScalingMode = ParcelReader.ReadInt32(); - int Transform = ParcelReader.ReadInt32(); - int StickyTransform = ParcelReader.ReadInt32(); - int Unknown34 = ParcelReader.ReadInt32(); - int Unknown38 = ParcelReader.ReadInt32(); - int IsFenceValid = ParcelReader.ReadInt32(); - int Fence0Id = ParcelReader.ReadInt32(); - int Fence0Value = ParcelReader.ReadInt32(); - int Fence1Id = ParcelReader.ReadInt32(); - int Fence1Value = ParcelReader.ReadInt32(); - - BufferQueue[Slot].Transform = (HalTransform)Transform; - - BufferQueue[Slot].Crop.Top = CropTop; - BufferQueue[Slot].Crop.Left = CropLeft; - BufferQueue[Slot].Crop.Right = CropRight; - BufferQueue[Slot].Crop.Bottom = CropBottom; - - BufferQueue[Slot].State = BufferState.Queued; - - SendFrameBuffer(Context, Slot); - - return MakeReplyParcel(Context, 1280, 720, 0, 0, 0); - } - - private long GbpDetachBuffer(ServiceCtx Context, BinaryReader ParcelReader) - { - return MakeReplyParcel(Context, 0); - } - - private long GbpCancelBuffer(ServiceCtx Context, BinaryReader ParcelReader) - { - //TODO: Errors. - int Slot = ParcelReader.ReadInt32(); - - BufferQueue[Slot].State = BufferState.Free; - - return MakeReplyParcel(Context, 0); - } - - private long GbpQuery(ServiceCtx Context, BinaryReader ParcelReader) - { - return MakeReplyParcel(Context, 0, 0); - } - - private long GbpConnect(ServiceCtx Context, BinaryReader ParcelReader) - { - return MakeReplyParcel(Context, 1280, 720, 0, 0, 0); - } - - private long GbpDisconnect(ServiceCtx Context, BinaryReader ParcelReader) - { - return MakeReplyParcel(Context, 0); - } - - private long GbpPreallocBuffer(ServiceCtx Context, BinaryReader ParcelReader) - { - int Slot = ParcelReader.ReadInt32(); - - int BufferCount = ParcelReader.ReadInt32(); - - if (BufferCount > 0) - { - long BufferSize = ParcelReader.ReadInt64(); - - BufferQueue[Slot].State = BufferState.Free; - - BufferQueue[Slot].Data = new GbpBuffer(ParcelReader); - } - - return MakeReplyParcel(Context, 0); - } - - private long MakeReplyParcel(ServiceCtx Context, params int[] Ints) - { - using (MemoryStream MS = new MemoryStream()) - { - BinaryWriter Writer = new BinaryWriter(MS); - - foreach (int Int in Ints) - { - Writer.Write(Int); - } - - return MakeReplyParcel(Context, MS.ToArray()); - } - } - - private long MakeReplyParcel(ServiceCtx Context, byte[] Data) - { - long ReplyPos = Context.Request.ReceiveBuff[0].Position; - long ReplySize = Context.Request.ReceiveBuff[0].Size; - - byte[] Reply = MakeParcel(Data, new byte[0]); - - Context.Memory.WriteBytes(ReplyPos, Reply); - - return 0; - } - - private void SendFrameBuffer(ServiceCtx Context, int Slot) - { - int FbWidth = 1280; - int FbHeight = 720; - - int NvMapHandle = BitConverter.ToInt32(BufferQueue[Slot].Data.RawData, 0x4c); - int BufferOffset = BitConverter.ToInt32(BufferQueue[Slot].Data.RawData, 0x50); - - NvMapHandle Map = NvMapIoctl.GetNvMap(Context, NvMapHandle);; - - long FbAddr = Map.Address + BufferOffset; - - BufferQueue[Slot].State = BufferState.Acquired; - - Rect Crop = BufferQueue[Slot].Crop; - - int RealWidth = FbWidth; - int RealHeight = FbHeight; - - float XSign = BufferQueue[Slot].Transform.HasFlag(HalTransform.FlipX) ? -1 : 1; - float YSign = BufferQueue[Slot].Transform.HasFlag(HalTransform.FlipY) ? -1 : 1; - - float ScaleX = 1; - float ScaleY = 1; - - float OffsX = 0; - float OffsY = 0; - - if (Crop.Right != 0 && - Crop.Bottom != 0) - { - //Who knows if this is right, I was never good with math... - RealWidth = Crop.Right - Crop.Left; - RealHeight = Crop.Bottom - Crop.Top; - - if (BufferQueue[Slot].Transform.HasFlag(HalTransform.Rotate90)) - { - ScaleY = (float)FbHeight / RealHeight; - ScaleX = (float)FbWidth / RealWidth; - - OffsY = ((-(float)Crop.Left / Crop.Right) + ScaleX - 1) * -XSign; - OffsX = ((-(float)Crop.Top / Crop.Bottom) + ScaleY - 1) * -YSign; - } - else - { - ScaleX = (float)FbWidth / RealWidth; - ScaleY = (float)FbHeight / RealHeight; - - OffsX = ((-(float)Crop.Left / Crop.Right) + ScaleX - 1) * XSign; - OffsY = ((-(float)Crop.Top / Crop.Bottom) + ScaleY - 1) * -YSign; - } - } - - ScaleX *= XSign; - ScaleY *= YSign; - - float Rotate = 0; - - if (BufferQueue[Slot].Transform.HasFlag(HalTransform.Rotate90)) - { - Rotate = -MathF.PI * 0.5f; - } - - Renderer.QueueAction(() => Renderer.FrameBuffer.SetTransform(ScaleX, ScaleY, Rotate, OffsX, OffsY)); - - //TODO: Support double buffering here aswell, it is broken for GPU - //frame buffers because it seems to be completely out of sync. - if (Context.Ns.Gpu.Engine3d.IsFrameBufferPosition(FbAddr)) - { - //Frame buffer is rendered to by the GPU, we can just - //bind the frame buffer texture, it's not necessary to read anything. - Renderer.QueueAction(() => Renderer.FrameBuffer.Set(FbAddr)); - } - else - { - //Frame buffer is not set on the GPU registers, in this case - //assume that the app is manually writing to it. - TextureInfo Texture = new TextureInfo(FbAddr, FbWidth, FbHeight); - - byte[] Data = TextureReader.Read(Context.Memory, Texture); - - Renderer.QueueAction(() => Renderer.FrameBuffer.Set(Data, FbWidth, FbHeight)); - } - - Context.Ns.Gpu.Renderer.QueueAction(() => ReleaseBuffer(Slot)); - } - - private void ReleaseBuffer(int Slot) - { - BufferQueue[Slot].State = BufferState.Free; - - ReleaseEvent.WaitEvent.Set(); - - lock (WaitBufferFree) - { - WaitBufferFree.Set(); - } - } - - private int GetFreeSlotBlocking(int Width, int Height) - { - int Slot; - - do - { - lock (WaitBufferFree) - { - if ((Slot = GetFreeSlot(Width, Height)) != -1) - { - break; - } - - if (Disposed) - { - break; - } - - WaitBufferFree.Reset(); - } - - WaitBufferFree.WaitOne(); - } - while (!Disposed); - - return Slot; - } - - private int GetFreeSlot(int Width, int Height) - { - lock (BufferQueue) - { - for (int Slot = 0; Slot < BufferQueue.Length; Slot++) - { - if (BufferQueue[Slot].State != BufferState.Free) - { - continue; - } - - GbpBuffer Data = BufferQueue[Slot].Data; - - if (Data.Width == Width && - Data.Height == Height) - { - BufferQueue[Slot].State = BufferState.Dequeued; - - return Slot; - } - } - } - - return -1; - } - - public void Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool Disposing) - { - if (Disposing && !Disposed) - { - Disposed = true; - - lock (WaitBufferFree) - { - WaitBufferFree.Set(); - } - - WaitBufferFree.Dispose(); - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Vi/Parcel.cs b/Ryujinx.HLE/OsHle/Services/Vi/Parcel.cs deleted file mode 100644 index 009ed8c127..0000000000 --- a/Ryujinx.HLE/OsHle/Services/Vi/Parcel.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using System.IO; - -namespace Ryujinx.HLE.OsHle.Services.Android -{ - static class Parcel - { - public static byte[] GetParcelData(byte[] Parcel) - { - if (Parcel == null) - { - throw new ArgumentNullException(nameof(Parcel)); - } - - using (MemoryStream MS = new MemoryStream(Parcel)) - { - BinaryReader Reader = new BinaryReader(MS); - - int DataSize = Reader.ReadInt32(); - int DataOffset = Reader.ReadInt32(); - int ObjsSize = Reader.ReadInt32(); - int ObjsOffset = Reader.ReadInt32(); - - MS.Seek(DataOffset - 0x10, SeekOrigin.Current); - - return Reader.ReadBytes(DataSize); - } - } - - public static byte[] MakeParcel(byte[] Data, byte[] Objs) - { - if (Data == null) - { - throw new ArgumentNullException(nameof(Data)); - } - - if (Objs == null) - { - throw new ArgumentNullException(nameof(Objs)); - } - - using (MemoryStream MS = new MemoryStream()) - { - BinaryWriter Writer = new BinaryWriter(MS); - - Writer.Write(Data.Length); - Writer.Write(0x10); - Writer.Write(Objs.Length); - Writer.Write(Data.Length + 0x10); - - Writer.Write(Data); - Writer.Write(Objs); - - return MS.ToArray(); - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/SystemStateMgr.cs b/Ryujinx.HLE/OsHle/SystemStateMgr.cs deleted file mode 100644 index e78082c45a..0000000000 --- a/Ryujinx.HLE/OsHle/SystemStateMgr.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System; - -namespace Ryujinx.HLE.OsHle -{ - public class SystemStateMgr - { - internal static string[] LanguageCodes = new string[] - { - "ja", - "en-US", - "fr", - "de", - "it", - "es", - "zh-CN", - "ko", - "nl", - "pt", - "ru", - "zh-TW", - "en-GB", - "fr-CA", - "es-419", - "zh-Hans", - "zh-Hant" - }; - - internal static string[] AudioOutputs = new string[] - { - "AudioTvOutput", - "AudioStereoJackOutput", - "AudioBuiltInSpeakerOutput" - }; - - internal long DesiredLanguageCode { get; private set; } - - internal string ActiveAudioOutput { get; private set; } - - public SystemStateMgr() - { - SetLanguage(SystemLanguage.AmericanEnglish); - - SetAudioOutputAsBuiltInSpeaker(); - } - - public void SetLanguage(SystemLanguage Language) - { - DesiredLanguageCode = GetLanguageCode((int)Language); - } - - public void SetAudioOutputAsTv() - { - ActiveAudioOutput = AudioOutputs[0]; - } - - public void SetAudioOutputAsStereoJack() - { - ActiveAudioOutput = AudioOutputs[1]; - } - - public void SetAudioOutputAsBuiltInSpeaker() - { - ActiveAudioOutput = AudioOutputs[2]; - } - - internal static long GetLanguageCode(int Index) - { - if ((uint)Index >= LanguageCodes.Length) - { - throw new ArgumentOutOfRangeException(nameof(Index)); - } - - long Code = 0; - int Shift = 0; - - foreach (char Chr in LanguageCodes[Index]) - { - Code |= (long)(byte)Chr << Shift++ * 8; - } - - return Code; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Utilities/EndianSwap.cs b/Ryujinx.HLE/OsHle/Utilities/EndianSwap.cs deleted file mode 100644 index 46a2edcb6c..0000000000 --- a/Ryujinx.HLE/OsHle/Utilities/EndianSwap.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Ryujinx.HLE.OsHle.Utilities -{ - static class EndianSwap - { - public static short Swap16(short Value) => (short)(((Value >> 8) & 0xff) | (Value << 8)); - - public static int Swap32(int Value) - { - uint UintVal = (uint)Value; - - return (int)(((UintVal >> 24) & 0x000000ff) | - ((UintVal >> 8) & 0x0000ff00) | - ((UintVal << 8) & 0x00ff0000) | - ((UintVal << 24) & 0xff000000)); - } - } -} diff --git a/Ryujinx.HLE/OsHle/Utilities/IntUtils.cs b/Ryujinx.HLE/OsHle/Utilities/IntUtils.cs deleted file mode 100644 index ba0726c317..0000000000 --- a/Ryujinx.HLE/OsHle/Utilities/IntUtils.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Ryujinx.HLE.OsHle.Utilities -{ - static class IntUtils - { - public static int RoundUp(int Value, int Size) - { - return (Value + (Size - 1)) & ~(Size - 1); - } - - public static long RoundUp(long Value, int Size) - { - return (Value + (Size - 1)) & ~((long)Size - 1); - } - } -} diff --git a/Ryujinx.HLE/PerformanceStatistics.cs b/Ryujinx.HLE/PerformanceStatistics.cs index 9bc3d6b477..896ab67b09 100644 --- a/Ryujinx.HLE/PerformanceStatistics.cs +++ b/Ryujinx.HLE/PerformanceStatistics.cs @@ -1,7 +1,7 @@ -using System.Diagnostics; +using Ryujinx.Profiler; +using System.Diagnostics; using System.Timers; - namespace Ryujinx.HLE { public class PerformanceStatistics @@ -11,43 +11,43 @@ namespace Ryujinx.HLE private const int FrameTypeSystem = 0; private const int FrameTypeGame = 1; - private double[] AverageFrameRate; - private double[] AccumulatedFrameTime; - private double[] PreviousFrameTime; + private double[] _averageFrameRate; + private double[] _accumulatedFrameTime; + private double[] _previousFrameTime; - private long[] FramesRendered; + private long[] _framesRendered; - private object[] FrameLock; + private object[] _frameLock; - private double TicksToSeconds; + private double _ticksToSeconds; - private Stopwatch ExecutionTime; + private Stopwatch _executionTime; - private Timer ResetTimer; + private Timer _resetTimer; public PerformanceStatistics() { - AverageFrameRate = new double[2]; - AccumulatedFrameTime = new double[2]; - PreviousFrameTime = new double[2]; + _averageFrameRate = new double[2]; + _accumulatedFrameTime = new double[2]; + _previousFrameTime = new double[2]; - FramesRendered = new long[2]; + _framesRendered = new long[2]; - FrameLock = new object[] { new object(), new object() }; + _frameLock = new object[] { new object(), new object() }; - ExecutionTime = new Stopwatch(); + _executionTime = new Stopwatch(); - ExecutionTime.Start(); + _executionTime.Start(); - ResetTimer = new Timer(1000); + _resetTimer = new Timer(1000); - ResetTimer.Elapsed += ResetTimerElapsed; + _resetTimer.Elapsed += ResetTimerElapsed; - ResetTimer.AutoReset = true; + _resetTimer.AutoReset = true; - ResetTimer.Start(); + _resetTimer.Start(); - TicksToSeconds = 1.0 / Stopwatch.Frequency; + _ticksToSeconds = 1.0 / Stopwatch.Frequency; } private void ResetTimerElapsed(object sender, ElapsedEventArgs e) @@ -56,64 +56,66 @@ namespace Ryujinx.HLE CalculateAverageFrameRate(FrameTypeGame); } - private void CalculateAverageFrameRate(int FrameType) + private void CalculateAverageFrameRate(int frameType) { - double FrameRate = 0; + double frameRate = 0; - if (AccumulatedFrameTime[FrameType] > 0) + if (_accumulatedFrameTime[frameType] > 0) { - FrameRate = FramesRendered[FrameType] / AccumulatedFrameTime[FrameType]; + frameRate = _framesRendered[frameType] / _accumulatedFrameTime[frameType]; } - lock (FrameLock[FrameType]) + lock (_frameLock[frameType]) { - AverageFrameRate[FrameType] = LinearInterpolate(AverageFrameRate[FrameType], FrameRate); + _averageFrameRate[frameType] = LinearInterpolate(_averageFrameRate[frameType], frameRate); - FramesRendered[FrameType] = 0; + _framesRendered[frameType] = 0; - AccumulatedFrameTime[FrameType] = 0; + _accumulatedFrameTime[frameType] = 0; } } - private double LinearInterpolate(double Old, double New) + private double LinearInterpolate(double old, double New) { - return Old * (1.0 - FrameRateWeight) + New * FrameRateWeight; + return old * (1.0 - FrameRateWeight) + New * FrameRateWeight; } public void RecordSystemFrameTime() { RecordFrameTime(FrameTypeSystem); + Profile.FlagTime(TimingFlagType.SystemFrame); } public void RecordGameFrameTime() { RecordFrameTime(FrameTypeGame); + Profile.FlagTime(TimingFlagType.FrameSwap); } - private void RecordFrameTime(int FrameType) + private void RecordFrameTime(int frameType) { - double CurrentFrameTime = ExecutionTime.ElapsedTicks * TicksToSeconds; + double currentFrameTime = _executionTime.ElapsedTicks * _ticksToSeconds; - double ElapsedFrameTime = CurrentFrameTime - PreviousFrameTime[FrameType]; + double elapsedFrameTime = currentFrameTime - _previousFrameTime[frameType]; - PreviousFrameTime[FrameType] = CurrentFrameTime; + _previousFrameTime[frameType] = currentFrameTime; - lock (FrameLock[FrameType]) + lock (_frameLock[frameType]) { - AccumulatedFrameTime[FrameType] += ElapsedFrameTime; + _accumulatedFrameTime[frameType] += elapsedFrameTime; - FramesRendered[FrameType]++; + _framesRendered[frameType]++; } } public double GetSystemFrameRate() { - return AverageFrameRate[FrameTypeSystem]; + return _averageFrameRate[FrameTypeSystem]; } public double GetGameFrameRate() { - return AverageFrameRate[FrameTypeGame]; + return _averageFrameRate[FrameTypeGame]; } } } diff --git a/Ryujinx.HLE/Ryujinx.HLE.csproj b/Ryujinx.HLE/Ryujinx.HLE.csproj index acef4be9e7..15c0c8c0c1 100644 --- a/Ryujinx.HLE/Ryujinx.HLE.csproj +++ b/Ryujinx.HLE/Ryujinx.HLE.csproj @@ -1,22 +1,59 @@ - + - netcoreapp2.1 - win10-x64;osx-x64;linux-x64 + netcoreapp3.0 + win-x64;osx-x64;linux-x64 + Debug;Release;Profile Debug;Profile Release true + + true + TRACE;USE_PROFILING + false + + true + + true + TRACE;USE_PROFILING + true + + + + + NU1605 + + + + + + + + + + + + - - + + + + + + + + + + + diff --git a/Ryujinx.HLE/RyujinxProfileImage.jpg b/Ryujinx.HLE/RyujinxProfileImage.jpg new file mode 100644 index 0000000000..55e4c43cf2 Binary files /dev/null and b/Ryujinx.HLE/RyujinxProfileImage.jpg differ diff --git a/Ryujinx.HLE/Settings/SystemSettings.cs b/Ryujinx.HLE/Settings/SystemSettings.cs deleted file mode 100644 index 555e83bbae..0000000000 --- a/Ryujinx.HLE/Settings/SystemSettings.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Ryujinx.HLE.Settings -{ - public class SystemSettings - { - public ColorSet ThemeColor; - } -} diff --git a/Ryujinx.HLE/Switch.cs b/Ryujinx.HLE/Switch.cs index f7b263cd0f..90723c4caf 100644 --- a/Ryujinx.HLE/Switch.cs +++ b/Ryujinx.HLE/Switch.cs @@ -1,11 +1,15 @@ +using LibHac.FsSystem; using Ryujinx.Audio; -using Ryujinx.Graphics.Gal; -using Ryujinx.HLE.Gpu; +using Ryujinx.Configuration; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu; +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.HOS; +using Ryujinx.HLE.HOS.Services; +using Ryujinx.HLE.HOS.SystemState; using Ryujinx.HLE.Input; -using Ryujinx.HLE.Logging; -using Ryujinx.HLE.OsHle; -using Ryujinx.HLE.Settings; using System; +using System.Threading; namespace Ryujinx.HLE { @@ -13,72 +17,126 @@ namespace Ryujinx.HLE { internal IAalOutput AudioOut { get; private set; } - public Logger Log { get; private set; } + internal DeviceMemory Memory { get; private set; } - internal NvGpu Gpu { get; private set; } + internal GpuContext Gpu { get; private set; } - internal VirtualFileSystem VFs { get; private set; } + public VirtualFileSystem FileSystem { get; private set; } - public Horizon Os { get; private set; } - - public SystemSettings Settings { get; private set; } + public Horizon System { get; private set; } public PerformanceStatistics Statistics { get; private set; } public Hid Hid { get; private set; } + public bool EnableDeviceVsync { get; set; } = true; + + public AutoResetEvent VsyncEvent { get; private set; } + public event EventHandler Finish; - public Switch(IGalRenderer Renderer, IAalOutput AudioOut) + public Switch(IRenderer renderer, IAalOutput audioOut) { - if (Renderer == null) + if (renderer == null) { - throw new ArgumentNullException(nameof(Renderer)); + throw new ArgumentNullException(nameof(renderer)); } - if (AudioOut == null) + if (audioOut == null) { - throw new ArgumentNullException(nameof(AudioOut)); + throw new ArgumentNullException(nameof(audioOut)); } - this.AudioOut = AudioOut; + AudioOut = audioOut; - Log = new Logger(); + Memory = new DeviceMemory(); - Gpu = new NvGpu(Renderer); + Gpu = new GpuContext(renderer); - VFs = new VirtualFileSystem(); + FileSystem = new VirtualFileSystem(); - Os = new Horizon(this); - - Settings = new SystemSettings(); + System = new Horizon(this); Statistics = new PerformanceStatistics(); - Hid = new Hid(Log); + Hid = new Hid(this, System.HidBaseAddress); - Os.HidSharedMem.MemoryMapped += Hid.ShMemMap; - Os.HidSharedMem.MemoryUnmapped += Hid.ShMemUnmap; + VsyncEvent = new AutoResetEvent(true); } - public void LoadCart(string ExeFsDir, string RomFsFile = null) + public void Initialize() { - Os.LoadCart(ExeFsDir, RomFsFile); + System.State.SetLanguage((SystemLanguage)ConfigurationState.Instance.System.Language.Value); + + EnableDeviceVsync = ConfigurationState.Instance.Graphics.EnableVsync; + + // TODO: Make this reloadable and implement Docking/Undocking logic. + System.State.DockedMode = ConfigurationState.Instance.System.EnableDockedMode; + + if (ConfigurationState.Instance.System.EnableMulticoreScheduling) + { + System.EnableMultiCoreScheduling(); + } + + System.FsIntegrityCheckLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks + ? IntegrityCheckLevel.ErrorOnInvalid + : IntegrityCheckLevel.None; + + System.GlobalAccessLogMode = ConfigurationState.Instance.System.FsGlobalAccessLogMode; + + ServiceConfiguration.IgnoreMissingServices = ConfigurationState.Instance.System.IgnoreMissingServices; } - public void LoadProgram(string FileName) + public void LoadCart(string exeFsDir, string romFsFile = null) { - Os.LoadProgram(FileName); + System.LoadCart(exeFsDir, romFsFile); + } + + public void LoadXci(string xciFile) + { + System.LoadXci(xciFile); + } + + public void LoadNca(string ncaFile) + { + System.LoadNca(ncaFile); + } + + public void LoadNsp(string nspFile) + { + System.LoadNsp(nspFile); + } + + public void LoadProgram(string fileName) + { + System.LoadProgram(fileName); + } + + public bool WaitFifo() + { + return Gpu.DmaPusher.WaitForCommands(); } public void ProcessFrame() { - Gpu.Fifo.DispatchCalls(); + Gpu.DmaPusher.DispatchCalls(); } - internal virtual void OnFinish(EventArgs e) + public void PresentFrame(Action swapBuffersCallback) { - Finish?.Invoke(this, e); + Gpu.Window.Present(swapBuffersCallback); + } + + internal void Unload() + { + FileSystem.Dispose(); + + Memory.Dispose(); + } + + public void DisposeGpu() + { + Gpu.Dispose(); } public void Dispose() @@ -86,13 +144,13 @@ namespace Ryujinx.HLE Dispose(true); } - protected virtual void Dispose(bool Disposing) + protected virtual void Dispose(bool disposing) { - if (Disposing) + if (disposing) { - Os.Dispose(); - VFs.Dispose(); + System.Dispose(); + VsyncEvent.Dispose(); } } } -} \ No newline at end of file +} diff --git a/Ryujinx.HLE/Utilities/FontUtils.cs b/Ryujinx.HLE/Utilities/FontUtils.cs new file mode 100644 index 0000000000..3da0ef68b8 --- /dev/null +++ b/Ryujinx.HLE/Utilities/FontUtils.cs @@ -0,0 +1,37 @@ +using System.IO; + +namespace Ryujinx.HLE.Utilities +{ + public static class FontUtils + { + private static readonly uint FontKey = 0x06186249; + + public static byte[] DecryptFont(Stream bfttfStream) + { + uint KXor(uint In) => In ^ 0x06186249; + + using (BinaryReader reader = new BinaryReader(bfttfStream)) + { + using (MemoryStream ttfStream = new MemoryStream()) + { + using (BinaryWriter output = new BinaryWriter(ttfStream)) + { + if (KXor(reader.ReadUInt32()) != 0x18029a7f) + { + throw new InvalidDataException("Error: Input file is not in BFTTF format!"); + } + + bfttfStream.Position += 4; + + for (int i = 0; i < (bfttfStream.Length - 8) / 4; i++) + { + output.Write(KXor(reader.ReadUInt32())); + } + + return ttfStream.ToArray(); + } + } + } + } + } +} diff --git a/Ryujinx.HLE/Utilities/LinuxError.cs b/Ryujinx.HLE/Utilities/LinuxError.cs new file mode 100644 index 0000000000..81a9d7be5a --- /dev/null +++ b/Ryujinx.HLE/Utilities/LinuxError.cs @@ -0,0 +1,155 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Ryujinx.HLE.Utilities +{ + [SuppressMessage("ReSharper", "InconsistentNaming")] + enum LinuxError + { + SUCCESS = 0, + EPERM = 1 /* Operation not permitted */, + ENOENT = 2 /* No such file or directory */, + ESRCH = 3 /* No such process */, + EINTR = 4 /* Interrupted system call */, + EIO = 5 /* I/O error */, + ENXIO = 6 /* No such device or address */, + E2BIG = 7 /* Argument list too long */, + ENOEXEC = 8 /* Exec format error */, + EBADF = 9 /* Bad file number */, + ECHILD = 10 /* No child processes */, + EAGAIN = 11 /* Try again */, + ENOMEM = 12 /* Out of memory */, + EACCES = 13 /* Permission denied */, + EFAULT = 14 /* Bad address */, + ENOTBLK = 15 /* Block device required */, + EBUSY = 16 /* Device or resource busy */, + EEXIST = 17 /* File exists */, + EXDEV = 18 /* Cross-device link */, + ENODEV = 19 /* No such device */, + ENOTDIR = 20 /* Not a directory */, + EISDIR = 21 /* Is a directory */, + EINVAL = 22 /* Invalid argument */, + ENFILE = 23 /* File table overflow */, + EMFILE = 24 /* Too many open files */, + ENOTTY = 25 /* Not a typewriter */, + ETXTBSY = 26 /* Text file busy */, + EFBIG = 27 /* File too large */, + ENOSPC = 28 /* No space left on device */, + ESPIPE = 29 /* Illegal seek */, + EROFS = 30 /* Read-only file system */, + EMLINK = 31 /* Too many links */, + EPIPE = 32 /* Broken pipe */, + EDOM = 33 /* Math argument out of domain of func */, + ERANGE = 34 /* Math result not representable */, + EDEADLK = 35 /* Resource deadlock would occur */, + ENAMETOOLONG = 36 /* File name too long */, + ENOLCK = 37 /* No record locks available */, + + /* + * This error code is special: arch syscall entry code will return + * -ENOSYS if users try to call a syscall that doesn't exist. To keep + * failures of syscalls that really do exist distinguishable from + * failures due to attempts to use a nonexistent syscall, syscall + * implementations should refrain from returning -ENOSYS. + */ + ENOSYS = 38 /* Invalid system call number */, + ENOTEMPTY = 39 /* Directory not empty */, + ELOOP = 40 /* Too many symbolic links encountered */, + EWOULDBLOCK = EAGAIN /* Operation would block */, + ENOMSG = 42 /* No message of desired type */, + EIDRM = 43 /* Identifier removed */, + ECHRNG = 44 /* Channel number out of range */, + EL2NSYNC = 45 /* Level 2 not synchronized */, + EL3HLT = 46 /* Level 3 halted */, + EL3RST = 47 /* Level 3 reset */, + ELNRNG = 48 /* Link number out of range */, + EUNATCH = 49 /* Protocol driver not attached */, + ENOCSI = 50 /* No CSI structure available */, + EL2HLT = 51 /* Level 2 halted */, + EBADE = 52 /* Invalid exchange */, + EBADR = 53 /* Invalid request descriptor */, + EXFULL = 54 /* Exchange full */, + ENOANO = 55 /* No anode */, + EBADRQC = 56 /* Invalid request code */, + EBADSLT = 57 /* Invalid slot */, + EDEADLOCK = EDEADLK, + EBFONT = 59 /* Bad font file format */, + ENOSTR = 60 /* Device not a stream */, + ENODATA = 61 /* No data available */, + ETIME = 62 /* Timer expired */, + ENOSR = 63 /* Out of streams resources */, + ENONET = 64 /* Machine is not on the network */, + ENOPKG = 65 /* Package not installed */, + EREMOTE = 66 /* Object is remote */, + ENOLINK = 67 /* Link has been severed */, + EADV = 68 /* Advertise error */, + ESRMNT = 69 /* Srmount error */, + ECOMM = 70 /* Communication error on send */, + EPROTO = 71 /* Protocol error */, + EMULTIHOP = 72 /* Multihop attempted */, + EDOTDOT = 73 /* RFS specific error */, + EBADMSG = 74 /* Not a data message */, + EOVERFLOW = 75 /* Value too large for defined data type */, + ENOTUNIQ = 76 /* Name not unique on network */, + EBADFD = 77 /* File descriptor in bad state */, + EREMCHG = 78 /* Remote address changed */, + ELIBACC = 79 /* Can not access a needed shared library */, + ELIBBAD = 80 /* Accessing a corrupted shared library */, + ELIBSCN = 81 /* .lib section in a.out corrupted */, + ELIBMAX = 82 /* Attempting to link in too many shared libraries */, + ELIBEXEC = 83 /* Cannot exec a shared library directly */, + EILSEQ = 84 /* Illegal byte sequence */, + ERESTART = 85 /* Interrupted system call should be restarted */, + ESTRPIPE = 86 /* Streams pipe error */, + EUSERS = 87 /* Too many users */, + ENOTSOCK = 88 /* Socket operation on non-socket */, + EDESTADDRREQ = 89 /* Destination address required */, + EMSGSIZE = 90 /* Message too long */, + EPROTOTYPE = 91 /* Protocol wrong type for socket */, + ENOPROTOOPT = 92 /* Protocol not available */, + EPROTONOSUPPORT = 93 /* Protocol not supported */, + ESOCKTNOSUPPORT = 94 /* Socket type not supported */, + EOPNOTSUPP = 95 /* Operation not supported on transport endpoint */, + EPFNOSUPPORT = 96 /* Protocol family not supported */, + EAFNOSUPPORT = 97 /* Address family not supported by protocol */, + EADDRINUSE = 98 /* Address already in use */, + EADDRNOTAVAIL = 99 /* Cannot assign requested address */, + ENETDOWN = 100 /* Network is down */, + ENETUNREACH = 101 /* Network is unreachable */, + ENETRESET = 102 /* Network dropped connection because of reset */, + ECONNABORTED = 103 /* Software caused connection abort */, + ECONNRESET = 104 /* Connection reset by peer */, + ENOBUFS = 105 /* No buffer space available */, + EISCONN = 106 /* Transport endpoint is already connected */, + ENOTCONN = 107 /* Transport endpoint is not connected */, + ESHUTDOWN = 108 /* Cannot send after transport endpoint shutdown */, + ETOOMANYREFS = 109 /* Too many references: cannot splice */, + ETIMEDOUT = 110 /* Connection timed out */, + ECONNREFUSED = 111 /* Connection refused */, + EHOSTDOWN = 112 /* Host is down */, + EHOSTUNREACH = 113 /* No route to host */, + EALREADY = 114 /* Operation already in progress */, + EINPROGRESS = 115 /* Operation now in progress */, + ESTALE = 116 /* Stale file handle */, + EUCLEAN = 117 /* Structure needs cleaning */, + ENOTNAM = 118 /* Not a XENIX named type file */, + ENAVAIL = 119 /* No XENIX semaphores available */, + EISNAM = 120 /* Is a named type file */, + EREMOTEIO = 121 /* Remote I/O error */, + EDQUOT = 122 /* Quota exceeded */, + ENOMEDIUM = 123 /* No medium found */, + EMEDIUMTYPE = 124 /* Wrong medium type */, + ECANCELED = 125 /* Operation Canceled */, + ENOKEY = 126 /* Required key not available */, + EKEYEXPIRED = 127 /* Key has expired */, + EKEYREVOKED = 128 /* Key has been revoked */, + EKEYREJECTED = 129 /* Key was rejected by service */, + + /* for robust mutexes */ + EOWNERDEAD = 130 /* Owner died */, + ENOTRECOVERABLE = 131 /* State not recoverable */, + + ERFKILL = 132 /* Operation not possible due to RF-kill */, + + EHWPOISON = 133 /* Memory page has hardware error */ + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/Utilities/StreamUtils.cs b/Ryujinx.HLE/Utilities/StreamUtils.cs new file mode 100644 index 0000000000..7b44bc17a0 --- /dev/null +++ b/Ryujinx.HLE/Utilities/StreamUtils.cs @@ -0,0 +1,16 @@ +using System.IO; + +namespace Ryujinx.HLE.Utilities +{ + static class StreamUtils + { + public static byte[] StreamToBytes(Stream input) + { + using (MemoryStream ms = new MemoryStream()) + { + input.CopyTo(ms); + return ms.ToArray(); + } + } + } +} diff --git a/Ryujinx.HLE/Utilities/StringUtils.cs b/Ryujinx.HLE/Utilities/StringUtils.cs new file mode 100644 index 0000000000..4ce0823c11 --- /dev/null +++ b/Ryujinx.HLE/Utilities/StringUtils.cs @@ -0,0 +1,125 @@ +using Ryujinx.HLE.HOS; +using System; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; + +namespace Ryujinx.HLE.Utilities +{ + static class StringUtils + { + public static byte[] GetFixedLengthBytes(string inputString, int size, Encoding encoding) + { + inputString = inputString + "\0"; + + int bytesCount = encoding.GetByteCount(inputString); + + byte[] output = new byte[size]; + + if (bytesCount < size) + { + encoding.GetBytes(inputString, 0, inputString.Length, output, 0); + } + else + { + int nullSize = encoding.GetByteCount("\0"); + + output = encoding.GetBytes(inputString); + + Array.Resize(ref output, size - nullSize); + + output = output.Concat(encoding.GetBytes("\0")).ToArray(); + } + + return output; + } + + public static byte[] HexToBytes(string hexString) + { + // Ignore last character if HexLength % 2 != 0. + int bytesInHex = hexString.Length / 2; + + byte[] output = new byte[bytesInHex]; + + for (int index = 0; index < bytesInHex; index++) + { + output[index] = byte.Parse(hexString.Substring(index * 2, 2), NumberStyles.HexNumber); + } + + return output; + } + + public static string ReadUtf8String(ServiceCtx context, int index = 0) + { + long position = context.Request.PtrBuff[index].Position; + long size = context.Request.PtrBuff[index].Size; + + using (MemoryStream ms = new MemoryStream()) + { + while (size-- > 0) + { + byte value = context.Memory.ReadByte(position++); + + if (value == 0) + { + break; + } + + ms.WriteByte(value); + } + + return Encoding.UTF8.GetString(ms.ToArray()); + } + } + + public static string ReadUtf8StringSend(ServiceCtx context, int index = 0) + { + long position = context.Request.SendBuff[index].Position; + long size = context.Request.SendBuff[index].Size; + + using (MemoryStream ms = new MemoryStream()) + { + while (size-- > 0) + { + byte value = context.Memory.ReadByte(position++); + + if (value == 0) + { + break; + } + + ms.WriteByte(value); + } + + return Encoding.UTF8.GetString(ms.ToArray()); + } + } + + public static unsafe int CompareCStr(char* s1, char* s2) + { + int s1Index = 0; + int s2Index = 0; + + while (s1[s1Index] != 0 && s2[s2Index] != 0 && s1[s1Index] == s2[s2Index]) + { + s1Index += 1; + s2Index += 1; + } + + return s2[s2Index] - s1[s1Index]; + } + + public static unsafe int LengthCstr(char* s) + { + int i = 0; + + while (s[i] != '\0') + { + i++; + } + + return i; + } + } +} diff --git a/Ryujinx.HLE/Utilities/StructReader.cs b/Ryujinx.HLE/Utilities/StructReader.cs new file mode 100644 index 0000000000..cd274a9582 --- /dev/null +++ b/Ryujinx.HLE/Utilities/StructReader.cs @@ -0,0 +1,45 @@ +using ARMeilleure.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.Utilities +{ + class StructReader + { + private MemoryManager _memory; + + public long Position { get; private set; } + + public StructReader(MemoryManager memory, long position) + { + _memory = memory; + Position = position; + } + + public T Read() where T : struct + { + T value = MemoryHelper.Read(_memory, Position); + + Position += Marshal.SizeOf(); + + return value; + } + + public T[] Read(int size) where T : struct + { + int structSize = Marshal.SizeOf(); + + int count = size / structSize; + + T[] output = new T[count]; + + for (int index = 0; index < count; index++) + { + output[index] = MemoryHelper.Read(_memory, Position); + + Position += structSize; + } + + return output; + } + } +} diff --git a/Ryujinx.HLE/Utilities/StructWriter.cs b/Ryujinx.HLE/Utilities/StructWriter.cs new file mode 100644 index 0000000000..b488b5d613 --- /dev/null +++ b/Ryujinx.HLE/Utilities/StructWriter.cs @@ -0,0 +1,25 @@ +using ARMeilleure.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.Utilities +{ + class StructWriter + { + private MemoryManager _memory; + + public long Position { get; private set; } + + public StructWriter(MemoryManager memory, long position) + { + _memory = memory; + Position = position; + } + + public void Write(T value) where T : struct + { + MemoryHelper.Write(_memory, Position, value); + + Position += Marshal.SizeOf(); + } + } +} diff --git a/Ryujinx.HLE/Utilities/UInt128.cs b/Ryujinx.HLE/Utilities/UInt128.cs new file mode 100644 index 0000000000..22d87f6b17 --- /dev/null +++ b/Ryujinx.HLE/Utilities/UInt128.cs @@ -0,0 +1,75 @@ +using System; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.Utilities +{ + [StructLayout(LayoutKind.Sequential)] + public struct UInt128 : IEquatable + { + public readonly long Low; + public readonly long High; + + public bool IsNull => (Low | High) == 0; + + public UInt128(long low, long high) + { + Low = low; + High = high; + } + + public UInt128(byte[] bytes) + { + Low = BitConverter.ToInt64(bytes, 0); + High = BitConverter.ToInt64(bytes, 8); + } + + public UInt128(string hex) + { + if (hex == null || hex.Length != 32 || !hex.All("0123456789abcdefABCDEF".Contains)) + { + throw new ArgumentException("Invalid Hex value!", nameof(hex)); + } + + Low = Convert.ToInt64(hex.Substring(16), 16); + High = Convert.ToInt64(hex.Substring(0, 16), 16); + } + + public void Write(BinaryWriter binaryWriter) + { + binaryWriter.Write(Low); + binaryWriter.Write(High); + } + + public override string ToString() + { + return High.ToString("x16") + Low.ToString("x16"); + } + + public static bool operator ==(UInt128 x, UInt128 y) + { + return x.Equals(y); + } + + public static bool operator !=(UInt128 x, UInt128 y) + { + return !x.Equals(y); + } + + public override bool Equals(object obj) + { + return obj is UInt128 uint128 && Equals(uint128); + } + + public bool Equals(UInt128 cmpObj) + { + return Low == cmpObj.Low && High == cmpObj.High; + } + + public override int GetHashCode() + { + return HashCode.Combine(Low, High); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/Utilities/WSAError.cs b/Ryujinx.HLE/Utilities/WSAError.cs new file mode 100644 index 0000000000..81294b8b99 --- /dev/null +++ b/Ryujinx.HLE/Utilities/WSAError.cs @@ -0,0 +1,134 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Ryujinx.HLE.Utilities +{ + [SuppressMessage("ReSharper", "InconsistentNaming")] + enum WsaError + { + /* + * All Windows Sockets error constants are biased by WSABASEERR from + * the "normal" + */ + WSABASEERR = 10000, + + /* + * Windows Sockets definitions of regular Microsoft C error constants + */ + WSAEINTR = (WSABASEERR + 4), + WSAEBADF = (WSABASEERR + 9), + WSAEACCES = (WSABASEERR + 13), + WSAEFAULT = (WSABASEERR + 14), + WSAEINVAL = (WSABASEERR + 22), + WSAEMFILE = (WSABASEERR + 24), + + /* + * Windows Sockets definitions of regular Berkeley error constants + */ + WSAEWOULDBLOCK = (WSABASEERR + 35), + WSAEINPROGRESS = (WSABASEERR + 36), + WSAEALREADY = (WSABASEERR + 37), + WSAENOTSOCK = (WSABASEERR + 38), + WSAEDESTADDRREQ = (WSABASEERR + 39), + WSAEMSGSIZE = (WSABASEERR + 40), + WSAEPROTOTYPE = (WSABASEERR + 41), + WSAENOPROTOOPT = (WSABASEERR + 42), + WSAEPROTONOSUPPORT = (WSABASEERR + 43), + WSAESOCKTNOSUPPORT = (WSABASEERR + 44), + WSAEOPNOTSUPP = (WSABASEERR + 45), + WSAEPFNOSUPPORT = (WSABASEERR + 46), + WSAEAFNOSUPPORT = (WSABASEERR + 47), + WSAEADDRINUSE = (WSABASEERR + 48), + WSAEADDRNOTAVAIL = (WSABASEERR + 49), + WSAENETDOWN = (WSABASEERR + 50), + WSAENETUNREACH = (WSABASEERR + 51), + WSAENETRESET = (WSABASEERR + 52), + WSAECONNABORTED = (WSABASEERR + 53), + WSAECONNRESET = (WSABASEERR + 54), + WSAENOBUFS = (WSABASEERR + 55), + WSAEISCONN = (WSABASEERR + 56), + WSAENOTCONN = (WSABASEERR + 57), + WSAESHUTDOWN = (WSABASEERR + 58), + WSAETOOMANYREFS = (WSABASEERR + 59), + WSAETIMEDOUT = (WSABASEERR + 60), + WSAECONNREFUSED = (WSABASEERR + 61), + WSAELOOP = (WSABASEERR + 62), + WSAENAMETOOLONG = (WSABASEERR + 63), + WSAEHOSTDOWN = (WSABASEERR + 64), + WSAEHOSTUNREACH = (WSABASEERR + 65), + WSAENOTEMPTY = (WSABASEERR + 66), + WSAEPROCLIM = (WSABASEERR + 67), + WSAEUSERS = (WSABASEERR + 68), + WSAEDQUOT = (WSABASEERR + 69), + WSAESTALE = (WSABASEERR + 70), + WSAEREMOTE = (WSABASEERR + 71), + + /* + * Extended Windows Sockets error constant definitions + */ + WSASYSNOTREADY = (WSABASEERR + 91), + WSAVERNOTSUPPORTED = (WSABASEERR + 92), + WSANOTINITIALISED = (WSABASEERR + 93), + WSAEDISCON = (WSABASEERR + 101), + WSAENOMORE = (WSABASEERR + 102), + WSAECANCELLED = (WSABASEERR + 103), + WSAEINVALIDPROCTABLE = (WSABASEERR + 104), + WSAEINVALIDPROVIDER = (WSABASEERR + 105), + WSAEPROVIDERFAILEDINIT = (WSABASEERR + 106), + WSASYSCALLFAILURE = (WSABASEERR + 107), + WSASERVICE_NOT_FOUND = (WSABASEERR + 108), + WSATYPE_NOT_FOUND = (WSABASEERR + 109), + WSA_E_NO_MORE = (WSABASEERR + 110), + WSA_E_CANCELLED = (WSABASEERR + 111), + WSAEREFUSED = (WSABASEERR + 112), + + /* + * Error return codes from gethostbyname() and gethostbyaddr() + * (when using the resolver). Note that these errors are + * retrieved via WSAGetLastError() and must therefore follow + * the rules for avoiding clashes with error numbers from + * specific implementations or language run-time systems. + * For this reason the codes are based at WSABASEERR+1001. + * Note also that [WSA]NO_ADDRESS is defined only for + * compatibility purposes. + */ + + /* Authoritative Answer: Host not found */ + WSAHOST_NOT_FOUND = (WSABASEERR + 1001), + + /* Non-Authoritative: Host not found, or SERVERFAIL */ + WSATRY_AGAIN = (WSABASEERR + 1002), + + /* Non-recoverable errors, FORMERR, REFUSED, NOTIMP */ + WSANO_RECOVERY = (WSABASEERR + 1003), + + /* Valid name, no data record of requested type */ + WSANO_DATA = (WSABASEERR + 1004), + + /* + * Define QOS related error return codes + * + */ + WSA_QOS_RECEIVERS = (WSABASEERR + 1005), + /* at least one Reserve has arrived */ + WSA_QOS_SENDERS = (WSABASEERR + 1006), + /* at least one Path has arrived */ + WSA_QOS_NO_SENDERS = (WSABASEERR + 1007), + /* there are no senders */ + WSA_QOS_NO_RECEIVERS = (WSABASEERR + 1008), + /* there are no receivers */ + WSA_QOS_REQUEST_CONFIRMED = (WSABASEERR + 1009), + /* Reserve has been confirmed */ + WSA_QOS_ADMISSION_FAILURE = (WSABASEERR + 1010), + /* error due to lack of resources */ + WSA_QOS_POLICY_FAILURE = (WSABASEERR + 1011), + /* rejected for administrative reasons - bad credentials */ + WSA_QOS_BAD_STYLE = (WSABASEERR + 1012), + /* unknown or conflicting style */ + WSA_QOS_BAD_OBJECT = (WSABASEERR + 1013), + /* problem with some part of the filterspec or providerspecific + * buffer in general */ + WSA_QOS_TRAFFIC_CTRL_ERROR = (WSABASEERR + 1014), + /* problem with some part of the flowspec */ + WSA_QOS_GENERIC_ERROR = (WSABASEERR + 1015) + } +} diff --git a/Ryujinx.HLE/VirtualFileSystem.cs b/Ryujinx.HLE/VirtualFileSystem.cs deleted file mode 100644 index 8b71caa97a..0000000000 --- a/Ryujinx.HLE/VirtualFileSystem.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System; -using System.IO; - -namespace Ryujinx.HLE -{ - class VirtualFileSystem : IDisposable - { - private const string BasePath = "RyuFs"; - private const string NandPath = "nand"; - private const string SdCardPath = "sdmc"; - - public Stream RomFs { get; private set; } - - public void LoadRomFs(string FileName) - { - RomFs = new FileStream(FileName, FileMode.Open, FileAccess.Read); - } - - public string GetFullPath(string BasePath, string FileName) - { - if (FileName.StartsWith("//")) - { - FileName = FileName.Substring(2); - } - else if (FileName.StartsWith('/')) - { - FileName = FileName.Substring(1); - } - else - { - return null; - } - - string FullPath = Path.GetFullPath(Path.Combine(BasePath, FileName)); - - if (!FullPath.StartsWith(GetBasePath())) - { - return null; - } - - return FullPath; - } - - public string GetSdCardPath() => MakeDirAndGetFullPath(SdCardPath); - - public string GetGameSavesPath() => MakeDirAndGetFullPath(NandPath); - - private string MakeDirAndGetFullPath(string Dir) - { - string FullPath = Path.Combine(GetBasePath(), Dir); - - if (!Directory.Exists(FullPath)) - { - Directory.CreateDirectory(FullPath); - } - - return FullPath; - } - - public DriveInfo GetDrive() - { - return new DriveInfo(Path.GetPathRoot(GetBasePath())); - } - - public string GetBasePath() - { - string AppDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); - - return Path.Combine(AppDataPath, BasePath); - } - - public void Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - RomFs?.Dispose(); - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.LLE/Luea.csproj b/Ryujinx.LLE/Luea.csproj index de1c5f61d8..7eb546d8fe 100644 --- a/Ryujinx.LLE/Luea.csproj +++ b/Ryujinx.LLE/Luea.csproj @@ -1,9 +1,20 @@ + netcoreapp3.0 + win-x64;osx-x64;linux-x64 Exe - netcoreapp2.1 - win10-x64;osx-x64;linux-x64 + Debug;Release;Profile Debug;Profile Release + + + + TRACE;USE_PROFILING + true + + + + TRACE;USE_PROFILING + false diff --git a/Ryujinx.Profiler/DumpProfile.cs b/Ryujinx.Profiler/DumpProfile.cs new file mode 100644 index 0000000000..62a027615d --- /dev/null +++ b/Ryujinx.Profiler/DumpProfile.cs @@ -0,0 +1,35 @@ +using Ryujinx.Common; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Ryujinx.Profiler +{ + public static class DumpProfile + { + public static void ToFile(string path, InternalProfile profile) + { + String fileData = "Category,Session Group,Session Item,Count,Average(ms),Total(ms)\r\n"; + + foreach (KeyValuePair time in profile.Timers.OrderBy(key => key.Key.Tag)) + { + fileData += $"{time.Key.Category}," + + $"{time.Key.SessionGroup}," + + $"{time.Key.SessionItem}," + + $"{time.Value.Count}," + + $"{time.Value.AverageTime / PerformanceCounter.TicksPerMillisecond}," + + $"{time.Value.TotalTime / PerformanceCounter.TicksPerMillisecond}\r\n"; + } + + // Ensure file directory exists before write + FileInfo fileInfo = new FileInfo(path); + if (fileInfo == null) + throw new Exception("Unknown logging error, probably a bad file path"); + if (fileInfo.Directory != null && !fileInfo.Directory.Exists) + Directory.CreateDirectory(fileInfo.Directory.FullName); + + File.WriteAllText(fileInfo.FullName, fileData); + } + } +} diff --git a/Ryujinx.Profiler/InternalProfile.cs b/Ryujinx.Profiler/InternalProfile.cs new file mode 100644 index 0000000000..0346244423 --- /dev/null +++ b/Ryujinx.Profiler/InternalProfile.cs @@ -0,0 +1,223 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Ryujinx.Common; + +namespace Ryujinx.Profiler +{ + public class InternalProfile + { + private struct TimerQueueValue + { + public ProfileConfig Config; + public long Time; + public bool IsBegin; + } + + internal Dictionary Timers { get; set; } + + private readonly object _timerQueueClearLock = new object(); + private ConcurrentQueue _timerQueue; + + private int _sessionCounter = 0; + + // Cleanup thread + private readonly Thread _cleanupThread; + private bool _cleanupRunning; + private readonly long _history; + private long _preserve; + + // Timing flags + private TimingFlag[] _timingFlags; + private long[] _timingFlagAverages; + private long[] _timingFlagLast; + private long[] _timingFlagLastDelta; + private int _timingFlagCount; + private int _timingFlagIndex; + + private int _maxFlags; + + private Action _timingFlagCallback; + + public InternalProfile(long history, int maxFlags) + { + _maxFlags = maxFlags; + Timers = new Dictionary(); + _timingFlags = new TimingFlag[_maxFlags]; + _timingFlagAverages = new long[(int)TimingFlagType.Count]; + _timingFlagLast = new long[(int)TimingFlagType.Count]; + _timingFlagLastDelta = new long[(int)TimingFlagType.Count]; + _timerQueue = new ConcurrentQueue(); + _history = history; + _cleanupRunning = true; + + // Create cleanup thread. + _cleanupThread = new Thread(CleanupLoop) + { + Name = "Profiler.CleanupThread" + }; + _cleanupThread.Start(); + } + + private void CleanupLoop() + { + bool queueCleared = false; + + while (_cleanupRunning) + { + // Ensure we only ever have 1 instance modifying timers or timerQueue + if (Monitor.TryEnter(_timerQueueClearLock)) + { + queueCleared = ClearTimerQueue(); + + // Calculate before foreach to mitigate redundant calculations + long cleanupBefore = PerformanceCounter.ElapsedTicks - _history; + long preserveStart = _preserve - _history; + + // Each cleanup is self contained so run in parallel for maximum efficiency + Parallel.ForEach(Timers, (t) => t.Value.Cleanup(cleanupBefore, preserveStart, _preserve)); + + Monitor.Exit(_timerQueueClearLock); + } + + // Only sleep if queue was successfully cleared + if (queueCleared) + { + Thread.Sleep(5); + } + } + } + + private bool ClearTimerQueue() + { + int count = 0; + + while (_timerQueue.TryDequeue(out TimerQueueValue item)) + { + if (!Timers.TryGetValue(item.Config, out TimingInfo value)) + { + value = new TimingInfo(); + Timers.Add(item.Config, value); + } + + if (item.IsBegin) + { + value.Begin(item.Time); + } + else + { + value.End(item.Time); + } + + // Don't block for too long as memory disposal is blocked while this function runs + if (count++ > 10000) + { + return false; + } + } + + return true; + } + + public void FlagTime(TimingFlagType flagType) + { + int flagId = (int)flagType; + + _timingFlags[_timingFlagIndex] = new TimingFlag() + { + FlagType = flagType, + Timestamp = PerformanceCounter.ElapsedTicks + }; + + _timingFlagCount = Math.Max(_timingFlagCount + 1, _maxFlags); + + // Work out average + if (_timingFlagLast[flagId] != 0) + { + _timingFlagLastDelta[flagId] = _timingFlags[_timingFlagIndex].Timestamp - _timingFlagLast[flagId]; + _timingFlagAverages[flagId] = (_timingFlagAverages[flagId] == 0) ? _timingFlagLastDelta[flagId] : + (_timingFlagLastDelta[flagId] + _timingFlagAverages[flagId]) >> 1; + } + _timingFlagLast[flagId] = _timingFlags[_timingFlagIndex].Timestamp; + + // Notify subscribers + _timingFlagCallback?.Invoke(_timingFlags[_timingFlagIndex]); + + if (++_timingFlagIndex >= _maxFlags) + { + _timingFlagIndex = 0; + } + } + + public void BeginProfile(ProfileConfig config) + { + _timerQueue.Enqueue(new TimerQueueValue() + { + Config = config, + IsBegin = true, + Time = PerformanceCounter.ElapsedTicks, + }); + } + + public void EndProfile(ProfileConfig config) + { + _timerQueue.Enqueue(new TimerQueueValue() + { + Config = config, + IsBegin = false, + Time = PerformanceCounter.ElapsedTicks, + }); + } + + public string GetSession() + { + // Can be called from multiple threads so we need to ensure no duplicate sessions are generated + return Interlocked.Increment(ref _sessionCounter).ToString(); + } + + public List> GetProfilingData() + { + _preserve = PerformanceCounter.ElapsedTicks; + + lock (_timerQueueClearLock) + { + ClearTimerQueue(); + return Timers.ToList(); + } + } + + public TimingFlag[] GetTimingFlags() + { + int count = Math.Max(_timingFlagCount, _maxFlags); + TimingFlag[] outFlags = new TimingFlag[count]; + + for (int i = 0, sourceIndex = _timingFlagIndex; i < count; i++, sourceIndex++) + { + if (sourceIndex >= _maxFlags) + sourceIndex = 0; + outFlags[i] = _timingFlags[sourceIndex]; + } + + return outFlags; + } + + public (long[], long[]) GetTimingAveragesAndLast() + { + return (_timingFlagAverages, _timingFlagLastDelta); + } + + public void RegisterFlagReceiver(Action receiver) + { + _timingFlagCallback = receiver; + } + + public void Dispose() + { + _cleanupRunning = false; + _cleanupThread.Join(); + } + } +} diff --git a/Ryujinx.Profiler/Profile.cs b/Ryujinx.Profiler/Profile.cs new file mode 100644 index 0000000000..4dba6ea542 --- /dev/null +++ b/Ryujinx.Profiler/Profile.cs @@ -0,0 +1,144 @@ +using Ryujinx.Common; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; + +namespace Ryujinx.Profiler +{ + public static class Profile + { + public static float UpdateRate => _settings.UpdateRate; + public static long HistoryLength => _settings.History; + + public static ProfilerKeyboardHandler Controls => _settings.Controls; + + private static InternalProfile _profileInstance; + private static ProfilerSettings _settings; + + [Conditional("USE_PROFILING")] + public static void Initialize() + { + var config = ProfilerConfiguration.Load(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ProfilerConfig.jsonc")); + + _settings = new ProfilerSettings() + { + Enabled = config.Enabled, + FileDumpEnabled = config.DumpPath != "", + DumpLocation = config.DumpPath, + UpdateRate = (config.UpdateRate <= 0) ? -1 : 1.0f / config.UpdateRate, + History = (long)(config.History * PerformanceCounter.TicksPerSecond), + MaxLevel = config.MaxLevel, + Controls = config.Controls, + MaxFlags = config.MaxFlags, + }; + } + + public static bool ProfilingEnabled() + { +#if USE_PROFILING + if (!_settings.Enabled) + return false; + + if (_profileInstance == null) + _profileInstance = new InternalProfile(_settings.History, _settings.MaxFlags); + + return true; +#else + return false; +#endif + } + + [Conditional("USE_PROFILING")] + public static void FinishProfiling() + { + if (!ProfilingEnabled()) + return; + + if (_settings.FileDumpEnabled) + DumpProfile.ToFile(_settings.DumpLocation, _profileInstance); + + _profileInstance.Dispose(); + } + + [Conditional("USE_PROFILING")] + public static void FlagTime(TimingFlagType flagType) + { + if (!ProfilingEnabled()) + return; + _profileInstance.FlagTime(flagType); + } + + [Conditional("USE_PROFILING")] + public static void RegisterFlagReceiver(Action receiver) + { + if (!ProfilingEnabled()) + return; + _profileInstance.RegisterFlagReceiver(receiver); + } + + [Conditional("USE_PROFILING")] + public static void Begin(ProfileConfig config) + { + if (!ProfilingEnabled()) + return; + if (config.Level > _settings.MaxLevel) + return; + _profileInstance.BeginProfile(config); + } + + [Conditional("USE_PROFILING")] + public static void End(ProfileConfig config) + { + if (!ProfilingEnabled()) + return; + if (config.Level > _settings.MaxLevel) + return; + _profileInstance.EndProfile(config); + } + + public static string GetSession() + { +#if USE_PROFILING + if (!ProfilingEnabled()) + return null; + return _profileInstance.GetSession(); +#else + return ""; +#endif + } + + public static List> GetProfilingData() + { +#if USE_PROFILING + if (!ProfilingEnabled()) + return new List>(); + return _profileInstance.GetProfilingData(); +#else + return new List>(); +#endif + } + + public static TimingFlag[] GetTimingFlags() + { +#if USE_PROFILING + if (!ProfilingEnabled()) + return new TimingFlag[0]; + return _profileInstance.GetTimingFlags(); +#else + return new TimingFlag[0]; +#endif + } + + public static (long[], long[]) GetTimingAveragesAndLast() + { +#if USE_PROFILING + if (!ProfilingEnabled()) + return (new long[0], new long[0]); + return _profileInstance.GetTimingAveragesAndLast(); +#else + return (new long[0], new long[0]); +#endif + } + } +} diff --git a/Ryujinx.Profiler/ProfileConfig.cs b/Ryujinx.Profiler/ProfileConfig.cs new file mode 100644 index 0000000000..4271bd2b86 --- /dev/null +++ b/Ryujinx.Profiler/ProfileConfig.cs @@ -0,0 +1,254 @@ +using System; + +namespace Ryujinx.Profiler +{ + public struct ProfileConfig : IEquatable + { + public string Category; + public string SessionGroup; + public string SessionItem; + + public int Level; + + // Private cached variables + private string _cachedTag; + private string _cachedSession; + private string _cachedSearch; + + // Public helpers to get config in more user friendly format, + // Cached because they never change and are called often + public string Search + { + get + { + if (_cachedSearch == null) + { + _cachedSearch = $"{Category}.{SessionGroup}.{SessionItem}"; + } + + return _cachedSearch; + } + } + + public string Tag + { + get + { + if (_cachedTag == null) + _cachedTag = $"{Category}{(Session == "" ? "" : $" ({Session})")}"; + return _cachedTag; + } + } + + public string Session + { + get + { + if (_cachedSession == null) + { + if (SessionGroup != null && SessionItem != null) + { + _cachedSession = $"{SessionGroup}: {SessionItem}"; + } + else if (SessionGroup != null) + { + _cachedSession = $"{SessionGroup}"; + } + else if (SessionItem != null) + { + _cachedSession = $"---: {SessionItem}"; + } + else + { + _cachedSession = ""; + } + } + + return _cachedSession; + } + } + + /// + /// The default comparison is far too slow for the number of comparisons needed because it doesn't know what's important to compare + /// + /// Object to compare to + /// + public bool Equals(ProfileConfig cmpObj) + { + // Order here is important. + // Multiple entries with the same item is considerable less likely that multiple items with the same group. + // Likewise for group and category. + return (cmpObj.SessionItem == SessionItem && + cmpObj.SessionGroup == SessionGroup && + cmpObj.Category == Category); + } + } + + /// + /// Predefined configs to make profiling easier, + /// nested so you can reference as Profiles.Category.Group.Item where item and group may be optional + /// + public static class Profiles + { + public static class CPU + { + public static ProfileConfig TranslateTier0 = new ProfileConfig() + { + Category = "CPU", + SessionGroup = "TranslateTier0" + }; + + public static ProfileConfig TranslateTier1 = new ProfileConfig() + { + Category = "CPU", + SessionGroup = "TranslateTier1" + }; + } + + public static class Input + { + public static ProfileConfig ControllerInput = new ProfileConfig + { + Category = "Input", + SessionGroup = "ControllerInput" + }; + + public static ProfileConfig TouchInput = new ProfileConfig + { + Category = "Input", + SessionGroup = "TouchInput" + }; + } + + public static class GPU + { + public static class Engine2d + { + public static ProfileConfig TextureCopy = new ProfileConfig() + { + Category = "GPU.Engine2D", + SessionGroup = "TextureCopy" + }; + } + + public static class Engine3d + { + public static ProfileConfig CallMethod = new ProfileConfig() + { + Category = "GPU.Engine3D", + SessionGroup = "CallMethod", + }; + + public static ProfileConfig VertexEnd = new ProfileConfig() + { + Category = "GPU.Engine3D", + SessionGroup = "VertexEnd" + }; + + public static ProfileConfig ClearBuffers = new ProfileConfig() + { + Category = "GPU.Engine3D", + SessionGroup = "ClearBuffers" + }; + + public static ProfileConfig SetFrameBuffer = new ProfileConfig() + { + Category = "GPU.Engine3D", + SessionGroup = "SetFrameBuffer", + }; + + public static ProfileConfig SetZeta = new ProfileConfig() + { + Category = "GPU.Engine3D", + SessionGroup = "SetZeta" + }; + + public static ProfileConfig UploadShaders = new ProfileConfig() + { + Category = "GPU.Engine3D", + SessionGroup = "UploadShaders" + }; + + public static ProfileConfig UploadTextures = new ProfileConfig() + { + Category = "GPU.Engine3D", + SessionGroup = "UploadTextures" + }; + + public static ProfileConfig UploadTexture = new ProfileConfig() + { + Category = "GPU.Engine3D", + SessionGroup = "UploadTexture" + }; + + public static ProfileConfig UploadConstBuffers = new ProfileConfig() + { + Category = "GPU.Engine3D", + SessionGroup = "UploadConstBuffers" + }; + + public static ProfileConfig UploadVertexArrays = new ProfileConfig() + { + Category = "GPU.Engine3D", + SessionGroup = "UploadVertexArrays" + }; + + public static ProfileConfig ConfigureState = new ProfileConfig() + { + Category = "GPU.Engine3D", + SessionGroup = "ConfigureState" + }; + } + + public static class EngineM2mf + { + public static ProfileConfig CallMethod = new ProfileConfig() + { + Category = "GPU.EngineM2mf", + SessionGroup = "CallMethod", + }; + + public static ProfileConfig Execute = new ProfileConfig() + { + Category = "GPU.EngineM2mf", + SessionGroup = "Execute", + }; + } + + public static class EngineP2mf + { + public static ProfileConfig CallMethod = new ProfileConfig() + { + Category = "GPU.EngineP2mf", + SessionGroup = "CallMethod", + }; + + public static ProfileConfig Execute = new ProfileConfig() + { + Category = "GPU.EngineP2mf", + SessionGroup = "Execute", + }; + + public static ProfileConfig PushData = new ProfileConfig() + { + Category = "GPU.EngineP2mf", + SessionGroup = "PushData", + }; + } + + public static class Shader + { + public static ProfileConfig Decompile = new ProfileConfig() + { + Category = "GPU.Shader", + SessionGroup = "Decompile", + }; + } + } + + public static ProfileConfig ServiceCall = new ProfileConfig() + { + Category = "ServiceCall", + }; + } +} diff --git a/Ryujinx.Profiler/ProfilerConfig.jsonc b/Ryujinx.Profiler/ProfilerConfig.jsonc new file mode 100644 index 0000000000..e671438695 --- /dev/null +++ b/Ryujinx.Profiler/ProfilerConfig.jsonc @@ -0,0 +1,28 @@ +{ + // Enable profiling (Only available on a profiling enabled builds) + "enabled": true, + + // Set profile file dump location, if blank file dumping disabled. (e.g. `ProfileDump.csv`) + "dump_path": "", + + // Update rate for profiler UI, in hertz. -1 updates every time a frame is issued + "update_rate": 4.0, + + // Set how long to keep profiling data in seconds, reduce if profiling is taking too much RAM + "history": 5.0, + + // Set the maximum profiling level. Higher values may cause a heavy load on your system but will allow you to profile in more detail + "max_level": 0, + + // Sets the maximum number of flags to keep + "max_flags": 1000, + + // Keyboard Controls + // https://github.com/opentk/opentk/blob/master/src/OpenTK/Input/Key.cs + "controls": { + "buttons": { + // Show/Hide the profiler + "toggle_profiler": "F2" + } + } +} \ No newline at end of file diff --git a/Ryujinx.Profiler/ProfilerConfiguration.cs b/Ryujinx.Profiler/ProfilerConfiguration.cs new file mode 100644 index 0000000000..4fe616fa99 --- /dev/null +++ b/Ryujinx.Profiler/ProfilerConfiguration.cs @@ -0,0 +1,70 @@ +using OpenTK.Input; +using System; +using System.IO; +using Utf8Json; +using Utf8Json.Resolvers; + +namespace Ryujinx.Profiler +{ + public class ProfilerConfiguration + { + public bool Enabled { get; private set; } + public string DumpPath { get; private set; } + public float UpdateRate { get; private set; } + public int MaxLevel { get; private set; } + public int MaxFlags { get; private set; } + public float History { get; private set; } + + public ProfilerKeyboardHandler Controls { get; private set; } + + /// + /// Loads a configuration file from disk + /// + /// The path to the JSON configuration file + public static ProfilerConfiguration Load(string path) + { + var resolver = CompositeResolver.Create( + new[] { new ConfigurationEnumFormatter() }, + new[] { StandardResolver.AllowPrivateSnakeCase } + ); + + if (!File.Exists(path)) + { + throw new FileNotFoundException($"Profiler configuration file {path} not found"); + } + + using (Stream stream = File.OpenRead(path)) + { + return JsonSerializer.Deserialize(stream, resolver); + } + } + + private class ConfigurationEnumFormatter : IJsonFormatter + where T : struct + { + public void Serialize(ref JsonWriter writer, T value, IJsonFormatterResolver formatterResolver) + { + formatterResolver.GetFormatterWithVerify() + .Serialize(ref writer, value.ToString(), formatterResolver); + } + + public T Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver) + { + if (reader.ReadIsNull()) + { + return default(T); + } + + string enumName = formatterResolver.GetFormatterWithVerify() + .Deserialize(ref reader, formatterResolver); + + if (Enum.TryParse(enumName, out T result)) + { + return result; + } + + return default(T); + } + } + } +} diff --git a/Ryujinx.Profiler/ProfilerKeyboardHandler.cs b/Ryujinx.Profiler/ProfilerKeyboardHandler.cs new file mode 100644 index 0000000000..e6207e8924 --- /dev/null +++ b/Ryujinx.Profiler/ProfilerKeyboardHandler.cs @@ -0,0 +1,28 @@ +using OpenTK.Input; + +namespace Ryujinx.Profiler +{ + public struct ProfilerButtons + { + public Key ToggleProfiler; + } + + public class ProfilerKeyboardHandler + { + public ProfilerButtons Buttons; + + private KeyboardState _prevKeyboard; + + public ProfilerKeyboardHandler(ProfilerButtons buttons) + { + Buttons = buttons; + } + + public bool TogglePressed(KeyboardState keyboard) => !keyboard[Buttons.ToggleProfiler] && _prevKeyboard[Buttons.ToggleProfiler]; + + public void SetPrevKeyboardState(KeyboardState keyboard) + { + _prevKeyboard = keyboard; + } + } +} diff --git a/Ryujinx.Profiler/Ryujinx.Profiler.csproj b/Ryujinx.Profiler/Ryujinx.Profiler.csproj new file mode 100644 index 0000000000..0e089ccf52 --- /dev/null +++ b/Ryujinx.Profiler/Ryujinx.Profiler.csproj @@ -0,0 +1,39 @@ + + + + netcoreapp3.0 + win-x64;osx-x64;linux-x64 + true + Debug;Release;Profile Debug;Profile Release + + + + TRACE + + + + TRACE;USE_PROFILING + false + + + + TRACE;USE_PROFILING + true + + + + + + + + + + + + + + PreserveNewest + + + + diff --git a/Ryujinx.Profiler/Settings.cs b/Ryujinx.Profiler/Settings.cs new file mode 100644 index 0000000000..f0c851b229 --- /dev/null +++ b/Ryujinx.Profiler/Settings.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.Profiler +{ + public class ProfilerSettings + { + // Default settings for profiler + public bool Enabled { get; set; } = false; + public bool FileDumpEnabled { get; set; } = false; + public string DumpLocation { get; set; } = ""; + public float UpdateRate { get; set; } = 0.1f; + public int MaxLevel { get; set; } = 0; + public int MaxFlags { get; set; } = 1000; + + // 19531225 = 5 seconds in ticks on most pc's. + // It should get set on boot to the time specified in config + public long History { get; set; } = 19531225; + + // Controls + public ProfilerKeyboardHandler Controls; + } +} diff --git a/Ryujinx.Profiler/TimingFlag.cs b/Ryujinx.Profiler/TimingFlag.cs new file mode 100644 index 0000000000..0cf55bdf32 --- /dev/null +++ b/Ryujinx.Profiler/TimingFlag.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.Profiler +{ + public enum TimingFlagType + { + FrameSwap = 0, + SystemFrame = 1, + + // Update this for new flags + Count = 2, + } + + public struct TimingFlag + { + public TimingFlagType FlagType; + public long Timestamp; + } +} diff --git a/Ryujinx.Profiler/TimingInfo.cs b/Ryujinx.Profiler/TimingInfo.cs new file mode 100644 index 0000000000..6058ddbd81 --- /dev/null +++ b/Ryujinx.Profiler/TimingInfo.cs @@ -0,0 +1,174 @@ +using System; +using System.Collections.Generic; + +namespace Ryujinx.Profiler +{ + public struct Timestamp + { + public long BeginTime; + public long EndTime; + } + + public class TimingInfo + { + // Timestamps + public long TotalTime { get; set; } + public long Instant { get; set; } + + // Measurement counts + public int Count { get; set; } + public int InstantCount { get; set; } + + // Work out average + public long AverageTime => (Count == 0) ? -1 : TotalTime / Count; + + // Intentionally not locked as it's only a get count + public bool IsActive => _timestamps.Count > 0; + + public long BeginTime + { + get + { + lock (_timestampLock) + { + if (_depth > 0) + { + return _currentTimestamp.BeginTime; + } + + return -1; + } + } + } + + // Timestamp collection + private List _timestamps; + private readonly object _timestampLock = new object(); + private readonly object _timestampListLock = new object(); + private Timestamp _currentTimestamp; + + // Depth of current timer, + // each begin call increments and each end call decrements + private int _depth; + + public TimingInfo() + { + _timestamps = new List(); + _depth = 0; + } + + public void Begin(long beginTime) + { + lock (_timestampLock) + { + // Finish current timestamp if already running + if (_depth > 0) + { + EndUnsafe(beginTime); + } + + BeginUnsafe(beginTime); + _depth++; + } + } + + private void BeginUnsafe(long beginTime) + { + _currentTimestamp.BeginTime = beginTime; + _currentTimestamp.EndTime = -1; + } + + public void End(long endTime) + { + lock (_timestampLock) + { + _depth--; + + if (_depth < 0) + { + throw new Exception("Timing info end called without corresponding begin"); + } + + EndUnsafe(endTime); + + // Still have others using this timing info so recreate start for them + if (_depth > 0) + { + BeginUnsafe(endTime); + } + } + } + + private void EndUnsafe(long endTime) + { + _currentTimestamp.EndTime = endTime; + lock (_timestampListLock) + { + _timestamps.Add(_currentTimestamp); + } + + long delta = _currentTimestamp.EndTime - _currentTimestamp.BeginTime; + TotalTime += delta; + Instant += delta; + + Count++; + InstantCount++; + } + + // Remove any timestamps before given timestamp to free memory + public void Cleanup(long before, long preserveStart, long preserveEnd) + { + lock (_timestampListLock) + { + int toRemove = 0; + int toPreserveStart = 0; + int toPreserveLen = 0; + + for (int i = 0; i < _timestamps.Count; i++) + { + if (_timestamps[i].EndTime < preserveStart) + { + toPreserveStart++; + InstantCount--; + Instant -= _timestamps[i].EndTime - _timestamps[i].BeginTime; + } + else if (_timestamps[i].EndTime < preserveEnd) + { + toPreserveLen++; + } + else if (_timestamps[i].EndTime < before) + { + toRemove++; + InstantCount--; + Instant -= _timestamps[i].EndTime - _timestamps[i].BeginTime; + } + else + { + // Assume timestamps are in chronological order so no more need to be removed + break; + } + } + + if (toPreserveStart > 0) + { + _timestamps.RemoveRange(0, toPreserveStart); + } + + if (toRemove > 0) + { + _timestamps.RemoveRange(toPreserveLen, toRemove); + } + } + } + + public Timestamp[] GetAllTimestamps() + { + lock (_timestampListLock) + { + Timestamp[] returnTimestamps = new Timestamp[_timestamps.Count]; + _timestamps.CopyTo(returnTimestamps); + return returnTimestamps; + } + } + } +} diff --git a/Ryujinx.Profiler/UI/ProfileButton.cs b/Ryujinx.Profiler/UI/ProfileButton.cs new file mode 100644 index 0000000000..7e2ae72884 --- /dev/null +++ b/Ryujinx.Profiler/UI/ProfileButton.cs @@ -0,0 +1,110 @@ +using System; +using OpenTK; +using OpenTK.Graphics.OpenGL; +using Ryujinx.Profiler.UI.SharpFontHelpers; + +namespace Ryujinx.Profiler.UI +{ + public class ProfileButton + { + // Store font service + private FontService _fontService; + + // Layout information + private int _left, _right; + private int _bottom, _top; + private int _height; + private int _padding; + + // Label information + private int _labelX, _labelY; + private string _label; + + // Misc + private Action _clicked; + private bool _visible; + + public ProfileButton(FontService fontService, Action clicked) + : this(fontService, clicked, 0, 0, 0, 0, 0) + { + _visible = false; + } + + public ProfileButton(FontService fontService, Action clicked, int x, int y, int padding, int height, int width) + : this(fontService, "", clicked, x, y, padding, height, width) + { + _visible = false; + } + + public ProfileButton(FontService fontService, string label, Action clicked, int x, int y, int padding, int height, int width = -1) + { + _fontService = fontService; + _clicked = clicked; + + UpdateSize(label, x, y, padding, height, width); + } + + public int UpdateSize(string label, int x, int y, int padding, int height, int width = -1) + { + _visible = true; + _label = label; + + if (width == -1) + { + // Dummy draw to measure size + width = (int)_fontService.DrawText(label, 0, 0, height, false); + } + + UpdateSize(x, y, padding, width, height); + + return _right - _left; + } + + public void UpdateSize(int x, int y, int padding, int width, int height) + { + _height = height; + _left = x; + _bottom = y; + _labelX = x + padding / 2; + _labelY = y + padding / 2; + _top = y + height + padding; + _right = x + width + padding; + } + + public void Draw() + { + if (!_visible) + { + return; + } + + // Draw backing rectangle + GL.Begin(PrimitiveType.Triangles); + GL.Color3(Color.Black); + GL.Vertex2(_left, _bottom); + GL.Vertex2(_left, _top); + GL.Vertex2(_right, _top); + + GL.Vertex2(_right, _top); + GL.Vertex2(_right, _bottom); + GL.Vertex2(_left, _bottom); + GL.End(); + + // Use font service to draw label + _fontService.DrawText(_label, _labelX, _labelY, _height); + } + + public bool ProcessClick(int x, int y) + { + // If button contains x, y + if (x > _left && x < _right && + y > _bottom && y < _top) + { + _clicked(); + return true; + } + + return false; + } + } +} diff --git a/Ryujinx.Profiler/UI/ProfileSorters.cs b/Ryujinx.Profiler/UI/ProfileSorters.cs new file mode 100644 index 0000000000..9f66de224c --- /dev/null +++ b/Ryujinx.Profiler/UI/ProfileSorters.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; + +namespace Ryujinx.Profiler.UI +{ + public static class ProfileSorters + { + public class InstantAscending : IComparer> + { + public int Compare(KeyValuePair pair1, KeyValuePair pair2) + => pair2.Value.Instant.CompareTo(pair1.Value.Instant); + } + + public class AverageAscending : IComparer> + { + public int Compare(KeyValuePair pair1, KeyValuePair pair2) + => pair2.Value.AverageTime.CompareTo(pair1.Value.AverageTime); + } + + public class TotalAscending : IComparer> + { + public int Compare(KeyValuePair pair1, KeyValuePair pair2) + => pair2.Value.TotalTime.CompareTo(pair1.Value.TotalTime); + } + + public class TagAscending : IComparer> + { + public int Compare(KeyValuePair pair1, KeyValuePair pair2) + => StringComparer.CurrentCulture.Compare(pair1.Key.Search, pair2.Key.Search); + } + } +} diff --git a/Ryujinx.Profiler/UI/ProfileWindow.cs b/Ryujinx.Profiler/UI/ProfileWindow.cs new file mode 100644 index 0000000000..1db70bc791 --- /dev/null +++ b/Ryujinx.Profiler/UI/ProfileWindow.cs @@ -0,0 +1,773 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text.RegularExpressions; +using OpenTK; +using OpenTK.Graphics; +using OpenTK.Graphics.OpenGL; +using OpenTK.Input; +using Ryujinx.Common; +using Ryujinx.Profiler.UI.SharpFontHelpers; + +namespace Ryujinx.Profiler.UI +{ + public partial class ProfileWindow : GameWindow + { + // List all buttons for index in button array + private enum ButtonIndex + { + TagTitle = 0, + InstantTitle = 1, + AverageTitle = 2, + TotalTitle = 3, + FilterBar = 4, + ShowHideInactive = 5, + Pause = 6, + ChangeDisplay = 7, + + // Don't automatically draw after here + ToggleFlags = 8, + Step = 9, + + // Update this when new buttons are added. + // These are indexes to the enum list + Autodraw = 8, + Count = 10, + } + + // Font service + private FontService _fontService; + + // UI variables + private ProfileButton[] _buttons; + + private bool _initComplete = false; + private bool _visible = true; + private bool _visibleChanged = true; + private bool _viewportUpdated = true; + private bool _redrawPending = true; + private bool _displayGraph = true; + private bool _displayFlags = true; + private bool _showInactive = true; + private bool _paused = false; + private bool _doStep = false; + + // Layout + private const int LineHeight = 16; + private const int TitleHeight = 24; + private const int TitleFontHeight = 16; + private const int LinePadding = 2; + private const int ColumnSpacing = 15; + private const int FilterHeight = 24; + private const int BottomBarHeight = FilterHeight + LineHeight; + + // Sorting + private List> _unsortedProfileData; + private IComparer> _sortAction = new ProfileSorters.TagAscending(); + + // Flag data + private long[] _timingFlagsAverages; + private long[] _timingFlagsLast; + + // Filtering + private string _filterText = ""; + private bool _regexEnabled = false; + + // Scrolling + private float _scrollPos = 0; + private float _minScroll = 0; + private float _maxScroll = 0; + + // Profile data storage + private List> _sortedProfileData; + private long _captureTime; + + // Input + private bool _backspaceDown = false; + private bool _prevBackspaceDown = false; + private double _backspaceDownTime = 0; + + // F35 used as no key + private Key _graphControlKey = Key.F35; + + // Event management + private double _updateTimer; + private double _processEventTimer; + private bool _profileUpdated = false; + private readonly object _profileDataLock = new object(); + + public ProfileWindow() + // Graphics mode enables 2xAA + : base(1280, 720, new GraphicsMode(new ColorFormat(8, 8, 8, 8), 1, 1, 2)) + { + Title = "Profiler"; + Location = new Point(DisplayDevice.Default.Width - 1280, + (DisplayDevice.Default.Height - 720) - 50); + + if (Profile.UpdateRate <= 0) + { + // Perform step regardless of flag type + Profile.RegisterFlagReceiver((t) => + { + if (!_paused) + { + _doStep = true; + } + }); + } + + // Large number to force an update on first update + _updateTimer = 0xFFFF; + + Init(); + + // Release context for render thread + Context.MakeCurrent(null); + } + + public void ToggleVisible() + { + _visible = !_visible; + _visibleChanged = true; + } + + private void SetSort(IComparer> filter) + { + _sortAction = filter; + _profileUpdated = true; + } + +#region OnLoad + /// + /// Setup OpenGL and load resources + /// + public void Init() + { + GL.ClearColor(Color.Black); + _fontService = new FontService(); + _fontService.InitializeTextures(); + _fontService.UpdateScreenHeight(Height); + + _buttons = new ProfileButton[(int)ButtonIndex.Count]; + _buttons[(int)ButtonIndex.TagTitle] = new ProfileButton(_fontService, () => SetSort(new ProfileSorters.TagAscending())); + _buttons[(int)ButtonIndex.InstantTitle] = new ProfileButton(_fontService, () => SetSort(new ProfileSorters.InstantAscending())); + _buttons[(int)ButtonIndex.AverageTitle] = new ProfileButton(_fontService, () => SetSort(new ProfileSorters.AverageAscending())); + _buttons[(int)ButtonIndex.TotalTitle] = new ProfileButton(_fontService, () => SetSort(new ProfileSorters.TotalAscending())); + _buttons[(int)ButtonIndex.Step] = new ProfileButton(_fontService, () => _doStep = true); + _buttons[(int)ButtonIndex.FilterBar] = new ProfileButton(_fontService, () => + { + _profileUpdated = true; + _regexEnabled = !_regexEnabled; + }); + + _buttons[(int)ButtonIndex.ShowHideInactive] = new ProfileButton(_fontService, () => + { + _profileUpdated = true; + _showInactive = !_showInactive; + }); + + _buttons[(int)ButtonIndex.Pause] = new ProfileButton(_fontService, () => + { + _profileUpdated = true; + _paused = !_paused; + }); + + _buttons[(int)ButtonIndex.ToggleFlags] = new ProfileButton(_fontService, () => + { + _displayFlags = !_displayFlags; + _redrawPending = true; + }); + + _buttons[(int)ButtonIndex.ChangeDisplay] = new ProfileButton(_fontService, () => + { + _displayGraph = !_displayGraph; + _redrawPending = true; + }); + + Visible = _visible; + } +#endregion + +#region OnResize + /// + /// Respond to resize events + /// + /// Contains information on the new GameWindow size. + /// There is no need to call the base implementation. + protected override void OnResize(EventArgs e) + { + _viewportUpdated = true; + } +#endregion + +#region OnClose + /// + /// Intercept close event and hide instead + /// + protected override void OnClosing(CancelEventArgs e) + { + // Hide window + _visible = false; + _visibleChanged = true; + + // Cancel close + e.Cancel = true; + + base.OnClosing(e); + } +#endregion + +#region OnUpdateFrame + /// + /// Profile Update Loop + /// + /// Contains timing information. + /// There is no need to call the base implementation. + public void Update(FrameEventArgs e) + { + if (_visibleChanged) + { + Visible = _visible; + _visibleChanged = false; + } + + // Backspace handling + if (_backspaceDown) + { + if (!_prevBackspaceDown) + { + _backspaceDownTime = 0; + FilterBackspace(); + } + else + { + _backspaceDownTime += e.Time; + if (_backspaceDownTime > 0.3) + { + _backspaceDownTime -= 0.05; + FilterBackspace(); + } + } + } + _prevBackspaceDown = _backspaceDown; + + // Get timing data if enough time has passed + _updateTimer += e.Time; + if (_doStep || ((Profile.UpdateRate > 0) && (!_paused && (_updateTimer > Profile.UpdateRate)))) + { + _updateTimer = 0; + _captureTime = PerformanceCounter.ElapsedTicks; + _timingFlags = Profile.GetTimingFlags(); + _doStep = false; + _profileUpdated = true; + + _unsortedProfileData = Profile.GetProfilingData(); + (_timingFlagsAverages, _timingFlagsLast) = Profile.GetTimingAveragesAndLast(); + + } + + // Filtering + if (_profileUpdated) + { + lock (_profileDataLock) + { + _sortedProfileData = _showInactive ? _unsortedProfileData : _unsortedProfileData.FindAll(kvp => kvp.Value.IsActive); + + if (_sortAction != null) + { + _sortedProfileData.Sort(_sortAction); + } + + if (_regexEnabled) + { + try + { + Regex filterRegex = new Regex(_filterText, RegexOptions.IgnoreCase); + if (_filterText != "") + { + _sortedProfileData = _sortedProfileData.Where((pair => filterRegex.IsMatch(pair.Key.Search))).ToList(); + } + } + catch (ArgumentException argException) + { + // Skip filtering for invalid regex + } + } + else + { + // Regular filtering + _sortedProfileData = _sortedProfileData.Where((pair => pair.Key.Search.ToLower().Contains(_filterText.ToLower()))).ToList(); + } + } + + _profileUpdated = false; + _redrawPending = true; + _initComplete = true; + } + + // Check for events 20 times a second + _processEventTimer += e.Time; + if (_processEventTimer > 0.05) + { + ProcessEvents(); + + if (_graphControlKey != Key.F35) + { + switch (_graphControlKey) + { + case Key.Left: + _graphPosition += (long) (GraphMoveSpeed * e.Time); + break; + + case Key.Right: + _graphPosition = Math.Max(_graphPosition - (long) (GraphMoveSpeed * e.Time), 0); + break; + + case Key.Up: + _graphZoom = MathF.Min(_graphZoom + (float) (GraphZoomSpeed * e.Time), 100.0f); + break; + + case Key.Down: + _graphZoom = MathF.Max(_graphZoom - (float) (GraphZoomSpeed * e.Time), 1f); + break; + } + + _redrawPending = true; + } + + _processEventTimer = 0; + } + } +#endregion + +#region OnRenderFrame + /// + /// Profile Render Loop + /// + /// There is no need to call the base implementation. + public void Draw() + { + if (!_visible || !_initComplete) + { + return; + } + + // Update viewport + if (_viewportUpdated) + { + GL.Viewport(0, 0, Width, Height); + + GL.MatrixMode(MatrixMode.Projection); + GL.LoadIdentity(); + GL.Ortho(0, Width, 0, Height, 0.0, 4.0); + + _fontService.UpdateScreenHeight(Height); + + _viewportUpdated = false; + _redrawPending = true; + } + + if (!_redrawPending) + { + return; + } + + // Frame setup + GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); + GL.ClearColor(Color.Black); + + _fontService.fontColor = Color.White; + int verticalIndex = 0; + + float width; + float maxWidth = 0; + float yOffset = _scrollPos - TitleHeight; + float xOffset = 10; + float timingDataLeft; + float timingWidth; + + // Background lines to make reading easier + #region Background Lines + GL.Enable(EnableCap.ScissorTest); + GL.Scissor(0, BottomBarHeight, Width, Height - TitleHeight - BottomBarHeight); + GL.Begin(PrimitiveType.Triangles); + GL.Color3(0.2f, 0.2f, 0.2f); + for (int i = 0; i < _sortedProfileData.Count; i += 2) + { + float top = GetLineY(yOffset, LineHeight, LinePadding, false, i - 1); + float bottom = GetLineY(yOffset, LineHeight, LinePadding, false, i); + + // Skip rendering out of bounds bars + if (top < 0 || bottom > Height) + continue; + + GL.Vertex2(0, bottom); + GL.Vertex2(0, top); + GL.Vertex2(Width, top); + + GL.Vertex2(Width, top); + GL.Vertex2(Width, bottom); + GL.Vertex2(0, bottom); + } + GL.End(); + _maxScroll = (LineHeight + LinePadding) * (_sortedProfileData.Count - 1); +#endregion + + lock (_profileDataLock) + { +// Display category +#region Category + verticalIndex = 0; + foreach (var entry in _sortedProfileData) + { + if (entry.Key.Category == null) + { + verticalIndex++; + continue; + } + + float y = GetLineY(yOffset, LineHeight, LinePadding, true, verticalIndex++); + width = _fontService.DrawText(entry.Key.Category, xOffset, y, LineHeight); + + if (width > maxWidth) + { + maxWidth = width; + } + } + GL.Disable(EnableCap.ScissorTest); + + width = _fontService.DrawText("Category", xOffset, Height - TitleFontHeight, TitleFontHeight); + if (width > maxWidth) + maxWidth = width; + + xOffset += maxWidth + ColumnSpacing; +#endregion + +// Display session group +#region Session Group + maxWidth = 0; + verticalIndex = 0; + + GL.Enable(EnableCap.ScissorTest); + foreach (var entry in _sortedProfileData) + { + if (entry.Key.SessionGroup == null) + { + verticalIndex++; + continue; + } + + float y = GetLineY(yOffset, LineHeight, LinePadding, true, verticalIndex++); + width = _fontService.DrawText(entry.Key.SessionGroup, xOffset, y, LineHeight); + + if (width > maxWidth) + { + maxWidth = width; + } + } + GL.Disable(EnableCap.ScissorTest); + + width = _fontService.DrawText("Group", xOffset, Height - TitleFontHeight, TitleFontHeight); + if (width > maxWidth) + maxWidth = width; + + xOffset += maxWidth + ColumnSpacing; +#endregion + +// Display session item +#region Session Item + maxWidth = 0; + verticalIndex = 0; + GL.Enable(EnableCap.ScissorTest); + foreach (var entry in _sortedProfileData) + { + if (entry.Key.SessionItem == null) + { + verticalIndex++; + continue; + } + + float y = GetLineY(yOffset, LineHeight, LinePadding, true, verticalIndex++); + width = _fontService.DrawText(entry.Key.SessionItem, xOffset, y, LineHeight); + + if (width > maxWidth) + { + maxWidth = width; + } + } + GL.Disable(EnableCap.ScissorTest); + + width = _fontService.DrawText("Item", xOffset, Height - TitleFontHeight, TitleFontHeight); + if (width > maxWidth) + maxWidth = width; + + xOffset += maxWidth + ColumnSpacing; + _buttons[(int)ButtonIndex.TagTitle].UpdateSize(0, Height - TitleFontHeight, 0, (int)xOffset, TitleFontHeight); +#endregion + + // Timing data + timingWidth = Width - xOffset - 370; + timingDataLeft = xOffset; + + GL.Scissor((int)xOffset, BottomBarHeight, (int)timingWidth, Height - TitleHeight - BottomBarHeight); + + if (_displayGraph) + { + DrawGraph(xOffset, yOffset, timingWidth); + } + else + { + DrawBars(xOffset, yOffset, timingWidth); + } + + GL.Scissor(0, BottomBarHeight, Width, Height - TitleHeight - BottomBarHeight); + + if (!_displayGraph) + { + _fontService.DrawText("Blue: Instant, Green: Avg, Red: Total", xOffset, Height - TitleFontHeight, TitleFontHeight); + } + + xOffset = Width - 360; + +// Display timestamps +#region Timestamps + verticalIndex = 0; + long totalInstant = 0; + long totalAverage = 0; + long totalTime = 0; + long totalCount = 0; + + GL.Enable(EnableCap.ScissorTest); + foreach (var entry in _sortedProfileData) + { + float y = GetLineY(yOffset, LineHeight, LinePadding, true, verticalIndex++); + + _fontService.DrawText($"{GetTimeString(entry.Value.Instant)} ({entry.Value.InstantCount})", xOffset, y, LineHeight); + + _fontService.DrawText(GetTimeString(entry.Value.AverageTime), 150 + xOffset, y, LineHeight); + + _fontService.DrawText(GetTimeString(entry.Value.TotalTime), 260 + xOffset, y, LineHeight); + + totalInstant += entry.Value.Instant; + totalAverage += entry.Value.AverageTime; + totalTime += entry.Value.TotalTime; + totalCount += entry.Value.InstantCount; + } + GL.Disable(EnableCap.ScissorTest); + + float yHeight = Height - TitleFontHeight; + + _fontService.DrawText("Instant (Count)", xOffset, yHeight, TitleFontHeight); + _buttons[(int)ButtonIndex.InstantTitle].UpdateSize((int)xOffset, (int)yHeight, 0, 130, TitleFontHeight); + + _fontService.DrawText("Average", 150 + xOffset, yHeight, TitleFontHeight); + _buttons[(int)ButtonIndex.AverageTitle].UpdateSize((int)(150 + xOffset), (int)yHeight, 0, 130, TitleFontHeight); + + _fontService.DrawText("Total (ms)", 260 + xOffset, yHeight, TitleFontHeight); + _buttons[(int)ButtonIndex.TotalTitle].UpdateSize((int)(260 + xOffset), (int)yHeight, 0, Width, TitleFontHeight); + + // Totals + yHeight = FilterHeight + 3; + int textHeight = LineHeight - 2; + + _fontService.fontColor = new Color(100, 100, 255, 255); + float tempWidth = _fontService.DrawText($"Host {GetTimeString(_timingFlagsLast[(int)TimingFlagType.SystemFrame])} " + + $"({GetTimeString(_timingFlagsAverages[(int)TimingFlagType.SystemFrame])})", 5, yHeight, textHeight); + + _fontService.fontColor = Color.Red; + _fontService.DrawText($"Game {GetTimeString(_timingFlagsLast[(int)TimingFlagType.FrameSwap])} " + + $"({GetTimeString(_timingFlagsAverages[(int)TimingFlagType.FrameSwap])})", 15 + tempWidth, yHeight, textHeight); + _fontService.fontColor = Color.White; + + + _fontService.DrawText($"{GetTimeString(totalInstant)} ({totalCount})", xOffset, yHeight, textHeight); + _fontService.DrawText(GetTimeString(totalAverage), 150 + xOffset, yHeight, textHeight); + _fontService.DrawText(GetTimeString(totalTime), 260 + xOffset, yHeight, textHeight); +#endregion + } + +#region Bottom bar + // Show/Hide Inactive + float widthShowHideButton = _buttons[(int)ButtonIndex.ShowHideInactive].UpdateSize($"{(_showInactive ? "Hide" : "Show")} Inactive", 5, 5, 4, 16); + + // Play/Pause + float widthPlayPauseButton = _buttons[(int)ButtonIndex.Pause].UpdateSize(_paused ? "Play" : "Pause", 15 + (int)widthShowHideButton, 5, 4, 16) + widthShowHideButton; + + // Step + float widthStepButton = widthPlayPauseButton; + + if (_paused) + { + widthStepButton += _buttons[(int)ButtonIndex.Step].UpdateSize("Step", (int)(25 + widthPlayPauseButton), 5, 4, 16) + 10; + _buttons[(int)ButtonIndex.Step].Draw(); + } + + // Change display + float widthChangeDisplay = _buttons[(int)ButtonIndex.ChangeDisplay].UpdateSize($"View: {(_displayGraph ? "Graph" : "Bars")}", 25 + (int)widthStepButton, 5, 4, 16) + widthStepButton; + + width = widthChangeDisplay; + + if (_displayGraph) + { + width += _buttons[(int) ButtonIndex.ToggleFlags].UpdateSize($"{(_displayFlags ? "Hide" : "Show")} Flags", 35 + (int)widthChangeDisplay, 5, 4, 16) + 10; + _buttons[(int)ButtonIndex.ToggleFlags].Draw(); + } + + // Filter bar + _fontService.DrawText($"{(_regexEnabled ? "Regex " : "Filter")}: {_filterText}", 35 + width, 7, 16); + _buttons[(int)ButtonIndex.FilterBar].UpdateSize((int)(45 + width), 0, 0, Width, FilterHeight); +#endregion + + // Draw buttons + for (int i = 0; i < (int)ButtonIndex.Autodraw; i++) + { + _buttons[i].Draw(); + } + +// Dividing lines +#region Dividing lines + GL.Color3(Color.White); + GL.Begin(PrimitiveType.Lines); + // Top divider + GL.Vertex2(0, Height -TitleHeight); + GL.Vertex2(Width, Height - TitleHeight); + + // Bottom divider + GL.Vertex2(0, FilterHeight); + GL.Vertex2(Width, FilterHeight); + + GL.Vertex2(0, BottomBarHeight); + GL.Vertex2(Width, BottomBarHeight); + + // Bottom vertical dividers + GL.Vertex2(widthShowHideButton + 10, 0); + GL.Vertex2(widthShowHideButton + 10, FilterHeight); + + GL.Vertex2(widthPlayPauseButton + 20, 0); + GL.Vertex2(widthPlayPauseButton + 20, FilterHeight); + + if (_paused) + { + GL.Vertex2(widthStepButton + 20, 0); + GL.Vertex2(widthStepButton + 20, FilterHeight); + } + + if (_displayGraph) + { + GL.Vertex2(widthChangeDisplay + 30, 0); + GL.Vertex2(widthChangeDisplay + 30, FilterHeight); + } + + GL.Vertex2(width + 30, 0); + GL.Vertex2(width + 30, FilterHeight); + + // Column dividers + float timingDataTop = Height - TitleHeight; + + GL.Vertex2(timingDataLeft, FilterHeight); + GL.Vertex2(timingDataLeft, timingDataTop); + + GL.Vertex2(timingWidth + timingDataLeft, FilterHeight); + GL.Vertex2(timingWidth + timingDataLeft, timingDataTop); + GL.End(); +#endregion + + _redrawPending = false; + SwapBuffers(); + } +#endregion + + private string GetTimeString(long timestamp) + { + float time = (float)timestamp / PerformanceCounter.TicksPerMillisecond; + return (time < 1) ? $"{time * 1000:F3}us" : $"{time:F3}ms"; + } + + private void FilterBackspace() + { + if (_filterText.Length <= 1) + { + _filterText = ""; + } + else + { + _filterText = _filterText.Remove(_filterText.Length - 1, 1); + } + } + + private float GetLineY(float offset, float lineHeight, float padding, bool centre, int line) + { + return Height + offset - lineHeight - padding - ((lineHeight + padding) * line) + ((centre) ? padding : 0); + } + + protected override void OnKeyPress(KeyPressEventArgs e) + { + _filterText += e.KeyChar; + _profileUpdated = true; + } + + protected override void OnKeyDown(KeyboardKeyEventArgs e) + { + switch (e.Key) + { + case Key.BackSpace: + _profileUpdated = _backspaceDown = true; + return; + + case Key.Left: + case Key.Right: + case Key.Up: + case Key.Down: + _graphControlKey = e.Key; + return; + } + base.OnKeyUp(e); + } + + protected override void OnKeyUp(KeyboardKeyEventArgs e) + { + // Can't go into switch as value isn't constant + if (e.Key == Profile.Controls.Buttons.ToggleProfiler) + { + ToggleVisible(); + return; + } + + switch (e.Key) + { + case Key.BackSpace: + _backspaceDown = false; + return; + + case Key.Left: + case Key.Right: + case Key.Up: + case Key.Down: + _graphControlKey = Key.F35; + return; + } + base.OnKeyUp(e); + } + + protected override void OnMouseUp(MouseButtonEventArgs e) + { + foreach (ProfileButton button in _buttons) + { + if (button.ProcessClick(e.X, Height - e.Y)) + return; + } + } + + protected override void OnMouseWheel(MouseWheelEventArgs e) + { + _scrollPos += e.Delta * -30; + if (_scrollPos < _minScroll) + _scrollPos = _minScroll; + if (_scrollPos > _maxScroll) + _scrollPos = _maxScroll; + + _redrawPending = true; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Profiler/UI/ProfileWindowBars.cs b/Ryujinx.Profiler/UI/ProfileWindowBars.cs new file mode 100644 index 0000000000..ab5b4fd131 --- /dev/null +++ b/Ryujinx.Profiler/UI/ProfileWindowBars.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using OpenTK; +using OpenTK.Graphics.OpenGL; + +namespace Ryujinx.Profiler.UI +{ + public partial class ProfileWindow + { + private void DrawBars(float xOffset, float yOffset, float width) + { + if (_sortedProfileData.Count != 0) + { + long maxAverage; + long maxTotal; + + int verticalIndex = 0; + float barHeight = (LineHeight - LinePadding) / 3.0f; + + // Get max values + long maxInstant = maxAverage = maxTotal = 0; + foreach (KeyValuePair kvp in _sortedProfileData) + { + maxInstant = Math.Max(maxInstant, kvp.Value.Instant); + maxAverage = Math.Max(maxAverage, kvp.Value.AverageTime); + maxTotal = Math.Max(maxTotal, kvp.Value.TotalTime); + } + + GL.Enable(EnableCap.ScissorTest); + GL.Begin(PrimitiveType.Triangles); + foreach (var entry in _sortedProfileData) + { + // Instant + GL.Color3(Color.Blue); + float bottom = GetLineY(yOffset, LineHeight, LinePadding, true, verticalIndex++); + float top = bottom + barHeight; + float right = (float)entry.Value.Instant / maxInstant * width + xOffset; + + // Skip rendering out of bounds bars + if (top < 0 || bottom > Height) + continue; + + GL.Vertex2(xOffset, bottom); + GL.Vertex2(xOffset, top); + GL.Vertex2(right, top); + + GL.Vertex2(right, top); + GL.Vertex2(right, bottom); + GL.Vertex2(xOffset, bottom); + + // Average + GL.Color3(Color.Green); + top += barHeight; + bottom += barHeight; + right = (float)entry.Value.AverageTime / maxAverage * width + xOffset; + + GL.Vertex2(xOffset, bottom); + GL.Vertex2(xOffset, top); + GL.Vertex2(right, top); + + GL.Vertex2(right, top); + GL.Vertex2(right, bottom); + GL.Vertex2(xOffset, bottom); + + // Total + GL.Color3(Color.Red); + top += barHeight; + bottom += barHeight; + right = (float)entry.Value.TotalTime / maxTotal * width + xOffset; + + GL.Vertex2(xOffset, bottom); + GL.Vertex2(xOffset, top); + GL.Vertex2(right, top); + + GL.Vertex2(right, top); + GL.Vertex2(right, bottom); + GL.Vertex2(xOffset, bottom); + } + + GL.End(); + GL.Disable(EnableCap.ScissorTest); + } + } + } +} diff --git a/Ryujinx.Profiler/UI/ProfileWindowGraph.cs b/Ryujinx.Profiler/UI/ProfileWindowGraph.cs new file mode 100644 index 0000000000..6a4a52a998 --- /dev/null +++ b/Ryujinx.Profiler/UI/ProfileWindowGraph.cs @@ -0,0 +1,151 @@ +using System; +using OpenTK; +using OpenTK.Graphics.OpenGL; +using Ryujinx.Common; + +namespace Ryujinx.Profiler.UI +{ + public partial class ProfileWindow + { + // Color index equal to timing flag type as int + private Color[] _timingFlagColors = new[] + { + new Color(150, 25, 25, 50), // FrameSwap = 0 + new Color(25, 25, 150, 50), // SystemFrame = 1 + }; + + private TimingFlag[] _timingFlags; + + private const float GraphMoveSpeed = 40000; + private const float GraphZoomSpeed = 50; + + private float _graphZoom = 1; + private float _graphPosition = 0; + + private void DrawGraph(float xOffset, float yOffset, float width) + { + if (_sortedProfileData.Count != 0) + { + int left, right; + float top, bottom; + + int verticalIndex = 0; + float graphRight = xOffset + width; + float barHeight = (LineHeight - LinePadding); + long history = Profile.HistoryLength; + double timeWidthTicks = history / (double)_graphZoom; + long graphPositionTicks = (long)(_graphPosition * PerformanceCounter.TicksPerMillisecond); + long ticksPerPixel = (long)(timeWidthTicks / width); + + // Reset start point if out of bounds + if (timeWidthTicks + graphPositionTicks > history) + { + graphPositionTicks = history - (long)timeWidthTicks; + _graphPosition = (float)graphPositionTicks / PerformanceCounter.TicksPerMillisecond; + } + + graphPositionTicks = _captureTime - graphPositionTicks; + + GL.Enable(EnableCap.ScissorTest); + + // Draw timing flags + if (_displayFlags) + { + TimingFlagType prevType = TimingFlagType.Count; + + GL.Enable(EnableCap.Blend); + GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha); + + GL.Begin(PrimitiveType.Lines); + foreach (TimingFlag timingFlag in _timingFlags) + { + if (prevType != timingFlag.FlagType) + { + prevType = timingFlag.FlagType; + GL.Color4(_timingFlagColors[(int)prevType]); + } + + int x = (int)(graphRight - ((graphPositionTicks - timingFlag.Timestamp) / timeWidthTicks) * width); + GL.Vertex2(x, 0); + GL.Vertex2(x, Height); + } + GL.End(); + GL.Disable(EnableCap.Blend); + } + + // Draw bars + GL.Begin(PrimitiveType.Triangles); + foreach (var entry in _sortedProfileData) + { + long furthest = 0; + + bottom = GetLineY(yOffset, LineHeight, LinePadding, true, verticalIndex); + top = bottom + barHeight; + + // Skip rendering out of bounds bars + if (top < 0 || bottom > Height) + { + verticalIndex++; + continue; + } + + + GL.Color3(Color.Green); + foreach (Timestamp timestamp in entry.Value.GetAllTimestamps()) + { + // Skip drawing multiple timestamps on same pixel + if (timestamp.EndTime < furthest) + continue; + furthest = timestamp.EndTime + ticksPerPixel; + + left = (int)(graphRight - ((graphPositionTicks - timestamp.BeginTime) / timeWidthTicks) * width); + right = (int)(graphRight - ((graphPositionTicks - timestamp.EndTime) / timeWidthTicks) * width); + + // Make sure width is at least 1px + right = Math.Max(left + 1, right); + + GL.Vertex2(left, bottom); + GL.Vertex2(left, top); + GL.Vertex2(right, top); + + GL.Vertex2(right, top); + GL.Vertex2(right, bottom); + GL.Vertex2(left, bottom); + } + + // Currently capturing timestamp + GL.Color3(Color.Red); + long entryBegin = entry.Value.BeginTime; + if (entryBegin != -1) + { + left = (int)(graphRight - ((graphPositionTicks - entryBegin) / timeWidthTicks) * width); + + // Make sure width is at least 1px + left = Math.Min(left - 1, (int)graphRight); + + GL.Vertex2(left, bottom); + GL.Vertex2(left, top); + GL.Vertex2(graphRight, top); + + GL.Vertex2(graphRight, top); + GL.Vertex2(graphRight, bottom); + GL.Vertex2(left, bottom); + } + + verticalIndex++; + } + + GL.End(); + GL.Disable(EnableCap.ScissorTest); + + string label = $"-{MathF.Round(_graphPosition, 2)} ms"; + + // Dummy draw for measure + float labelWidth = _fontService.DrawText(label, 0, 0, LineHeight, false); + _fontService.DrawText(label, graphRight - labelWidth - LinePadding, FilterHeight + LinePadding, LineHeight); + + _fontService.DrawText($"-{MathF.Round((float)((timeWidthTicks / PerformanceCounter.TicksPerMillisecond) + _graphPosition), 2)} ms", xOffset + LinePadding, FilterHeight + LinePadding, LineHeight); + } + } + } +} diff --git a/Ryujinx.Profiler/UI/ProfileWindowManager.cs b/Ryujinx.Profiler/UI/ProfileWindowManager.cs new file mode 100644 index 0000000000..1360302933 --- /dev/null +++ b/Ryujinx.Profiler/UI/ProfileWindowManager.cs @@ -0,0 +1,95 @@ +using System.Threading; +using OpenTK; +using OpenTK.Input; +using Ryujinx.Common; + +namespace Ryujinx.Profiler.UI +{ + public class ProfileWindowManager + { + private ProfileWindow _window; + private Thread _profileThread; + private Thread _renderThread; + private bool _profilerRunning; + + // Timing + private double _prevTime; + + public ProfileWindowManager() + { + if (Profile.ProfilingEnabled()) + { + _profilerRunning = true; + _prevTime = 0; + _profileThread = new Thread(ProfileLoop) + { + Name = "Profiler.ProfileThread" + }; + _profileThread.Start(); + } + } + + public void ToggleVisible() + { + if (Profile.ProfilingEnabled()) + { + _window.ToggleVisible(); + } + } + + public void Close() + { + if (_window != null) + { + _profilerRunning = false; + _window.Close(); + _window.Dispose(); + } + + _window = null; + } + + public void UpdateKeyInput(KeyboardState keyboard) + { + if (Profile.Controls.TogglePressed(keyboard)) + { + ToggleVisible(); + } + Profile.Controls.SetPrevKeyboardState(keyboard); + } + + private void ProfileLoop() + { + using (_window = new ProfileWindow()) + { + // Create thread for render loop + _renderThread = new Thread(RenderLoop) + { + Name = "Profiler.RenderThread" + }; + _renderThread.Start(); + + while (_profilerRunning) + { + double time = (double)PerformanceCounter.ElapsedTicks / PerformanceCounter.TicksPerSecond; + _window.Update(new FrameEventArgs(time - _prevTime)); + _prevTime = time; + + // Sleep to be less taxing, update usually does very little + Thread.Sleep(1); + } + } + } + + private void RenderLoop() + { + _window.Context.MakeCurrent(_window.WindowInfo); + + while (_profilerRunning) + { + _window.Draw(); + Thread.Sleep(1); + } + } + } +} diff --git a/Ryujinx.Profiler/UI/SharpFontHelpers/FontService.cs b/Ryujinx.Profiler/UI/SharpFontHelpers/FontService.cs new file mode 100644 index 0000000000..32846977e9 --- /dev/null +++ b/Ryujinx.Profiler/UI/SharpFontHelpers/FontService.cs @@ -0,0 +1,257 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; +using OpenTK; +using OpenTK.Graphics.OpenGL; +using SharpFont; + +namespace Ryujinx.Profiler.UI.SharpFontHelpers +{ + public class FontService + { + private struct CharacterInfo + { + public float Left; + public float Right; + public float Top; + public float Bottom; + + public int Width; + public float Height; + + public float AspectRatio; + + public float BearingX; + public float BearingY; + public float Advance; + } + + private const int SheetWidth = 1024; + private const int SheetHeight = 512; + private int ScreenWidth, ScreenHeight; + private int CharacterTextureSheet; + private CharacterInfo[] characters; + + public Color fontColor { get; set; } = Color.Black; + + private string GetFontPath() + { + string fontFolder = Environment.GetFolderPath(Environment.SpecialFolder.Fonts); + + // Only uses Arial, add more fonts here if wanted + string path = Path.Combine(fontFolder, "arial.ttf"); + if (File.Exists(path)) + { + return path; + } + + throw new Exception($"Profiler exception. Required font Courier New or Arial not installed to {fontFolder}"); + } + + public void InitializeTextures() + { + // Create and init some vars + uint[] rawCharacterSheet = new uint[SheetWidth * SheetHeight]; + int x; + int y; + int lineOffset; + int maxHeight; + + x = y = lineOffset = maxHeight = 0; + characters = new CharacterInfo[94]; + + // Get font + var font = new FontFace(File.OpenRead(GetFontPath())); + + // Update raw data for each character + for (int i = 0; i < 94; i++) + { + var surface = RenderSurface((char)(i + 33), font, out float xBearing, out float yBearing, out float advance); + + characters[i] = UpdateTexture(surface, ref rawCharacterSheet, ref x, ref y, ref lineOffset); + characters[i].BearingX = xBearing; + characters[i].BearingY = yBearing; + characters[i].Advance = advance; + + if (maxHeight < characters[i].Height) + maxHeight = (int)characters[i].Height; + } + + // Fix height for characters shorter than line height + for (int i = 0; i < 94; i++) + { + characters[i].BearingX /= characters[i].Width; + characters[i].BearingY /= maxHeight; + characters[i].Advance /= characters[i].Width; + characters[i].Height /= maxHeight; + characters[i].AspectRatio = (float)characters[i].Width / maxHeight; + } + + // Convert raw data into texture + CharacterTextureSheet = GL.GenTexture(); + GL.BindTexture(TextureTarget.Texture2D, CharacterTextureSheet); + + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Clamp); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Clamp); + + GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, SheetWidth, SheetHeight, 0, PixelFormat.Rgba, PixelType.UnsignedInt8888, rawCharacterSheet); + + GL.BindTexture(TextureTarget.Texture2D, 0); + } + + public void UpdateScreenHeight(int height) + { + ScreenHeight = height; + } + + public float DrawText(string text, float x, float y, float height, bool draw = true) + { + float originalX = x; + + // Skip out of bounds draw + if (y < height * -2 || y > ScreenHeight + height * 2) + { + draw = false; + } + + if (draw) + { + // Use font map texture + GL.BindTexture(TextureTarget.Texture2D, CharacterTextureSheet); + + // Enable blending and textures + GL.Enable(EnableCap.Texture2D); + GL.Enable(EnableCap.Blend); + GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha); + + // Draw all characters + GL.Begin(PrimitiveType.Triangles); + GL.Color4(fontColor); + } + + for (int i = 0; i < text.Length; i++) + { + if (text[i] == ' ') + { + x += height / 4; + continue; + } + + CharacterInfo charInfo = characters[text[i] - 33]; + float width = (charInfo.AspectRatio * height); + x += (charInfo.BearingX * charInfo.AspectRatio) * width; + float right = x + width; + if (draw) + { + DrawChar(charInfo, x, right, y + height * (charInfo.Height - charInfo.BearingY), y - height * charInfo.BearingY); + } + x = right + charInfo.Advance * charInfo.AspectRatio + 1; + } + + if (draw) + { + GL.End(); + + // Cleanup for caller + GL.BindTexture(TextureTarget.Texture2D, 0); + GL.Disable(EnableCap.Texture2D); + GL.Disable(EnableCap.Blend); + } + + // Return width of rendered text + return x - originalX; + } + + private void DrawChar(CharacterInfo charInfo, float left, float right, float top, float bottom) + { + GL.TexCoord2(charInfo.Left, charInfo.Bottom); GL.Vertex2(left, bottom); + GL.TexCoord2(charInfo.Left, charInfo.Top); GL.Vertex2(left, top); + GL.TexCoord2(charInfo.Right, charInfo.Top); GL.Vertex2(right, top); + + GL.TexCoord2(charInfo.Right, charInfo.Top); GL.Vertex2(right, top); + GL.TexCoord2(charInfo.Right, charInfo.Bottom); GL.Vertex2(right, bottom); + GL.TexCoord2(charInfo.Left, charInfo.Bottom); GL.Vertex2(left, bottom); + } + + public unsafe Surface RenderSurface(char c, FontFace font, out float xBearing, out float yBearing, out float advance) + { + var glyph = font.GetGlyph(c, 64); + xBearing = glyph.HorizontalMetrics.Bearing.X; + yBearing = glyph.RenderHeight - glyph.HorizontalMetrics.Bearing.Y; + advance = glyph.HorizontalMetrics.Advance; + + var surface = new Surface + { + Bits = Marshal.AllocHGlobal(glyph.RenderWidth * glyph.RenderHeight), + Width = glyph.RenderWidth, + Height = glyph.RenderHeight, + Pitch = glyph.RenderWidth + }; + + var stuff = (byte*)surface.Bits; + for (int i = 0; i < surface.Width * surface.Height; i++) + *stuff++ = 0; + + glyph.RenderTo(surface); + + return surface; + } + + private CharacterInfo UpdateTexture(Surface surface, ref uint[] rawCharMap, ref int posX, ref int posY, ref int lineOffset) + { + int width = surface.Width; + int height = surface.Height; + int len = width * height; + byte[] data = new byte[len]; + + // Get character bitmap + Marshal.Copy(surface.Bits, data, 0, len); + + // Find a slot + if (posX + width > SheetWidth) + { + posX = 0; + posY += lineOffset; + lineOffset = 0; + } + + // Update lineOffset + if (lineOffset < height) + { + lineOffset = height + 1; + } + + // Copy char to sheet + for (int y = 0; y < height; y++) + { + int destOffset = (y + posY) * SheetWidth + posX; + int sourceOffset = y * width; + + for (int x = 0; x < width; x++) + { + rawCharMap[destOffset + x] = (uint)((0xFFFFFF << 8) | data[sourceOffset + x]); + } + } + + // Generate character info + CharacterInfo charInfo = new CharacterInfo() + { + Left = (float)posX / SheetWidth, + Right = (float)(posX + width) / SheetWidth, + Top = (float)(posY - 1) / SheetHeight, + Bottom = (float)(posY + height) / SheetHeight, + Width = width, + Height = height, + }; + + // Update x + posX += width + 1; + + // Give the memory back + Marshal.FreeHGlobal(surface.Bits); + return charInfo; + } + } +} \ No newline at end of file diff --git a/Ryujinx.ShaderTools/Memory.cs b/Ryujinx.ShaderTools/Memory.cs deleted file mode 100644 index f801ab39a9..0000000000 --- a/Ryujinx.ShaderTools/Memory.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Ryujinx.Graphics.Gal; -using System.IO; - -namespace Ryujinx.ShaderTools -{ - class Memory : IGalMemory - { - private Stream BaseStream; - - private BinaryReader Reader; - - public Memory(Stream BaseStream) - { - this.BaseStream = BaseStream; - - Reader = new BinaryReader(BaseStream); - } - - public int ReadInt32(long Position) - { - BaseStream.Seek(Position, SeekOrigin.Begin); - - return Reader.ReadInt32(); - } - } -} diff --git a/Ryujinx.ShaderTools/Program.cs b/Ryujinx.ShaderTools/Program.cs index 3597f25624..25ac8d2af8 100644 --- a/Ryujinx.ShaderTools/Program.cs +++ b/Ryujinx.ShaderTools/Program.cs @@ -1,5 +1,5 @@ -using Ryujinx.Graphics.Gal; -using Ryujinx.Graphics.Gal.Shader; +using Ryujinx.Graphics.Shader; +using Ryujinx.Graphics.Shader.Translation; using System; using System.IO; @@ -9,34 +9,25 @@ namespace Ryujinx.ShaderTools { static void Main(string[] args) { - if (args.Length == 2) + if (args.Length == 1 || args.Length == 2) { - GlslDecompiler Decompiler = new GlslDecompiler(); + TranslationFlags flags = TranslationFlags.DebugMode; - GalShaderType ShaderType = GalShaderType.Vertex; - - switch (args[0].ToLower()) + if (args.Length == 2 && args[0] == "--compute") { - case "v": ShaderType = GalShaderType.Vertex; break; - case "tc": ShaderType = GalShaderType.TessControl; break; - case "te": ShaderType = GalShaderType.TessEvaluation; break; - case "g": ShaderType = GalShaderType.Geometry; break; - case "f": ShaderType = GalShaderType.Fragment; break; + flags |= TranslationFlags.Compute; } - using (FileStream FS = new FileStream(args[1], FileMode.Open, FileAccess.Read)) - { - Memory Mem = new Memory(FS); + byte[] data = File.ReadAllBytes(args[^1]); - GlslProgram Program = Decompiler.Decompile(Mem, 0, ShaderType); + string code = Translator.Translate(data, new TranslatorCallbacks(null, null), flags).Code; - Console.WriteLine(Program.Code); - } + Console.WriteLine(code); } else { - Console.WriteLine("Usage: Ryujinx.ShaderTools [v|tc|te|g|f] shader.bin"); + Console.WriteLine("Usage: Ryujinx.ShaderTools [--compute] shader.bin"); } } } -} +} \ No newline at end of file diff --git a/Ryujinx.ShaderTools/Ryujinx.ShaderTools.csproj b/Ryujinx.ShaderTools/Ryujinx.ShaderTools.csproj index 24f31efeff..2f9c777278 100644 --- a/Ryujinx.ShaderTools/Ryujinx.ShaderTools.csproj +++ b/Ryujinx.ShaderTools/Ryujinx.ShaderTools.csproj @@ -1,13 +1,24 @@ - + + netcoreapp3.0 + win-x64;osx-x64;linux-x64 Exe - netcoreapp2.1 - win10-x64;osx-x64;linux-x64 + Debug;Release;Profile Debug;Profile Release + + + + TRACE;USE_PROFILING + true + + + + TRACE;USE_PROFILING + false diff --git a/Ryujinx.Tests.Unicorn/IndexedProperty.cs b/Ryujinx.Tests.Unicorn/IndexedProperty.cs new file mode 100644 index 0000000000..65d445fc03 --- /dev/null +++ b/Ryujinx.Tests.Unicorn/IndexedProperty.cs @@ -0,0 +1,28 @@ +using System; + +namespace Ryujinx.Tests.Unicorn +{ + public class IndexedProperty + { + private Func _getFunc; + private Action _setAction; + + public IndexedProperty(Func getFunc, Action setAction) + { + _getFunc = getFunc; + _setAction = setAction; + } + + public TValue this[TIndex index] + { + get + { + return _getFunc(index); + } + set + { + _setAction(index, value); + } + } + } +} diff --git a/Ryujinx.Tests.Unicorn/MemoryPermission.cs b/Ryujinx.Tests.Unicorn/MemoryPermission.cs new file mode 100644 index 0000000000..a14c4e9cf1 --- /dev/null +++ b/Ryujinx.Tests.Unicorn/MemoryPermission.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.Tests.Unicorn +{ + public enum MemoryPermission + { + NONE = 0, + READ = 1, + WRITE = 2, + EXEC = 4, + ALL = 7, + } +} diff --git a/Ryujinx.Tests.Unicorn/Native/ArmRegister.cs b/Ryujinx.Tests.Unicorn/Native/ArmRegister.cs new file mode 100644 index 0000000000..af331bd1cf --- /dev/null +++ b/Ryujinx.Tests.Unicorn/Native/ArmRegister.cs @@ -0,0 +1,295 @@ +// ReSharper disable InconsistentNaming +namespace Ryujinx.Tests.Unicorn.Native +{ + public enum ArmRegister + { + INVALID = 0, + + X29, + X30, + NZCV, + SP, + WSP, + WZR, + XZR, + B0, + B1, + B2, + B3, + B4, + B5, + B6, + B7, + B8, + B9, + B10, + B11, + B12, + B13, + B14, + B15, + B16, + B17, + B18, + B19, + B20, + B21, + B22, + B23, + B24, + B25, + B26, + B27, + B28, + B29, + B30, + B31, + D0, + D1, + D2, + D3, + D4, + D5, + D6, + D7, + D8, + D9, + D10, + D11, + D12, + D13, + D14, + D15, + D16, + D17, + D18, + D19, + D20, + D21, + D22, + D23, + D24, + D25, + D26, + D27, + D28, + D29, + D30, + D31, + H0, + H1, + H2, + H3, + H4, + H5, + H6, + H7, + H8, + H9, + H10, + H11, + H12, + H13, + H14, + H15, + H16, + H17, + H18, + H19, + H20, + H21, + H22, + H23, + H24, + H25, + H26, + H27, + H28, + H29, + H30, + H31, + Q0, + Q1, + Q2, + Q3, + Q4, + Q5, + Q6, + Q7, + Q8, + Q9, + Q10, + Q11, + Q12, + Q13, + Q14, + Q15, + Q16, + Q17, + Q18, + Q19, + Q20, + Q21, + Q22, + Q23, + Q24, + Q25, + Q26, + Q27, + Q28, + Q29, + Q30, + Q31, + S0, + S1, + S2, + S3, + S4, + S5, + S6, + S7, + S8, + S9, + S10, + S11, + S12, + S13, + S14, + S15, + S16, + S17, + S18, + S19, + S20, + S21, + S22, + S23, + S24, + S25, + S26, + S27, + S28, + S29, + S30, + S31, + W0, + W1, + W2, + W3, + W4, + W5, + W6, + W7, + W8, + W9, + W10, + W11, + W12, + W13, + W14, + W15, + W16, + W17, + W18, + W19, + W20, + W21, + W22, + W23, + W24, + W25, + W26, + W27, + W28, + W29, + W30, + X0, + X1, + X2, + X3, + X4, + X5, + X6, + X7, + X8, + X9, + X10, + X11, + X12, + X13, + X14, + X15, + X16, + X17, + X18, + X19, + X20, + X21, + X22, + X23, + X24, + X25, + X26, + X27, + X28, + + V0, + V1, + V2, + V3, + V4, + V5, + V6, + V7, + V8, + V9, + V10, + V11, + V12, + V13, + V14, + V15, + V16, + V17, + V18, + V19, + V20, + V21, + V22, + V23, + V24, + V25, + V26, + V27, + V28, + V29, + V30, + V31, + + // > pseudo registers + PC, // program counter register + + CPACR_EL1, + ESR, + + // > thread registers + TPIDR_EL0, + TPIDRRO_EL0, + TPIDR_EL1, + + PSTATE, // PSTATE pseudoregister + + // > floating point control and status registers + FPCR, + FPSR, + + ENDING, // <-- mark the end of the list of registers + + // > alias registers + + IP0 = X16, + IP1 = X17, + FP = X29, + LR = X30, + } +} diff --git a/Ryujinx.Tests.Unicorn/Native/Interface.cs b/Ryujinx.Tests.Unicorn/Native/Interface.cs new file mode 100644 index 0000000000..59b1da0792 --- /dev/null +++ b/Ryujinx.Tests.Unicorn/Native/Interface.cs @@ -0,0 +1,69 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Tests.Unicorn.Native +{ + public class Interface + { + public static void Checked(UnicornError error) + { + if (error != UnicornError.UC_ERR_OK) + { + throw new UnicornException(error); + } + } + + public static void MarshalArrayOf(IntPtr input, int length, out T[] output) + { + int size = Marshal.SizeOf(typeof(T)); + + output = new T[length]; + + for (int i = 0; i < length; i++) + { + IntPtr item = new IntPtr(input.ToInt64() + i * size); + + output[i] = Marshal.PtrToStructure(item); + } + } + + [DllImport("unicorn", CallingConvention = CallingConvention.Cdecl)] + public static extern uint uc_version(out uint major, out uint minor); + + [DllImport("unicorn", CallingConvention = CallingConvention.Cdecl)] + public static extern UnicornError uc_open(UnicornArch arch, UnicornMode mode, out IntPtr uc); + + [DllImport("unicorn", CallingConvention = CallingConvention.Cdecl)] + public static extern UnicornError uc_close(IntPtr uc); + + [DllImport("unicorn", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr uc_strerror(UnicornError err); + + [DllImport("unicorn", CallingConvention = CallingConvention.Cdecl)] + public static extern UnicornError uc_reg_write(IntPtr uc, int regid, byte[] value); + + [DllImport("unicorn", CallingConvention = CallingConvention.Cdecl)] + public static extern UnicornError uc_reg_read(IntPtr uc, int regid, byte[] value); + + [DllImport("unicorn", CallingConvention = CallingConvention.Cdecl)] + public static extern UnicornError uc_mem_write(IntPtr uc, ulong address, byte[] bytes, ulong size); + + [DllImport("unicorn", CallingConvention = CallingConvention.Cdecl)] + public static extern UnicornError uc_mem_read(IntPtr uc, ulong address, byte[] bytes, ulong size); + + [DllImport("unicorn", CallingConvention = CallingConvention.Cdecl)] + public static extern UnicornError uc_emu_start(IntPtr uc, ulong begin, ulong until, ulong timeout, ulong count); + + [DllImport("unicorn", CallingConvention = CallingConvention.Cdecl)] + public static extern UnicornError uc_mem_map(IntPtr uc, ulong address, ulong size, uint perms); + + [DllImport("unicorn", CallingConvention = CallingConvention.Cdecl)] + public static extern UnicornError uc_mem_unmap(IntPtr uc, ulong address, ulong size); + + [DllImport("unicorn", CallingConvention = CallingConvention.Cdecl)] + public static extern UnicornError uc_mem_protect(IntPtr uc, ulong address, ulong size, uint perms); + + [DllImport("unicorn", CallingConvention = CallingConvention.Cdecl)] + public static extern UnicornError uc_mem_regions(IntPtr uc, out IntPtr regions, out uint count); + } +} diff --git a/Ryujinx.Tests.Unicorn/Native/UnicornArch.cs b/Ryujinx.Tests.Unicorn/Native/UnicornArch.cs new file mode 100644 index 0000000000..ff633293e1 --- /dev/null +++ b/Ryujinx.Tests.Unicorn/Native/UnicornArch.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.Tests.Unicorn.Native +{ + public enum UnicornArch : uint + { + UC_ARCH_ARM = 1, // ARM architecture (including Thumb, Thumb-2) + UC_ARCH_ARM64, // ARM-64, also called AArch64 + UC_ARCH_MIPS, // Mips architecture + UC_ARCH_X86, // X86 architecture (including x86 & x86-64) + UC_ARCH_PPC, // PowerPC architecture (currently unsupported) + UC_ARCH_SPARC, // Sparc architecture + UC_ARCH_M68K, // M68K architecture + UC_ARCH_MAX, + } +} diff --git a/Ryujinx.Tests.Unicorn/Native/UnicornMemoryRegion.cs b/Ryujinx.Tests.Unicorn/Native/UnicornMemoryRegion.cs new file mode 100644 index 0000000000..7ee34a74a5 --- /dev/null +++ b/Ryujinx.Tests.Unicorn/Native/UnicornMemoryRegion.cs @@ -0,0 +1,13 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Tests.Unicorn.Native +{ + [StructLayout(LayoutKind.Sequential)] + public struct UnicornMemoryRegion + { + public UInt64 begin; // begin address of the region (inclusive) + public UInt64 end; // end address of the region (inclusive) + public UInt32 perms; // memory permissions of the region + } +} diff --git a/Ryujinx.Tests.Unicorn/Native/UnicornMode.cs b/Ryujinx.Tests.Unicorn/Native/UnicornMode.cs new file mode 100644 index 0000000000..8045f2dac4 --- /dev/null +++ b/Ryujinx.Tests.Unicorn/Native/UnicornMode.cs @@ -0,0 +1,33 @@ +// ReSharper disable InconsistentNaming +namespace Ryujinx.Tests.Unicorn.Native +{ + public enum UnicornMode : uint + { + UC_MODE_LITTLE_ENDIAN = 0, // little-endian mode (default mode) + UC_MODE_BIG_ENDIAN = 1 << 30, // big-endian mode + // arm / arm64 + UC_MODE_ARM = 0, // ARM mode + UC_MODE_THUMB = 1 << 4, // THUMB mode (including Thumb-2) + UC_MODE_MCLASS = 1 << 5, // ARM's Cortex-M series (currently unsupported) + UC_MODE_V8 = 1 << 6, // ARMv8 A32 encodings for ARM (currently unsupported) + // mips + UC_MODE_MICRO = 1 << 4, // MicroMips mode (currently unsupported) + UC_MODE_MIPS3 = 1 << 5, // Mips III ISA (currently unsupported) + UC_MODE_MIPS32R6 = 1 << 6, // Mips32r6 ISA (currently unsupported) + UC_MODE_MIPS32 = 1 << 2, // Mips32 ISA + UC_MODE_MIPS64 = 1 << 3, // Mips64 ISA + // x86 / x64 + UC_MODE_16 = 1 << 1, // 16-bit mode + UC_MODE_32 = 1 << 2, // 32-bit mode + UC_MODE_64 = 1 << 3, // 64-bit mode + // ppc + UC_MODE_PPC32 = 1 << 2, // 32-bit mode (currently unsupported) + UC_MODE_PPC64 = 1 << 3, // 64-bit mode (currently unsupported) + UC_MODE_QPX = 1 << 4, // Quad Processing eXtensions mode (currently unsupported) + // sparc + UC_MODE_SPARC32 = 1 << 2, // 32-bit mode + UC_MODE_SPARC64 = 1 << 3, // 64-bit mode + UC_MODE_V9 = 1 << 4, // SparcV9 mode (currently unsupported) + // m68k + } +} diff --git a/Ryujinx.Tests.Unicorn/Ryujinx.Tests.Unicorn.csproj b/Ryujinx.Tests.Unicorn/Ryujinx.Tests.Unicorn.csproj new file mode 100644 index 0000000000..36310f3d2a --- /dev/null +++ b/Ryujinx.Tests.Unicorn/Ryujinx.Tests.Unicorn.csproj @@ -0,0 +1,28 @@ + + + + netcoreapp3.0 + win-x64;osx-x64;linux-x64 + true + Debug;Release;Profile Debug;Profile Release + + + + false + + + + TRACE;USE_PROFILING + true + + + + TRACE;USE_PROFILING + false + + + + + + + diff --git a/Ryujinx.Tests.Unicorn/SimdValue.cs b/Ryujinx.Tests.Unicorn/SimdValue.cs new file mode 100644 index 0000000000..2b52843058 --- /dev/null +++ b/Ryujinx.Tests.Unicorn/SimdValue.cs @@ -0,0 +1,112 @@ +using System; + +namespace Ryujinx.Tests.Unicorn +{ + public struct SimdValue : IEquatable + { + private ulong _e0; + private ulong _e1; + + public SimdValue(ulong e0, ulong e1) + { + _e0 = e0; + _e1 = e1; + } + + public SimdValue(byte[] data) + { + _e0 = (ulong)BitConverter.ToInt64(data, 0); + _e1 = (ulong)BitConverter.ToInt64(data, 8); + } + + public float AsFloat() + { + return GetFloat(0); + } + + public double AsDouble() + { + return GetDouble(0); + } + + public float GetFloat(int index) + { + return BitConverter.Int32BitsToSingle(GetInt32(index)); + } + + public double GetDouble(int index) + { + return BitConverter.Int64BitsToDouble(GetInt64(index)); + } + + public int GetInt32(int index) => (int)GetUInt32(index); + public long GetInt64(int index) => (long)GetUInt64(index); + + public uint GetUInt32(int index) + { + switch (index) + { + case 0: return (uint)(_e0 >> 0); + case 1: return (uint)(_e0 >> 32); + case 2: return (uint)(_e1 >> 0); + case 3: return (uint)(_e1 >> 32); + } + + throw new ArgumentOutOfRangeException(nameof(index)); + } + + public ulong GetUInt64(int index) + { + switch (index) + { + case 0: return _e0; + case 1: return _e1; + } + + throw new ArgumentOutOfRangeException(nameof(index)); + } + + public byte[] ToArray() + { + byte[] e0Data = BitConverter.GetBytes(_e0); + byte[] e1Data = BitConverter.GetBytes(_e1); + + byte[] data = new byte[16]; + + Buffer.BlockCopy(e0Data, 0, data, 0, 8); + Buffer.BlockCopy(e1Data, 0, data, 8, 8); + + return data; + } + + public override int GetHashCode() + { + return HashCode.Combine(_e0, _e1); + } + + public static bool operator ==(SimdValue x, SimdValue y) + { + return x.Equals(y); + } + + public static bool operator !=(SimdValue x, SimdValue y) + { + return !x.Equals(y); + } + + public override bool Equals(object obj) + { + return obj is SimdValue vector && Equals(vector); + } + + public bool Equals(SimdValue other) + { + return other._e0 == _e0 && other._e1 == _e1; + } + + public override string ToString() + { + return $"0x{_e1:X16}{_e0:X16}"; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Tests.Unicorn/UnicornAArch64.cs b/Ryujinx.Tests.Unicorn/UnicornAArch64.cs new file mode 100644 index 0000000000..4453d18d00 --- /dev/null +++ b/Ryujinx.Tests.Unicorn/UnicornAArch64.cs @@ -0,0 +1,311 @@ +using Ryujinx.Tests.Unicorn.Native; +using System; + +namespace Ryujinx.Tests.Unicorn +{ + public class UnicornAArch64 + { + internal readonly IntPtr uc; + + public IndexedProperty X + { + get + { + return new IndexedProperty( + (int i) => GetX(i), + (int i, ulong value) => SetX(i, value)); + } + } + + public IndexedProperty Q + { + get + { + return new IndexedProperty( + (int i) => GetQ(i), + (int i, SimdValue value) => SetQ(i, value)); + } + } + + public ulong LR + { + get => GetRegister(ArmRegister.LR); + set => SetRegister(ArmRegister.LR, value); + } + + public ulong SP + { + get => GetRegister(ArmRegister.SP); + set => SetRegister(ArmRegister.SP, value); + } + + public ulong PC + { + get => GetRegister(ArmRegister.PC); + set => SetRegister(ArmRegister.PC, value); + } + + public uint Pstate + { + get => (uint)GetRegister(ArmRegister.PSTATE); + set => SetRegister(ArmRegister.PSTATE, (uint)value); + } + + public int Fpcr + { + get => (int)GetRegister(ArmRegister.FPCR); + set => SetRegister(ArmRegister.FPCR, (uint)value); + } + + public int Fpsr + { + get => (int)GetRegister(ArmRegister.FPSR); + set => SetRegister(ArmRegister.FPSR, (uint)value); + } + + public bool OverflowFlag + { + get => (Pstate & 0x10000000u) != 0; + set => Pstate = (Pstate & ~0x10000000u) | (value ? 0x10000000u : 0u); + } + + public bool CarryFlag + { + get => (Pstate & 0x20000000u) != 0; + set => Pstate = (Pstate & ~0x20000000u) | (value ? 0x20000000u : 0u); + } + + public bool ZeroFlag + { + get => (Pstate & 0x40000000u) != 0; + set => Pstate = (Pstate & ~0x40000000u) | (value ? 0x40000000u : 0u); + } + + public bool NegativeFlag + { + get => (Pstate & 0x80000000u) != 0; + set => Pstate = (Pstate & ~0x80000000u) | (value ? 0x80000000u : 0u); + } + + public UnicornAArch64() + { + Interface.Checked(Interface.uc_open(UnicornArch.UC_ARCH_ARM64, UnicornMode.UC_MODE_LITTLE_ENDIAN, out uc)); + + SetRegister(ArmRegister.CPACR_EL1, 0x00300000); + } + + ~UnicornAArch64() + { + Interface.Checked(Native.Interface.uc_close(uc)); + } + + public void RunForCount(ulong count) + { + Interface.Checked(Native.Interface.uc_emu_start(uc, this.PC, 0xFFFFFFFFFFFFFFFFu, 0, count)); + } + + public void Step() + { + RunForCount(1); + } + + private static ArmRegister[] XRegisters = new ArmRegister[31] + { + ArmRegister.X0, + ArmRegister.X1, + ArmRegister.X2, + ArmRegister.X3, + ArmRegister.X4, + ArmRegister.X5, + ArmRegister.X6, + ArmRegister.X7, + ArmRegister.X8, + ArmRegister.X9, + ArmRegister.X10, + ArmRegister.X11, + ArmRegister.X12, + ArmRegister.X13, + ArmRegister.X14, + ArmRegister.X15, + ArmRegister.X16, + ArmRegister.X17, + ArmRegister.X18, + ArmRegister.X19, + ArmRegister.X20, + ArmRegister.X21, + ArmRegister.X22, + ArmRegister.X23, + ArmRegister.X24, + ArmRegister.X25, + ArmRegister.X26, + ArmRegister.X27, + ArmRegister.X28, + ArmRegister.X29, + ArmRegister.X30, + }; + + private static ArmRegister[] QRegisters = new ArmRegister[32] + { + ArmRegister.Q0, + ArmRegister.Q1, + ArmRegister.Q2, + ArmRegister.Q3, + ArmRegister.Q4, + ArmRegister.Q5, + ArmRegister.Q6, + ArmRegister.Q7, + ArmRegister.Q8, + ArmRegister.Q9, + ArmRegister.Q10, + ArmRegister.Q11, + ArmRegister.Q12, + ArmRegister.Q13, + ArmRegister.Q14, + ArmRegister.Q15, + ArmRegister.Q16, + ArmRegister.Q17, + ArmRegister.Q18, + ArmRegister.Q19, + ArmRegister.Q20, + ArmRegister.Q21, + ArmRegister.Q22, + ArmRegister.Q23, + ArmRegister.Q24, + ArmRegister.Q25, + ArmRegister.Q26, + ArmRegister.Q27, + ArmRegister.Q28, + ArmRegister.Q29, + ArmRegister.Q30, + ArmRegister.Q31, + }; + + public ulong GetX(int index) + { + if ((uint)index > 30) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + return GetRegister(XRegisters[index]); + } + + public void SetX(int index, ulong value) + { + if ((uint)index > 30) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + SetRegister(XRegisters[index], value); + } + + public SimdValue GetQ(int index) + { + if ((uint)index > 31) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + return GetVector(QRegisters[index]); + } + + public void SetQ(int index, SimdValue value) + { + if ((uint)index > 31) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + SetVector(QRegisters[index], value); + } + + private ulong GetRegister(ArmRegister register) + { + byte[] data = new byte[8]; + + Interface.Checked(Native.Interface.uc_reg_read(uc, (int)register, data)); + + return (ulong)BitConverter.ToInt64(data, 0); + } + + private void SetRegister(ArmRegister register, ulong value) + { + byte[] data = BitConverter.GetBytes(value); + + Interface.Checked(Interface.uc_reg_write(uc, (int)register, data)); + } + + private SimdValue GetVector(ArmRegister register) + { + byte[] data = new byte[16]; + + Interface.Checked(Interface.uc_reg_read(uc, (int)register, data)); + + return new SimdValue(data); + } + + private void SetVector(ArmRegister register, SimdValue value) + { + byte[] data = value.ToArray(); + + Interface.Checked(Interface.uc_reg_write(uc, (int)register, data)); + } + + public byte[] MemoryRead(ulong address, ulong size) + { + byte[] value = new byte[size]; + + Interface.Checked(Interface.uc_mem_read(uc, address, value, size)); + + return value; + } + + public byte MemoryRead8 (ulong address) => MemoryRead(address, 1)[0]; + public UInt16 MemoryRead16(ulong address) => (UInt16)BitConverter.ToInt16(MemoryRead(address, 2), 0); + public UInt32 MemoryRead32(ulong address) => (UInt32)BitConverter.ToInt32(MemoryRead(address, 4), 0); + public UInt64 MemoryRead64(ulong address) => (UInt64)BitConverter.ToInt64(MemoryRead(address, 8), 0); + + public void MemoryWrite(ulong address, byte[] value) + { + Interface.Checked(Interface.uc_mem_write(uc, address, value, (ulong)value.Length)); + } + + public void MemoryWrite8 (ulong address, byte value) => MemoryWrite(address, new byte[]{value}); + public void MemoryWrite16(ulong address, Int16 value) => MemoryWrite(address, BitConverter.GetBytes(value)); + public void MemoryWrite16(ulong address, UInt16 value) => MemoryWrite(address, BitConverter.GetBytes(value)); + public void MemoryWrite32(ulong address, Int32 value) => MemoryWrite(address, BitConverter.GetBytes(value)); + public void MemoryWrite32(ulong address, UInt32 value) => MemoryWrite(address, BitConverter.GetBytes(value)); + public void MemoryWrite64(ulong address, Int64 value) => MemoryWrite(address, BitConverter.GetBytes(value)); + public void MemoryWrite64(ulong address, UInt64 value) => MemoryWrite(address, BitConverter.GetBytes(value)); + + public void MemoryMap(ulong address, ulong size, MemoryPermission permissions) + { + Interface.Checked(Interface.uc_mem_map(uc, address, size, (uint)permissions)); + } + + public void MemoryUnmap(ulong address, ulong size) + { + Interface.Checked(Interface.uc_mem_unmap(uc, address, size)); + } + + public void MemoryProtect(ulong address, ulong size, MemoryPermission permissions) + { + Interface.Checked(Interface.uc_mem_protect(uc, address, size, (uint)permissions)); + } + + public static bool IsAvailable() + { + try + { + Interface.uc_version(out _, out _); + + return true; + } + catch (DllNotFoundException) + { + return false; + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Tests.Unicorn/UnicornError.cs b/Ryujinx.Tests.Unicorn/UnicornError.cs new file mode 100644 index 0000000000..ac324089c1 --- /dev/null +++ b/Ryujinx.Tests.Unicorn/UnicornError.cs @@ -0,0 +1,29 @@ +// ReSharper disable InconsistentNaming +namespace Ryujinx.Tests.Unicorn +{ + public enum UnicornError + { + UC_ERR_OK = 0, // No error: everything was fine + UC_ERR_NOMEM, // Out-Of-Memory error: uc_open(), uc_emulate() + UC_ERR_ARCH, // Unsupported architecture: uc_open() + UC_ERR_HANDLE, // Invalid handle + UC_ERR_MODE, // Invalid/unsupported mode: uc_open() + UC_ERR_VERSION, // Unsupported version (bindings) + UC_ERR_READ_UNMAPPED, // Quit emulation due to READ on unmapped memory: uc_emu_start() + UC_ERR_WRITE_UNMAPPED, // Quit emulation due to WRITE on unmapped memory: uc_emu_start() + UC_ERR_FETCH_UNMAPPED, // Quit emulation due to FETCH on unmapped memory: uc_emu_start() + UC_ERR_HOOK, // Invalid hook type: uc_hook_add() + UC_ERR_INSN_INVALID, // Quit emulation due to invalid instruction: uc_emu_start() + UC_ERR_MAP, // Invalid memory mapping: uc_mem_map() + UC_ERR_WRITE_PROT, // Quit emulation due to UC_MEM_WRITE_PROT violation: uc_emu_start() + UC_ERR_READ_PROT, // Quit emulation due to UC_MEM_READ_PROT violation: uc_emu_start() + UC_ERR_FETCH_PROT, // Quit emulation due to UC_MEM_FETCH_PROT violation: uc_emu_start() + UC_ERR_ARG, // Invalid argument provided to uc_xxx function (See specific function API) + UC_ERR_READ_UNALIGNED, // Unaligned read + UC_ERR_WRITE_UNALIGNED, // Unaligned write + UC_ERR_FETCH_UNALIGNED, // Unaligned fetch + UC_ERR_HOOK_EXIST, // hook for this event already existed + UC_ERR_RESOURCE, // Insufficient resource: uc_emu_start() + UC_ERR_EXCEPTION // Unhandled CPU exception + } +} diff --git a/Ryujinx.Tests.Unicorn/UnicornException.cs b/Ryujinx.Tests.Unicorn/UnicornException.cs new file mode 100644 index 0000000000..3cb693703c --- /dev/null +++ b/Ryujinx.Tests.Unicorn/UnicornException.cs @@ -0,0 +1,23 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Tests.Unicorn +{ + public class UnicornException : Exception + { + public readonly UnicornError Error; + + internal UnicornException(UnicornError error) + { + Error = error; + } + + public override string Message + { + get + { + return Marshal.PtrToStringAnsi(Native.Interface.uc_strerror(Error)); + } + } + } +} diff --git a/Ryujinx.Tests.Unicorn/libs/README.md b/Ryujinx.Tests.Unicorn/libs/README.md new file mode 100644 index 0000000000..bb37016410 --- /dev/null +++ b/Ryujinx.Tests.Unicorn/libs/README.md @@ -0,0 +1,3 @@ +The pre-compiled dynamic libraries in this directory are licenced under the GPLv2. + +The source code for windows/unicorn.dll is available at: https://github.com/MerryMage/UnicornDotNet/tree/299451c02d9c810d2feca51f5e9cb6d8b2f38960 diff --git a/Ryujinx.Tests.Unicorn/libs/windows/unicorn.dll b/Ryujinx.Tests.Unicorn/libs/windows/unicorn.dll new file mode 100644 index 0000000000..0e3cea589f Binary files /dev/null and b/Ryujinx.Tests.Unicorn/libs/windows/unicorn.dll differ diff --git a/Ryujinx.Tests/Cpu/CpuTest.cs b/Ryujinx.Tests/Cpu/CpuTest.cs index 70a8e19240..4b7b0f6ba9 100644 --- a/Ryujinx.Tests/Cpu/CpuTest.cs +++ b/Ryujinx.Tests/Cpu/CpuTest.cs @@ -1,47 +1,77 @@ -using ChocolArm64; -using ChocolArm64.Memory; -using ChocolArm64.State; +using ARMeilleure.Memory; +using ARMeilleure.State; +using ARMeilleure.Translation; using NUnit.Framework; +using Ryujinx.Tests.Unicorn; + using System; -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; -using System.Threading; +using System.Runtime.InteropServices; namespace Ryujinx.Tests.Cpu { [TestFixture] public class CpuTest { - protected long Position { get; private set; } - private long Size; + private ulong _currAddress; + private long _size; - private long EntryPoint; + private ulong _entryPoint; - private AMemory Memory; - private AThread Thread; + private IntPtr _ramPointer; + + private MemoryManager _memory; + + private ExecutionContext _context; + + private Translator _translator; + + private static bool _unicornAvailable; + private UnicornAArch64 _unicornEmu; + + static CpuTest() + { + _unicornAvailable = UnicornAArch64.IsAvailable(); + + if (!_unicornAvailable) + { + Console.WriteLine("WARNING: Could not find Unicorn."); + } + } [SetUp] public void Setup() { - Position = 0x0; - Size = 0x1000; + _currAddress = 0x1000; + _size = 0x1000; - EntryPoint = Position; + _entryPoint = _currAddress; - ATranslator Translator = new ATranslator(); - Memory = new AMemory(); - Memory.Manager.Map(Position, Size, 2, AMemoryPerm.Read | AMemoryPerm.Write | AMemoryPerm.Execute); - Thread = new AThread(Translator, Memory, EntryPoint); + _ramPointer = Marshal.AllocHGlobal(new IntPtr(_size)); + _memory = new MemoryManager(_ramPointer); + _memory.Map((long)_currAddress, 0, _size); + + _context = new ExecutionContext(); + + _translator = new Translator(_memory); + + if (_unicornAvailable) + { + _unicornEmu = new UnicornAArch64(); + _unicornEmu.MemoryMap(_currAddress, (ulong)_size, MemoryPermission.READ | MemoryPermission.EXEC); + _unicornEmu.PC = _entryPoint; + } } [TearDown] public void Teardown() { - Memory.Dispose(); - Memory = null; - Thread = null; + Marshal.FreeHGlobal(_ramPointer); + _memory = null; + _context = null; + _translator = null; + _unicornEmu = null; } protected void Reset() @@ -50,115 +80,456 @@ namespace Ryujinx.Tests.Cpu Setup(); } - protected void Opcode(uint Opcode) + protected void Opcode(uint opcode) { - Thread.Memory.WriteUInt32Unchecked(Position, Opcode); - Position += 4; + _memory.WriteUInt32((long)_currAddress, opcode); + + if (_unicornAvailable) + { + _unicornEmu.MemoryWrite32((ulong)_currAddress, opcode); + } + + _currAddress += 4; } - protected void SetThreadState(ulong X0 = 0, ulong X1 = 0, ulong X2 = 0, ulong X3 = 0, ulong X31 = 0, - Vector128 V0 = default(Vector128), - Vector128 V1 = default(Vector128), - Vector128 V2 = default(Vector128), - bool Overflow = false, bool Carry = false, bool Zero = false, bool Negative = false, - int Fpcr = 0x0, int Fpsr = 0x0) + protected ExecutionContext GetContext() => _context; + + protected void SetContext(ulong x0 = 0, + ulong x1 = 0, + ulong x2 = 0, + ulong x3 = 0, + ulong x31 = 0, + V128 v0 = default, + V128 v1 = default, + V128 v2 = default, + V128 v3 = default, + V128 v4 = default, + V128 v5 = default, + V128 v30 = default, + V128 v31 = default, + bool overflow = false, + bool carry = false, + bool zero = false, + bool negative = false, + int fpcr = 0, + int fpsr = 0) { - Thread.ThreadState.X0 = X0; - Thread.ThreadState.X1 = X1; - Thread.ThreadState.X2 = X2; - Thread.ThreadState.X3 = X3; - Thread.ThreadState.X31 = X31; - Thread.ThreadState.V0 = V0; - Thread.ThreadState.V1 = V1; - Thread.ThreadState.V2 = V2; - Thread.ThreadState.Overflow = Overflow; - Thread.ThreadState.Carry = Carry; - Thread.ThreadState.Zero = Zero; - Thread.ThreadState.Negative = Negative; - Thread.ThreadState.Fpcr = Fpcr; - Thread.ThreadState.Fpsr = Fpsr; + _context.SetX(0, x0); + _context.SetX(1, x1); + _context.SetX(2, x2); + _context.SetX(3, x3); + + _context.SetX(31, x31); + + _context.SetV(0, v0); + _context.SetV(1, v1); + _context.SetV(2, v2); + _context.SetV(3, v3); + _context.SetV(4, v4); + _context.SetV(5, v5); + _context.SetV(30, v30); + _context.SetV(31, v31); + + _context.SetPstateFlag(PState.VFlag, overflow); + _context.SetPstateFlag(PState.CFlag, carry); + _context.SetPstateFlag(PState.ZFlag, zero); + _context.SetPstateFlag(PState.NFlag, negative); + + _context.Fpcr = (FPCR)fpcr; + _context.Fpsr = (FPSR)fpsr; + + if (_unicornAvailable) + { + _unicornEmu.X[0] = x0; + _unicornEmu.X[1] = x1; + _unicornEmu.X[2] = x2; + _unicornEmu.X[3] = x3; + + _unicornEmu.SP = x31; + + _unicornEmu.Q[0] = V128ToSimdValue(v0); + _unicornEmu.Q[1] = V128ToSimdValue(v1); + _unicornEmu.Q[2] = V128ToSimdValue(v2); + _unicornEmu.Q[3] = V128ToSimdValue(v3); + _unicornEmu.Q[4] = V128ToSimdValue(v4); + _unicornEmu.Q[5] = V128ToSimdValue(v5); + _unicornEmu.Q[30] = V128ToSimdValue(v30); + _unicornEmu.Q[31] = V128ToSimdValue(v31); + + _unicornEmu.OverflowFlag = overflow; + _unicornEmu.CarryFlag = carry; + _unicornEmu.ZeroFlag = zero; + _unicornEmu.NegativeFlag = negative; + + _unicornEmu.Fpcr = fpcr; + _unicornEmu.Fpsr = fpsr; + } } protected void ExecuteOpcodes() { - using (ManualResetEvent Wait = new ManualResetEvent(false)) - { - Thread.ThreadState.Break += (sender, e) => Thread.StopExecution(); - Thread.WorkFinished += (sender, e) => Wait.Set(); + _translator.Execute(_context, _entryPoint); - Thread.Execute(); - Wait.WaitOne(); + if (_unicornAvailable) + { + _unicornEmu.RunForCount((ulong)(_currAddress - _entryPoint - 4) / 4); } } - protected AThreadState GetThreadState() + protected ExecutionContext SingleOpcode(uint opcode, + ulong x0 = 0, + ulong x1 = 0, + ulong x2 = 0, + ulong x3 = 0, + ulong x31 = 0, + V128 v0 = default, + V128 v1 = default, + V128 v2 = default, + V128 v3 = default, + V128 v4 = default, + V128 v5 = default, + V128 v30 = default, + V128 v31 = default, + bool overflow = false, + bool carry = false, + bool zero = false, + bool negative = false, + int fpcr = 0, + int fpsr = 0) { - return Thread.ThreadState; - } - - protected AThreadState SingleOpcode(uint Opcode, - ulong X0 = 0, ulong X1 = 0, ulong X2 = 0, ulong X3 = 0, ulong X31 = 0, - Vector128 V0 = default(Vector128), - Vector128 V1 = default(Vector128), - Vector128 V2 = default(Vector128), - bool Overflow = false, bool Carry = false, bool Zero = false, bool Negative = false, - int Fpcr = 0x0, int Fpsr = 0x0) - { - this.Opcode(Opcode); - this.Opcode(0xD4200000); // BRK #0 - this.Opcode(0xD65F03C0); // RET - SetThreadState(X0, X1, X2, X3, X31, V0, V1, V2, Overflow, Carry, Zero, Negative, Fpcr, Fpsr); + Opcode(opcode); + Opcode(0xD65F03C0); // RET + SetContext(x0, x1, x2, x3, x31, v0, v1, v2, v3, v4, v5, v30, v31, overflow, carry, zero, negative, fpcr, fpsr); ExecuteOpcodes(); - return GetThreadState(); + return GetContext(); } - protected static Vector128 MakeVectorE0(double E0) + /// Rounding Mode control field. + public enum RMode { - return Sse.StaticCast(Sse2.SetVector128(0, BitConverter.DoubleToInt64Bits(E0))); + /// Round to Nearest mode. + Rn, + /// Round towards Plus Infinity mode. + Rp, + /// Round towards Minus Infinity mode. + Rm, + /// Round towards Zero mode. + Rz + }; + + /// Floating-point Control Register. + protected enum Fpcr + { + /// Rounding Mode control field. + RMode = 22, + /// Flush-to-zero mode control bit. + Fz = 24, + /// Default NaN mode control bit. + Dn = 25, + /// Alternative half-precision control bit. + Ahp = 26 } - protected static Vector128 MakeVectorE0E1(double E0, double E1) + /// Floating-point Status Register. + [Flags] protected enum Fpsr { - return Sse.StaticCast(Sse2.SetVector128(BitConverter.DoubleToInt64Bits(E1), - BitConverter.DoubleToInt64Bits(E0))); + None = 0, + + /// Invalid Operation cumulative floating-point exception bit. + Ioc = 1 << 0, + /// Divide by Zero cumulative floating-point exception bit. + Dzc = 1 << 1, + /// Overflow cumulative floating-point exception bit. + Ofc = 1 << 2, + /// Underflow cumulative floating-point exception bit. + Ufc = 1 << 3, + /// Inexact cumulative floating-point exception bit. + Ixc = 1 << 4, + /// Input Denormal cumulative floating-point exception bit. + Idc = 1 << 7, + + /// Cumulative saturation bit. + Qc = 1 << 27 } - protected static Vector128 MakeVectorE1(double E1) + [Flags] protected enum FpSkips { - return Sse.StaticCast(Sse2.SetVector128(BitConverter.DoubleToInt64Bits(E1), 0)); + None = 0, + + IfNaNS = 1, + IfNaND = 2, + + IfUnderflow = 4, + IfOverflow = 8 } - protected static double VectorExtractDouble(Vector128 Vector, byte Index) + protected enum FpTolerances { - long Value = Sse41.Extract(Sse.StaticCast(Vector), Index); + None, - return BitConverter.Int64BitsToDouble(Value); + UpToOneUlpsS, + UpToOneUlpsD } - protected static Vector128 MakeVectorE0(ulong E0) + protected void CompareAgainstUnicorn( + Fpsr fpsrMask = Fpsr.None, + FpSkips fpSkips = FpSkips.None, + FpTolerances fpTolerances = FpTolerances.None) { - return Sse.StaticCast(Sse2.SetVector128(0, E0)); + if (!_unicornAvailable) + { + return; + } + + if (fpSkips != FpSkips.None) + { + ManageFpSkips(fpSkips); + } + + Assert.That(_context.GetX(0), Is.EqualTo(_unicornEmu.X[0])); + Assert.That(_context.GetX(1), Is.EqualTo(_unicornEmu.X[1])); + Assert.That(_context.GetX(2), Is.EqualTo(_unicornEmu.X[2])); + Assert.That(_context.GetX(3), Is.EqualTo(_unicornEmu.X[3])); + Assert.That(_context.GetX(4), Is.EqualTo(_unicornEmu.X[4])); + Assert.That(_context.GetX(5), Is.EqualTo(_unicornEmu.X[5])); + Assert.That(_context.GetX(6), Is.EqualTo(_unicornEmu.X[6])); + Assert.That(_context.GetX(7), Is.EqualTo(_unicornEmu.X[7])); + Assert.That(_context.GetX(8), Is.EqualTo(_unicornEmu.X[8])); + Assert.That(_context.GetX(9), Is.EqualTo(_unicornEmu.X[9])); + Assert.That(_context.GetX(10), Is.EqualTo(_unicornEmu.X[10])); + Assert.That(_context.GetX(11), Is.EqualTo(_unicornEmu.X[11])); + Assert.That(_context.GetX(12), Is.EqualTo(_unicornEmu.X[12])); + Assert.That(_context.GetX(13), Is.EqualTo(_unicornEmu.X[13])); + Assert.That(_context.GetX(14), Is.EqualTo(_unicornEmu.X[14])); + Assert.That(_context.GetX(15), Is.EqualTo(_unicornEmu.X[15])); + Assert.That(_context.GetX(16), Is.EqualTo(_unicornEmu.X[16])); + Assert.That(_context.GetX(17), Is.EqualTo(_unicornEmu.X[17])); + Assert.That(_context.GetX(18), Is.EqualTo(_unicornEmu.X[18])); + Assert.That(_context.GetX(19), Is.EqualTo(_unicornEmu.X[19])); + Assert.That(_context.GetX(20), Is.EqualTo(_unicornEmu.X[20])); + Assert.That(_context.GetX(21), Is.EqualTo(_unicornEmu.X[21])); + Assert.That(_context.GetX(22), Is.EqualTo(_unicornEmu.X[22])); + Assert.That(_context.GetX(23), Is.EqualTo(_unicornEmu.X[23])); + Assert.That(_context.GetX(24), Is.EqualTo(_unicornEmu.X[24])); + Assert.That(_context.GetX(25), Is.EqualTo(_unicornEmu.X[25])); + Assert.That(_context.GetX(26), Is.EqualTo(_unicornEmu.X[26])); + Assert.That(_context.GetX(27), Is.EqualTo(_unicornEmu.X[27])); + Assert.That(_context.GetX(28), Is.EqualTo(_unicornEmu.X[28])); + Assert.That(_context.GetX(29), Is.EqualTo(_unicornEmu.X[29])); + Assert.That(_context.GetX(30), Is.EqualTo(_unicornEmu.X[30])); + + Assert.That(_context.GetX(31), Is.EqualTo(_unicornEmu.SP)); + + if (fpTolerances == FpTolerances.None) + { + Assert.That(V128ToSimdValue(_context.GetV(0)), Is.EqualTo(_unicornEmu.Q[0])); + } + else + { + ManageFpTolerances(fpTolerances); + } + Assert.That(V128ToSimdValue(_context.GetV(1)), Is.EqualTo(_unicornEmu.Q[1])); + Assert.That(V128ToSimdValue(_context.GetV(2)), Is.EqualTo(_unicornEmu.Q[2])); + Assert.That(V128ToSimdValue(_context.GetV(3)), Is.EqualTo(_unicornEmu.Q[3])); + Assert.That(V128ToSimdValue(_context.GetV(4)), Is.EqualTo(_unicornEmu.Q[4])); + Assert.That(V128ToSimdValue(_context.GetV(5)), Is.EqualTo(_unicornEmu.Q[5])); + Assert.That(V128ToSimdValue(_context.GetV(6)), Is.EqualTo(_unicornEmu.Q[6])); + Assert.That(V128ToSimdValue(_context.GetV(7)), Is.EqualTo(_unicornEmu.Q[7])); + Assert.That(V128ToSimdValue(_context.GetV(8)), Is.EqualTo(_unicornEmu.Q[8])); + Assert.That(V128ToSimdValue(_context.GetV(9)), Is.EqualTo(_unicornEmu.Q[9])); + Assert.That(V128ToSimdValue(_context.GetV(10)), Is.EqualTo(_unicornEmu.Q[10])); + Assert.That(V128ToSimdValue(_context.GetV(11)), Is.EqualTo(_unicornEmu.Q[11])); + Assert.That(V128ToSimdValue(_context.GetV(12)), Is.EqualTo(_unicornEmu.Q[12])); + Assert.That(V128ToSimdValue(_context.GetV(13)), Is.EqualTo(_unicornEmu.Q[13])); + Assert.That(V128ToSimdValue(_context.GetV(14)), Is.EqualTo(_unicornEmu.Q[14])); + Assert.That(V128ToSimdValue(_context.GetV(15)), Is.EqualTo(_unicornEmu.Q[15])); + Assert.That(V128ToSimdValue(_context.GetV(16)), Is.EqualTo(_unicornEmu.Q[16])); + Assert.That(V128ToSimdValue(_context.GetV(17)), Is.EqualTo(_unicornEmu.Q[17])); + Assert.That(V128ToSimdValue(_context.GetV(18)), Is.EqualTo(_unicornEmu.Q[18])); + Assert.That(V128ToSimdValue(_context.GetV(19)), Is.EqualTo(_unicornEmu.Q[19])); + Assert.That(V128ToSimdValue(_context.GetV(20)), Is.EqualTo(_unicornEmu.Q[20])); + Assert.That(V128ToSimdValue(_context.GetV(21)), Is.EqualTo(_unicornEmu.Q[21])); + Assert.That(V128ToSimdValue(_context.GetV(22)), Is.EqualTo(_unicornEmu.Q[22])); + Assert.That(V128ToSimdValue(_context.GetV(23)), Is.EqualTo(_unicornEmu.Q[23])); + Assert.That(V128ToSimdValue(_context.GetV(24)), Is.EqualTo(_unicornEmu.Q[24])); + Assert.That(V128ToSimdValue(_context.GetV(25)), Is.EqualTo(_unicornEmu.Q[25])); + Assert.That(V128ToSimdValue(_context.GetV(26)), Is.EqualTo(_unicornEmu.Q[26])); + Assert.That(V128ToSimdValue(_context.GetV(27)), Is.EqualTo(_unicornEmu.Q[27])); + Assert.That(V128ToSimdValue(_context.GetV(28)), Is.EqualTo(_unicornEmu.Q[28])); + Assert.That(V128ToSimdValue(_context.GetV(29)), Is.EqualTo(_unicornEmu.Q[29])); + Assert.That(V128ToSimdValue(_context.GetV(30)), Is.EqualTo(_unicornEmu.Q[30])); + Assert.That(V128ToSimdValue(_context.GetV(31)), Is.EqualTo(_unicornEmu.Q[31])); + + Assert.That((int)_context.Fpcr, Is.EqualTo(_unicornEmu.Fpcr)); + Assert.That((int)_context.Fpsr & (int)fpsrMask, Is.EqualTo(_unicornEmu.Fpsr & (int)fpsrMask)); + + Assert.That(_context.GetPstateFlag(PState.VFlag), Is.EqualTo(_unicornEmu.OverflowFlag)); + Assert.That(_context.GetPstateFlag(PState.CFlag), Is.EqualTo(_unicornEmu.CarryFlag)); + Assert.That(_context.GetPstateFlag(PState.ZFlag), Is.EqualTo(_unicornEmu.ZeroFlag)); + Assert.That(_context.GetPstateFlag(PState.NFlag), Is.EqualTo(_unicornEmu.NegativeFlag)); } - protected static Vector128 MakeVectorE0E1(ulong E0, ulong E1) + private void ManageFpSkips(FpSkips fpSkips) { - return Sse.StaticCast(Sse2.SetVector128(E1, E0)); + if (fpSkips.HasFlag(FpSkips.IfNaNS)) + { + if (float.IsNaN(_unicornEmu.Q[0].AsFloat())) + { + Assert.Ignore("NaN test."); + } + } + else if (fpSkips.HasFlag(FpSkips.IfNaND)) + { + if (double.IsNaN(_unicornEmu.Q[0].AsDouble())) + { + Assert.Ignore("NaN test."); + } + } + + if (fpSkips.HasFlag(FpSkips.IfUnderflow)) + { + if ((_unicornEmu.Fpsr & (int)Fpsr.Ufc) != 0) + { + Assert.Ignore("Underflow test."); + } + } + + if (fpSkips.HasFlag(FpSkips.IfOverflow)) + { + if ((_unicornEmu.Fpsr & (int)Fpsr.Ofc) != 0) + { + Assert.Ignore("Overflow test."); + } + } } - protected static Vector128 MakeVectorE1(ulong E1) + private void ManageFpTolerances(FpTolerances fpTolerances) { - return Sse.StaticCast(Sse2.SetVector128(E1, 0)); + bool IsNormalOrSubnormalS(float f) => float.IsNormal(f) || float.IsSubnormal(f); + bool IsNormalOrSubnormalD(double d) => double.IsNormal(d) || double.IsSubnormal(d); + + if (!Is.EqualTo(_unicornEmu.Q[0]).ApplyTo(V128ToSimdValue(_context.GetV(0))).IsSuccess) + { + if (fpTolerances == FpTolerances.UpToOneUlpsS) + { + if (IsNormalOrSubnormalS(_unicornEmu.Q[0].AsFloat()) && + IsNormalOrSubnormalS(_context.GetV(0).AsFloat())) + { + Assert.That (_context.GetV(0).GetFloat(0), + Is.EqualTo(_unicornEmu.Q[0].GetFloat(0)).Within(1).Ulps); + Assert.That (_context.GetV(0).GetFloat(1), + Is.EqualTo(_unicornEmu.Q[0].GetFloat(1)).Within(1).Ulps); + Assert.That (_context.GetV(0).GetFloat(2), + Is.EqualTo(_unicornEmu.Q[0].GetFloat(2)).Within(1).Ulps); + Assert.That (_context.GetV(0).GetFloat(3), + Is.EqualTo(_unicornEmu.Q[0].GetFloat(3)).Within(1).Ulps); + + Console.WriteLine(fpTolerances); + } + else + { + Assert.That(V128ToSimdValue(_context.GetV(0)), Is.EqualTo(_unicornEmu.Q[0])); + } + } + + if (fpTolerances == FpTolerances.UpToOneUlpsD) + { + if (IsNormalOrSubnormalD(_unicornEmu.Q[0].AsDouble()) && + IsNormalOrSubnormalD(_context.GetV(0).AsDouble())) + { + Assert.That (_context.GetV(0).GetDouble(0), + Is.EqualTo(_unicornEmu.Q[0].GetDouble(0)).Within(1).Ulps); + Assert.That (_context.GetV(0).GetDouble(1), + Is.EqualTo(_unicornEmu.Q[0].GetDouble(1)).Within(1).Ulps); + + Console.WriteLine(fpTolerances); + } + else + { + Assert.That(V128ToSimdValue(_context.GetV(0)), Is.EqualTo(_unicornEmu.Q[0])); + } + } + } } - protected static ulong GetVectorE0(Vector128 Vector) + private static SimdValue V128ToSimdValue(V128 value) { - return Sse41.Extract(Sse.StaticCast(Vector), (byte)0); + return new SimdValue(value.GetUInt64(0), value.GetUInt64(1)); } - protected static ulong GetVectorE1(Vector128 Vector) + protected static V128 MakeVectorScalar(float value) => new V128(value); + protected static V128 MakeVectorScalar(double value) => new V128(value); + + protected static V128 MakeVectorE0(ulong e0) => new V128(e0, 0); + protected static V128 MakeVectorE1(ulong e1) => new V128(0, e1); + + protected static V128 MakeVectorE0E1(ulong e0, ulong e1) => new V128(e0, e1); + + protected static ulong GetVectorE0(V128 vector) => vector.GetUInt64(0); + protected static ulong GetVectorE1(V128 vector) => vector.GetUInt64(1); + + protected static ushort GenNormalH() { - return Sse41.Extract(Sse.StaticCast(Vector), (byte)1); + uint rnd; + + do rnd = TestContext.CurrentContext.Random.NextUShort(); + while (( rnd & 0x7C00u) == 0u || + (~rnd & 0x7C00u) == 0u); + + return (ushort)rnd; + } + + protected static ushort GenSubnormalH() + { + uint rnd; + + do rnd = TestContext.CurrentContext.Random.NextUShort(); + while ((rnd & 0x03FFu) == 0u); + + return (ushort)(rnd & 0x83FFu); + } + + protected static uint GenNormalS() + { + uint rnd; + + do rnd = TestContext.CurrentContext.Random.NextUInt(); + while (( rnd & 0x7F800000u) == 0u || + (~rnd & 0x7F800000u) == 0u); + + return rnd; + } + + protected static uint GenSubnormalS() + { + uint rnd; + + do rnd = TestContext.CurrentContext.Random.NextUInt(); + while ((rnd & 0x007FFFFFu) == 0u); + + return rnd & 0x807FFFFFu; + } + + protected static ulong GenNormalD() + { + ulong rnd; + + do rnd = TestContext.CurrentContext.Random.NextULong(); + while (( rnd & 0x7FF0000000000000ul) == 0ul || + (~rnd & 0x7FF0000000000000ul) == 0ul); + + return rnd; + } + + protected static ulong GenSubnormalD() + { + ulong rnd; + + do rnd = TestContext.CurrentContext.Random.NextULong(); + while ((rnd & 0x000FFFFFFFFFFFFFul) == 0ul); + + return rnd & 0x800FFFFFFFFFFFFFul; } } } diff --git a/Ryujinx.Tests/Cpu/CpuTestAlu.cs b/Ryujinx.Tests/Cpu/CpuTestAlu.cs index 564fadec2b..0c4aa3b0e3 100644 --- a/Ryujinx.Tests/Cpu/CpuTestAlu.cs +++ b/Ryujinx.Tests/Cpu/CpuTestAlu.cs @@ -1,330 +1,268 @@ -//#define Alu - -using ChocolArm64.State; +#define Alu using NUnit.Framework; +using System.Collections.Generic; + namespace Ryujinx.Tests.Cpu { - using Tester; - using Tester.Types; - - [Category("Alu"), Ignore("Tested: first half of 2018.")] + [Category("Alu")] public sealed class CpuTestAlu : CpuTest { #if Alu - [SetUp] - public void SetupTester() + +#region "Helper methods" + private static uint GenLeadingSignsMinus32(int cnt) // 0 <= cnt <= 31 { - AArch64.TakeReset(false); + return ~GenLeadingZeros32(cnt + 1); } - [Test, Description("CLS , ")] - public void Cls_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(256)] ulong Xn) + private static ulong GenLeadingSignsMinus64(int cnt) // 0 <= cnt <= 63 { - uint Opcode = 0xDAC01400; // CLS X0, X0 - Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + return ~GenLeadingZeros64(cnt + 1); + } - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X31: _X31); + private static uint GenLeadingSignsPlus32(int cnt) // 0 <= cnt <= 31 + { + return GenLeadingZeros32(cnt + 1); + } - if (Rd != 31) + private static ulong GenLeadingSignsPlus64(int cnt) // 0 <= cnt <= 63 + { + return GenLeadingZeros64(cnt + 1); + } + + private static uint GenLeadingZeros32(int cnt) // 0 <= cnt <= 32 + { + if (cnt == 32) return 0u; + if (cnt == 31) return 1u; + + uint rnd = TestContext.CurrentContext.Random.NextUInt(); + int mask = int.MinValue; + + return (rnd >> (cnt + 1)) | ((uint)mask >> cnt); + } + + private static ulong GenLeadingZeros64(int cnt) // 0 <= cnt <= 64 + { + if (cnt == 64) return 0ul; + if (cnt == 63) return 1ul; + + ulong rnd = TestContext.CurrentContext.Random.NextULong(); + long mask = long.MinValue; + + return (rnd >> (cnt + 1)) | ((ulong)mask >> cnt); + } +#endregion + +#region "ValueSource (Types)" + private static IEnumerable _GenLeadingSignsX_() + { + for (int cnt = 0; cnt <= 63; cnt++) { - Bits Op = new Bits(Opcode); - - AArch64.X((int)Rn, new Bits(Xn)); - Base.Cls(Op[31], Op[9, 5], Op[4, 0]); - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); + yield return GenLeadingSignsMinus64(cnt); + yield return GenLeadingSignsPlus64(cnt); } } - [Test, Description("CLS , ")] - public void Cls_32bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(256)] uint Wn) + private static IEnumerable _GenLeadingSignsW_() { - uint Opcode = 0x5AC01400; // CLS W0, W0 - Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); - - uint _W31 = TestContext.CurrentContext.Random.NextUInt(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X31: _W31); - - if (Rd != 31) + for (int cnt = 0; cnt <= 31; cnt++) { - Bits Op = new Bits(Opcode); - - AArch64.X((int)Rn, new Bits(Wn)); - Base.Cls(Op[31], Op[9, 5], Op[4, 0]); - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); - - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); + yield return GenLeadingSignsMinus32(cnt); + yield return GenLeadingSignsPlus32(cnt); } } - [Test, Description("CLZ , ")] - public void Clz_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(256)] ulong Xn) + private static IEnumerable _GenLeadingZerosX_() { - uint Opcode = 0xDAC01000; // CLZ X0, X0 - Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); - - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X31: _X31); - - if (Rd != 31) + for (int cnt = 0; cnt <= 64; cnt++) { - Bits Op = new Bits(Opcode); - - AArch64.X((int)Rn, new Bits(Xn)); - Base.Clz(Op[31], Op[9, 5], Op[4, 0]); - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); + yield return GenLeadingZeros64(cnt); } } - [Test, Description("CLZ , ")] - public void Clz_32bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(256)] uint Wn) + private static IEnumerable _GenLeadingZerosW_() { - uint Opcode = 0x5AC01000; // CLZ W0, W0 - Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); - - uint _W31 = TestContext.CurrentContext.Random.NextUInt(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X31: _W31); - - if (Rd != 31) + for (int cnt = 0; cnt <= 32; cnt++) { - Bits Op = new Bits(Opcode); - - AArch64.X((int)Rn, new Bits(Wn)); - Base.Clz(Op[31], Op[9, 5], Op[4, 0]); - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); - - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); + yield return GenLeadingZeros32(cnt); } } +#endregion - [Test, Description("RBIT , ")] - public void Rbit_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, + private const int RndCnt = 2; + + [Test, Pairwise, Description("CLS , ")] + public void Cls_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [ValueSource("_GenLeadingSignsX_")] [Random(RndCnt)] ulong xn) + { + uint opcode = 0xDAC01400; // CLS X0, X0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xn, x31: x31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CLS , ")] + public void Cls_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [ValueSource("_GenLeadingSignsW_")] [Random(RndCnt)] uint wn) + { + uint opcode = 0x5AC01400; // CLS W0, W0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x31: w31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CLZ , ")] + public void Clz_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [ValueSource("_GenLeadingZerosX_")] [Random(RndCnt)] ulong xn) + { + uint opcode = 0xDAC01000; // CLZ X0, X0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xn, x31: x31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CLZ , ")] + public void Clz_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [ValueSource("_GenLeadingZerosW_")] [Random(RndCnt)] uint wn) + { + uint opcode = 0x5AC01000; // CLZ W0, W0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x31: w31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("RBIT , ")] + public void Rbit_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(256)] ulong Xn) + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xn) { - uint Opcode = 0xDAC00000; // RBIT X0, X0 - Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + uint opcode = 0xDAC00000; // RBIT X0, X0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X31: _X31); + ulong x31 = TestContext.CurrentContext.Random.NextULong(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: xn, x31: x31); - AArch64.X((int)Rn, new Bits(Xn)); - Base.Rbit(Op[31], Op[9, 5], Op[4, 0]); - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); - } + CompareAgainstUnicorn(); } - [Test, Description("RBIT , ")] - public void Rbit_32bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, + [Test, Pairwise, Description("RBIT , ")] + public void Rbit_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(256)] uint Wn) + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wn) { - uint Opcode = 0x5AC00000; // RBIT W0, W0 - Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + uint opcode = 0x5AC00000; // RBIT W0, W0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); - uint _W31 = TestContext.CurrentContext.Random.NextUInt(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X31: _W31); + uint w31 = TestContext.CurrentContext.Random.NextUInt(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: wn, x31: w31); - AArch64.X((int)Rn, new Bits(Wn)); - Base.Rbit(Op[31], Op[9, 5], Op[4, 0]); - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); - - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); - } + CompareAgainstUnicorn(); } - [Test, Description("REV16 , ")] - public void Rev16_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, + [Test, Pairwise, Description("REV16 , ")] + public void Rev16_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(256)] ulong Xn) + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xn) { - uint Opcode = 0xDAC00400; // REV16 X0, X0 - Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + uint opcode = 0xDAC00400; // REV16 X0, X0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X31: _X31); + ulong x31 = TestContext.CurrentContext.Random.NextULong(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: xn, x31: x31); - AArch64.X((int)Rn, new Bits(Xn)); - Base.Rev16(Op[31], Op[9, 5], Op[4, 0]); - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); - } + CompareAgainstUnicorn(); } - [Test, Description("REV16 , ")] - public void Rev16_32bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, + [Test, Pairwise, Description("REV16 , ")] + public void Rev16_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(256)] uint Wn) + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wn) { - uint Opcode = 0x5AC00400; // REV16 W0, W0 - Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + uint opcode = 0x5AC00400; // REV16 W0, W0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); - uint _W31 = TestContext.CurrentContext.Random.NextUInt(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X31: _W31); + uint w31 = TestContext.CurrentContext.Random.NextUInt(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: wn, x31: w31); - AArch64.X((int)Rn, new Bits(Wn)); - Base.Rev16(Op[31], Op[9, 5], Op[4, 0]); - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); - - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); - } + CompareAgainstUnicorn(); } - [Test, Description("REV32 , ")] - public void Rev32_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, + [Test, Pairwise, Description("REV32 , ")] + public void Rev32_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(256)] ulong Xn) + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xn) { - uint Opcode = 0xDAC00800; // REV32 X0, X0 - Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + uint opcode = 0xDAC00800; // REV32 X0, X0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X31: _X31); + ulong x31 = TestContext.CurrentContext.Random.NextULong(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: xn, x31: x31); - AArch64.X((int)Rn, new Bits(Xn)); - Base.Rev32(Op[31], Op[9, 5], Op[4, 0]); - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); - } + CompareAgainstUnicorn(); } - [Test, Description("REV , ")] - public void Rev32_32bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, + [Test, Pairwise, Description("REV , ")] + public void Rev32_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(256)] uint Wn) + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wn) { - uint Opcode = 0x5AC00800; // REV W0, W0 - Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + uint opcode = 0x5AC00800; // REV W0, W0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); - uint _W31 = TestContext.CurrentContext.Random.NextUInt(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X31: _W31); + uint w31 = TestContext.CurrentContext.Random.NextUInt(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: wn, x31: w31); - AArch64.X((int)Rn, new Bits(Wn)); - Base.Rev32(Op[31], Op[9, 5], Op[4, 0]); - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); - - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); - } + CompareAgainstUnicorn(); } - [Test, Description("REV64 , ")] - public void Rev64_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, + [Test, Pairwise, Description("REV64 , ")] + public void Rev64_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(256)] ulong Xn) + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xn) { - uint Opcode = 0xDAC00C00; // REV64 X0, X0 - Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + uint opcode = 0xDAC00C00; // REV64 X0, X0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X31: _X31); + ulong x31 = TestContext.CurrentContext.Random.NextULong(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: xn, x31: x31); - AArch64.X((int)Rn, new Bits(Xn)); - Base.Rev64(Op[9, 5], Op[4, 0]); - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); - } + CompareAgainstUnicorn(); } #endif } diff --git a/Ryujinx.Tests/Cpu/CpuTestAluBinary.cs b/Ryujinx.Tests/Cpu/CpuTestAluBinary.cs new file mode 100644 index 0000000000..2823477fc5 --- /dev/null +++ b/Ryujinx.Tests/Cpu/CpuTestAluBinary.cs @@ -0,0 +1,238 @@ +#define AluBinary + +using NUnit.Framework; + +namespace Ryujinx.Tests.Cpu +{ + [Category("AluBinary")] + public sealed class CpuTestAluBinary : CpuTest + { +#if AluBinary + private const int RndCnt = 2; + + [Test, Pairwise, Description("CRC32X , , "), Ignore("Unicorn fails.")] + public void Crc32x([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x00000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wn, + [Values((ulong)0x00_00_00_00_00_00_00_00, + (ulong)0x7F_FF_FF_FF_FF_FF_FF_FF, + (ulong)0x80_00_00_00_00_00_00_00, + (ulong)0xFF_FF_FF_FF_FF_FF_FF_FF)] [Random(RndCnt)] ulong xm) + { + uint opcode = 0x9AC04C00; // CRC32X W0, W0, X0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x2: xm, x31: w31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CRC32W , , "), Ignore("Unicorn fails.")] + public void Crc32w([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x00000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wn, + [Values((uint)0x00_00_00_00, (uint)0x7F_FF_FF_FF, + (uint)0x80_00_00_00, (uint)0xFF_FF_FF_FF)] [Random(RndCnt)] uint wm) + { + uint opcode = 0x1AC04800; // CRC32W W0, W0, W0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CRC32H , , "), Ignore("Unicorn fails.")] + public void Crc32h([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x00000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wn, + [Values((ushort)0x00_00, (ushort)0x7F_FF, + (ushort)0x80_00, (ushort)0xFF_FF)] [Random(RndCnt)] ushort wm) + { + uint opcode = 0x1AC04400; // CRC32H W0, W0, W0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CRC32B , , "), Ignore("Unicorn fails.")] + public void Crc32b([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x00000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wn, + [Values((byte)0x00, (byte)0x7F, + (byte)0x80, (byte)0xFF)] [Random(RndCnt)] byte wm) + { + uint opcode = 0x1AC04000; // CRC32B W0, W0, W0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CRC32CX , , ")] + public void Crc32cx([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x00000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wn, + [Values((ulong)0x00_00_00_00_00_00_00_00, + (ulong)0x7F_FF_FF_FF_FF_FF_FF_FF, + (ulong)0x80_00_00_00_00_00_00_00, + (ulong)0xFF_FF_FF_FF_FF_FF_FF_FF)] [Random(RndCnt)] ulong xm) + { + uint opcode = 0x9AC05C00; // CRC32CX W0, W0, X0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x2: xm, x31: w31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CRC32CW , , ")] + public void Crc32cw([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x00000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wn, + [Values((uint)0x00_00_00_00, (uint)0x7F_FF_FF_FF, + (uint)0x80_00_00_00, (uint)0xFF_FF_FF_FF)] [Random(RndCnt)] uint wm) + { + uint opcode = 0x1AC05800; // CRC32CW W0, W0, W0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CRC32CH , , ")] + public void Crc32ch([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x00000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wn, + [Values((ushort)0x00_00, (ushort)0x7F_FF, + (ushort)0x80_00, (ushort)0xFF_FF)] [Random(RndCnt)] ushort wm) + { + uint opcode = 0x1AC05400; // CRC32CH W0, W0, W0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CRC32CB , , ")] + public void Crc32cb([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x00000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wn, + [Values((byte)0x00, (byte)0x7F, + (byte)0x80, (byte)0xFF)] [Random(RndCnt)] byte wm) + { + uint opcode = 0x1AC05000; // CRC32CB W0, W0, W0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SDIV , , ")] + public void Sdiv_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xm) + { + uint opcode = 0x9AC00C00; // SDIV X0, X0, X0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xn, x2: xm, x31: x31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SDIV , , ")] + public void Sdiv_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wm) + { + uint opcode = 0x1AC00C00; // SDIV W0, W0, W0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UDIV , , ")] + public void Udiv_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xm) + { + uint opcode = 0x9AC00800; // UDIV X0, X0, X0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xn, x2: xm, x31: x31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UDIV , , ")] + public void Udiv_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wm) + { + uint opcode = 0x1AC00800; // UDIV W0, W0, W0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31); + + CompareAgainstUnicorn(); + } +#endif + } +} diff --git a/Ryujinx.Tests/Cpu/CpuTestAluImm.cs b/Ryujinx.Tests/Cpu/CpuTestAluImm.cs index 5d1f0b6bae..9551ce2ce4 100644 --- a/Ryujinx.Tests/Cpu/CpuTestAluImm.cs +++ b/Ryujinx.Tests/Cpu/CpuTestAluImm.cs @@ -1,810 +1,436 @@ -//#define AluImm - -using ChocolArm64.State; +#define AluImm using NUnit.Framework; namespace Ryujinx.Tests.Cpu { - using Tester; - using Tester.Types; - - [Category("AluImm"), Ignore("Tested: first half of 2018.")] + [Category("AluImm")] public sealed class CpuTestAluImm : CpuTest { #if AluImm - [SetUp] - public void SetupTester() - { - AArch64.TakeReset(false); - } + private const int RndCnt = 2; + private const int RndCntImm = 2; + private const int RndCntImms = 2; + private const int RndCntImmr = 2; - [Test, Description("ADD , , #{, }")] - public void Add_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, + [Test, Pairwise, Description("ADD , , #{, }")] + public void Add_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(8)] ulong Xn_SP, - [Values(0u, 4095u)] [Random(0u, 4095u, 10)] uint imm, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xnSp, + [Values(0u, 4095u)] [Random(0u, 4095u, RndCntImm)] uint imm, [Values(0b00u, 0b01u)] uint shift) // { - uint Opcode = 0x91000000; // ADD X0, X0, #0, LSL #0 - Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((shift & 3) << 22) | ((imm & 4095) << 10); - Bits Op = new Bits(Opcode); + uint opcode = 0x91000000; // ADD X0, X0, #0, LSL #0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((imm & 4095) << 10); - AThreadState ThreadState; - - if (Rn != 31) + if (rn != 31) { - ThreadState = SingleOpcode(Opcode, X1: Xn_SP); - - AArch64.X((int)Rn, new Bits(Xn_SP)); + SingleOpcode(opcode, x1: xnSp); } else { - ThreadState = SingleOpcode(Opcode, X31: Xn_SP); - - AArch64.SP(new Bits(Xn_SP)); + SingleOpcode(opcode, x31: xnSp); } - Base.Add_Imm(Op[31], Op[23, 22], Op[21, 10], Op[9, 5], Op[4, 0]); - - if (Rd != 31) - { - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - ulong SP = AArch64.SP(64).ToUInt64(); - - Assert.That((ulong)ThreadState.X31, Is.EqualTo(SP)); - } + CompareAgainstUnicorn(); } - [Test, Description("ADD , , #{, }")] - public void Add_32bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, + [Test, Pairwise, Description("ADD , , #{, }")] + public void Add_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(8)] uint Wn_WSP, - [Values(0u, 4095u)] [Random(0u, 4095u, 10)] uint imm, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wnWsp, + [Values(0u, 4095u)] [Random(0u, 4095u, RndCntImm)] uint imm, [Values(0b00u, 0b01u)] uint shift) // { - uint Opcode = 0x11000000; // ADD W0, W0, #0, LSL #0 - Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((shift & 3) << 22) | ((imm & 4095) << 10); - Bits Op = new Bits(Opcode); + uint opcode = 0x11000000; // ADD W0, W0, #0, LSL #0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((imm & 4095) << 10); - AThreadState ThreadState; - - if (Rn != 31) + if (rn != 31) { - ThreadState = SingleOpcode(Opcode, X1: Wn_WSP); - - AArch64.X((int)Rn, new Bits(Wn_WSP)); + SingleOpcode(opcode, x1: wnWsp); } else { - ThreadState = SingleOpcode(Opcode, X31: Wn_WSP); - - AArch64.SP(new Bits(Wn_WSP)); + SingleOpcode(opcode, x31: wnWsp); } - Base.Add_Imm(Op[31], Op[23, 22], Op[21, 10], Op[9, 5], Op[4, 0]); - - if (Rd != 31) - { - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); - - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - uint WSP = AArch64.SP(32).ToUInt32(); - - Assert.That((uint)ThreadState.X31, Is.EqualTo(WSP)); - } + CompareAgainstUnicorn(); } - [Test, Description("ADDS , , #{, }")] - public void Adds_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, + [Test, Pairwise, Description("ADDS , , #{, }")] + public void Adds_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(8)] ulong Xn_SP, - [Values(0u, 4095u)] [Random(0u, 4095u, 10)] uint imm, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xnSp, + [Values(0u, 4095u)] [Random(0u, 4095u, RndCntImm)] uint imm, [Values(0b00u, 0b01u)] uint shift) // { - uint Opcode = 0xB1000000; // ADDS X0, X0, #0, LSL #0 - Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((shift & 3) << 22) | ((imm & 4095) << 10); - Bits Op = new Bits(Opcode); + uint opcode = 0xB1000000; // ADDS X0, X0, #0, LSL #0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((imm & 4095) << 10); - AThreadState ThreadState; - - if (Rn != 31) + if (rn != 31) { - ThreadState = SingleOpcode(Opcode, X1: Xn_SP); - - AArch64.X((int)Rn, new Bits(Xn_SP)); + SingleOpcode(opcode, x1: xnSp); } else { - ThreadState = SingleOpcode(Opcode, X31: Xn_SP); - - AArch64.SP(new Bits(Xn_SP)); + SingleOpcode(opcode, x31: xnSp); } - Base.Adds_Imm(Op[31], Op[23, 22], Op[21, 10], Op[9, 5], Op[4, 0]); - - if (Rd != 31) - { - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - ulong _X31 = AArch64.SP(64).ToUInt64(); - - Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); - } - Assert.Multiple(() => - { - Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); - Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); - Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); - Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); - }); + CompareAgainstUnicorn(); } - [Test, Description("ADDS , , #{, }")] - public void Adds_32bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, + [Test, Pairwise, Description("ADDS , , #{, }")] + public void Adds_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(8)] uint Wn_WSP, - [Values(0u, 4095u)] [Random(0u, 4095u, 10)] uint imm, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wnWsp, + [Values(0u, 4095u)] [Random(0u, 4095u, RndCntImm)] uint imm, [Values(0b00u, 0b01u)] uint shift) // { - uint Opcode = 0x31000000; // ADDS W0, W0, #0, LSL #0 - Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((shift & 3) << 22) | ((imm & 4095) << 10); - Bits Op = new Bits(Opcode); + uint opcode = 0x31000000; // ADDS W0, W0, #0, LSL #0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((imm & 4095) << 10); - AThreadState ThreadState; - - if (Rn != 31) + if (rn != 31) { - ThreadState = SingleOpcode(Opcode, X1: Wn_WSP); - - AArch64.X((int)Rn, new Bits(Wn_WSP)); + SingleOpcode(opcode, x1: wnWsp); } else { - ThreadState = SingleOpcode(Opcode, X31: Wn_WSP); - - AArch64.SP(new Bits(Wn_WSP)); + SingleOpcode(opcode, x31: wnWsp); } - Base.Adds_Imm(Op[31], Op[23, 22], Op[21, 10], Op[9, 5], Op[4, 0]); - - if (Rd != 31) - { - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); - - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - uint _W31 = AArch64.SP(32).ToUInt32(); - - Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); - } - Assert.Multiple(() => - { - Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); - Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); - Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); - Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); - }); + CompareAgainstUnicorn(); } - [Test, Description("AND , , #")] - public void And_N1_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, + [Test, Pairwise, Description("AND , , #")] + public void And_N1_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(2)] ulong Xn, - [Values(0u, 31u, 32u, 62u)] [Random(0u, 62u, 2)] uint imms, // - [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, 2)] uint immr) // + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xn, + [Values(0u, 31u, 32u, 62u)] [Random(0u, 62u, RndCntImms)] uint imms, // + [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, RndCntImmr)] uint immr) // { - uint Opcode = 0x92400000; // AND X0, X0, #0x1 - Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); - Bits Op = new Bits(Opcode); + uint opcode = 0x92400000; // AND X0, X0, #0x1 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X31: _X31); + ulong x31 = TestContext.CurrentContext.Random.NextULong(); - AArch64.X((int)Rn, new Bits(Xn)); - Base.And_Imm(Op[31], Op[22], Op[21, 16], Op[15, 10], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, x1: xn, x31: x31); - if (Rd != 31) - { - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - ulong SP = AArch64.SP(64).ToUInt64(); - - Assert.That((ulong)ThreadState.X31, Is.EqualTo(SP)); - } + CompareAgainstUnicorn(); } - [Test, Description("AND , , #")] - public void And_N0_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, + [Test, Pairwise, Description("AND , , #")] + public void And_N0_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(2)] ulong Xn, - [Values(0u, 15u, 16u, 30u)] [Random(0u, 30u, 2)] uint imms, // - [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, 2)] uint immr) // + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xn, + [Values(0u, 15u, 16u, 30u)] [Random(0u, 30u, RndCntImms)] uint imms, // + [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, RndCntImmr)] uint immr) // { - uint Opcode = 0x92000000; // AND X0, X0, #0x100000001 - Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); - Bits Op = new Bits(Opcode); + uint opcode = 0x92000000; // AND X0, X0, #0x100000001 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X31: _X31); + ulong x31 = TestContext.CurrentContext.Random.NextULong(); - AArch64.X((int)Rn, new Bits(Xn)); - Base.And_Imm(Op[31], Op[22], Op[21, 16], Op[15, 10], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, x1: xn, x31: x31); - if (Rd != 31) - { - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - ulong SP = AArch64.SP(64).ToUInt64(); - - Assert.That((ulong)ThreadState.X31, Is.EqualTo(SP)); - } + CompareAgainstUnicorn(); } - [Test, Description("AND , , #")] - public void And_32bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, + [Test, Pairwise, Description("AND , , #")] + public void And_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(2)] uint Wn, - [Values(0u, 15u, 16u, 30u)] [Random(0u, 30u, 2)] uint imms, // - [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, 2)] uint immr) // + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wn, + [Values(0u, 15u, 16u, 30u)] [Random(0u, 30u, RndCntImms)] uint imms, // + [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, RndCntImmr)] uint immr) // { - uint Opcode = 0x12000000; // AND W0, W0, #0x1 - Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); - Bits Op = new Bits(Opcode); + uint opcode = 0x12000000; // AND W0, W0, #0x1 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); - uint _W31 = TestContext.CurrentContext.Random.NextUInt(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X31: _W31); + uint w31 = TestContext.CurrentContext.Random.NextUInt(); - AArch64.X((int)Rn, new Bits(Wn)); - Base.And_Imm(Op[31], Op[22], Op[21, 16], Op[15, 10], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, x1: wn, x31: w31); - if (Rd != 31) - { - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); - - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - uint WSP = AArch64.SP(32).ToUInt32(); - - Assert.That((uint)ThreadState.X31, Is.EqualTo(WSP)); - } + CompareAgainstUnicorn(); } - [Test, Description("ANDS , , #")] - public void Ands_N1_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, + [Test, Pairwise, Description("ANDS , , #")] + public void Ands_N1_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(2)] ulong Xn, - [Values(0u, 31u, 32u, 62u)] [Random(0u, 62u, 2)] uint imms, // - [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, 2)] uint immr) // + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xn, + [Values(0u, 31u, 32u, 62u)] [Random(0u, 62u, RndCntImms)] uint imms, // + [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, RndCntImmr)] uint immr) // { - uint Opcode = 0xF2400000; // ANDS X0, X0, #0x1 - Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); - Bits Op = new Bits(Opcode); + uint opcode = 0xF2400000; // ANDS X0, X0, #0x1 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X31: _X31); + ulong x31 = TestContext.CurrentContext.Random.NextULong(); - AArch64.X((int)Rn, new Bits(Xn)); - Base.Ands_Imm(Op[31], Op[22], Op[21, 16], Op[15, 10], Op[9, 5], Op[4, 0]); - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + SingleOpcode(opcode, x1: xn, x31: x31); - if (Rd != 31) - { - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); - } - Assert.Multiple(() => - { - Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); - Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); - Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); - Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); - }); + CompareAgainstUnicorn(); } - [Test, Description("ANDS , , #")] - public void Ands_N0_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, + [Test, Pairwise, Description("ANDS , , #")] + public void Ands_N0_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(2)] ulong Xn, - [Values(0u, 15u, 16u, 30u)] [Random(0u, 30u, 2)] uint imms, // - [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, 2)] uint immr) // + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xn, + [Values(0u, 15u, 16u, 30u)] [Random(0u, 30u, RndCntImms)] uint imms, // + [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, RndCntImmr)] uint immr) // { - uint Opcode = 0xF2000000; // ANDS X0, X0, #0x100000001 - Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); - Bits Op = new Bits(Opcode); + uint opcode = 0xF2000000; // ANDS X0, X0, #0x100000001 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X31: _X31); + ulong x31 = TestContext.CurrentContext.Random.NextULong(); - AArch64.X((int)Rn, new Bits(Xn)); - Base.Ands_Imm(Op[31], Op[22], Op[21, 16], Op[15, 10], Op[9, 5], Op[4, 0]); - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + SingleOpcode(opcode, x1: xn, x31: x31); - if (Rd != 31) - { - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); - } - Assert.Multiple(() => - { - Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); - Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); - Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); - Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); - }); + CompareAgainstUnicorn(); } - [Test, Description("ANDS , , #")] - public void Ands_32bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, + [Test, Pairwise, Description("ANDS , , #")] + public void Ands_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(2)] uint Wn, - [Values(0u, 15u, 16u, 30u)] [Random(0u, 30u, 2)] uint imms, // - [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, 2)] uint immr) // + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wn, + [Values(0u, 15u, 16u, 30u)] [Random(0u, 30u, RndCntImms)] uint imms, // + [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, RndCntImmr)] uint immr) // { - uint Opcode = 0x72000000; // ANDS W0, W0, #0x1 - Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); - Bits Op = new Bits(Opcode); + uint opcode = 0x72000000; // ANDS W0, W0, #0x1 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); - uint _W31 = TestContext.CurrentContext.Random.NextUInt(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X31: _W31); + uint w31 = TestContext.CurrentContext.Random.NextUInt(); - AArch64.X((int)Rn, new Bits(Wn)); - Base.Ands_Imm(Op[31], Op[22], Op[21, 16], Op[15, 10], Op[9, 5], Op[4, 0]); - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + SingleOpcode(opcode, x1: wn, x31: w31); - if (Rd != 31) - { - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); - } - Assert.Multiple(() => - { - Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); - Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); - Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); - Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); - }); + CompareAgainstUnicorn(); } - [Test, Description("EOR , , #")] - public void Eor_N1_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, + [Test, Pairwise, Description("EOR , , #")] + public void Eor_N1_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(2)] ulong Xn, - [Values(0u, 31u, 32u, 62u)] [Random(0u, 62u, 2)] uint imms, // - [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, 2)] uint immr) // + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xn, + [Values(0u, 31u, 32u, 62u)] [Random(0u, 62u, RndCntImms)] uint imms, // + [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, RndCntImmr)] uint immr) // { - uint Opcode = 0xD2400000; // EOR X0, X0, #0x1 - Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); - Bits Op = new Bits(Opcode); + uint opcode = 0xD2400000; // EOR X0, X0, #0x1 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X31: _X31); + ulong x31 = TestContext.CurrentContext.Random.NextULong(); - AArch64.X((int)Rn, new Bits(Xn)); - Base.Eor_Imm(Op[31], Op[22], Op[21, 16], Op[15, 10], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, x1: xn, x31: x31); - if (Rd != 31) - { - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - ulong SP = AArch64.SP(64).ToUInt64(); - - Assert.That((ulong)ThreadState.X31, Is.EqualTo(SP)); - } + CompareAgainstUnicorn(); } - [Test, Description("EOR , , #")] - public void Eor_N0_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, + [Test, Pairwise, Description("EOR , , #")] + public void Eor_N0_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(2)] ulong Xn, - [Values(0u, 15u, 16u, 30u)] [Random(0u, 30u, 2)] uint imms, // - [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, 2)] uint immr) // + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xn, + [Values(0u, 15u, 16u, 30u)] [Random(0u, 30u, RndCntImms)] uint imms, // + [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, RndCntImmr)] uint immr) // { - uint Opcode = 0xD2000000; // EOR X0, X0, #0x100000001 - Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); - Bits Op = new Bits(Opcode); + uint opcode = 0xD2000000; // EOR X0, X0, #0x100000001 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X31: _X31); + ulong x31 = TestContext.CurrentContext.Random.NextULong(); - AArch64.X((int)Rn, new Bits(Xn)); - Base.Eor_Imm(Op[31], Op[22], Op[21, 16], Op[15, 10], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, x1: xn, x31: x31); - if (Rd != 31) - { - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - ulong SP = AArch64.SP(64).ToUInt64(); - - Assert.That((ulong)ThreadState.X31, Is.EqualTo(SP)); - } + CompareAgainstUnicorn(); } - [Test, Description("EOR , , #")] - public void Eor_32bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, + [Test, Pairwise, Description("EOR , , #")] + public void Eor_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(2)] uint Wn, - [Values(0u, 15u, 16u, 30u)] [Random(0u, 30u, 2)] uint imms, // - [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, 2)] uint immr) // + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wn, + [Values(0u, 15u, 16u, 30u)] [Random(0u, 30u, RndCntImms)] uint imms, // + [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, RndCntImmr)] uint immr) // { - uint Opcode = 0x52000000; // EOR W0, W0, #0x1 - Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); - Bits Op = new Bits(Opcode); + uint opcode = 0x52000000; // EOR W0, W0, #0x1 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); - uint _W31 = TestContext.CurrentContext.Random.NextUInt(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X31: _W31); + uint w31 = TestContext.CurrentContext.Random.NextUInt(); - AArch64.X((int)Rn, new Bits(Wn)); - Base.Eor_Imm(Op[31], Op[22], Op[21, 16], Op[15, 10], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, x1: wn, x31: w31); - if (Rd != 31) - { - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); - - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - uint WSP = AArch64.SP(32).ToUInt32(); - - Assert.That((uint)ThreadState.X31, Is.EqualTo(WSP)); - } + CompareAgainstUnicorn(); } - [Test, Description("ORR , , #")] - public void Orr_N1_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, + [Test, Pairwise, Description("ORR , , #")] + public void Orr_N1_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(2)] ulong Xn, - [Values(0u, 31u, 32u, 62u)] [Random(0u, 62u, 2)] uint imms, // - [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, 2)] uint immr) // + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xn, + [Values(0u, 31u, 32u, 62u)] [Random(0u, 62u, RndCntImms)] uint imms, // + [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, RndCntImmr)] uint immr) // { - uint Opcode = 0xB2400000; // ORR X0, X0, #0x1 - Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); - Bits Op = new Bits(Opcode); + uint opcode = 0xB2400000; // ORR X0, X0, #0x1 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X31: _X31); + ulong x31 = TestContext.CurrentContext.Random.NextULong(); - AArch64.X((int)Rn, new Bits(Xn)); - Base.Orr_Imm(Op[31], Op[22], Op[21, 16], Op[15, 10], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, x1: xn, x31: x31); - if (Rd != 31) - { - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - ulong SP = AArch64.SP(64).ToUInt64(); - - Assert.That((ulong)ThreadState.X31, Is.EqualTo(SP)); - } + CompareAgainstUnicorn(); } - [Test, Description("ORR , , #")] - public void Orr_N0_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, + [Test, Pairwise, Description("ORR , , #")] + public void Orr_N0_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(2)] ulong Xn, - [Values(0u, 15u, 16u, 30u)] [Random(0u, 30u, 2)] uint imms, // - [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, 2)] uint immr) // + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xn, + [Values(0u, 15u, 16u, 30u)] [Random(0u, 30u, RndCntImms)] uint imms, // + [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, RndCntImmr)] uint immr) // { - uint Opcode = 0xB2000000; // ORR X0, X0, #0x100000001 - Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); - Bits Op = new Bits(Opcode); + uint opcode = 0xB2000000; // ORR X0, X0, #0x100000001 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X31: _X31); + ulong x31 = TestContext.CurrentContext.Random.NextULong(); - AArch64.X((int)Rn, new Bits(Xn)); - Base.Orr_Imm(Op[31], Op[22], Op[21, 16], Op[15, 10], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, x1: xn, x31: x31); - if (Rd != 31) - { - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - ulong SP = AArch64.SP(64).ToUInt64(); - - Assert.That((ulong)ThreadState.X31, Is.EqualTo(SP)); - } + CompareAgainstUnicorn(); } - [Test, Description("ORR , , #")] - public void Orr_32bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, + [Test, Pairwise, Description("ORR , , #")] + public void Orr_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(2)] uint Wn, - [Values(0u, 15u, 16u, 30u)] [Random(0u, 30u, 2)] uint imms, // - [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, 2)] uint immr) // + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wn, + [Values(0u, 15u, 16u, 30u)] [Random(0u, 30u, RndCntImms)] uint imms, // + [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, RndCntImmr)] uint immr) // { - uint Opcode = 0x32000000; // ORR W0, W0, #0x1 - Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); - Bits Op = new Bits(Opcode); + uint opcode = 0x32000000; // ORR W0, W0, #0x1 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); - uint _W31 = TestContext.CurrentContext.Random.NextUInt(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X31: _W31); + uint w31 = TestContext.CurrentContext.Random.NextUInt(); - AArch64.X((int)Rn, new Bits(Wn)); - Base.Orr_Imm(Op[31], Op[22], Op[21, 16], Op[15, 10], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, x1: wn, x31: w31); - if (Rd != 31) - { - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); - - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - uint WSP = AArch64.SP(32).ToUInt32(); - - Assert.That((uint)ThreadState.X31, Is.EqualTo(WSP)); - } + CompareAgainstUnicorn(); } - [Test, Description("SUB , , #{, }")] - public void Sub_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, + [Test, Pairwise, Description("SUB , , #{, }")] + public void Sub_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(8)] ulong Xn_SP, - [Values(0u, 4095u)] [Random(0u, 4095u, 10)] uint imm, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xnSp, + [Values(0u, 4095u)] [Random(0u, 4095u, RndCntImm)] uint imm, [Values(0b00u, 0b01u)] uint shift) // { - uint Opcode = 0xD1000000; // SUB X0, X0, #0, LSL #0 - Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((shift & 3) << 22) | ((imm & 4095) << 10); - Bits Op = new Bits(Opcode); + uint opcode = 0xD1000000; // SUB X0, X0, #0, LSL #0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((imm & 4095) << 10); - AThreadState ThreadState; - - if (Rn != 31) + if (rn != 31) { - ThreadState = SingleOpcode(Opcode, X1: Xn_SP); - - AArch64.X((int)Rn, new Bits(Xn_SP)); + SingleOpcode(opcode, x1: xnSp); } else { - ThreadState = SingleOpcode(Opcode, X31: Xn_SP); - - AArch64.SP(new Bits(Xn_SP)); + SingleOpcode(opcode, x31: xnSp); } - Base.Sub_Imm(Op[31], Op[23, 22], Op[21, 10], Op[9, 5], Op[4, 0]); - - if (Rd != 31) - { - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - ulong SP = AArch64.SP(64).ToUInt64(); - - Assert.That((ulong)ThreadState.X31, Is.EqualTo(SP)); - } + CompareAgainstUnicorn(); } - [Test, Description("SUB , , #{, }")] - public void Sub_32bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, + [Test, Pairwise, Description("SUB , , #{, }")] + public void Sub_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(8)] uint Wn_WSP, - [Values(0u, 4095u)] [Random(0u, 4095u, 10)] uint imm, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wnWsp, + [Values(0u, 4095u)] [Random(0u, 4095u, RndCntImm)] uint imm, [Values(0b00u, 0b01u)] uint shift) // { - uint Opcode = 0x51000000; // SUB W0, W0, #0, LSL #0 - Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((shift & 3) << 22) | ((imm & 4095) << 10); - Bits Op = new Bits(Opcode); + uint opcode = 0x51000000; // SUB W0, W0, #0, LSL #0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((imm & 4095) << 10); - AThreadState ThreadState; - - if (Rn != 31) + if (rn != 31) { - ThreadState = SingleOpcode(Opcode, X1: Wn_WSP); - - AArch64.X((int)Rn, new Bits(Wn_WSP)); + SingleOpcode(opcode, x1: wnWsp); } else { - ThreadState = SingleOpcode(Opcode, X31: Wn_WSP); - - AArch64.SP(new Bits(Wn_WSP)); + SingleOpcode(opcode, x31: wnWsp); } - Base.Sub_Imm(Op[31], Op[23, 22], Op[21, 10], Op[9, 5], Op[4, 0]); - - if (Rd != 31) - { - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); - - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - uint WSP = AArch64.SP(32).ToUInt32(); - - Assert.That((uint)ThreadState.X31, Is.EqualTo(WSP)); - } + CompareAgainstUnicorn(); } - [Test, Description("SUBS , , #{, }")] - public void Subs_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, + [Test, Pairwise, Description("SUBS , , #{, }")] + public void Subs_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(8)] ulong Xn_SP, - [Values(0u, 4095u)] [Random(0u, 4095u, 10)] uint imm, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xnSp, + [Values(0u, 4095u)] [Random(0u, 4095u, RndCntImm)] uint imm, [Values(0b00u, 0b01u)] uint shift) // { - uint Opcode = 0xF1000000; // SUBS X0, X0, #0, LSL #0 - Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((shift & 3) << 22) | ((imm & 4095) << 10); - Bits Op = new Bits(Opcode); + uint opcode = 0xF1000000; // SUBS X0, X0, #0, LSL #0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((imm & 4095) << 10); - AThreadState ThreadState; - - if (Rn != 31) + if (rn != 31) { - ThreadState = SingleOpcode(Opcode, X1: Xn_SP); - - AArch64.X((int)Rn, new Bits(Xn_SP)); + SingleOpcode(opcode, x1: xnSp); } else { - ThreadState = SingleOpcode(Opcode, X31: Xn_SP); - - AArch64.SP(new Bits(Xn_SP)); + SingleOpcode(opcode, x31: xnSp); } - Base.Subs_Imm(Op[31], Op[23, 22], Op[21, 10], Op[9, 5], Op[4, 0]); - - if (Rd != 31) - { - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - ulong _X31 = AArch64.SP(64).ToUInt64(); - - Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); - } - Assert.Multiple(() => - { - Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); - Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); - Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); - Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); - }); + CompareAgainstUnicorn(); } - [Test, Description("SUBS , , #{, }")] - public void Subs_32bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, + [Test, Pairwise, Description("SUBS , , #{, }")] + public void Subs_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(8)] uint Wn_WSP, - [Values(0u, 4095u)] [Random(0u, 4095u, 10)] uint imm, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wnWsp, + [Values(0u, 4095u)] [Random(0u, 4095u, RndCntImm)] uint imm, [Values(0b00u, 0b01u)] uint shift) // { - uint Opcode = 0x71000000; // SUBS W0, W0, #0, LSL #0 - Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((shift & 3) << 22) | ((imm & 4095) << 10); - Bits Op = new Bits(Opcode); + uint opcode = 0x71000000; // SUBS W0, W0, #0, LSL #0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((imm & 4095) << 10); - AThreadState ThreadState; - - if (Rn != 31) + if (rn != 31) { - ThreadState = SingleOpcode(Opcode, X1: Wn_WSP); - - AArch64.X((int)Rn, new Bits(Wn_WSP)); + SingleOpcode(opcode, x1: wnWsp); } else { - ThreadState = SingleOpcode(Opcode, X31: Wn_WSP); - - AArch64.SP(new Bits(Wn_WSP)); + SingleOpcode(opcode, x31: wnWsp); } - Base.Subs_Imm(Op[31], Op[23, 22], Op[21, 10], Op[9, 5], Op[4, 0]); - - if (Rd != 31) - { - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); - - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - uint _W31 = AArch64.SP(32).ToUInt32(); - - Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); - } - Assert.Multiple(() => - { - Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); - Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); - Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); - Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); - }); + CompareAgainstUnicorn(); } #endif } diff --git a/Ryujinx.Tests/Cpu/CpuTestAluRs.cs b/Ryujinx.Tests/Cpu/CpuTestAluRs.cs index b81f7100c9..418dd56d23 100644 --- a/Ryujinx.Tests/Cpu/CpuTestAluRs.cs +++ b/Ryujinx.Tests/Cpu/CpuTestAluRs.cs @@ -1,1910 +1,897 @@ -//#define AluRs - -using ChocolArm64.State; +#define AluRs using NUnit.Framework; namespace Ryujinx.Tests.Cpu { - using Tester; - using Tester.Types; - - [Category("AluRs"), Ignore("Tested: first half of 2018.")] + [Category("AluRs")] public sealed class CpuTestAluRs : CpuTest { #if AluRs - [SetUp] - public void SetupTester() + private const int RndCnt = 2; + private const int RndCntAmount = 2; + private const int RndCntLsb = 2; + + [Test, Pairwise, Description("ADC , , ")] + public void Adc_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xm, + [Values] bool carryIn) { - AArch64.TakeReset(false); + uint opcode = 0x9A000000; // ADC X0, X0, X0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xn, x2: xm, x31: x31, carry: carryIn); + + CompareAgainstUnicorn(); } - [Test, Description("ADC , , ")] - public void Adc_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, - [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(4)] ulong Xn, - [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(4)] ulong Xm, - [Values] bool CarryIn) - { - uint Opcode = 0x9A000000; // ADC X0, X0, X0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X31: _X31, Carry: CarryIn); - - if (Rd != 31) - { - Bits Op = new Bits(Opcode); - - AArch64.X((int)Rn, new Bits(Xn)); - AArch64.X((int)Rm, new Bits(Xm)); - Shared.PSTATE.C = CarryIn; - Base.Adc(Op[31], Op[20, 16], Op[9, 5], Op[4, 0]); - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); - } - } - - [Test, Description("ADC , , ")] - public void Adc_32bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("ADC , , ")] + public void Adc_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(4)] uint Wn, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wn, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(4)] uint Wm, - [Values] bool CarryIn) + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wm, + [Values] bool carryIn) { - uint Opcode = 0x1A000000; // ADC W0, W0, W0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + uint opcode = 0x1A000000; // ADC W0, W0, W0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); - uint _W31 = TestContext.CurrentContext.Random.NextUInt(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31, Carry: CarryIn); + uint w31 = TestContext.CurrentContext.Random.NextUInt(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31, carry: carryIn); - AArch64.X((int)Rn, new Bits(Wn)); - AArch64.X((int)Rm, new Bits(Wm)); - Shared.PSTATE.C = CarryIn; - Base.Adc(Op[31], Op[20, 16], Op[9, 5], Op[4, 0]); - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); - - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); - } + CompareAgainstUnicorn(); } - [Test, Description("ADCS , , ")] - public void Adcs_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("ADCS , , ")] + public void Adcs_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(4)] ulong Xn, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xn, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(4)] ulong Xm, - [Values] bool CarryIn) + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xm, + [Values] bool carryIn) { - uint Opcode = 0xBA000000; // ADCS X0, X0, X0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Bits Op = new Bits(Opcode); + uint opcode = 0xBA000000; // ADCS X0, X0, X0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X31: _X31, Carry: CarryIn); + ulong x31 = TestContext.CurrentContext.Random.NextULong(); - AArch64.X((int)Rn, new Bits(Xn)); - AArch64.X((int)Rm, new Bits(Xm)); - Shared.PSTATE.C = CarryIn; - Base.Adcs(Op[31], Op[20, 16], Op[9, 5], Op[4, 0]); - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + SingleOpcode(opcode, x1: xn, x2: xm, x31: x31, carry: carryIn); - if (Rd != 31) - { - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); - } - Assert.Multiple(() => - { - Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); - Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); - Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); - Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); - }); + CompareAgainstUnicorn(); } - [Test, Description("ADCS , , ")] - public void Adcs_32bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("ADCS , , ")] + public void Adcs_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(4)] uint Wn, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wn, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(4)] uint Wm, - [Values] bool CarryIn) + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wm, + [Values] bool carryIn) { - uint Opcode = 0x3A000000; // ADCS W0, W0, W0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Bits Op = new Bits(Opcode); + uint opcode = 0x3A000000; // ADCS W0, W0, W0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); - uint _W31 = TestContext.CurrentContext.Random.NextUInt(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31, Carry: CarryIn); + uint w31 = TestContext.CurrentContext.Random.NextUInt(); - AArch64.X((int)Rn, new Bits(Wn)); - AArch64.X((int)Rm, new Bits(Wm)); - Shared.PSTATE.C = CarryIn; - Base.Adcs(Op[31], Op[20, 16], Op[9, 5], Op[4, 0]); - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31, carry: carryIn); - if (Rd != 31) - { - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); - } - Assert.Multiple(() => - { - Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); - Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); - Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); - Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); - }); + CompareAgainstUnicorn(); } - [Test, Description("ADD , , {, #}")] - public void Add_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("ADD , , {, #}")] + public void Add_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xn, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xm, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xm, [Values(0b00u, 0b01u, 0b10u)] uint shift, // - [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, 1)] uint amount) + [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, RndCntAmount)] uint amount) { - uint Opcode = 0x8B000000; // ADD X0, X0, X0, LSL #0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + uint opcode = 0x8B000000; // ADD X0, X0, X0, LSL #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X31: _X31); + ulong x31 = TestContext.CurrentContext.Random.NextULong(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: xn, x2: xm, x31: x31); - AArch64.X((int)Rn, new Bits(Xn)); - AArch64.X((int)Rm, new Bits(Xm)); - Base.Add_Rs(Op[31], Op[23, 22], Op[20, 16], Op[15, 10], Op[9, 5], Op[4, 0]); - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); - } + CompareAgainstUnicorn(); } - [Test, Description("ADD , , {, #}")] - public void Add_32bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("ADD , , {, #}")] + public void Add_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wn, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wm, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wm, [Values(0b00u, 0b01u, 0b10u)] uint shift, // - [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, 1)] uint amount) + [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, RndCntAmount)] uint amount) { - uint Opcode = 0x0B000000; // ADD W0, W0, W0, LSL #0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + uint opcode = 0x0B000000; // ADD W0, W0, W0, LSL #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); - uint _W31 = TestContext.CurrentContext.Random.NextUInt(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31); + uint w31 = TestContext.CurrentContext.Random.NextUInt(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31); - AArch64.X((int)Rn, new Bits(Wn)); - AArch64.X((int)Rm, new Bits(Wm)); - Base.Add_Rs(Op[31], Op[23, 22], Op[20, 16], Op[15, 10], Op[9, 5], Op[4, 0]); - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); - - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); - } + CompareAgainstUnicorn(); } - [Test, Description("ADDS , , {, #}")] - public void Adds_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("ADDS , , {, #}")] + public void Adds_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xn, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xm, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xm, [Values(0b00u, 0b01u, 0b10u)] uint shift, // - [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, 1)] uint amount) + [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, RndCntAmount)] uint amount) { - uint Opcode = 0xAB000000; // ADDS X0, X0, X0, LSL #0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); - Bits Op = new Bits(Opcode); + uint opcode = 0xAB000000; // ADDS X0, X0, X0, LSL #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X31: _X31); + ulong x31 = TestContext.CurrentContext.Random.NextULong(); - AArch64.X((int)Rn, new Bits(Xn)); - AArch64.X((int)Rm, new Bits(Xm)); - Base.Adds_Rs(Op[31], Op[23, 22], Op[20, 16], Op[15, 10], Op[9, 5], Op[4, 0]); - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + SingleOpcode(opcode, x1: xn, x2: xm, x31: x31); - if (Rd != 31) - { - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); - } - Assert.Multiple(() => - { - Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); - Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); - Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); - Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); - }); + CompareAgainstUnicorn(); } - [Test, Description("ADDS , , {, #}")] - public void Adds_32bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("ADDS , , {, #}")] + public void Adds_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wn, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wm, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wm, [Values(0b00u, 0b01u, 0b10u)] uint shift, // - [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, 1)] uint amount) + [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, RndCntAmount)] uint amount) { - uint Opcode = 0x2B000000; // ADDS W0, W0, W0, LSL #0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); - Bits Op = new Bits(Opcode); + uint opcode = 0x2B000000; // ADDS W0, W0, W0, LSL #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); - uint _W31 = TestContext.CurrentContext.Random.NextUInt(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31); + uint w31 = TestContext.CurrentContext.Random.NextUInt(); - AArch64.X((int)Rn, new Bits(Wn)); - AArch64.X((int)Rm, new Bits(Wm)); - Base.Adds_Rs(Op[31], Op[23, 22], Op[20, 16], Op[15, 10], Op[9, 5], Op[4, 0]); - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31); - if (Rd != 31) - { - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); - } - Assert.Multiple(() => - { - Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); - Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); - Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); - Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); - }); + CompareAgainstUnicorn(); } - [Test, Description("AND , , {, #}")] - public void And_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("AND , , {, #}")] + public void And_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xn, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xm, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xm, [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint shift, // - [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, 1)] uint amount) + [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, RndCntAmount)] uint amount) { - uint Opcode = 0x8A000000; // AND X0, X0, X0, LSL #0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + uint opcode = 0x8A000000; // AND X0, X0, X0, LSL #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X31: _X31); + ulong x31 = TestContext.CurrentContext.Random.NextULong(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: xn, x2: xm, x31: x31); - AArch64.X((int)Rn, new Bits(Xn)); - AArch64.X((int)Rm, new Bits(Xm)); - Base.And_Rs(Op[31], Op[23, 22], Op[20, 16], Op[15, 10], Op[9, 5], Op[4, 0]); - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); - } + CompareAgainstUnicorn(); } - [Test, Description("AND , , {, #}")] - public void And_32bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("AND , , {, #}")] + public void And_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wn, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wm, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wm, [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint shift, // - [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, 1)] uint amount) + [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, RndCntAmount)] uint amount) { - uint Opcode = 0x0A000000; // AND W0, W0, W0, LSL #0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + uint opcode = 0x0A000000; // AND W0, W0, W0, LSL #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); - uint _W31 = TestContext.CurrentContext.Random.NextUInt(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31); + uint w31 = TestContext.CurrentContext.Random.NextUInt(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31); - AArch64.X((int)Rn, new Bits(Wn)); - AArch64.X((int)Rm, new Bits(Wm)); - Base.And_Rs(Op[31], Op[23, 22], Op[20, 16], Op[15, 10], Op[9, 5], Op[4, 0]); - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); - - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); - } + CompareAgainstUnicorn(); } - [Test, Description("ANDS , , {, #}")] - public void Ands_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("ANDS , , {, #}")] + public void Ands_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xn, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xm, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xm, [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint shift, // - [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, 1)] uint amount) + [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, RndCntAmount)] uint amount) { - uint Opcode = 0xEA000000; // ANDS X0, X0, X0, LSL #0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); - Bits Op = new Bits(Opcode); + uint opcode = 0xEA000000; // ANDS X0, X0, X0, LSL #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X31: _X31); + ulong x31 = TestContext.CurrentContext.Random.NextULong(); - AArch64.X((int)Rn, new Bits(Xn)); - AArch64.X((int)Rm, new Bits(Xm)); - Base.Ands_Rs(Op[31], Op[23, 22], Op[20, 16], Op[15, 10], Op[9, 5], Op[4, 0]); - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + SingleOpcode(opcode, x1: xn, x2: xm, x31: x31); - if (Rd != 31) - { - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); - } - Assert.Multiple(() => - { - Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); - Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); - Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); - Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); - }); + CompareAgainstUnicorn(); } - [Test, Description("ANDS , , {, #}")] - public void Ands_32bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("ANDS , , {, #}")] + public void Ands_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wn, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wm, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wm, [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint shift, // - [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, 1)] uint amount) + [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, RndCntAmount)] uint amount) { - uint Opcode = 0x6A000000; // ANDS W0, W0, W0, LSL #0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); - Bits Op = new Bits(Opcode); + uint opcode = 0x6A000000; // ANDS W0, W0, W0, LSL #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); - uint _W31 = TestContext.CurrentContext.Random.NextUInt(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31); + uint w31 = TestContext.CurrentContext.Random.NextUInt(); - AArch64.X((int)Rn, new Bits(Wn)); - AArch64.X((int)Rm, new Bits(Wm)); - Base.Ands_Rs(Op[31], Op[23, 22], Op[20, 16], Op[15, 10], Op[9, 5], Op[4, 0]); - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31); - if (Rd != 31) - { - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); - } - Assert.Multiple(() => - { - Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); - Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); - Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); - Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); - }); + CompareAgainstUnicorn(); } - [Test, Description("ASRV , , ")] - public void Asrv_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("ASRV , , ")] + public void Asrv_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(8)] ulong Xn, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xn, [Values(0ul, 31ul, 32ul, 63ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(5)] ulong Xm) + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xm) { - uint Opcode = 0x9AC02800; // ASRV X0, X0, X0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + uint opcode = 0x9AC02800; // ASRV X0, X0, X0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X31: _X31); + ulong x31 = TestContext.CurrentContext.Random.NextULong(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: xn, x2: xm, x31: x31); - AArch64.X((int)Rn, new Bits(Xn)); - AArch64.X((int)Rm, new Bits(Xm)); - Base.Asrv(Op[31], Op[20, 16], Op[9, 5], Op[4, 0]); - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); - } + CompareAgainstUnicorn(); } - [Test, Description("ASRV , , ")] - public void Asrv_32bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("ASRV , , ")] + public void Asrv_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(8)] uint Wn, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wn, [Values(0u, 15u, 16u, 31u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(5)] uint Wm) + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wm) { - uint Opcode = 0x1AC02800; // ASRV W0, W0, W0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + uint opcode = 0x1AC02800; // ASRV W0, W0, W0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); - uint _W31 = TestContext.CurrentContext.Random.NextUInt(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31); + uint w31 = TestContext.CurrentContext.Random.NextUInt(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31); - AArch64.X((int)Rn, new Bits(Wn)); - AArch64.X((int)Rm, new Bits(Wm)); - Base.Asrv(Op[31], Op[20, 16], Op[9, 5], Op[4, 0]); - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); - - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); - } + CompareAgainstUnicorn(); } - [Test, Description("BIC , , {, #}")] - public void Bic_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("BIC , , {, #}")] + public void Bic_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xn, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xm, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xm, [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint shift, // - [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, 1)] uint amount) + [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, RndCntAmount)] uint amount) { - uint Opcode = 0x8A200000; // BIC X0, X0, X0, LSL #0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + uint opcode = 0x8A200000; // BIC X0, X0, X0, LSL #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X31: _X31); + ulong x31 = TestContext.CurrentContext.Random.NextULong(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: xn, x2: xm, x31: x31); - AArch64.X((int)Rn, new Bits(Xn)); - AArch64.X((int)Rm, new Bits(Xm)); - Base.Bic(Op[31], Op[23, 22], Op[20, 16], Op[15, 10], Op[9, 5], Op[4, 0]); - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); - } + CompareAgainstUnicorn(); } - [Test, Description("BIC , , {, #}")] - public void Bic_32bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("BIC , , {, #}")] + public void Bic_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wn, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wm, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wm, [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint shift, // - [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, 1)] uint amount) + [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, RndCntAmount)] uint amount) { - uint Opcode = 0x0A200000; // BIC W0, W0, W0, LSL #0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + uint opcode = 0x0A200000; // BIC W0, W0, W0, LSL #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); - uint _W31 = TestContext.CurrentContext.Random.NextUInt(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31); + uint w31 = TestContext.CurrentContext.Random.NextUInt(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31); - AArch64.X((int)Rn, new Bits(Wn)); - AArch64.X((int)Rm, new Bits(Wm)); - Base.Bic(Op[31], Op[23, 22], Op[20, 16], Op[15, 10], Op[9, 5], Op[4, 0]); - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); - - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); - } + CompareAgainstUnicorn(); } - [Test, Description("BICS , , {, #}")] - public void Bics_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("BICS , , {, #}")] + public void Bics_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xn, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xm, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xm, [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint shift, // - [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, 1)] uint amount) + [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, RndCntAmount)] uint amount) { - uint Opcode = 0xEA200000; // BICS X0, X0, X0, LSL #0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); - Bits Op = new Bits(Opcode); + uint opcode = 0xEA200000; // BICS X0, X0, X0, LSL #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X31: _X31); + ulong x31 = TestContext.CurrentContext.Random.NextULong(); - AArch64.X((int)Rn, new Bits(Xn)); - AArch64.X((int)Rm, new Bits(Xm)); - Base.Bics(Op[31], Op[23, 22], Op[20, 16], Op[15, 10], Op[9, 5], Op[4, 0]); - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + SingleOpcode(opcode, x1: xn, x2: xm, x31: x31); - if (Rd != 31) - { - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); - } - Assert.Multiple(() => - { - Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); - Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); - Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); - Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); - }); + CompareAgainstUnicorn(); } - [Test, Description("BICS , , {, #}")] - public void Bics_32bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("BICS , , {, #}")] + public void Bics_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wn, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wm, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wm, [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint shift, // - [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, 1)] uint amount) + [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, RndCntAmount)] uint amount) { - uint Opcode = 0x6A200000; // BICS W0, W0, W0, LSL #0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); - Bits Op = new Bits(Opcode); + uint opcode = 0x6A200000; // BICS W0, W0, W0, LSL #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); - uint _W31 = TestContext.CurrentContext.Random.NextUInt(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31); + uint w31 = TestContext.CurrentContext.Random.NextUInt(); - AArch64.X((int)Rn, new Bits(Wn)); - AArch64.X((int)Rm, new Bits(Wm)); - Base.Bics(Op[31], Op[23, 22], Op[20, 16], Op[15, 10], Op[9, 5], Op[4, 0]); - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31); - if (Rd != 31) - { - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); - } - Assert.Multiple(() => - { - Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); - Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); - Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); - Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); - }); + CompareAgainstUnicorn(); } - [Test, Description("CRC32X , , ")] - public void Crc32x([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, - [Values(0x00000000u, 0xFFFFFFFFu)] [Random(2)] uint Wn, - [Values((ulong)0x00_00_00_00_00_00_00_00, - (ulong)0x7F_FF_FF_FF_FF_FF_FF_FF, - (ulong)0x80_00_00_00_00_00_00_00, - (ulong)0xFF_FF_FF_FF_FF_FF_FF_FF)] [Random(64)] ulong Xm) - { - uint Opcode = 0x9AC04C00; // CRC32X W0, W0, X0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - - uint _W31 = TestContext.CurrentContext.Random.NextUInt(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Xm, X31: _W31); - - if (Rd != 31) - { - Bits Op = new Bits(Opcode); - - AArch64.X((int)Rn, new Bits(Wn)); - AArch64.X((int)Rm, new Bits(Xm)); - Base.Crc32(Op[31], Op[20, 16], Op[11, 10], Op[9, 5], Op[4, 0]); - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); - - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); - } - } - - [Test, Description("CRC32W , , ")] - public void Crc32w([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, - [Values(0x00000000u, 0xFFFFFFFFu)] [Random(2)] uint Wn, - [Values((uint)0x00_00_00_00, (uint)0x7F_FF_FF_FF, - (uint)0x80_00_00_00, (uint)0xFF_FF_FF_FF)] [Random(64)] uint Wm) - { - uint Opcode = 0x1AC04800; // CRC32W W0, W0, W0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - - uint _W31 = TestContext.CurrentContext.Random.NextUInt(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31); - - if (Rd != 31) - { - Bits Op = new Bits(Opcode); - - AArch64.X((int)Rn, new Bits(Wn)); - AArch64.X((int)Rm, new Bits(Wm)); - Base.Crc32(Op[31], Op[20, 16], Op[11, 10], Op[9, 5], Op[4, 0]); - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); - - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); - } - } - - [Test, Description("CRC32H , , ")] - public void Crc32h([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, - [Values(0x00000000u, 0xFFFFFFFFu)] [Random(2)] uint Wn, - [Values((ushort)0x00_00, (ushort)0x7F_FF, - (ushort)0x80_00, (ushort)0xFF_FF)] [Random(64)] ushort Wm) - { - uint Opcode = 0x1AC04400; // CRC32H W0, W0, W0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - - uint _W31 = TestContext.CurrentContext.Random.NextUInt(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31); - - if (Rd != 31) - { - Bits Op = new Bits(Opcode); - - AArch64.X((int)Rn, new Bits(Wn)); - AArch64.X((int)Rm, new Bits(Wm)); - Base.Crc32(Op[31], Op[20, 16], Op[11, 10], Op[9, 5], Op[4, 0]); - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); - - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); - } - } - - [Test, Description("CRC32B , , ")] - public void Crc32b([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, - [Values(0x00000000u, 0xFFFFFFFFu)] [Random(2)] uint Wn, - [Values((byte)0x00, (byte)0x7F, - (byte)0x80, (byte)0xFF)] [Random(64)] byte Wm) - { - uint Opcode = 0x1AC04000; // CRC32B W0, W0, W0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - - uint _W31 = TestContext.CurrentContext.Random.NextUInt(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31); - - if (Rd != 31) - { - Bits Op = new Bits(Opcode); - - AArch64.X((int)Rn, new Bits(Wn)); - AArch64.X((int)Rm, new Bits(Wm)); - Base.Crc32(Op[31], Op[20, 16], Op[11, 10], Op[9, 5], Op[4, 0]); - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); - - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); - } - } - - [Test, Description("CRC32CX , , ")] - public void Crc32cx([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, - [Values(0x00000000u, 0xFFFFFFFFu)] [Random(2)] uint Wn, - [Values((ulong)0x00_00_00_00_00_00_00_00, - (ulong)0x7F_FF_FF_FF_FF_FF_FF_FF, - (ulong)0x80_00_00_00_00_00_00_00, - (ulong)0xFF_FF_FF_FF_FF_FF_FF_FF)] [Random(64)] ulong Xm) - { - uint Opcode = 0x9AC05C00; // CRC32CX W0, W0, X0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - - uint _W31 = TestContext.CurrentContext.Random.NextUInt(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Xm, X31: _W31); - - if (Rd != 31) - { - Bits Op = new Bits(Opcode); - - AArch64.X((int)Rn, new Bits(Wn)); - AArch64.X((int)Rm, new Bits(Xm)); - Base.Crc32c(Op[31], Op[20, 16], Op[11, 10], Op[9, 5], Op[4, 0]); - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); - - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); - } - } - - [Test, Description("CRC32CW , , ")] - public void Crc32cw([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, - [Values(0x00000000u, 0xFFFFFFFFu)] [Random(2)] uint Wn, - [Values((uint)0x00_00_00_00, (uint)0x7F_FF_FF_FF, - (uint)0x80_00_00_00, (uint)0xFF_FF_FF_FF)] [Random(64)] uint Wm) - { - uint Opcode = 0x1AC05800; // CRC32CW W0, W0, W0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - - uint _W31 = TestContext.CurrentContext.Random.NextUInt(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31); - - if (Rd != 31) - { - Bits Op = new Bits(Opcode); - - AArch64.X((int)Rn, new Bits(Wn)); - AArch64.X((int)Rm, new Bits(Wm)); - Base.Crc32c(Op[31], Op[20, 16], Op[11, 10], Op[9, 5], Op[4, 0]); - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); - - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); - } - } - - [Test, Description("CRC32CH , , ")] - public void Crc32ch([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, - [Values(0x00000000u, 0xFFFFFFFFu)] [Random(2)] uint Wn, - [Values((ushort)0x00_00, (ushort)0x7F_FF, - (ushort)0x80_00, (ushort)0xFF_FF)] [Random(64)] ushort Wm) - { - uint Opcode = 0x1AC05400; // CRC32CH W0, W0, W0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - - uint _W31 = TestContext.CurrentContext.Random.NextUInt(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31); - - if (Rd != 31) - { - Bits Op = new Bits(Opcode); - - AArch64.X((int)Rn, new Bits(Wn)); - AArch64.X((int)Rm, new Bits(Wm)); - Base.Crc32c(Op[31], Op[20, 16], Op[11, 10], Op[9, 5], Op[4, 0]); - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); - - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); - } - } - - [Test, Description("CRC32CB , , ")] - public void Crc32cb([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, - [Values(0x00000000u, 0xFFFFFFFFu)] [Random(2)] uint Wn, - [Values((byte)0x00, (byte)0x7F, - (byte)0x80, (byte)0xFF)] [Random(64)] byte Wm) - { - uint Opcode = 0x1AC05000; // CRC32CB W0, W0, W0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - - uint _W31 = TestContext.CurrentContext.Random.NextUInt(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31); - - if (Rd != 31) - { - Bits Op = new Bits(Opcode); - - AArch64.X((int)Rn, new Bits(Wn)); - AArch64.X((int)Rm, new Bits(Wm)); - Base.Crc32c(Op[31], Op[20, 16], Op[11, 10], Op[9, 5], Op[4, 0]); - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); - - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); - } - } - - [Test, Description("EON , , {, #}")] - public void Eon_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("EON , , {, #}")] + public void Eon_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xn, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xm, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xm, [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint shift, // - [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, 1)] uint amount) + [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, RndCntAmount)] uint amount) { - uint Opcode = 0xCA200000; // EON X0, X0, X0, LSL #0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + uint opcode = 0xCA200000; // EON X0, X0, X0, LSL #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X31: _X31); + ulong x31 = TestContext.CurrentContext.Random.NextULong(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: xn, x2: xm, x31: x31); - AArch64.X((int)Rn, new Bits(Xn)); - AArch64.X((int)Rm, new Bits(Xm)); - Base.Eon(Op[31], Op[23, 22], Op[20, 16], Op[15, 10], Op[9, 5], Op[4, 0]); - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); - } + CompareAgainstUnicorn(); } - [Test, Description("EON , , {, #}")] - public void Eon_32bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("EON , , {, #}")] + public void Eon_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wn, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wm, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wm, [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint shift, // - [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, 1)] uint amount) + [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, RndCntAmount)] uint amount) { - uint Opcode = 0x4A200000; // EON W0, W0, W0, LSL #0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + uint opcode = 0x4A200000; // EON W0, W0, W0, LSL #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); - uint _W31 = TestContext.CurrentContext.Random.NextUInt(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31); + uint w31 = TestContext.CurrentContext.Random.NextUInt(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31); - AArch64.X((int)Rn, new Bits(Wn)); - AArch64.X((int)Rm, new Bits(Wm)); - Base.Eon(Op[31], Op[23, 22], Op[20, 16], Op[15, 10], Op[9, 5], Op[4, 0]); - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); - - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); - } + CompareAgainstUnicorn(); } - [Test, Description("EOR , , {, #}")] - public void Eor_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("EOR , , {, #}")] + public void Eor_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xn, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xm, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xm, [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint shift, // - [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, 1)] uint amount) + [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, RndCntAmount)] uint amount) { - uint Opcode = 0xCA000000; // EOR X0, X0, X0, LSL #0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + uint opcode = 0xCA000000; // EOR X0, X0, X0, LSL #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X31: _X31); + ulong x31 = TestContext.CurrentContext.Random.NextULong(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: xn, x2: xm, x31: x31); - AArch64.X((int)Rn, new Bits(Xn)); - AArch64.X((int)Rm, new Bits(Xm)); - Base.Eor_Rs(Op[31], Op[23, 22], Op[20, 16], Op[15, 10], Op[9, 5], Op[4, 0]); - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); - } + CompareAgainstUnicorn(); } - [Test, Description("EOR , , {, #}")] - public void Eor_32bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("EOR , , {, #}")] + public void Eor_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wn, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wm, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wm, [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint shift, // - [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, 1)] uint amount) + [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, RndCntAmount)] uint amount) { - uint Opcode = 0x4A000000; // EOR W0, W0, W0, LSL #0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + uint opcode = 0x4A000000; // EOR W0, W0, W0, LSL #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); - uint _W31 = TestContext.CurrentContext.Random.NextUInt(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31); + uint w31 = TestContext.CurrentContext.Random.NextUInt(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31); - AArch64.X((int)Rn, new Bits(Wn)); - AArch64.X((int)Rm, new Bits(Wm)); - Base.Eor_Rs(Op[31], Op[23, 22], Op[20, 16], Op[15, 10], Op[9, 5], Op[4, 0]); - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); - - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); - } + CompareAgainstUnicorn(); } - [Test, Description("EXTR , , , #")] - public void Extr_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("EXTR , , , #")] + public void Extr_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(2)] ulong Xn, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xn, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(2)] ulong Xm, - [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, 2)] uint lsb) + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xm, + [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, RndCntLsb)] uint lsb) { - uint Opcode = 0x93C00000; // EXTR X0, X0, X0, #0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((lsb & 63) << 10); + uint opcode = 0x93C00000; // EXTR X0, X0, X0, #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((lsb & 63) << 10); - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X31: _X31); + ulong x31 = TestContext.CurrentContext.Random.NextULong(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: xn, x2: xm, x31: x31); - AArch64.X((int)Rn, new Bits(Xn)); - AArch64.X((int)Rm, new Bits(Xm)); - Base.Extr(Op[31], Op[22], Op[20, 16], Op[15, 10], Op[9, 5], Op[4, 0]); - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); - } + CompareAgainstUnicorn(); } - [Test, Description("EXTR , , , #")] - public void Extr_32bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("EXTR , , , #")] + public void Extr_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(2)] uint Wn, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wn, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(2)] uint Wm, - [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, 2)] uint lsb) + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wm, + [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, RndCntLsb)] uint lsb) { - uint Opcode = 0x13800000; // EXTR W0, W0, W0, #0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((lsb & 63) << 10); + uint opcode = 0x13800000; // EXTR W0, W0, W0, #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((lsb & 63) << 10); - uint _W31 = TestContext.CurrentContext.Random.NextUInt(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31); + uint w31 = TestContext.CurrentContext.Random.NextUInt(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31); - AArch64.X((int)Rn, new Bits(Wn)); - AArch64.X((int)Rm, new Bits(Wm)); - Base.Extr(Op[31], Op[22], Op[20, 16], Op[15, 10], Op[9, 5], Op[4, 0]); - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); - - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); - } + CompareAgainstUnicorn(); } - [Test, Description("LSLV , , ")] - public void Lslv_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("LSLV , , ")] + public void Lslv_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(8)] ulong Xn, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xn, [Values(0ul, 31ul, 32ul, 63ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(5)] ulong Xm) + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xm) { - uint Opcode = 0x9AC02000; // LSLV X0, X0, X0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + uint opcode = 0x9AC02000; // LSLV X0, X0, X0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X31: _X31); + ulong x31 = TestContext.CurrentContext.Random.NextULong(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: xn, x2: xm, x31: x31); - AArch64.X((int)Rn, new Bits(Xn)); - AArch64.X((int)Rm, new Bits(Xm)); - Base.Lslv(Op[31], Op[20, 16], Op[9, 5], Op[4, 0]); - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); - } + CompareAgainstUnicorn(); } - [Test, Description("LSLV , , ")] - public void Lslv_32bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("LSLV , , ")] + public void Lslv_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(8)] uint Wn, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wn, [Values(0u, 15u, 16u, 31u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(5)] uint Wm) + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wm) { - uint Opcode = 0x1AC02000; // LSLV W0, W0, W0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + uint opcode = 0x1AC02000; // LSLV W0, W0, W0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); - uint _W31 = TestContext.CurrentContext.Random.NextUInt(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31); + uint w31 = TestContext.CurrentContext.Random.NextUInt(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31); - AArch64.X((int)Rn, new Bits(Wn)); - AArch64.X((int)Rm, new Bits(Wm)); - Base.Lslv(Op[31], Op[20, 16], Op[9, 5], Op[4, 0]); - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); - - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); - } + CompareAgainstUnicorn(); } - [Test, Description("LSRV , , ")] - public void Lsrv_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("LSRV , , ")] + public void Lsrv_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(8)] ulong Xn, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xn, [Values(0ul, 31ul, 32ul, 63ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(5)] ulong Xm) + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xm) { - uint Opcode = 0x9AC02400; // LSRV X0, X0, X0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + uint opcode = 0x9AC02400; // LSRV X0, X0, X0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X31: _X31); + ulong x31 = TestContext.CurrentContext.Random.NextULong(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: xn, x2: xm, x31: x31); - AArch64.X((int)Rn, new Bits(Xn)); - AArch64.X((int)Rm, new Bits(Xm)); - Base.Lsrv(Op[31], Op[20, 16], Op[9, 5], Op[4, 0]); - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); - } + CompareAgainstUnicorn(); } - [Test, Description("LSRV , , ")] - public void Lsrv_32bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("LSRV , , ")] + public void Lsrv_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(8)] uint Wn, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wn, [Values(0u, 15u, 16u, 31u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(5)] uint Wm) + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wm) { - uint Opcode = 0x1AC02400; // LSRV W0, W0, W0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + uint opcode = 0x1AC02400; // LSRV W0, W0, W0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); - uint _W31 = TestContext.CurrentContext.Random.NextUInt(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31); + uint w31 = TestContext.CurrentContext.Random.NextUInt(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31); - AArch64.X((int)Rn, new Bits(Wn)); - AArch64.X((int)Rm, new Bits(Wm)); - Base.Lsrv(Op[31], Op[20, 16], Op[9, 5], Op[4, 0]); - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); - - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); - } + CompareAgainstUnicorn(); } - [Test, Description("ORN , , {, #}")] - public void Orn_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("ORN , , {, #}")] + public void Orn_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xn, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xm, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xm, [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint shift, // - [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, 1)] uint amount) + [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, RndCntAmount)] uint amount) { - uint Opcode = 0xAA200000; // ORN X0, X0, X0, LSL #0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + uint opcode = 0xAA200000; // ORN X0, X0, X0, LSL #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X31: _X31); + ulong x31 = TestContext.CurrentContext.Random.NextULong(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: xn, x2: xm, x31: x31); - AArch64.X((int)Rn, new Bits(Xn)); - AArch64.X((int)Rm, new Bits(Xm)); - Base.Orn(Op[31], Op[23, 22], Op[20, 16], Op[15, 10], Op[9, 5], Op[4, 0]); - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); - } + CompareAgainstUnicorn(); } - [Test, Description("ORN , , {, #}")] - public void Orn_32bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("ORN , , {, #}")] + public void Orn_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wn, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wm, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wm, [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint shift, // - [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, 1)] uint amount) + [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, RndCntAmount)] uint amount) { - uint Opcode = 0x2A200000; // ORN W0, W0, W0, LSL #0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + uint opcode = 0x2A200000; // ORN W0, W0, W0, LSL #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); - uint _W31 = TestContext.CurrentContext.Random.NextUInt(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31); + uint w31 = TestContext.CurrentContext.Random.NextUInt(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31); - AArch64.X((int)Rn, new Bits(Wn)); - AArch64.X((int)Rm, new Bits(Wm)); - Base.Orn(Op[31], Op[23, 22], Op[20, 16], Op[15, 10], Op[9, 5], Op[4, 0]); - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); - - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); - } + CompareAgainstUnicorn(); } - [Test, Description("ORR , , {, #}")] - public void Orr_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("ORR , , {, #}")] + public void Orr_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xn, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xm, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xm, [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint shift, // - [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, 1)] uint amount) + [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, RndCntAmount)] uint amount) { - uint Opcode = 0xAA000000; // ORR X0, X0, X0, LSL #0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + uint opcode = 0xAA000000; // ORR X0, X0, X0, LSL #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X31: _X31); + ulong x31 = TestContext.CurrentContext.Random.NextULong(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: xn, x2: xm, x31: x31); - AArch64.X((int)Rn, new Bits(Xn)); - AArch64.X((int)Rm, new Bits(Xm)); - Base.Orr_Rs(Op[31], Op[23, 22], Op[20, 16], Op[15, 10], Op[9, 5], Op[4, 0]); - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); - } + CompareAgainstUnicorn(); } - [Test, Description("ORR , , {, #}")] - public void Orr_32bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("ORR , , {, #}")] + public void Orr_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wn, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wm, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wm, [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint shift, // - [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, 1)] uint amount) + [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, RndCntAmount)] uint amount) { - uint Opcode = 0x2A000000; // ORR W0, W0, W0, LSL #0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + uint opcode = 0x2A000000; // ORR W0, W0, W0, LSL #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); - uint _W31 = TestContext.CurrentContext.Random.NextUInt(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31); + uint w31 = TestContext.CurrentContext.Random.NextUInt(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31); - AArch64.X((int)Rn, new Bits(Wn)); - AArch64.X((int)Rm, new Bits(Wm)); - Base.Orr_Rs(Op[31], Op[23, 22], Op[20, 16], Op[15, 10], Op[9, 5], Op[4, 0]); - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); - - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); - } + CompareAgainstUnicorn(); } - [Test, Description("RORV , , ")] - public void Rorv_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("RORV , , ")] + public void Rorv_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(8)] ulong Xn, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xn, [Values(0ul, 31ul, 32ul, 63ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(5)] ulong Xm) + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xm) { - uint Opcode = 0x9AC02C00; // RORV X0, X0, X0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + uint opcode = 0x9AC02C00; // RORV X0, X0, X0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X31: _X31); + ulong x31 = TestContext.CurrentContext.Random.NextULong(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: xn, x2: xm, x31: x31); - AArch64.X((int)Rn, new Bits(Xn)); - AArch64.X((int)Rm, new Bits(Xm)); - Base.Rorv(Op[31], Op[20, 16], Op[9, 5], Op[4, 0]); - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); - } + CompareAgainstUnicorn(); } - [Test, Description("RORV , , ")] - public void Rorv_32bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("RORV , , ")] + public void Rorv_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(8)] uint Wn, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wn, [Values(0u, 15u, 16u, 31u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(5)] uint Wm) + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wm) { - uint Opcode = 0x1AC02C00; // RORV W0, W0, W0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + uint opcode = 0x1AC02C00; // RORV W0, W0, W0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); - uint _W31 = TestContext.CurrentContext.Random.NextUInt(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31); + uint w31 = TestContext.CurrentContext.Random.NextUInt(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31); - AArch64.X((int)Rn, new Bits(Wn)); - AArch64.X((int)Rm, new Bits(Wm)); - Base.Rorv(Op[31], Op[20, 16], Op[9, 5], Op[4, 0]); - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); - - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); - } + CompareAgainstUnicorn(); } - [Test, Description("SBC , , ")] - public void Sbc_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("SBC , , ")] + public void Sbc_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(4)] ulong Xn, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xn, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(4)] ulong Xm, - [Values] bool CarryIn) + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xm, + [Values] bool carryIn) { - uint Opcode = 0xDA000000; // SBC X0, X0, X0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + uint opcode = 0xDA000000; // SBC X0, X0, X0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X31: _X31, Carry: CarryIn); + ulong x31 = TestContext.CurrentContext.Random.NextULong(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: xn, x2: xm, x31: x31, carry: carryIn); - AArch64.X((int)Rn, new Bits(Xn)); - AArch64.X((int)Rm, new Bits(Xm)); - Shared.PSTATE.C = CarryIn; - Base.Sbc(Op[31], Op[20, 16], Op[9, 5], Op[4, 0]); - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); - } + CompareAgainstUnicorn(); } - [Test, Description("SBC , , ")] - public void Sbc_32bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("SBC , , ")] + public void Sbc_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(4)] uint Wn, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wn, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(4)] uint Wm, - [Values] bool CarryIn) + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wm, + [Values] bool carryIn) { - uint Opcode = 0x5A000000; // SBC W0, W0, W0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + uint opcode = 0x5A000000; // SBC W0, W0, W0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); - uint _W31 = TestContext.CurrentContext.Random.NextUInt(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31, Carry: CarryIn); + uint w31 = TestContext.CurrentContext.Random.NextUInt(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31, carry: carryIn); - AArch64.X((int)Rn, new Bits(Wn)); - AArch64.X((int)Rm, new Bits(Wm)); - Shared.PSTATE.C = CarryIn; - Base.Sbc(Op[31], Op[20, 16], Op[9, 5], Op[4, 0]); - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); - - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); - } + CompareAgainstUnicorn(); } - [Test, Description("SBCS , , ")] - public void Sbcs_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("SBCS , , ")] + public void Sbcs_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(4)] ulong Xn, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xn, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(4)] ulong Xm, - [Values] bool CarryIn) + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xm, + [Values] bool carryIn) { - uint Opcode = 0xFA000000; // SBCS X0, X0, X0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Bits Op = new Bits(Opcode); + uint opcode = 0xFA000000; // SBCS X0, X0, X0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X31: _X31, Carry: CarryIn); + ulong x31 = TestContext.CurrentContext.Random.NextULong(); - AArch64.X((int)Rn, new Bits(Xn)); - AArch64.X((int)Rm, new Bits(Xm)); - Shared.PSTATE.C = CarryIn; - Base.Sbcs(Op[31], Op[20, 16], Op[9, 5], Op[4, 0]); - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + SingleOpcode(opcode, x1: xn, x2: xm, x31: x31, carry: carryIn); - if (Rd != 31) - { - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); - } - Assert.Multiple(() => - { - Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); - Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); - Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); - Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); - }); + CompareAgainstUnicorn(); } - [Test, Description("SBCS , , ")] - public void Sbcs_32bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("SBCS , , ")] + public void Sbcs_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(4)] uint Wn, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wn, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(4)] uint Wm, - [Values] bool CarryIn) + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wm, + [Values] bool carryIn) { - uint Opcode = 0x7A000000; // SBCS W0, W0, W0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Bits Op = new Bits(Opcode); + uint opcode = 0x7A000000; // SBCS W0, W0, W0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); - uint _W31 = TestContext.CurrentContext.Random.NextUInt(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31, Carry: CarryIn); + uint w31 = TestContext.CurrentContext.Random.NextUInt(); - AArch64.X((int)Rn, new Bits(Wn)); - AArch64.X((int)Rm, new Bits(Wm)); - Shared.PSTATE.C = CarryIn; - Base.Sbcs(Op[31], Op[20, 16], Op[9, 5], Op[4, 0]); - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31, carry: carryIn); - if (Rd != 31) - { - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); - } - Assert.Multiple(() => - { - Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); - Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); - Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); - Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); - }); + CompareAgainstUnicorn(); } - [Test, Description("SDIV , , ")] - public void Sdiv_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, - [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(8)] ulong Xn, - [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(8)] ulong Xm) - { - uint Opcode = 0x9AC00C00; // SDIV X0, X0, X0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X31: _X31); - - if (Rd != 31) - { - Bits Op = new Bits(Opcode); - - AArch64.X((int)Rn, new Bits(Xn)); - AArch64.X((int)Rm, new Bits(Xm)); - Base.Sdiv(Op[31], Op[20, 16], Op[9, 5], Op[4, 0]); - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); - } - } - - [Test, Description("SDIV , , ")] - public void Sdiv_32bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, - [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(8)] uint Wn, - [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(8)] uint Wm) - { - uint Opcode = 0x1AC00C00; // SDIV W0, W0, W0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - - uint _W31 = TestContext.CurrentContext.Random.NextUInt(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31); - - if (Rd != 31) - { - Bits Op = new Bits(Opcode); - - AArch64.X((int)Rn, new Bits(Wn)); - AArch64.X((int)Rm, new Bits(Wm)); - Base.Sdiv(Op[31], Op[20, 16], Op[9, 5], Op[4, 0]); - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); - - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); - } - } - - [Test, Description("SUB , , {, #}")] - public void Sub_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("SUB , , {, #}")] + public void Sub_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xn, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xm, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xm, [Values(0b00u, 0b01u, 0b10u)] uint shift, // - [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, 1)] uint amount) + [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, RndCntAmount)] uint amount) { - uint Opcode = 0xCB000000; // SUB X0, X0, X0, LSL #0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + uint opcode = 0xCB000000; // SUB X0, X0, X0, LSL #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X31: _X31); + ulong x31 = TestContext.CurrentContext.Random.NextULong(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: xn, x2: xm, x31: x31); - AArch64.X((int)Rn, new Bits(Xn)); - AArch64.X((int)Rm, new Bits(Xm)); - Base.Sub_Rs(Op[31], Op[23, 22], Op[20, 16], Op[15, 10], Op[9, 5], Op[4, 0]); - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); - } + CompareAgainstUnicorn(); } - [Test, Description("SUB , , {, #}")] - public void Sub_32bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("SUB , , {, #}")] + public void Sub_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wn, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wm, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wm, [Values(0b00u, 0b01u, 0b10u)] uint shift, // - [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, 1)] uint amount) + [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, RndCntAmount)] uint amount) { - uint Opcode = 0x4B000000; // SUB W0, W0, W0, LSL #0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + uint opcode = 0x4B000000; // SUB W0, W0, W0, LSL #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); - uint _W31 = TestContext.CurrentContext.Random.NextUInt(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31); + uint w31 = TestContext.CurrentContext.Random.NextUInt(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31); - AArch64.X((int)Rn, new Bits(Wn)); - AArch64.X((int)Rm, new Bits(Wm)); - Base.Sub_Rs(Op[31], Op[23, 22], Op[20, 16], Op[15, 10], Op[9, 5], Op[4, 0]); - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); - - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); - } + CompareAgainstUnicorn(); } - [Test, Description("SUBS , , {, #}")] - public void Subs_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("SUBS , , {, #}")] + public void Subs_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xn, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xm, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xm, [Values(0b00u, 0b01u, 0b10u)] uint shift, // - [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, 1)] uint amount) + [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, RndCntAmount)] uint amount) { - uint Opcode = 0xEB000000; // SUBS X0, X0, X0, LSL #0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); - Bits Op = new Bits(Opcode); + uint opcode = 0xEB000000; // SUBS X0, X0, X0, LSL #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X31: _X31); + ulong x31 = TestContext.CurrentContext.Random.NextULong(); - AArch64.X((int)Rn, new Bits(Xn)); - AArch64.X((int)Rm, new Bits(Xm)); - Base.Subs_Rs(Op[31], Op[23, 22], Op[20, 16], Op[15, 10], Op[9, 5], Op[4, 0]); - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); + SingleOpcode(opcode, x1: xn, x2: xm, x31: x31); - if (Rd != 31) - { - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); - } - Assert.Multiple(() => - { - Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); - Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); - Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); - Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); - }); + CompareAgainstUnicorn(); } - [Test, Description("SUBS , , {, #}")] - public void Subs_32bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("SUBS , , {, #}")] + public void Subs_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wn, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wm, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wm, [Values(0b00u, 0b01u, 0b10u)] uint shift, // - [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, 1)] uint amount) + [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, RndCntAmount)] uint amount) { - uint Opcode = 0x6B000000; // SUBS W0, W0, W0, LSL #0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); - Bits Op = new Bits(Opcode); + uint opcode = 0x6B000000; // SUBS W0, W0, W0, LSL #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); - uint _W31 = TestContext.CurrentContext.Random.NextUInt(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31); + uint w31 = TestContext.CurrentContext.Random.NextUInt(); - AArch64.X((int)Rn, new Bits(Wn)); - AArch64.X((int)Rm, new Bits(Wm)); - Base.Subs_Rs(Op[31], Op[23, 22], Op[20, 16], Op[15, 10], Op[9, 5], Op[4, 0]); - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31); - if (Rd != 31) - { - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); - } - Assert.Multiple(() => - { - Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); - Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); - Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); - Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); - }); - } - - [Test, Description("UDIV , , ")] - public void Udiv_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, - [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(8)] ulong Xn, - [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(8)] ulong Xm) - { - uint Opcode = 0x9AC00800; // UDIV X0, X0, X0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X31: _X31); - - if (Rd != 31) - { - Bits Op = new Bits(Opcode); - - AArch64.X((int)Rn, new Bits(Xn)); - AArch64.X((int)Rm, new Bits(Xm)); - Base.Udiv(Op[31], Op[20, 16], Op[9, 5], Op[4, 0]); - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); - } - } - - [Test, Description("UDIV , , ")] - public void Udiv_32bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, - [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(8)] uint Wn, - [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(8)] uint Wm) - { - uint Opcode = 0x1AC00800; // UDIV W0, W0, W0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - - uint _W31 = TestContext.CurrentContext.Random.NextUInt(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31); - - if (Rd != 31) - { - Bits Op = new Bits(Opcode); - - AArch64.X((int)Rn, new Bits(Wn)); - AArch64.X((int)Rm, new Bits(Wm)); - Base.Udiv(Op[31], Op[20, 16], Op[9, 5], Op[4, 0]); - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); - - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); - } + CompareAgainstUnicorn(); } #endif } diff --git a/Ryujinx.Tests/Cpu/CpuTestAluRx.cs b/Ryujinx.Tests/Cpu/CpuTestAluRx.cs index 26169bca67..357a96ab94 100644 --- a/Ryujinx.Tests/Cpu/CpuTestAluRx.cs +++ b/Ryujinx.Tests/Cpu/CpuTestAluRx.cs @@ -1,1348 +1,723 @@ -//#define AluRx - -using ChocolArm64.State; +#define AluRx using NUnit.Framework; namespace Ryujinx.Tests.Cpu { - using Tester; - using Tester.Types; - - [Category("AluRx"), Ignore("Tested: first half of 2018.")] + [Category("AluRx")] public sealed class CpuTestAluRx : CpuTest { #if AluRx - [SetUp] - public void SetupTester() - { - AArch64.TakeReset(false); - } + private const int RndCnt = 2; - [Test, Description("ADD , , {, {#}}")] - public void Add_X_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("ADD , , {, {#}}")] + public void Add_X_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn_SP, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xnSp, [Values((ulong)0x0000000000000000, (ulong)0x7FFFFFFFFFFFFFFF, - (ulong)0x8000000000000000, (ulong)0xFFFFFFFFFFFFFFFF)] [Random(2)] ulong Xm, + (ulong)0x8000000000000000, (ulong)0xFFFFFFFFFFFFFFFF)] [Random(RndCnt)] ulong xm, [Values(0b011u, 0b111u)] uint extend, // [Values(0u, 1u, 2u, 3u, 4u)] uint amount) { - uint Opcode = 0x8B206000; // ADD X0, X0, X0, UXTX #0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); - Bits Op = new Bits(Opcode); + uint opcode = 0x8B206000; // ADD X0, X0, X0, UXTX #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); - AThreadState ThreadState; - - if (Rn != 31) + if (rn != 31) { - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - ThreadState = SingleOpcode(Opcode, X1: Xn_SP, X2: Xm, X31: _X31); + ulong x31 = TestContext.CurrentContext.Random.NextULong(); - AArch64.X((int)Rn, new Bits(Xn_SP)); + SingleOpcode(opcode, x1: xnSp, x2: xm, x31: x31); } else { - ThreadState = SingleOpcode(Opcode, X31: Xn_SP, X2: Xm); - - AArch64.SP(new Bits(Xn_SP)); + SingleOpcode(opcode, x31: xnSp, x2: xm); } - AArch64.X((int)Rm, new Bits(Xm)); - Base.Add_Rx(Op[31], Op[20, 16], Op[15, 13], Op[12, 10], Op[9, 5], Op[4, 0]); - - if (Rd != 31) - { - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - ulong SP = AArch64.SP(64).ToUInt64(); - - Assert.That((ulong)ThreadState.X31, Is.EqualTo(SP)); - } + CompareAgainstUnicorn(); } - [Test, Description("ADD , , {, {#}}")] - public void Add_W_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("ADD , , {, {#}}")] + public void Add_W_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn_SP, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xnSp, [Values((uint)0x00000000, (uint)0x7FFFFFFF, - (uint)0x80000000, (uint)0xFFFFFFFF)] [Random(2)] uint Wm, + (uint)0x80000000, (uint)0xFFFFFFFF)] [Random(RndCnt)] uint wm, [Values(0b000u, 0b001u, 0b010u, // [Values(0u, 1u, 2u, 3u, 4u)] uint amount) { - uint Opcode = 0x8B200000; // ADD X0, X0, W0, UXTB #0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); - Bits Op = new Bits(Opcode); + uint opcode = 0x8B200000; // ADD X0, X0, W0, UXTB #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); - AThreadState ThreadState; - - if (Rn != 31) + if (rn != 31) { - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - ThreadState = SingleOpcode(Opcode, X1: Xn_SP, X2: Wm, X31: _X31); + ulong x31 = TestContext.CurrentContext.Random.NextULong(); - AArch64.X((int)Rn, new Bits(Xn_SP)); + SingleOpcode(opcode, x1: xnSp, x2: wm, x31: x31); } else { - ThreadState = SingleOpcode(Opcode, X31: Xn_SP, X2: Wm); - - AArch64.SP(new Bits(Xn_SP)); + SingleOpcode(opcode, x31: xnSp, x2: wm); } - AArch64.X((int)Rm, new Bits(Wm)); - Base.Add_Rx(Op[31], Op[20, 16], Op[15, 13], Op[12, 10], Op[9, 5], Op[4, 0]); - - if (Rd != 31) - { - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - ulong SP = AArch64.SP(64).ToUInt64(); - - Assert.That((ulong)ThreadState.X31, Is.EqualTo(SP)); - } + CompareAgainstUnicorn(); } - [Test, Description("ADD , , {, {#}}")] - public void Add_H_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("ADD , , {, {#}}")] + public void Add_H_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn_SP, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xnSp, [Values((ushort)0x0000, (ushort)0x7FFF, - (ushort)0x8000, (ushort)0xFFFF)] [Random(2)] ushort Wm, + (ushort)0x8000, (ushort)0xFFFF)] [Random(RndCnt)] ushort wm, [Values(0b000u, 0b001u, 0b010u, // [Values(0u, 1u, 2u, 3u, 4u)] uint amount) { - uint Opcode = 0x8B200000; // ADD X0, X0, W0, UXTB #0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); - Bits Op = new Bits(Opcode); + uint opcode = 0x8B200000; // ADD X0, X0, W0, UXTB #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); - AThreadState ThreadState; - - if (Rn != 31) + if (rn != 31) { - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - ThreadState = SingleOpcode(Opcode, X1: Xn_SP, X2: Wm, X31: _X31); + ulong x31 = TestContext.CurrentContext.Random.NextULong(); - AArch64.X((int)Rn, new Bits(Xn_SP)); + SingleOpcode(opcode, x1: xnSp, x2: wm, x31: x31); } else { - ThreadState = SingleOpcode(Opcode, X31: Xn_SP, X2: Wm); - - AArch64.SP(new Bits(Xn_SP)); + SingleOpcode(opcode, x31: xnSp, x2: wm); } - AArch64.X((int)Rm, new Bits(Wm)); - Base.Add_Rx(Op[31], Op[20, 16], Op[15, 13], Op[12, 10], Op[9, 5], Op[4, 0]); - - if (Rd != 31) - { - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - ulong SP = AArch64.SP(64).ToUInt64(); - - Assert.That((ulong)ThreadState.X31, Is.EqualTo(SP)); - } + CompareAgainstUnicorn(); } - [Test, Description("ADD , , {, {#}}")] - public void Add_B_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("ADD , , {, {#}}")] + public void Add_B_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn_SP, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xnSp, [Values((byte)0x00, (byte)0x7F, - (byte)0x80, (byte)0xFF)] [Random(2)] byte Wm, + (byte)0x80, (byte)0xFF)] [Random(RndCnt)] byte wm, [Values(0b000u, 0b001u, 0b010u, // [Values(0u, 1u, 2u, 3u, 4u)] uint amount) { - uint Opcode = 0x8B200000; // ADD X0, X0, W0, UXTB #0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); - Bits Op = new Bits(Opcode); + uint opcode = 0x8B200000; // ADD X0, X0, W0, UXTB #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); - AThreadState ThreadState; - - if (Rn != 31) + if (rn != 31) { - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - ThreadState = SingleOpcode(Opcode, X1: Xn_SP, X2: Wm, X31: _X31); + ulong x31 = TestContext.CurrentContext.Random.NextULong(); - AArch64.X((int)Rn, new Bits(Xn_SP)); + SingleOpcode(opcode, x1: xnSp, x2: wm, x31: x31); } else { - ThreadState = SingleOpcode(Opcode, X31: Xn_SP, X2: Wm); - - AArch64.SP(new Bits(Xn_SP)); + SingleOpcode(opcode, x31: xnSp, x2: wm); } - AArch64.X((int)Rm, new Bits(Wm)); - Base.Add_Rx(Op[31], Op[20, 16], Op[15, 13], Op[12, 10], Op[9, 5], Op[4, 0]); - - if (Rd != 31) - { - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - ulong SP = AArch64.SP(64).ToUInt64(); - - Assert.That((ulong)ThreadState.X31, Is.EqualTo(SP)); - } + CompareAgainstUnicorn(); } - [Test, Description("ADD , , {, {#}}")] - public void Add_W_32bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("ADD , , {, {#}}")] + public void Add_W_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn_WSP, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wnWsp, [Values((uint)0x00000000, (uint)0x7FFFFFFF, - (uint)0x80000000, (uint)0xFFFFFFFF)] [Random(2)] uint Wm, + (uint)0x80000000, (uint)0xFFFFFFFF)] [Random(RndCnt)] uint wm, [Values(0b000u, 0b001u, 0b010u, 0b011u, // [Values(0u, 1u, 2u, 3u, 4u)] uint amount) { - uint Opcode = 0x0B200000; // ADD W0, W0, W0, UXTB #0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); - Bits Op = new Bits(Opcode); + uint opcode = 0x0B200000; // ADD W0, W0, W0, UXTB #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); - AThreadState ThreadState; - - if (Rn != 31) + if (rn != 31) { - uint _W31 = TestContext.CurrentContext.Random.NextUInt(); - ThreadState = SingleOpcode(Opcode, X1: Wn_WSP, X2: Wm, X31: _W31); + uint w31 = TestContext.CurrentContext.Random.NextUInt(); - AArch64.X((int)Rn, new Bits(Wn_WSP)); + SingleOpcode(opcode, x1: wnWsp, x2: wm, x31: w31); } else { - ThreadState = SingleOpcode(Opcode, X31: Wn_WSP, X2: Wm); - - AArch64.SP(new Bits(Wn_WSP)); + SingleOpcode(opcode, x31: wnWsp, x2: wm); } - AArch64.X((int)Rm, new Bits(Wm)); - Base.Add_Rx(Op[31], Op[20, 16], Op[15, 13], Op[12, 10], Op[9, 5], Op[4, 0]); - - if (Rd != 31) - { - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); - - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - uint WSP = AArch64.SP(32).ToUInt32(); - - Assert.That((uint)ThreadState.X31, Is.EqualTo(WSP)); - } + CompareAgainstUnicorn(); } - [Test, Description("ADD , , {, {#}}")] - public void Add_H_32bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("ADD , , {, {#}}")] + public void Add_H_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn_WSP, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wnWsp, [Values((ushort)0x0000, (ushort)0x7FFF, - (ushort)0x8000, (ushort)0xFFFF)] [Random(2)] ushort Wm, + (ushort)0x8000, (ushort)0xFFFF)] [Random(RndCnt)] ushort wm, [Values(0b000u, 0b001u, 0b010u, 0b011u, // [Values(0u, 1u, 2u, 3u, 4u)] uint amount) { - uint Opcode = 0x0B200000; // ADD W0, W0, W0, UXTB #0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); - Bits Op = new Bits(Opcode); + uint opcode = 0x0B200000; // ADD W0, W0, W0, UXTB #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); - AThreadState ThreadState; - - if (Rn != 31) + if (rn != 31) { - uint _W31 = TestContext.CurrentContext.Random.NextUInt(); - ThreadState = SingleOpcode(Opcode, X1: Wn_WSP, X2: Wm, X31: _W31); + uint w31 = TestContext.CurrentContext.Random.NextUInt(); - AArch64.X((int)Rn, new Bits(Wn_WSP)); + SingleOpcode(opcode, x1: wnWsp, x2: wm, x31: w31); } else { - ThreadState = SingleOpcode(Opcode, X31: Wn_WSP, X2: Wm); - - AArch64.SP(new Bits(Wn_WSP)); + SingleOpcode(opcode, x31: wnWsp, x2: wm); } - AArch64.X((int)Rm, new Bits(Wm)); - Base.Add_Rx(Op[31], Op[20, 16], Op[15, 13], Op[12, 10], Op[9, 5], Op[4, 0]); - - if (Rd != 31) - { - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); - - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - uint WSP = AArch64.SP(32).ToUInt32(); - - Assert.That((uint)ThreadState.X31, Is.EqualTo(WSP)); - } + CompareAgainstUnicorn(); } - [Test, Description("ADD , , {, {#}}")] - public void Add_B_32bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("ADD , , {, {#}}")] + public void Add_B_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn_WSP, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wnWsp, [Values((byte)0x00, (byte)0x7F, - (byte)0x80, (byte)0xFF)] [Random(2)] byte Wm, + (byte)0x80, (byte)0xFF)] [Random(RndCnt)] byte wm, [Values(0b000u, 0b001u, 0b010u, 0b011u, // [Values(0u, 1u, 2u, 3u, 4u)] uint amount) { - uint Opcode = 0x0B200000; // ADD W0, W0, W0, UXTB #0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); - Bits Op = new Bits(Opcode); + uint opcode = 0x0B200000; // ADD W0, W0, W0, UXTB #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); - AThreadState ThreadState; - - if (Rn != 31) + if (rn != 31) { - uint _W31 = TestContext.CurrentContext.Random.NextUInt(); - ThreadState = SingleOpcode(Opcode, X1: Wn_WSP, X2: Wm, X31: _W31); + uint w31 = TestContext.CurrentContext.Random.NextUInt(); - AArch64.X((int)Rn, new Bits(Wn_WSP)); + SingleOpcode(opcode, x1: wnWsp, x2: wm, x31: w31); } else { - ThreadState = SingleOpcode(Opcode, X31: Wn_WSP, X2: Wm); - - AArch64.SP(new Bits(Wn_WSP)); + SingleOpcode(opcode, x31: wnWsp, x2: wm); } - AArch64.X((int)Rm, new Bits(Wm)); - Base.Add_Rx(Op[31], Op[20, 16], Op[15, 13], Op[12, 10], Op[9, 5], Op[4, 0]); - - if (Rd != 31) - { - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); - - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - uint WSP = AArch64.SP(32).ToUInt32(); - - Assert.That((uint)ThreadState.X31, Is.EqualTo(WSP)); - } + CompareAgainstUnicorn(); } - [Test, Description("ADDS , , {, {#}}")] - public void Adds_X_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("ADDS , , {, {#}}")] + public void Adds_X_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn_SP, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xnSp, [Values((ulong)0x0000000000000000, (ulong)0x7FFFFFFFFFFFFFFF, - (ulong)0x8000000000000000, (ulong)0xFFFFFFFFFFFFFFFF)] [Random(2)] ulong Xm, + (ulong)0x8000000000000000, (ulong)0xFFFFFFFFFFFFFFFF)] [Random(RndCnt)] ulong xm, [Values(0b011u, 0b111u)] uint extend, // [Values(0u, 1u, 2u, 3u, 4u)] uint amount) { - uint Opcode = 0xAB206000; // ADDS X0, X0, X0, UXTX #0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); - Bits Op = new Bits(Opcode); + uint opcode = 0xAB206000; // ADDS X0, X0, X0, UXTX #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn_SP, X2: Xm, X31: Xn_SP); + SingleOpcode(opcode, x1: xnSp, x2: xm, x31: xnSp); - AArch64.X((int)Rn, new Bits(Xn_SP)); - AArch64.X((int)Rm, new Bits(Xm)); - AArch64.SP(new Bits(Xn_SP)); - Base.Adds_Rx(Op[31], Op[20, 16], Op[15, 13], Op[12, 10], Op[9, 5], Op[4, 0]); - - if (Rd != 31) - { - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - ulong _X31 = AArch64.SP(64).ToUInt64(); - - Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); - } - Assert.Multiple(() => - { - Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); - Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); - Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); - Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); - }); + CompareAgainstUnicorn(); } - [Test, Description("ADDS , , {, {#}}")] - public void Adds_W_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("ADDS , , {, {#}}")] + public void Adds_W_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn_SP, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xnSp, [Values((uint)0x00000000, (uint)0x7FFFFFFF, - (uint)0x80000000, (uint)0xFFFFFFFF)] [Random(2)] uint Wm, + (uint)0x80000000, (uint)0xFFFFFFFF)] [Random(RndCnt)] uint wm, [Values(0b000u, 0b001u, 0b010u, // [Values(0u, 1u, 2u, 3u, 4u)] uint amount) { - uint Opcode = 0xAB200000; // ADDS X0, X0, W0, UXTB #0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); - Bits Op = new Bits(Opcode); + uint opcode = 0xAB200000; // ADDS X0, X0, W0, UXTB #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn_SP, X2: Wm, X31: Xn_SP); + SingleOpcode(opcode, x1: xnSp, x2: wm, x31: xnSp); - AArch64.X((int)Rn, new Bits(Xn_SP)); - AArch64.X((int)Rm, new Bits(Wm)); - AArch64.SP(new Bits(Xn_SP)); - Base.Adds_Rx(Op[31], Op[20, 16], Op[15, 13], Op[12, 10], Op[9, 5], Op[4, 0]); - - if (Rd != 31) - { - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - ulong _X31 = AArch64.SP(64).ToUInt64(); - - Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); - } - Assert.Multiple(() => - { - Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); - Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); - Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); - Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); - }); + CompareAgainstUnicorn(); } - [Test, Description("ADDS , , {, {#}}")] - public void Adds_H_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("ADDS , , {, {#}}")] + public void Adds_H_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn_SP, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xnSp, [Values((ushort)0x0000, (ushort)0x7FFF, - (ushort)0x8000, (ushort)0xFFFF)] [Random(2)] ushort Wm, + (ushort)0x8000, (ushort)0xFFFF)] [Random(RndCnt)] ushort wm, [Values(0b000u, 0b001u, 0b010u, // [Values(0u, 1u, 2u, 3u, 4u)] uint amount) { - uint Opcode = 0xAB200000; // ADDS X0, X0, W0, UXTB #0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); - Bits Op = new Bits(Opcode); + uint opcode = 0xAB200000; // ADDS X0, X0, W0, UXTB #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn_SP, X2: Wm, X31: Xn_SP); + SingleOpcode(opcode, x1: xnSp, x2: wm, x31: xnSp); - AArch64.X((int)Rn, new Bits(Xn_SP)); - AArch64.X((int)Rm, new Bits(Wm)); - AArch64.SP(new Bits(Xn_SP)); - Base.Adds_Rx(Op[31], Op[20, 16], Op[15, 13], Op[12, 10], Op[9, 5], Op[4, 0]); - - if (Rd != 31) - { - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - ulong _X31 = AArch64.SP(64).ToUInt64(); - - Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); - } - Assert.Multiple(() => - { - Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); - Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); - Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); - Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); - }); + CompareAgainstUnicorn(); } - [Test, Description("ADDS , , {, {#}}")] - public void Adds_B_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("ADDS , , {, {#}}")] + public void Adds_B_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn_SP, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xnSp, [Values((byte)0x00, (byte)0x7F, - (byte)0x80, (byte)0xFF)] [Random(2)] byte Wm, + (byte)0x80, (byte)0xFF)] [Random(RndCnt)] byte wm, [Values(0b000u, 0b001u, 0b010u, // [Values(0u, 1u, 2u, 3u, 4u)] uint amount) { - uint Opcode = 0xAB200000; // ADDS X0, X0, W0, UXTB #0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); - Bits Op = new Bits(Opcode); + uint opcode = 0xAB200000; // ADDS X0, X0, W0, UXTB #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn_SP, X2: Wm, X31: Xn_SP); + SingleOpcode(opcode, x1: xnSp, x2: wm, x31: xnSp); - AArch64.X((int)Rn, new Bits(Xn_SP)); - AArch64.X((int)Rm, new Bits(Wm)); - AArch64.SP(new Bits(Xn_SP)); - Base.Adds_Rx(Op[31], Op[20, 16], Op[15, 13], Op[12, 10], Op[9, 5], Op[4, 0]); - - if (Rd != 31) - { - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - ulong _X31 = AArch64.SP(64).ToUInt64(); - - Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); - } - Assert.Multiple(() => - { - Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); - Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); - Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); - Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); - }); + CompareAgainstUnicorn(); } - [Test, Description("ADDS , , {, {#}}")] - public void Adds_W_32bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("ADDS , , {, {#}}")] + public void Adds_W_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn_WSP, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wnWsp, [Values((uint)0x00000000, (uint)0x7FFFFFFF, - (uint)0x80000000, (uint)0xFFFFFFFF)] [Random(2)] uint Wm, + (uint)0x80000000, (uint)0xFFFFFFFF)] [Random(RndCnt)] uint wm, [Values(0b000u, 0b001u, 0b010u, 0b011u, // [Values(0u, 1u, 2u, 3u, 4u)] uint amount) { - uint Opcode = 0x2B200000; // ADDS W0, W0, W0, UXTB #0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); - Bits Op = new Bits(Opcode); + uint opcode = 0x2B200000; // ADDS W0, W0, W0, UXTB #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn_WSP, X2: Wm, X31: Wn_WSP); + SingleOpcode(opcode, x1: wnWsp, x2: wm, x31: wnWsp); - AArch64.X((int)Rn, new Bits(Wn_WSP)); - AArch64.X((int)Rm, new Bits(Wm)); - AArch64.SP(new Bits(Wn_WSP)); - Base.Adds_Rx(Op[31], Op[20, 16], Op[15, 13], Op[12, 10], Op[9, 5], Op[4, 0]); - - if (Rd != 31) - { - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); - - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - uint _W31 = AArch64.SP(32).ToUInt32(); - - Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); - } - Assert.Multiple(() => - { - Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); - Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); - Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); - Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); - }); + CompareAgainstUnicorn(); } - [Test, Description("ADDS , , {, {#}}")] - public void Adds_H_32bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("ADDS , , {, {#}}")] + public void Adds_H_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn_WSP, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wnWsp, [Values((ushort)0x0000, (ushort)0x7FFF, - (ushort)0x8000, (ushort)0xFFFF)] [Random(2)] ushort Wm, + (ushort)0x8000, (ushort)0xFFFF)] [Random(RndCnt)] ushort wm, [Values(0b000u, 0b001u, 0b010u, 0b011u, // [Values(0u, 1u, 2u, 3u, 4u)] uint amount) { - uint Opcode = 0x2B200000; // ADDS W0, W0, W0, UXTB #0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); - Bits Op = new Bits(Opcode); + uint opcode = 0x2B200000; // ADDS W0, W0, W0, UXTB #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn_WSP, X2: Wm, X31: Wn_WSP); + SingleOpcode(opcode, x1: wnWsp, x2: wm, x31: wnWsp); - AArch64.X((int)Rn, new Bits(Wn_WSP)); - AArch64.X((int)Rm, new Bits(Wm)); - AArch64.SP(new Bits(Wn_WSP)); - Base.Adds_Rx(Op[31], Op[20, 16], Op[15, 13], Op[12, 10], Op[9, 5], Op[4, 0]); - - if (Rd != 31) - { - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); - - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - uint _W31 = AArch64.SP(32).ToUInt32(); - - Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); - } - Assert.Multiple(() => - { - Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); - Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); - Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); - Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); - }); + CompareAgainstUnicorn(); } - [Test, Description("ADDS , , {, {#}}")] - public void Adds_B_32bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("ADDS , , {, {#}}")] + public void Adds_B_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn_WSP, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wnWsp, [Values((byte)0x00, (byte)0x7F, - (byte)0x80, (byte)0xFF)] [Random(2)] byte Wm, + (byte)0x80, (byte)0xFF)] [Random(RndCnt)] byte wm, [Values(0b000u, 0b001u, 0b010u, 0b011u, // [Values(0u, 1u, 2u, 3u, 4u)] uint amount) { - uint Opcode = 0x2B200000; // ADDS W0, W0, W0, UXTB #0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); - Bits Op = new Bits(Opcode); + uint opcode = 0x2B200000; // ADDS W0, W0, W0, UXTB #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn_WSP, X2: Wm, X31: Wn_WSP); + SingleOpcode(opcode, x1: wnWsp, x2: wm, x31: wnWsp); - AArch64.X((int)Rn, new Bits(Wn_WSP)); - AArch64.X((int)Rm, new Bits(Wm)); - AArch64.SP(new Bits(Wn_WSP)); - Base.Adds_Rx(Op[31], Op[20, 16], Op[15, 13], Op[12, 10], Op[9, 5], Op[4, 0]); - - if (Rd != 31) - { - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); - - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - uint _W31 = AArch64.SP(32).ToUInt32(); - - Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); - } - Assert.Multiple(() => - { - Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); - Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); - Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); - Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); - }); + CompareAgainstUnicorn(); } - [Test, Description("SUB , , {, {#}}")] - public void Sub_X_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("SUB , , {, {#}}")] + public void Sub_X_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn_SP, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xnSp, [Values((ulong)0x0000000000000000, (ulong)0x7FFFFFFFFFFFFFFF, - (ulong)0x8000000000000000, (ulong)0xFFFFFFFFFFFFFFFF)] [Random(2)] ulong Xm, + (ulong)0x8000000000000000, (ulong)0xFFFFFFFFFFFFFFFF)] [Random(RndCnt)] ulong xm, [Values(0b011u, 0b111u)] uint extend, // [Values(0u, 1u, 2u, 3u, 4u)] uint amount) { - uint Opcode = 0xCB206000; // SUB X0, X0, X0, UXTX #0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); - Bits Op = new Bits(Opcode); + uint opcode = 0xCB206000; // SUB X0, X0, X0, UXTX #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); - AThreadState ThreadState; - - if (Rn != 31) + if (rn != 31) { - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - ThreadState = SingleOpcode(Opcode, X1: Xn_SP, X2: Xm, X31: _X31); + ulong x31 = TestContext.CurrentContext.Random.NextULong(); - AArch64.X((int)Rn, new Bits(Xn_SP)); + SingleOpcode(opcode, x1: xnSp, x2: xm, x31: x31); } else { - ThreadState = SingleOpcode(Opcode, X31: Xn_SP, X2: Xm); - - AArch64.SP(new Bits(Xn_SP)); + SingleOpcode(opcode, x31: xnSp, x2: xm); } - AArch64.X((int)Rm, new Bits(Xm)); - Base.Sub_Rx(Op[31], Op[20, 16], Op[15, 13], Op[12, 10], Op[9, 5], Op[4, 0]); - - if (Rd != 31) - { - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - ulong SP = AArch64.SP(64).ToUInt64(); - - Assert.That((ulong)ThreadState.X31, Is.EqualTo(SP)); - } + CompareAgainstUnicorn(); } - [Test, Description("SUB , , {, {#}}")] - public void Sub_W_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("SUB , , {, {#}}")] + public void Sub_W_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn_SP, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xnSp, [Values((uint)0x00000000, (uint)0x7FFFFFFF, - (uint)0x80000000, (uint)0xFFFFFFFF)] [Random(2)] uint Wm, + (uint)0x80000000, (uint)0xFFFFFFFF)] [Random(RndCnt)] uint wm, [Values(0b000u, 0b001u, 0b010u, // [Values(0u, 1u, 2u, 3u, 4u)] uint amount) { - uint Opcode = 0xCB200000; // SUB X0, X0, W0, UXTB #0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); - Bits Op = new Bits(Opcode); + uint opcode = 0xCB200000; // SUB X0, X0, W0, UXTB #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); - AThreadState ThreadState; - - if (Rn != 31) + if (rn != 31) { - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - ThreadState = SingleOpcode(Opcode, X1: Xn_SP, X2: Wm, X31: _X31); + ulong x31 = TestContext.CurrentContext.Random.NextULong(); - AArch64.X((int)Rn, new Bits(Xn_SP)); + SingleOpcode(opcode, x1: xnSp, x2: wm, x31: x31); } else { - ThreadState = SingleOpcode(Opcode, X31: Xn_SP, X2: Wm); - - AArch64.SP(new Bits(Xn_SP)); + SingleOpcode(opcode, x31: xnSp, x2: wm); } - AArch64.X((int)Rm, new Bits(Wm)); - Base.Sub_Rx(Op[31], Op[20, 16], Op[15, 13], Op[12, 10], Op[9, 5], Op[4, 0]); - - if (Rd != 31) - { - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - ulong SP = AArch64.SP(64).ToUInt64(); - - Assert.That((ulong)ThreadState.X31, Is.EqualTo(SP)); - } + CompareAgainstUnicorn(); } - [Test, Description("SUB , , {, {#}}")] - public void Sub_H_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("SUB , , {, {#}}")] + public void Sub_H_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn_SP, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xnSp, [Values((ushort)0x0000, (ushort)0x7FFF, - (ushort)0x8000, (ushort)0xFFFF)] [Random(2)] ushort Wm, + (ushort)0x8000, (ushort)0xFFFF)] [Random(RndCnt)] ushort wm, [Values(0b000u, 0b001u, 0b010u, // [Values(0u, 1u, 2u, 3u, 4u)] uint amount) { - uint Opcode = 0xCB200000; // SUB X0, X0, W0, UXTB #0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); - Bits Op = new Bits(Opcode); + uint opcode = 0xCB200000; // SUB X0, X0, W0, UXTB #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); - AThreadState ThreadState; - - if (Rn != 31) + if (rn != 31) { - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - ThreadState = SingleOpcode(Opcode, X1: Xn_SP, X2: Wm, X31: _X31); + ulong x31 = TestContext.CurrentContext.Random.NextULong(); - AArch64.X((int)Rn, new Bits(Xn_SP)); + SingleOpcode(opcode, x1: xnSp, x2: wm, x31: x31); } else { - ThreadState = SingleOpcode(Opcode, X31: Xn_SP, X2: Wm); - - AArch64.SP(new Bits(Xn_SP)); + SingleOpcode(opcode, x31: xnSp, x2: wm); } - AArch64.X((int)Rm, new Bits(Wm)); - Base.Sub_Rx(Op[31], Op[20, 16], Op[15, 13], Op[12, 10], Op[9, 5], Op[4, 0]); - - if (Rd != 31) - { - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - ulong SP = AArch64.SP(64).ToUInt64(); - - Assert.That((ulong)ThreadState.X31, Is.EqualTo(SP)); - } + CompareAgainstUnicorn(); } - [Test, Description("SUB , , {, {#}}")] - public void Sub_B_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("SUB , , {, {#}}")] + public void Sub_B_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn_SP, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xnSp, [Values((byte)0x00, (byte)0x7F, - (byte)0x80, (byte)0xFF)] [Random(2)] byte Wm, + (byte)0x80, (byte)0xFF)] [Random(RndCnt)] byte wm, [Values(0b000u, 0b001u, 0b010u, // [Values(0u, 1u, 2u, 3u, 4u)] uint amount) { - uint Opcode = 0xCB200000; // SUB X0, X0, W0, UXTB #0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); - Bits Op = new Bits(Opcode); + uint opcode = 0xCB200000; // SUB X0, X0, W0, UXTB #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); - AThreadState ThreadState; - - if (Rn != 31) + if (rn != 31) { - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - ThreadState = SingleOpcode(Opcode, X1: Xn_SP, X2: Wm, X31: _X31); + ulong x31 = TestContext.CurrentContext.Random.NextULong(); - AArch64.X((int)Rn, new Bits(Xn_SP)); + SingleOpcode(opcode, x1: xnSp, x2: wm, x31: x31); } else { - ThreadState = SingleOpcode(Opcode, X31: Xn_SP, X2: Wm); - - AArch64.SP(new Bits(Xn_SP)); + SingleOpcode(opcode, x31: xnSp, x2: wm); } - AArch64.X((int)Rm, new Bits(Wm)); - Base.Sub_Rx(Op[31], Op[20, 16], Op[15, 13], Op[12, 10], Op[9, 5], Op[4, 0]); - - if (Rd != 31) - { - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - ulong SP = AArch64.SP(64).ToUInt64(); - - Assert.That((ulong)ThreadState.X31, Is.EqualTo(SP)); - } + CompareAgainstUnicorn(); } - [Test, Description("SUB , , {, {#}}")] - public void Sub_W_32bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("SUB , , {, {#}}")] + public void Sub_W_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn_WSP, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wnWsp, [Values((uint)0x00000000, (uint)0x7FFFFFFF, - (uint)0x80000000, (uint)0xFFFFFFFF)] [Random(2)] uint Wm, + (uint)0x80000000, (uint)0xFFFFFFFF)] [Random(RndCnt)] uint wm, [Values(0b000u, 0b001u, 0b010u, 0b011u, // [Values(0u, 1u, 2u, 3u, 4u)] uint amount) { - uint Opcode = 0x4B200000; // SUB W0, W0, W0, UXTB #0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); - Bits Op = new Bits(Opcode); + uint opcode = 0x4B200000; // SUB W0, W0, W0, UXTB #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); - AThreadState ThreadState; - - if (Rn != 31) + if (rn != 31) { - uint _W31 = TestContext.CurrentContext.Random.NextUInt(); - ThreadState = SingleOpcode(Opcode, X1: Wn_WSP, X2: Wm, X31: _W31); + uint w31 = TestContext.CurrentContext.Random.NextUInt(); - AArch64.X((int)Rn, new Bits(Wn_WSP)); + SingleOpcode(opcode, x1: wnWsp, x2: wm, x31: w31); } else { - ThreadState = SingleOpcode(Opcode, X31: Wn_WSP, X2: Wm); - - AArch64.SP(new Bits(Wn_WSP)); + SingleOpcode(opcode, x31: wnWsp, x2: wm); } - AArch64.X((int)Rm, new Bits(Wm)); - Base.Sub_Rx(Op[31], Op[20, 16], Op[15, 13], Op[12, 10], Op[9, 5], Op[4, 0]); - - if (Rd != 31) - { - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); - - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - uint WSP = AArch64.SP(32).ToUInt32(); - - Assert.That((uint)ThreadState.X31, Is.EqualTo(WSP)); - } + CompareAgainstUnicorn(); } - [Test, Description("SUB , , {, {#}}")] - public void Sub_H_32bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("SUB , , {, {#}}")] + public void Sub_H_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn_WSP, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wnWsp, [Values((ushort)0x0000, (ushort)0x7FFF, - (ushort)0x8000, (ushort)0xFFFF)] [Random(2)] ushort Wm, + (ushort)0x8000, (ushort)0xFFFF)] [Random(RndCnt)] ushort wm, [Values(0b000u, 0b001u, 0b010u, 0b011u, // [Values(0u, 1u, 2u, 3u, 4u)] uint amount) { - uint Opcode = 0x4B200000; // SUB W0, W0, W0, UXTB #0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); - Bits Op = new Bits(Opcode); + uint opcode = 0x4B200000; // SUB W0, W0, W0, UXTB #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); - AThreadState ThreadState; - - if (Rn != 31) + if (rn != 31) { - uint _W31 = TestContext.CurrentContext.Random.NextUInt(); - ThreadState = SingleOpcode(Opcode, X1: Wn_WSP, X2: Wm, X31: _W31); + uint w31 = TestContext.CurrentContext.Random.NextUInt(); - AArch64.X((int)Rn, new Bits(Wn_WSP)); + SingleOpcode(opcode, x1: wnWsp, x2: wm, x31: w31); } else { - ThreadState = SingleOpcode(Opcode, X31: Wn_WSP, X2: Wm); - - AArch64.SP(new Bits(Wn_WSP)); + SingleOpcode(opcode, x31: wnWsp, x2: wm); } - AArch64.X((int)Rm, new Bits(Wm)); - Base.Sub_Rx(Op[31], Op[20, 16], Op[15, 13], Op[12, 10], Op[9, 5], Op[4, 0]); - - if (Rd != 31) - { - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); - - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - uint WSP = AArch64.SP(32).ToUInt32(); - - Assert.That((uint)ThreadState.X31, Is.EqualTo(WSP)); - } + CompareAgainstUnicorn(); } - [Test, Description("SUB , , {, {#}}")] - public void Sub_B_32bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("SUB , , {, {#}}")] + public void Sub_B_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn_WSP, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wnWsp, [Values((byte)0x00, (byte)0x7F, - (byte)0x80, (byte)0xFF)] [Random(2)] byte Wm, + (byte)0x80, (byte)0xFF)] [Random(RndCnt)] byte wm, [Values(0b000u, 0b001u, 0b010u, 0b011u, // [Values(0u, 1u, 2u, 3u, 4u)] uint amount) { - uint Opcode = 0x4B200000; // SUB W0, W0, W0, UXTB #0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); - Bits Op = new Bits(Opcode); + uint opcode = 0x4B200000; // SUB W0, W0, W0, UXTB #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); - AThreadState ThreadState; - - if (Rn != 31) + if (rn != 31) { - uint _W31 = TestContext.CurrentContext.Random.NextUInt(); - ThreadState = SingleOpcode(Opcode, X1: Wn_WSP, X2: Wm, X31: _W31); + uint w31 = TestContext.CurrentContext.Random.NextUInt(); - AArch64.X((int)Rn, new Bits(Wn_WSP)); + SingleOpcode(opcode, x1: wnWsp, x2: wm, x31: w31); } else { - ThreadState = SingleOpcode(Opcode, X31: Wn_WSP, X2: Wm); - - AArch64.SP(new Bits(Wn_WSP)); + SingleOpcode(opcode, x31: wnWsp, x2: wm); } - AArch64.X((int)Rm, new Bits(Wm)); - Base.Sub_Rx(Op[31], Op[20, 16], Op[15, 13], Op[12, 10], Op[9, 5], Op[4, 0]); - - if (Rd != 31) - { - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); - - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - uint WSP = AArch64.SP(32).ToUInt32(); - - Assert.That((uint)ThreadState.X31, Is.EqualTo(WSP)); - } + CompareAgainstUnicorn(); } - [Test, Description("SUBS , , {, {#}}")] - public void Subs_X_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("SUBS , , {, {#}}")] + public void Subs_X_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn_SP, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xnSp, [Values((ulong)0x0000000000000000, (ulong)0x7FFFFFFFFFFFFFFF, - (ulong)0x8000000000000000, (ulong)0xFFFFFFFFFFFFFFFF)] [Random(2)] ulong Xm, + (ulong)0x8000000000000000, (ulong)0xFFFFFFFFFFFFFFFF)] [Random(RndCnt)] ulong xm, [Values(0b011u, 0b111u)] uint extend, // [Values(0u, 1u, 2u, 3u, 4u)] uint amount) { - uint Opcode = 0xEB206000; // SUBS X0, X0, X0, UXTX #0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); - Bits Op = new Bits(Opcode); + uint opcode = 0xEB206000; // SUBS X0, X0, X0, UXTX #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn_SP, X2: Xm, X31: Xn_SP); + SingleOpcode(opcode, x1: xnSp, x2: xm, x31: xnSp); - AArch64.X((int)Rn, new Bits(Xn_SP)); - AArch64.X((int)Rm, new Bits(Xm)); - AArch64.SP(new Bits(Xn_SP)); - Base.Subs_Rx(Op[31], Op[20, 16], Op[15, 13], Op[12, 10], Op[9, 5], Op[4, 0]); - - if (Rd != 31) - { - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - ulong _X31 = AArch64.SP(64).ToUInt64(); - - Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); - } - Assert.Multiple(() => - { - Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); - Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); - Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); - Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); - }); + CompareAgainstUnicorn(); } - [Test, Description("SUBS , , {, {#}}")] - public void Subs_W_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("SUBS , , {, {#}}")] + public void Subs_W_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn_SP, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xnSp, [Values((uint)0x00000000, (uint)0x7FFFFFFF, - (uint)0x80000000, (uint)0xFFFFFFFF)] [Random(2)] uint Wm, + (uint)0x80000000, (uint)0xFFFFFFFF)] [Random(RndCnt)] uint wm, [Values(0b000u, 0b001u, 0b010u, // [Values(0u, 1u, 2u, 3u, 4u)] uint amount) { - uint Opcode = 0xEB200000; // SUBS X0, X0, W0, UXTB #0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); - Bits Op = new Bits(Opcode); + uint opcode = 0xEB200000; // SUBS X0, X0, W0, UXTB #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn_SP, X2: Wm, X31: Xn_SP); + SingleOpcode(opcode, x1: xnSp, x2: wm, x31: xnSp); - AArch64.X((int)Rn, new Bits(Xn_SP)); - AArch64.X((int)Rm, new Bits(Wm)); - AArch64.SP(new Bits(Xn_SP)); - Base.Subs_Rx(Op[31], Op[20, 16], Op[15, 13], Op[12, 10], Op[9, 5], Op[4, 0]); - - if (Rd != 31) - { - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - ulong _X31 = AArch64.SP(64).ToUInt64(); - - Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); - } - Assert.Multiple(() => - { - Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); - Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); - Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); - Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); - }); + CompareAgainstUnicorn(); } - [Test, Description("SUBS , , {, {#}}")] - public void Subs_H_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("SUBS , , {, {#}}")] + public void Subs_H_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn_SP, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xnSp, [Values((ushort)0x0000, (ushort)0x7FFF, - (ushort)0x8000, (ushort)0xFFFF)] [Random(2)] ushort Wm, + (ushort)0x8000, (ushort)0xFFFF)] [Random(RndCnt)] ushort wm, [Values(0b000u, 0b001u, 0b010u, // [Values(0u, 1u, 2u, 3u, 4u)] uint amount) { - uint Opcode = 0xEB200000; // SUBS X0, X0, W0, UXTB #0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); - Bits Op = new Bits(Opcode); + uint opcode = 0xEB200000; // SUBS X0, X0, W0, UXTB #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn_SP, X2: Wm, X31: Xn_SP); + SingleOpcode(opcode, x1: xnSp, x2: wm, x31: xnSp); - AArch64.X((int)Rn, new Bits(Xn_SP)); - AArch64.X((int)Rm, new Bits(Wm)); - AArch64.SP(new Bits(Xn_SP)); - Base.Subs_Rx(Op[31], Op[20, 16], Op[15, 13], Op[12, 10], Op[9, 5], Op[4, 0]); - - if (Rd != 31) - { - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - ulong _X31 = AArch64.SP(64).ToUInt64(); - - Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); - } - Assert.Multiple(() => - { - Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); - Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); - Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); - Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); - }); + CompareAgainstUnicorn(); } - [Test, Description("SUBS , , {, {#}}")] - public void Subs_B_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("SUBS , , {, {#}}")] + public void Subs_B_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn_SP, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xnSp, [Values((byte)0x00, (byte)0x7F, - (byte)0x80, (byte)0xFF)] [Random(2)] byte Wm, + (byte)0x80, (byte)0xFF)] [Random(RndCnt)] byte wm, [Values(0b000u, 0b001u, 0b010u, // [Values(0u, 1u, 2u, 3u, 4u)] uint amount) { - uint Opcode = 0xEB200000; // SUBS X0, X0, W0, UXTB #0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); - Bits Op = new Bits(Opcode); + uint opcode = 0xEB200000; // SUBS X0, X0, W0, UXTB #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn_SP, X2: Wm, X31: Xn_SP); + SingleOpcode(opcode, x1: xnSp, x2: wm, x31: xnSp); - AArch64.X((int)Rn, new Bits(Xn_SP)); - AArch64.X((int)Rm, new Bits(Wm)); - AArch64.SP(new Bits(Xn_SP)); - Base.Subs_Rx(Op[31], Op[20, 16], Op[15, 13], Op[12, 10], Op[9, 5], Op[4, 0]); - - if (Rd != 31) - { - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - ulong _X31 = AArch64.SP(64).ToUInt64(); - - Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); - } - Assert.Multiple(() => - { - Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); - Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); - Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); - Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); - }); + CompareAgainstUnicorn(); } - [Test, Description("SUBS , , {, {#}}")] - public void Subs_W_32bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("SUBS , , {, {#}}")] + public void Subs_W_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn_WSP, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wnWsp, [Values((uint)0x00000000, (uint)0x7FFFFFFF, - (uint)0x80000000, (uint)0xFFFFFFFF)] [Random(2)] uint Wm, + (uint)0x80000000, (uint)0xFFFFFFFF)] [Random(RndCnt)] uint wm, [Values(0b000u, 0b001u, 0b010u, 0b011u, // [Values(0u, 1u, 2u, 3u, 4u)] uint amount) { - uint Opcode = 0x6B200000; // SUBS W0, W0, W0, UXTB #0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); - Bits Op = new Bits(Opcode); + uint opcode = 0x6B200000; // SUBS W0, W0, W0, UXTB #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn_WSP, X2: Wm, X31: Wn_WSP); + SingleOpcode(opcode, x1: wnWsp, x2: wm, x31: wnWsp); - AArch64.X((int)Rn, new Bits(Wn_WSP)); - AArch64.X((int)Rm, new Bits(Wm)); - AArch64.SP(new Bits(Wn_WSP)); - Base.Subs_Rx(Op[31], Op[20, 16], Op[15, 13], Op[12, 10], Op[9, 5], Op[4, 0]); - - if (Rd != 31) - { - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); - - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - uint _W31 = AArch64.SP(32).ToUInt32(); - - Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); - } - Assert.Multiple(() => - { - Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); - Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); - Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); - Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); - }); + CompareAgainstUnicorn(); } - [Test, Description("SUBS , , {, {#}}")] - public void Subs_H_32bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("SUBS , , {, {#}}")] + public void Subs_H_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn_WSP, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wnWsp, [Values((ushort)0x0000, (ushort)0x7FFF, - (ushort)0x8000, (ushort)0xFFFF)] [Random(2)] ushort Wm, + (ushort)0x8000, (ushort)0xFFFF)] [Random(RndCnt)] ushort wm, [Values(0b000u, 0b001u, 0b010u, 0b011u, // [Values(0u, 1u, 2u, 3u, 4u)] uint amount) { - uint Opcode = 0x6B200000; // SUBS W0, W0, W0, UXTB #0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); - Bits Op = new Bits(Opcode); + uint opcode = 0x6B200000; // SUBS W0, W0, W0, UXTB #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn_WSP, X2: Wm, X31: Wn_WSP); + SingleOpcode(opcode, x1: wnWsp, x2: wm, x31: wnWsp); - AArch64.X((int)Rn, new Bits(Wn_WSP)); - AArch64.X((int)Rm, new Bits(Wm)); - AArch64.SP(new Bits(Wn_WSP)); - Base.Subs_Rx(Op[31], Op[20, 16], Op[15, 13], Op[12, 10], Op[9, 5], Op[4, 0]); - - if (Rd != 31) - { - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); - - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - uint _W31 = AArch64.SP(32).ToUInt32(); - - Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); - } - Assert.Multiple(() => - { - Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); - Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); - Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); - Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); - }); + CompareAgainstUnicorn(); } - [Test, Description("SUBS , , {, {#}}")] - public void Subs_B_32bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("SUBS , , {, {#}}")] + public void Subs_B_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn_WSP, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wnWsp, [Values((byte)0x00, (byte)0x7F, - (byte)0x80, (byte)0xFF)] [Random(2)] byte Wm, + (byte)0x80, (byte)0xFF)] [Random(RndCnt)] byte wm, [Values(0b000u, 0b001u, 0b010u, 0b011u, // [Values(0u, 1u, 2u, 3u, 4u)] uint amount) { - uint Opcode = 0x6B200000; // SUBS W0, W0, W0, UXTB #0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); - Bits Op = new Bits(Opcode); + uint opcode = 0x6B200000; // SUBS W0, W0, W0, UXTB #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn_WSP, X2: Wm, X31: Wn_WSP); + SingleOpcode(opcode, x1: wnWsp, x2: wm, x31: wnWsp); - AArch64.X((int)Rn, new Bits(Wn_WSP)); - AArch64.X((int)Rm, new Bits(Wm)); - AArch64.SP(new Bits(Wn_WSP)); - Base.Subs_Rx(Op[31], Op[20, 16], Op[15, 13], Op[12, 10], Op[9, 5], Op[4, 0]); - - if (Rd != 31) - { - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); - - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - uint _W31 = AArch64.SP(32).ToUInt32(); - - Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); - } - Assert.Multiple(() => - { - Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); - Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); - Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); - Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); - }); + CompareAgainstUnicorn(); } #endif } diff --git a/Ryujinx.Tests/Cpu/CpuTestBfm.cs b/Ryujinx.Tests/Cpu/CpuTestBfm.cs index 2952bca4c0..24f69036e4 100644 --- a/Ryujinx.Tests/Cpu/CpuTestBfm.cs +++ b/Ryujinx.Tests/Cpu/CpuTestBfm.cs @@ -1,212 +1,131 @@ -//#define Bfm - -using ChocolArm64.State; +#define Bfm using NUnit.Framework; namespace Ryujinx.Tests.Cpu { - using Tester; - using Tester.Types; - - [Category("Bfm"), Ignore("Tested: first half of 2018.")] + [Category("Bfm")] public sealed class CpuTestBfm : CpuTest { #if Bfm - [SetUp] - public void SetupTester() - { - AArch64.TakeReset(false); - } + private const int RndCnt = 2; + private const int RndCntImmr = 2; + private const int RndCntImms = 2; - [Test, Description("BFM , , #, #")] - public void Bfm_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Random(2)] ulong _Xd, + [Test, Pairwise, Description("BFM , , #, #")] + public void Bfm_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Random(RndCnt)] ulong xd, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(2)] ulong Xn, - [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, 2)] uint immr, - [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, 2)] uint imms) + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xn, + [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, RndCntImmr)] uint immr, + [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, RndCntImms)] uint imms) { - uint Opcode = 0xB3400000; // BFM X0, X0, #0, #0 - Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); + uint opcode = 0xB3400000; // BFM X0, X0, #0, #0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - AThreadState ThreadState = SingleOpcode(Opcode, X0: _Xd, X1: Xn, X31: _X31); + ulong x31 = TestContext.CurrentContext.Random.NextULong(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x0: xd, x1: xn, x31: x31); - AArch64.X((int)Rd, new Bits(_Xd)); - AArch64.X((int)Rn, new Bits(Xn)); - Base.Bfm(Op[31], Op[22], Op[21, 16], Op[15, 10], Op[9, 5], Op[4, 0]); - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); - } + CompareAgainstUnicorn(); } - [Test, Description("BFM , , #, #")] - public void Bfm_32bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Random(2)] uint _Wd, + [Test, Pairwise, Description("BFM , , #, #")] + public void Bfm_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Random(RndCnt)] uint wd, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(2)] uint Wn, - [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, 2)] uint immr, - [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, 2)] uint imms) + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wn, + [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, RndCntImmr)] uint immr, + [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, RndCntImms)] uint imms) { - uint Opcode = 0x33000000; // BFM W0, W0, #0, #0 - Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); + uint opcode = 0x33000000; // BFM W0, W0, #0, #0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); - uint _W31 = TestContext.CurrentContext.Random.NextUInt(); - AThreadState ThreadState = SingleOpcode(Opcode, X0: _Wd, X1: Wn, X31: _W31); + uint w31 = TestContext.CurrentContext.Random.NextUInt(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x0: wd, x1: wn, x31: w31); - AArch64.X((int)Rd, new Bits(_Wd)); - AArch64.X((int)Rn, new Bits(Wn)); - Base.Bfm(Op[31], Op[22], Op[21, 16], Op[15, 10], Op[9, 5], Op[4, 0]); - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); - - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); - } + CompareAgainstUnicorn(); } - [Test, Description("SBFM , , #, #")] - public void Sbfm_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, + [Test, Pairwise, Description("SBFM , , #, #")] + public void Sbfm_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(2)] ulong Xn, - [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, 2)] uint immr, - [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, 2)] uint imms) + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xn, + [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, RndCntImmr)] uint immr, + [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, RndCntImms)] uint imms) { - uint Opcode = 0x93400000; // SBFM X0, X0, #0, #0 - Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); + uint opcode = 0x93400000; // SBFM X0, X0, #0, #0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X31: _X31); + ulong x31 = TestContext.CurrentContext.Random.NextULong(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: xn, x31: x31); - AArch64.X((int)Rn, new Bits(Xn)); - Base.Sbfm(Op[31], Op[22], Op[21, 16], Op[15, 10], Op[9, 5], Op[4, 0]); - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); - } + CompareAgainstUnicorn(); } - [Test, Description("SBFM , , #, #")] - public void Sbfm_32bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, + [Test, Pairwise, Description("SBFM , , #, #")] + public void Sbfm_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(2)] uint Wn, - [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, 2)] uint immr, - [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, 2)] uint imms) + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wn, + [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, RndCntImmr)] uint immr, + [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, RndCntImms)] uint imms) { - uint Opcode = 0x13000000; // SBFM W0, W0, #0, #0 - Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); + uint opcode = 0x13000000; // SBFM W0, W0, #0, #0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); - uint _W31 = TestContext.CurrentContext.Random.NextUInt(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X31: _W31); + uint w31 = TestContext.CurrentContext.Random.NextUInt(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: wn, x31: w31); - AArch64.X((int)Rn, new Bits(Wn)); - Base.Sbfm(Op[31], Op[22], Op[21, 16], Op[15, 10], Op[9, 5], Op[4, 0]); - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); - - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); - } + CompareAgainstUnicorn(); } - [Test, Description("UBFM , , #, #")] - public void Ubfm_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, + [Test, Pairwise, Description("UBFM , , #, #")] + public void Ubfm_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(2)] ulong Xn, - [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, 2)] uint immr, - [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, 2)] uint imms) + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xn, + [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, RndCntImmr)] uint immr, + [Values(0u, 31u, 32u, 63u)] [Random(0u, 63u, RndCntImms)] uint imms) { - uint Opcode = 0xD3400000; // UBFM X0, X0, #0, #0 - Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); + uint opcode = 0xD3400000; // UBFM X0, X0, #0, #0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X31: _X31); + ulong x31 = TestContext.CurrentContext.Random.NextULong(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: xn, x31: x31); - AArch64.X((int)Rn, new Bits(Xn)); - Base.Ubfm(Op[31], Op[22], Op[21, 16], Op[15, 10], Op[9, 5], Op[4, 0]); - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); - } + CompareAgainstUnicorn(); } - [Test, Description("UBFM , , #, #")] - public void Ubfm_32bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, + [Test, Pairwise, Description("UBFM , , #, #")] + public void Ubfm_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(2)] uint Wn, - [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, 2)] uint immr, - [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, 2)] uint imms) + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wn, + [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, RndCntImmr)] uint immr, + [Values(0u, 15u, 16u, 31u)] [Random(0u, 31u, RndCntImms)] uint imms) { - uint Opcode = 0x53000000; // UBFM W0, W0, #0, #0 - Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); + uint opcode = 0x53000000; // UBFM W0, W0, #0, #0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); - uint _W31 = TestContext.CurrentContext.Random.NextUInt(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X31: _W31); + uint w31 = TestContext.CurrentContext.Random.NextUInt(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: wn, x31: w31); - AArch64.X((int)Rn, new Bits(Wn)); - Base.Ubfm(Op[31], Op[22], Op[21, 16], Op[15, 10], Op[9, 5], Op[4, 0]); - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); - - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); - } + CompareAgainstUnicorn(); } #endif } diff --git a/Ryujinx.Tests/Cpu/CpuTestCcmpImm.cs b/Ryujinx.Tests/Cpu/CpuTestCcmpImm.cs index 38d73878a2..a2c7344944 100644 --- a/Ryujinx.Tests/Cpu/CpuTestCcmpImm.cs +++ b/Ryujinx.Tests/Cpu/CpuTestCcmpImm.cs @@ -1,150 +1,103 @@ -//#define CcmpImm - -using ChocolArm64.State; +#define CcmpImm using NUnit.Framework; namespace Ryujinx.Tests.Cpu { - using Tester; - using Tester.Types; - - [Category("CcmpImm"), Ignore("Tested: first half of 2018.")] + [Category("CcmpImm")] public sealed class CpuTestCcmpImm : CpuTest { #if CcmpImm - [SetUp] - public void SetupTester() - { - AArch64.TakeReset(false); - } + private const int RndCnt = 2; + private const int RndCntImm = 2; + private const int RndCntNzcv = 2; - [Test, Description("CCMN , #, #, ")] - public void Ccmn_64bit([Values(1u, 31u)] uint Rn, + [Test, Pairwise, Description("CCMN , #, #, ")] + public void Ccmn_64bit([Values(1u, 31u)] uint rn, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn, - [Values(0u, 31u)] [Random(0u, 31u, 3)] uint imm, - [Random(0u, 15u, 1)] uint nzcv, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xn, + [Values(0u, 31u)] [Random(0u, 31u, RndCntImm)] uint imm, + [Random(0u, 15u, RndCntNzcv)] uint nzcv, [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // { - uint Opcode = 0xBA400800; // CCMN X0, #0, #0, EQ - Opcode |= ((Rn & 31) << 5); - Opcode |= ((imm & 31) << 16) | ((cond & 15) << 12) | ((nzcv & 15) << 0); + uint opcode = 0xBA400800; // CCMN X0, #0, #0, EQ + opcode |= ((rn & 31) << 5); + opcode |= ((imm & 31) << 16) | ((cond & 15) << 12) | ((nzcv & 15) << 0); - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X31: _X31); + ulong x31 = TestContext.CurrentContext.Random.NextULong(); - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: xn, x31: x31); - AArch64.X((int)Rn, new Bits(Xn)); - Base.Ccmn_Imm(Op[31], Op[20, 16], Op[15, 12], Op[9, 5], Op[3, 0]); - - Assert.Multiple(() => - { - Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); - Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); - Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); - Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); - }); + CompareAgainstUnicorn(); } - [Test, Description("CCMN , #, #, ")] - public void Ccmn_32bit([Values(1u, 31u)] uint Rn, + [Test, Pairwise, Description("CCMN , #, #, ")] + public void Ccmn_32bit([Values(1u, 31u)] uint rn, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn, - [Values(0u, 31u)] [Random(0u, 31u, 3)] uint imm, - [Random(0u, 15u, 1)] uint nzcv, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wn, + [Values(0u, 31u)] [Random(0u, 31u, RndCntImm)] uint imm, + [Random(0u, 15u, RndCntNzcv)] uint nzcv, [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // { - uint Opcode = 0x3A400800; // CCMN W0, #0, #0, EQ - Opcode |= ((Rn & 31) << 5); - Opcode |= ((imm & 31) << 16) | ((cond & 15) << 12) | ((nzcv & 15) << 0); + uint opcode = 0x3A400800; // CCMN W0, #0, #0, EQ + opcode |= ((rn & 31) << 5); + opcode |= ((imm & 31) << 16) | ((cond & 15) << 12) | ((nzcv & 15) << 0); - uint _W31 = TestContext.CurrentContext.Random.NextUInt(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X31: _W31); + uint w31 = TestContext.CurrentContext.Random.NextUInt(); - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: wn, x31: w31); - AArch64.X((int)Rn, new Bits(Wn)); - Base.Ccmn_Imm(Op[31], Op[20, 16], Op[15, 12], Op[9, 5], Op[3, 0]); - - Assert.Multiple(() => - { - Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); - Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); - Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); - Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); - }); + CompareAgainstUnicorn(); } - [Test, Description("CCMP , #, #, ")] - public void Ccmp_64bit([Values(1u, 31u)] uint Rn, + [Test, Pairwise, Description("CCMP , #, #, ")] + public void Ccmp_64bit([Values(1u, 31u)] uint rn, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn, - [Values(0u, 31u)] [Random(0u, 31u, 3)] uint imm, - [Random(0u, 15u, 1)] uint nzcv, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xn, + [Values(0u, 31u)] [Random(0u, 31u, RndCntImm)] uint imm, + [Random(0u, 15u, RndCntNzcv)] uint nzcv, [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // { - uint Opcode = 0xFA400800; // CCMP X0, #0, #0, EQ - Opcode |= ((Rn & 31) << 5); - Opcode |= ((imm & 31) << 16) | ((cond & 15) << 12) | ((nzcv & 15) << 0); + uint opcode = 0xFA400800; // CCMP X0, #0, #0, EQ + opcode |= ((rn & 31) << 5); + opcode |= ((imm & 31) << 16) | ((cond & 15) << 12) | ((nzcv & 15) << 0); - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X31: _X31); + ulong x31 = TestContext.CurrentContext.Random.NextULong(); - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: xn, x31: x31); - AArch64.X((int)Rn, new Bits(Xn)); - Base.Ccmp_Imm(Op[31], Op[20, 16], Op[15, 12], Op[9, 5], Op[3, 0]); - - Assert.Multiple(() => - { - Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); - Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); - Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); - Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); - }); + CompareAgainstUnicorn(); } - [Test, Description("CCMP , #, #, ")] - public void Ccmp_32bit([Values(1u, 31u)] uint Rn, + [Test, Pairwise, Description("CCMP , #, #, ")] + public void Ccmp_32bit([Values(1u, 31u)] uint rn, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn, - [Values(0u, 31u)] [Random(0u, 31u, 3)] uint imm, - [Random(0u, 15u, 1)] uint nzcv, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wn, + [Values(0u, 31u)] [Random(0u, 31u, RndCntImm)] uint imm, + [Random(0u, 15u, RndCntNzcv)] uint nzcv, [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // { - uint Opcode = 0x7A400800; // CCMP W0, #0, #0, EQ - Opcode |= ((Rn & 31) << 5); - Opcode |= ((imm & 31) << 16) | ((cond & 15) << 12) | ((nzcv & 15) << 0); + uint opcode = 0x7A400800; // CCMP W0, #0, #0, EQ + opcode |= ((rn & 31) << 5); + opcode |= ((imm & 31) << 16) | ((cond & 15) << 12) | ((nzcv & 15) << 0); - uint _W31 = TestContext.CurrentContext.Random.NextUInt(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X31: _W31); + uint w31 = TestContext.CurrentContext.Random.NextUInt(); - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: wn, x31: w31); - AArch64.X((int)Rn, new Bits(Wn)); - Base.Ccmp_Imm(Op[31], Op[20, 16], Op[15, 12], Op[9, 5], Op[3, 0]); - - Assert.Multiple(() => - { - Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); - Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); - Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); - Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); - }); + CompareAgainstUnicorn(); } #endif } diff --git a/Ryujinx.Tests/Cpu/CpuTestCcmpReg.cs b/Ryujinx.Tests/Cpu/CpuTestCcmpReg.cs index eb1c3abf2c..8cf5268ebf 100644 --- a/Ryujinx.Tests/Cpu/CpuTestCcmpReg.cs +++ b/Ryujinx.Tests/Cpu/CpuTestCcmpReg.cs @@ -1,162 +1,110 @@ -//#define CcmpReg - -using ChocolArm64.State; +#define CcmpReg using NUnit.Framework; namespace Ryujinx.Tests.Cpu { - using Tester; - using Tester.Types; - - [Category("CcmpReg"), Ignore("Tested: first half of 2018.")] + [Category("CcmpReg")] public sealed class CpuTestCcmpReg : CpuTest { #if CcmpReg - [SetUp] - public void SetupTester() - { - AArch64.TakeReset(false); - } + private const int RndCnt = 2; + private const int RndCntNzcv = 2; - [Test, Description("CCMN , , #, ")] - public void Ccmn_64bit([Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("CCMN , , #, ")] + public void Ccmn_64bit([Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xn, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xm, - [Random(0u, 15u, 1)] uint nzcv, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xm, + [Random(0u, 15u, RndCntNzcv)] uint nzcv, [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // { - uint Opcode = 0xBA400000; // CCMN X0, X0, #0, EQ - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5); - Opcode |= ((cond & 15) << 12) | ((nzcv & 15) << 0); + uint opcode = 0xBA400000; // CCMN X0, X0, #0, EQ + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5); + opcode |= ((cond & 15) << 12) | ((nzcv & 15) << 0); - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X31: _X31); + ulong x31 = TestContext.CurrentContext.Random.NextULong(); - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: xn, x2: xm, x31: x31); - AArch64.X((int)Rn, new Bits(Xn)); - AArch64.X((int)Rm, new Bits(Xm)); - Base.Ccmn_Reg(Op[31], Op[20, 16], Op[15, 12], Op[9, 5], Op[3, 0]); - - Assert.Multiple(() => - { - Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); - Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); - Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); - Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); - }); + CompareAgainstUnicorn(); } - [Test, Description("CCMN , , #, ")] - public void Ccmn_32bit([Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("CCMN , , #, ")] + public void Ccmn_32bit([Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wn, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wm, - [Random(0u, 15u, 1)] uint nzcv, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wm, + [Random(0u, 15u, RndCntNzcv)] uint nzcv, [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // { - uint Opcode = 0x3A400000; // CCMN W0, W0, #0, EQ - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5); - Opcode |= ((cond & 15) << 12) | ((nzcv & 15) << 0); + uint opcode = 0x3A400000; // CCMN W0, W0, #0, EQ + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5); + opcode |= ((cond & 15) << 12) | ((nzcv & 15) << 0); - uint _W31 = TestContext.CurrentContext.Random.NextUInt(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31); + uint w31 = TestContext.CurrentContext.Random.NextUInt(); - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31); - AArch64.X((int)Rn, new Bits(Wn)); - AArch64.X((int)Rm, new Bits(Wm)); - Base.Ccmn_Reg(Op[31], Op[20, 16], Op[15, 12], Op[9, 5], Op[3, 0]); - - Assert.Multiple(() => - { - Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); - Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); - Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); - Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); - }); + CompareAgainstUnicorn(); } - [Test, Description("CCMP , , #, ")] - public void Ccmp_64bit([Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("CCMP , , #, ")] + public void Ccmp_64bit([Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xn, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xm, - [Random(0u, 15u, 1)] uint nzcv, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xm, + [Random(0u, 15u, RndCntNzcv)] uint nzcv, [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // { - uint Opcode = 0xFA400000; // CCMP X0, X0, #0, EQ - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5); - Opcode |= ((cond & 15) << 12) | ((nzcv & 15) << 0); + uint opcode = 0xFA400000; // CCMP X0, X0, #0, EQ + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5); + opcode |= ((cond & 15) << 12) | ((nzcv & 15) << 0); - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X31: _X31); + ulong x31 = TestContext.CurrentContext.Random.NextULong(); - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: xn, x2: xm, x31: x31); - AArch64.X((int)Rn, new Bits(Xn)); - AArch64.X((int)Rm, new Bits(Xm)); - Base.Ccmp_Reg(Op[31], Op[20, 16], Op[15, 12], Op[9, 5], Op[3, 0]); - - Assert.Multiple(() => - { - Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); - Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); - Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); - Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); - }); + CompareAgainstUnicorn(); } - [Test, Description("CCMP , , #, ")] - public void Ccmp_32bit([Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("CCMP , , #, ")] + public void Ccmp_32bit([Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wn, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wm, - [Random(0u, 15u, 1)] uint nzcv, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wm, + [Random(0u, 15u, RndCntNzcv)] uint nzcv, [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // { - uint Opcode = 0x7A400000; // CCMP W0, W0, #0, EQ - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5); - Opcode |= ((cond & 15) << 12) | ((nzcv & 15) << 0); + uint opcode = 0x7A400000; // CCMP W0, W0, #0, EQ + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5); + opcode |= ((cond & 15) << 12) | ((nzcv & 15) << 0); - uint _W31 = TestContext.CurrentContext.Random.NextUInt(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31); + uint w31 = TestContext.CurrentContext.Random.NextUInt(); - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31); - AArch64.X((int)Rn, new Bits(Wn)); - AArch64.X((int)Rm, new Bits(Wm)); - Base.Ccmp_Reg(Op[31], Op[20, 16], Op[15, 12], Op[9, 5], Op[3, 0]); - - Assert.Multiple(() => - { - Assert.That(ThreadState.Negative, Is.EqualTo(Shared.PSTATE.N)); - Assert.That(ThreadState.Zero, Is.EqualTo(Shared.PSTATE.Z)); - Assert.That(ThreadState.Carry, Is.EqualTo(Shared.PSTATE.C)); - Assert.That(ThreadState.Overflow, Is.EqualTo(Shared.PSTATE.V)); - }); + CompareAgainstUnicorn(); } #endif } diff --git a/Ryujinx.Tests/Cpu/CpuTestCsel.cs b/Ryujinx.Tests/Cpu/CpuTestCsel.cs index 9dd61957f5..9764c2b782 100644 --- a/Ryujinx.Tests/Cpu/CpuTestCsel.cs +++ b/Ryujinx.Tests/Cpu/CpuTestCsel.cs @@ -1,318 +1,205 @@ -//#define Csel - -using ChocolArm64.State; +#define Csel using NUnit.Framework; namespace Ryujinx.Tests.Cpu { - using Tester; - using Tester.Types; - - [Category("Csel"), Ignore("Tested: first half of 2018.")] + [Category("Csel")] public sealed class CpuTestCsel : CpuTest { #if Csel - [SetUp] - public void SetupTester() - { - AArch64.TakeReset(false); - } + private const int RndCnt = 2; - [Test, Description("CSEL , , , ")] - public void Csel_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("CSEL , , , ")] + public void Csel_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xn, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xm, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xm, [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // { - uint Opcode = 0x9A800000; // CSEL X0, X0, X0, EQ - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((cond & 15) << 12); + uint opcode = 0x9A800000; // CSEL X0, X0, X0, EQ + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((cond & 15) << 12); - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X31: _X31); + ulong x31 = TestContext.CurrentContext.Random.NextULong(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: xn, x2: xm, x31: x31); - AArch64.X((int)Rn, new Bits(Xn)); - AArch64.X((int)Rm, new Bits(Xm)); - Base.Csel(Op[31], Op[20, 16], Op[15, 12], Op[9, 5], Op[4, 0]); - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); - } + CompareAgainstUnicorn(); } - [Test, Description("CSEL , , , ")] - public void Csel_32bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("CSEL , , , ")] + public void Csel_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wn, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wm, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wm, [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // { - uint Opcode = 0x1A800000; // CSEL W0, W0, W0, EQ - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((cond & 15) << 12); + uint opcode = 0x1A800000; // CSEL W0, W0, W0, EQ + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((cond & 15) << 12); - uint _W31 = TestContext.CurrentContext.Random.NextUInt(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31); + uint w31 = TestContext.CurrentContext.Random.NextUInt(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31); - AArch64.X((int)Rn, new Bits(Wn)); - AArch64.X((int)Rm, new Bits(Wm)); - Base.Csel(Op[31], Op[20, 16], Op[15, 12], Op[9, 5], Op[4, 0]); - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); - - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); - } + CompareAgainstUnicorn(); } - [Test, Description("CSINC , , , ")] - public void Csinc_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("CSINC , , , ")] + public void Csinc_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xn, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xm, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xm, [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // { - uint Opcode = 0x9A800400; // CSINC X0, X0, X0, EQ - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((cond & 15) << 12); + uint opcode = 0x9A800400; // CSINC X0, X0, X0, EQ + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((cond & 15) << 12); - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X31: _X31); + ulong x31 = TestContext.CurrentContext.Random.NextULong(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: xn, x2: xm, x31: x31); - AArch64.X((int)Rn, new Bits(Xn)); - AArch64.X((int)Rm, new Bits(Xm)); - Base.Csinc(Op[31], Op[20, 16], Op[15, 12], Op[9, 5], Op[4, 0]); - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); - } + CompareAgainstUnicorn(); } - [Test, Description("CSINC , , , ")] - public void Csinc_32bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("CSINC , , , ")] + public void Csinc_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wn, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wm, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wm, [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // { - uint Opcode = 0x1A800400; // CSINC W0, W0, W0, EQ - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((cond & 15) << 12); + uint opcode = 0x1A800400; // CSINC W0, W0, W0, EQ + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((cond & 15) << 12); - uint _W31 = TestContext.CurrentContext.Random.NextUInt(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31); + uint w31 = TestContext.CurrentContext.Random.NextUInt(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31); - AArch64.X((int)Rn, new Bits(Wn)); - AArch64.X((int)Rm, new Bits(Wm)); - Base.Csinc(Op[31], Op[20, 16], Op[15, 12], Op[9, 5], Op[4, 0]); - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); - - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); - } + CompareAgainstUnicorn(); } - [Test, Description("CSINV , , , ")] - public void Csinv_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("CSINV , , , ")] + public void Csinv_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xn, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xm, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xm, [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // { - uint Opcode = 0xDA800000; // CSINV X0, X0, X0, EQ - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((cond & 15) << 12); + uint opcode = 0xDA800000; // CSINV X0, X0, X0, EQ + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((cond & 15) << 12); - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X31: _X31); + ulong x31 = TestContext.CurrentContext.Random.NextULong(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: xn, x2: xm, x31: x31); - AArch64.X((int)Rn, new Bits(Xn)); - AArch64.X((int)Rm, new Bits(Xm)); - Base.Csinv(Op[31], Op[20, 16], Op[15, 12], Op[9, 5], Op[4, 0]); - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); - } + CompareAgainstUnicorn(); } - [Test, Description("CSINV , , , ")] - public void Csinv_32bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("CSINV , , , ")] + public void Csinv_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wn, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wm, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wm, [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // { - uint Opcode = 0x5A800000; // CSINV W0, W0, W0, EQ - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((cond & 15) << 12); + uint opcode = 0x5A800000; // CSINV W0, W0, W0, EQ + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((cond & 15) << 12); - uint _W31 = TestContext.CurrentContext.Random.NextUInt(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31); + uint w31 = TestContext.CurrentContext.Random.NextUInt(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31); - AArch64.X((int)Rn, new Bits(Wn)); - AArch64.X((int)Rm, new Bits(Wm)); - Base.Csinv(Op[31], Op[20, 16], Op[15, 12], Op[9, 5], Op[4, 0]); - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); - - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); - } + CompareAgainstUnicorn(); } - [Test, Description("CSNEG , , , ")] - public void Csneg_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("CSNEG , , , ")] + public void Csneg_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xn, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xn, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(1)] ulong Xm, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xm, [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // { - uint Opcode = 0xDA800400; // CSNEG X0, X0, X0, EQ - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((cond & 15) << 12); + uint opcode = 0xDA800400; // CSNEG X0, X0, X0, EQ + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((cond & 15) << 12); - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X31: _X31); + ulong x31 = TestContext.CurrentContext.Random.NextULong(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: xn, x2: xm, x31: x31); - AArch64.X((int)Rn, new Bits(Xn)); - AArch64.X((int)Rm, new Bits(Xm)); - Base.Csneg(Op[31], Op[20, 16], Op[15, 12], Op[9, 5], Op[4, 0]); - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); - } + CompareAgainstUnicorn(); } - [Test, Description("CSNEG , , , ")] - public void Csneg_32bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("CSNEG , , , ")] + public void Csneg_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wn, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wn, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(1)] uint Wm, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wm, [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // { - uint Opcode = 0x5A800400; // CSNEG W0, W0, W0, EQ - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((cond & 15) << 12); + uint opcode = 0x5A800400; // CSNEG W0, W0, W0, EQ + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((cond & 15) << 12); - uint _W31 = TestContext.CurrentContext.Random.NextUInt(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X31: _W31); + uint w31 = TestContext.CurrentContext.Random.NextUInt(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31); - AArch64.X((int)Rn, new Bits(Wn)); - AArch64.X((int)Rm, new Bits(Wm)); - Base.Csneg(Op[31], Op[20, 16], Op[15, 12], Op[9, 5], Op[4, 0]); - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); - - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); - } + CompareAgainstUnicorn(); } #endif } diff --git a/Ryujinx.Tests/Cpu/CpuTestMisc.cs b/Ryujinx.Tests/Cpu/CpuTestMisc.cs index 6db653d3fa..6d2440c183 100644 --- a/Ryujinx.Tests/Cpu/CpuTestMisc.cs +++ b/Ryujinx.Tests/Cpu/CpuTestMisc.cs @@ -1,19 +1,126 @@ -using ChocolArm64.State; +#define Misc + +using ARMeilleure.State; using NUnit.Framework; -using System.Runtime.Intrinsics.X86; - namespace Ryujinx.Tests.Cpu { - [Category("Misc"), Explicit] + [Category("Misc")] public sealed class CpuTestMisc : CpuTest { +#if Misc + private const int RndCnt = 2; + private const int RndCntImm = 2; + +#region "AluImm & Csel" + [Test, Pairwise] + public void Adds_Csinc_64bit([Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xn, + [Values(0u, 4095u)] [Random(0u, 4095u, RndCntImm)] uint imm, + [Values(0b00u, 0b01u)] uint shift, // + [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // + { + uint opCmn = 0xB100001F; // ADDS X31, X0, #0, LSL #0 -> CMN X0, #0, LSL #0 + uint opCset = 0x9A9F07E0; // CSINC X0, X31, X31, EQ -> CSET X0, NE + + opCmn |= ((shift & 3) << 22) | ((imm & 4095) << 10); + opCset |= ((cond & 15) << 12); + + SetContext(x0: xn); + Opcode(opCmn); + Opcode(opCset); + Opcode(0xD65F03C0); // RET + ExecuteOpcodes(); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Adds_Csinc_32bit([Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wn, + [Values(0u, 4095u)] [Random(0u, 4095u, RndCntImm)] uint imm, + [Values(0b00u, 0b01u)] uint shift, // + [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // + { + uint opCmn = 0x3100001F; // ADDS W31, W0, #0, LSL #0 -> CMN W0, #0, LSL #0 + uint opCset = 0x1A9F07E0; // CSINC W0, W31, W31, EQ -> CSET W0, NE + + opCmn |= ((shift & 3) << 22) | ((imm & 4095) << 10); + opCset |= ((cond & 15) << 12); + + SetContext(x0: wn); + Opcode(opCmn); + Opcode(opCset); + Opcode(0xD65F03C0); // RET + ExecuteOpcodes(); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Subs_Csinc_64bit([Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xn, + [Values(0u, 4095u)] [Random(0u, 4095u, RndCntImm)] uint imm, + [Values(0b00u, 0b01u)] uint shift, // + [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // + { + uint opCmp = 0xF100001F; // SUBS X31, X0, #0, LSL #0 -> CMP X0, #0, LSL #0 + uint opCset = 0x9A9F07E0; // CSINC X0, X31, X31, EQ -> CSET X0, NE + + opCmp |= ((shift & 3) << 22) | ((imm & 4095) << 10); + opCset |= ((cond & 15) << 12); + + SetContext(x0: xn); + Opcode(opCmp); + Opcode(opCset); + Opcode(0xD65F03C0); // RET + ExecuteOpcodes(); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Subs_Csinc_32bit([Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wn, + [Values(0u, 4095u)] [Random(0u, 4095u, RndCntImm)] uint imm, + [Values(0b00u, 0b01u)] uint shift, // + [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // + { + uint opCmp = 0x7100001F; // SUBS W31, W0, #0, LSL #0 -> CMP W0, #0, LSL #0 + uint opCset = 0x1A9F07E0; // CSINC W0, W31, W31, EQ -> CSET W0, NE + + opCmp |= ((shift & 3) << 22) | ((imm & 4095) << 10); + opCset |= ((cond & 15) << 12); + + SetContext(x0: wn); + Opcode(opCmp); + Opcode(opCset); + Opcode(0xD65F03C0); // RET + ExecuteOpcodes(); + + CompareAgainstUnicorn(); + } +#endregion + + [Explicit] [TestCase(0xFFFFFFFDu)] // Roots. [TestCase(0x00000005u)] - public void Misc1(uint A) + public void Misc1(uint a) { - // ((A + 3) * (A - 5)) / ((A + 5) * (A - 3)) = 0 + // ((a + 3) * (a - 5)) / ((a + 5) * (a - 3)) = 0 /* ADD W2, W0, 3 @@ -23,11 +130,10 @@ namespace Ryujinx.Tests.Cpu SUB W0, W0, #3 MUL W0, W1, W0 SDIV W0, W2, W0 - BRK #0 RET */ - SetThreadState(X0: A); + SetContext(x0: a); Opcode(0x11000C02); Opcode(0x51001401); Opcode(0x1B017C42); @@ -35,13 +141,13 @@ namespace Ryujinx.Tests.Cpu Opcode(0x51000C00); Opcode(0x1B007C20); Opcode(0x1AC00C40); - Opcode(0xD4200000); Opcode(0xD65F03C0); ExecuteOpcodes(); - Assert.That(GetThreadState().X0, Is.Zero); + Assert.That(GetContext().GetX(0), Is.Zero); } + [Explicit] [TestCase(-20f, -5f)] // 18 integer solutions. [TestCase(-12f, -6f)] [TestCase(-12f, 3f)] @@ -60,9 +166,9 @@ namespace Ryujinx.Tests.Cpu [TestCase( 12f, -3f)] [TestCase( 12f, 6f)] [TestCase( 20f, 5f)] - public void Misc2(float A, float B) + public void Misc2(float a, float b) { - // 1 / ((1 / A + 1 / B) ^ 2) = 16 + // 1 / ((1 / a + 1 / b) ^ 2) = 16 /* FMOV S2, 1.0e+0 @@ -71,26 +177,23 @@ namespace Ryujinx.Tests.Cpu FADD S0, S0, S1 FDIV S0, S2, S0 FMUL S0, S0, S0 - BRK #0 RET */ - SetThreadState( - V0: Sse.SetScalarVector128(A), - V1: Sse.SetScalarVector128(B)); + SetContext(v0: MakeVectorScalar(a), v1: MakeVectorScalar(b)); Opcode(0x1E2E1002); Opcode(0x1E201840); Opcode(0x1E211841); Opcode(0x1E212800); Opcode(0x1E201840); Opcode(0x1E200800); - Opcode(0xD4200000); Opcode(0xD65F03C0); ExecuteOpcodes(); - Assert.That(Sse41.Extract(GetThreadState().V0, 0), Is.EqualTo(16f)); + Assert.That(GetContext().GetV(0).AsFloat(), Is.EqualTo(16f)); } + [Explicit] [TestCase(-20d, -5d)] // 18 integer solutions. [TestCase(-12d, -6d)] [TestCase(-12d, 3d)] @@ -109,9 +212,9 @@ namespace Ryujinx.Tests.Cpu [TestCase( 12d, -3d)] [TestCase( 12d, 6d)] [TestCase( 20d, 5d)] - public void Misc3(double A, double B) + public void Misc3(double a, double b) { - // 1 / ((1 / A + 1 / B) ^ 2) = 16 + // 1 / ((1 / a + 1 / b) ^ 2) = 16 /* FMOV D2, 1.0e+0 @@ -120,77 +223,70 @@ namespace Ryujinx.Tests.Cpu FADD D0, D0, D1 FDIV D0, D2, D0 FMUL D0, D0, D0 - BRK #0 RET */ - SetThreadState( - V0: Sse.StaticCast(Sse2.SetScalarVector128(A)), - V1: Sse.StaticCast(Sse2.SetScalarVector128(B))); + SetContext(v0: MakeVectorScalar(a), v1: MakeVectorScalar(b)); Opcode(0x1E6E1002); Opcode(0x1E601840); Opcode(0x1E611841); Opcode(0x1E612800); Opcode(0x1E601840); Opcode(0x1E600800); - Opcode(0xD4200000); Opcode(0xD65F03C0); ExecuteOpcodes(); - Assert.That(VectorExtractDouble(GetThreadState().V0, 0), Is.EqualTo(16d)); + Assert.That(GetContext().GetV(0).AsDouble(), Is.EqualTo(16d)); } - [Test] - public void MiscF([Range(0u, 92u, 1u)] uint A) + [Test, Ignore("The Tester supports only one return point.")] + public void MiscF([Range(0u, 92u, 1u)] uint a) { - ulong F_n(uint n) + ulong Fn(uint n) { - ulong a = 0, b = 1, c; + ulong x = 0, y = 1, z; if (n == 0) { - return a; + return x; } for (uint i = 2; i <= n; i++) { - c = a + b; - a = b; - b = c; + z = x + y; + x = y; + y = z; } - return b; + return y; } /* - 0x0000000000000000: MOV W4, W0 - 0x0000000000000004: CBZ W0, #0x3C - 0x0000000000000008: CMP W0, #1 - 0x000000000000000C: B.LS #0x48 - 0x0000000000000010: MOVZ W2, #0x2 - 0x0000000000000014: MOVZ X1, #0x1 - 0x0000000000000018: MOVZ X3, #0 - 0x000000000000001C: ADD X0, X3, X1 - 0x0000000000000020: ADD W2, W2, #1 - 0x0000000000000024: MOV X3, X1 - 0x0000000000000028: MOV X1, X0 - 0x000000000000002C: CMP W4, W2 - 0x0000000000000030: B.HS #0x1C - 0x0000000000000034: BRK #0 - 0x0000000000000038: RET - 0x000000000000003C: MOVZ X0, #0 - 0x0000000000000040: BRK #0 - 0x0000000000000044: RET - 0x0000000000000048: MOVZ X0, #0x1 - 0x000000000000004C: BRK #0 - 0x0000000000000050: RET + 0x0000000000001000: MOV W4, W0 + 0x0000000000001004: CBZ W0, #0x34 + 0x0000000000001008: CMP W0, #1 + 0x000000000000100C: B.LS #0x34 + 0x0000000000001010: MOVZ W2, #0x2 + 0x0000000000001014: MOVZ X1, #0x1 + 0x0000000000001018: MOVZ X3, #0 + 0x000000000000101C: ADD X0, X3, X1 + 0x0000000000001020: ADD W2, W2, #1 + 0x0000000000001024: MOV X3, X1 + 0x0000000000001028: MOV X1, X0 + 0x000000000000102C: CMP W4, W2 + 0x0000000000001030: B.HS #-0x14 + 0x0000000000001034: RET + 0x0000000000001038: MOVZ X0, #0 + 0x000000000000103C: RET + 0x0000000000001040: MOVZ X0, #0x1 + 0x0000000000001044: RET */ - SetThreadState(X0: A); + SetContext(x0: a); Opcode(0x2A0003E4); - Opcode(0x340001C0); + Opcode(0x340001A0); Opcode(0x7100041F); - Opcode(0x540001E9); + Opcode(0x540001A9); Opcode(0x52800042); Opcode(0xD2800021); Opcode(0xD2800003); @@ -200,71 +296,67 @@ namespace Ryujinx.Tests.Cpu Opcode(0xAA0003E1); Opcode(0x6B02009F); Opcode(0x54FFFF62); - Opcode(0xD4200000); Opcode(0xD65F03C0); Opcode(0xD2800000); - Opcode(0xD4200000); Opcode(0xD65F03C0); Opcode(0xD2800020); - Opcode(0xD4200000); Opcode(0xD65F03C0); ExecuteOpcodes(); - Assert.That(GetThreadState().X0, Is.EqualTo(F_n(A))); + Assert.That(GetContext().GetX(0), Is.EqualTo(Fn(a))); } + [Explicit] [Test] public void MiscR() { - const ulong Result = 5; + const ulong result = 5; /* - 0x0000000000000000: MOV X0, #2 - 0x0000000000000004: MOV X1, #3 - 0x0000000000000008: ADD X0, X0, X1 - 0x000000000000000C: BRK #0 - 0x0000000000000010: RET + 0x0000000000001000: MOV X0, #2 + 0x0000000000001004: MOV X1, #3 + 0x0000000000001008: ADD X0, X0, X1 + 0x000000000000100C: RET */ Opcode(0xD2800040); Opcode(0xD2800061); Opcode(0x8B010000); - Opcode(0xD4200000); Opcode(0xD65F03C0); ExecuteOpcodes(); - Assert.That(GetThreadState().X0, Is.EqualTo(Result)); + Assert.That(GetContext().GetX(0), Is.EqualTo(result)); Reset(); /* - 0x0000000000000000: MOV X0, #3 - 0x0000000000000004: MOV X1, #2 - 0x0000000000000008: ADD X0, X0, X1 - 0x000000000000000C: BRK #0 - 0x0000000000000010: RET + 0x0000000000001000: MOV X0, #3 + 0x0000000000001004: MOV X1, #2 + 0x0000000000001008: ADD X0, X0, X1 + 0x000000000000100C: RET */ Opcode(0xD2800060); Opcode(0xD2800041); Opcode(0x8B010000); - Opcode(0xD4200000); Opcode(0xD65F03C0); ExecuteOpcodes(); - Assert.That(GetThreadState().X0, Is.EqualTo(Result)); + Assert.That(GetContext().GetX(0), Is.EqualTo(result)); } + [Explicit] [TestCase( 0ul)] [TestCase( 1ul)] [TestCase( 2ul)] [TestCase(42ul)] - public void SanityCheck(ulong A) + public void SanityCheck(ulong a) { - uint Opcode = 0xD503201F; // NOP - AThreadState ThreadState = SingleOpcode(Opcode, X0: A); + uint opcode = 0xD503201F; // NOP + ExecutionContext context = SingleOpcode(opcode, x0: a); - Assert.That(ThreadState.X0, Is.EqualTo(A)); + Assert.That(context.GetX(0), Is.EqualTo(a)); } +#endif } } diff --git a/Ryujinx.Tests/Cpu/CpuTestMov.cs b/Ryujinx.Tests/Cpu/CpuTestMov.cs index 9c7e3255af..fa51c07258 100644 --- a/Ryujinx.Tests/Cpu/CpuTestMov.cs +++ b/Ryujinx.Tests/Cpu/CpuTestMov.cs @@ -1,188 +1,112 @@ -//#define Mov - -using ChocolArm64.State; +#define Mov using NUnit.Framework; namespace Ryujinx.Tests.Cpu { - using Tester; - using Tester.Types; - - [Category("Mov"), Ignore("Tested: first half of 2018.")] + [Category("Mov")] public sealed class CpuTestMov : CpuTest { #if Mov - [SetUp] - public void SetupTester() - { - AArch64.TakeReset(false); - } + private const int RndCnt = 2; + private const int RndCntImm = 2; - [Test, Description("MOVK , #{, LSL #}")] - public void Movk_64bit([Values(0u, 31u)] uint Rd, - [Random(12)] ulong _Xd, - [Values(0u, 65535u)] [Random(0u, 65535u, 10)] uint imm, + [Test, Pairwise, Description("MOVK , #{, LSL #}")] + public void Movk_64bit([Values(0u, 31u)] uint rd, + [Random(RndCnt)] ulong xd, + [Values(0u, 65535u)] [Random(0u, 65535u, RndCntImm)] uint imm, [Values(0u, 16u, 32u, 48u)] uint shift) { - uint Opcode = 0xF2800000; // MOVK X0, #0, LSL #0 - Opcode |= ((Rd & 31) << 0); - Opcode |= (((shift / 16) & 3) << 21) | ((imm & 65535) << 5); + uint opcode = 0xF2800000; // MOVK X0, #0, LSL #0 + opcode |= ((rd & 31) << 0); + opcode |= (((shift / 16) & 3) << 21) | ((imm & 65535) << 5); - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - AThreadState ThreadState = SingleOpcode(Opcode, X0: _Xd, X31: _X31); + ulong x31 = TestContext.CurrentContext.Random.NextULong(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x0: xd, x31: x31); - AArch64.X((int)Rd, new Bits(_Xd)); - Base.Movk(Op[31], Op[22, 21], Op[20, 5], Op[4, 0]); - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); - } + CompareAgainstUnicorn(); } - [Test, Description("MOVK , #{, LSL #}")] - public void Movk_32bit([Values(0u, 31u)] uint Rd, - [Random(12)] uint _Wd, - [Values(0u, 65535u)] [Random(0u, 65535u, 10)] uint imm, + [Test, Pairwise, Description("MOVK , #{, LSL #}")] + public void Movk_32bit([Values(0u, 31u)] uint rd, + [Random(RndCnt)] uint wd, + [Values(0u, 65535u)] [Random(0u, 65535u, RndCntImm)] uint imm, [Values(0u, 16u)] uint shift) { - uint Opcode = 0x72800000; // MOVK W0, #0, LSL #0 - Opcode |= ((Rd & 31) << 0); - Opcode |= (((shift / 16) & 3) << 21) | ((imm & 65535) << 5); + uint opcode = 0x72800000; // MOVK W0, #0, LSL #0 + opcode |= ((rd & 31) << 0); + opcode |= (((shift / 16) & 3) << 21) | ((imm & 65535) << 5); - uint _W31 = TestContext.CurrentContext.Random.NextUInt(); - AThreadState ThreadState = SingleOpcode(Opcode, X0: _Wd, X31: _W31); + uint w31 = TestContext.CurrentContext.Random.NextUInt(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x0: wd, x31: w31); - AArch64.X((int)Rd, new Bits(_Wd)); - Base.Movk(Op[31], Op[22, 21], Op[20, 5], Op[4, 0]); - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); - - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); - } + CompareAgainstUnicorn(); } - [Test, Description("MOVN , #{, LSL #}")] - public void Movn_64bit([Values(0u, 31u)] uint Rd, - [Values(0u, 65535u)] [Random(0u, 65535u, 128)] uint imm, + [Test, Pairwise, Description("MOVN , #{, LSL #}")] + public void Movn_64bit([Values(0u, 31u)] uint rd, + [Values(0u, 65535u)] [Random(0u, 65535u, RndCntImm)] uint imm, [Values(0u, 16u, 32u, 48u)] uint shift) { - uint Opcode = 0x92800000; // MOVN X0, #0, LSL #0 - Opcode |= ((Rd & 31) << 0); - Opcode |= (((shift / 16) & 3) << 21) | ((imm & 65535) << 5); + uint opcode = 0x92800000; // MOVN X0, #0, LSL #0 + opcode |= ((rd & 31) << 0); + opcode |= (((shift / 16) & 3) << 21) | ((imm & 65535) << 5); - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - AThreadState ThreadState = SingleOpcode(Opcode, X31: _X31); + ulong x31 = TestContext.CurrentContext.Random.NextULong(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x31: x31); - Base.Movn(Op[31], Op[22, 21], Op[20, 5], Op[4, 0]); - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); - } + CompareAgainstUnicorn(); } - [Test, Description("MOVN , #{, LSL #}")] - public void Movn_32bit([Values(0u, 31u)] uint Rd, - [Values(0u, 65535u)] [Random(0u, 65535u, 128)] uint imm, + [Test, Pairwise, Description("MOVN , #{, LSL #}")] + public void Movn_32bit([Values(0u, 31u)] uint rd, + [Values(0u, 65535u)] [Random(0u, 65535u, RndCntImm)] uint imm, [Values(0u, 16u)] uint shift) { - uint Opcode = 0x12800000; // MOVN W0, #0, LSL #0 - Opcode |= ((Rd & 31) << 0); - Opcode |= (((shift / 16) & 3) << 21) | ((imm & 65535) << 5); + uint opcode = 0x12800000; // MOVN W0, #0, LSL #0 + opcode |= ((rd & 31) << 0); + opcode |= (((shift / 16) & 3) << 21) | ((imm & 65535) << 5); - uint _W31 = TestContext.CurrentContext.Random.NextUInt(); - AThreadState ThreadState = SingleOpcode(Opcode, X31: _W31); + uint w31 = TestContext.CurrentContext.Random.NextUInt(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x31: w31); - Base.Movn(Op[31], Op[22, 21], Op[20, 5], Op[4, 0]); - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); - - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); - } + CompareAgainstUnicorn(); } - [Test, Description("MOVZ , #{, LSL #}")] - public void Movz_64bit([Values(0u, 31u)] uint Rd, - [Values(0u, 65535u)] [Random(0u, 65535u, 128)] uint imm, + [Test, Pairwise, Description("MOVZ , #{, LSL #}")] + public void Movz_64bit([Values(0u, 31u)] uint rd, + [Values(0u, 65535u)] [Random(0u, 65535u, RndCntImm)] uint imm, [Values(0u, 16u, 32u, 48u)] uint shift) { - uint Opcode = 0xD2800000; // MOVZ X0, #0, LSL #0 - Opcode |= ((Rd & 31) << 0); - Opcode |= (((shift / 16) & 3) << 21) | ((imm & 65535) << 5); + uint opcode = 0xD2800000; // MOVZ X0, #0, LSL #0 + opcode |= ((rd & 31) << 0); + opcode |= (((shift / 16) & 3) << 21) | ((imm & 65535) << 5); - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - AThreadState ThreadState = SingleOpcode(Opcode, X31: _X31); + ulong x31 = TestContext.CurrentContext.Random.NextULong(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x31: x31); - Base.Movz(Op[31], Op[22, 21], Op[20, 5], Op[4, 0]); - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); - } + CompareAgainstUnicorn(); } - [Test, Description("MOVZ , #{, LSL #}")] - public void Movz_32bit([Values(0u, 31u)] uint Rd, - [Values(0u, 65535u)] [Random(0u, 65535u, 128)] uint imm, + [Test, Pairwise, Description("MOVZ , #{, LSL #}")] + public void Movz_32bit([Values(0u, 31u)] uint rd, + [Values(0u, 65535u)] [Random(0u, 65535u, RndCntImm)] uint imm, [Values(0u, 16u)] uint shift) { - uint Opcode = 0x52800000; // MOVZ W0, #0, LSL #0 - Opcode |= ((Rd & 31) << 0); - Opcode |= (((shift / 16) & 3) << 21) | ((imm & 65535) << 5); + uint opcode = 0x52800000; // MOVZ W0, #0, LSL #0 + opcode |= ((rd & 31) << 0); + opcode |= (((shift / 16) & 3) << 21) | ((imm & 65535) << 5); - uint _W31 = TestContext.CurrentContext.Random.NextUInt(); - AThreadState ThreadState = SingleOpcode(Opcode, X31: _W31); + uint w31 = TestContext.CurrentContext.Random.NextUInt(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x31: w31); - Base.Movz(Op[31], Op[22, 21], Op[20, 5], Op[4, 0]); - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); - - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); - } + CompareAgainstUnicorn(); } #endif } diff --git a/Ryujinx.Tests/Cpu/CpuTestMul.cs b/Ryujinx.Tests/Cpu/CpuTestMul.cs index 9bdc1fa652..4ad7cf1104 100644 --- a/Ryujinx.Tests/Cpu/CpuTestMul.cs +++ b/Ryujinx.Tests/Cpu/CpuTestMul.cs @@ -1,374 +1,227 @@ -//#define Mul - -using ChocolArm64.State; +#define Mul using NUnit.Framework; namespace Ryujinx.Tests.Cpu { - using Tester; - using Tester.Types; - - [Category("Mul"), Ignore("Tested: first half of 2018.")] + [Category("Mul")] public sealed class CpuTestMul : CpuTest { #if Mul - [SetUp] - public void SetupTester() + private const int RndCnt = 2; + + [Test, Pairwise, Description("MADD , , , ")] + public void Madd_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(3u, 31u)] uint ra, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xa) { - AArch64.TakeReset(false); + uint opcode = 0x9B000000; // MADD X0, X0, X0, X0 + opcode |= ((rm & 31) << 16) | ((ra & 31) << 10) | ((rn & 31) << 5) | ((rd & 31) << 0); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xn, x2: xm, x3: xa, x31: x31); + + CompareAgainstUnicorn(); } - [Test, Description("MADD , , , ")] - public void Madd_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, - [Values(3u, 31u)] uint Ra, - [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(2)] ulong Xn, - [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(2)] ulong Xm, - [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(2)] ulong Xa) + [Test, Pairwise, Description("MADD , , , ")] + public void Madd_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(3u, 31u)] uint ra, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wa) { - uint Opcode = 0x9B000000; // MADD X0, X0, X0, X0 - Opcode |= ((Rm & 31) << 16) | ((Ra & 31) << 10) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + uint opcode = 0x1B000000; // MADD W0, W0, W0, W0 + opcode |= ((rm & 31) << 16) | ((ra & 31) << 10) | ((rn & 31) << 5) | ((rd & 31) << 0); - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X3: Xa, X31: _X31); + uint w31 = TestContext.CurrentContext.Random.NextUInt(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: wn, x2: wm, x3: wa, x31: w31); - AArch64.X((int)Rn, new Bits(Xn)); - AArch64.X((int)Rm, new Bits(Xm)); - AArch64.X((int)Ra, new Bits(Xa)); - Base.Madd(Op[31], Op[20, 16], Op[14, 10], Op[9, 5], Op[4, 0]); - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); - } + CompareAgainstUnicorn(); } - [Test, Description("MADD , , , ")] - public void Madd_32bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, - [Values(3u, 31u)] uint Ra, - [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(2)] uint Wn, - [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(2)] uint Wm, - [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(2)] uint Wa) + [Test, Pairwise, Description("MSUB , , , ")] + public void Msub_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(3u, 31u)] uint ra, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xa) { - uint Opcode = 0x1B000000; // MADD W0, W0, W0, W0 - Opcode |= ((Rm & 31) << 16) | ((Ra & 31) << 10) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + uint opcode = 0x9B008000; // MSUB X0, X0, X0, X0 + opcode |= ((rm & 31) << 16) | ((ra & 31) << 10) | ((rn & 31) << 5) | ((rd & 31) << 0); - uint _W31 = TestContext.CurrentContext.Random.NextUInt(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X3: Wa, X31: _W31); + ulong x31 = TestContext.CurrentContext.Random.NextULong(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: xn, x2: xm, x3: xa, x31: x31); - AArch64.X((int)Rn, new Bits(Wn)); - AArch64.X((int)Rm, new Bits(Wm)); - AArch64.X((int)Ra, new Bits(Wa)); - Base.Madd(Op[31], Op[20, 16], Op[14, 10], Op[9, 5], Op[4, 0]); - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); - - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); - } + CompareAgainstUnicorn(); } - [Test, Description("MSUB , , , ")] - public void Msub_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, - [Values(3u, 31u)] uint Ra, - [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(2)] ulong Xn, - [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(2)] ulong Xm, - [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(2)] ulong Xa) + [Test, Pairwise, Description("MSUB , , , ")] + public void Msub_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(3u, 31u)] uint ra, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wa) { - uint Opcode = 0x9B008000; // MSUB X0, X0, X0, X0 - Opcode |= ((Rm & 31) << 16) | ((Ra & 31) << 10) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + uint opcode = 0x1B008000; // MSUB W0, W0, W0, W0 + opcode |= ((rm & 31) << 16) | ((ra & 31) << 10) | ((rn & 31) << 5) | ((rd & 31) << 0); - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X3: Xa, X31: _X31); + uint w31 = TestContext.CurrentContext.Random.NextUInt(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: wn, x2: wm, x3: wa, x31: w31); - AArch64.X((int)Rn, new Bits(Xn)); - AArch64.X((int)Rm, new Bits(Xm)); - AArch64.X((int)Ra, new Bits(Xa)); - Base.Msub(Op[31], Op[20, 16], Op[14, 10], Op[9, 5], Op[4, 0]); - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); - } + CompareAgainstUnicorn(); } - [Test, Description("MSUB , , , ")] - public void Msub_32bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, - [Values(3u, 31u)] uint Ra, - [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(2)] uint Wn, - [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(2)] uint Wm, - [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(2)] uint Wa) - { - uint Opcode = 0x1B008000; // MSUB W0, W0, W0, W0 - Opcode |= ((Rm & 31) << 16) | ((Ra & 31) << 10) | ((Rn & 31) << 5) | ((Rd & 31) << 0); - - uint _W31 = TestContext.CurrentContext.Random.NextUInt(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X3: Wa, X31: _W31); - - if (Rd != 31) - { - Bits Op = new Bits(Opcode); - - AArch64.X((int)Rn, new Bits(Wn)); - AArch64.X((int)Rm, new Bits(Wm)); - AArch64.X((int)Ra, new Bits(Wa)); - Base.Msub(Op[31], Op[20, 16], Op[14, 10], Op[9, 5], Op[4, 0]); - uint Wd = AArch64.X(32, (int)Rd).ToUInt32(); - - Assert.That((uint)ThreadState.X0, Is.EqualTo(Wd)); - } - else - { - Assert.That((uint)ThreadState.X31, Is.EqualTo(_W31)); - } - } - - [Test, Description("SMADDL , , , ")] - public void Smaddl_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, - [Values(3u, 31u)] uint Ra, + [Test, Pairwise, Description("SMADDL , , , ")] + public void Smaddl_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(3u, 31u)] uint ra, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(2)] uint Wn, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wn, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(2)] uint Wm, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wm, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(2)] ulong Xa) + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xa) { - uint Opcode = 0x9B200000; // SMADDL X0, W0, W0, X0 - Opcode |= ((Rm & 31) << 16) | ((Ra & 31) << 10) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + uint opcode = 0x9B200000; // SMADDL X0, W0, W0, X0 + opcode |= ((rm & 31) << 16) | ((ra & 31) << 10) | ((rn & 31) << 5) | ((rd & 31) << 0); - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X3: Xa, X31: _X31); + ulong x31 = TestContext.CurrentContext.Random.NextULong(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: wn, x2: wm, x3: xa, x31: x31); - AArch64.X((int)Rn, new Bits(Wn)); - AArch64.X((int)Rm, new Bits(Wm)); - AArch64.X((int)Ra, new Bits(Xa)); - Base.Smaddl(Op[20, 16], Op[14, 10], Op[9, 5], Op[4, 0]); - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); - } + CompareAgainstUnicorn(); } - [Test, Description("UMADDL , , , ")] - public void Umaddl_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, - [Values(3u, 31u)] uint Ra, + [Test, Pairwise, Description("UMADDL , , , ")] + public void Umaddl_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(3u, 31u)] uint ra, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(2)] uint Wn, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wn, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(2)] uint Wm, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wm, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(2)] ulong Xa) + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xa) { - uint Opcode = 0x9BA00000; // UMADDL X0, W0, W0, X0 - Opcode |= ((Rm & 31) << 16) | ((Ra & 31) << 10) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + uint opcode = 0x9BA00000; // UMADDL X0, W0, W0, X0 + opcode |= ((rm & 31) << 16) | ((ra & 31) << 10) | ((rn & 31) << 5) | ((rd & 31) << 0); - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X3: Xa, X31: _X31); + ulong x31 = TestContext.CurrentContext.Random.NextULong(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: wn, x2: wm, x3: xa, x31: x31); - AArch64.X((int)Rn, new Bits(Wn)); - AArch64.X((int)Rm, new Bits(Wm)); - AArch64.X((int)Ra, new Bits(Xa)); - Base.Umaddl(Op[20, 16], Op[14, 10], Op[9, 5], Op[4, 0]); - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); - } + CompareAgainstUnicorn(); } - [Test, Description("SMSUBL , , , ")] - public void Smsubl_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, - [Values(3u, 31u)] uint Ra, + [Test, Pairwise, Description("SMSUBL , , , ")] + public void Smsubl_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(3u, 31u)] uint ra, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(2)] uint Wn, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wn, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(2)] uint Wm, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wm, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(2)] ulong Xa) + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xa) { - uint Opcode = 0x9B208000; // SMSUBL X0, W0, W0, X0 - Opcode |= ((Rm & 31) << 16) | ((Ra & 31) << 10) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + uint opcode = 0x9B208000; // SMSUBL X0, W0, W0, X0 + opcode |= ((rm & 31) << 16) | ((ra & 31) << 10) | ((rn & 31) << 5) | ((rd & 31) << 0); - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X3: Xa, X31: _X31); + ulong x31 = TestContext.CurrentContext.Random.NextULong(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: wn, x2: wm, x3: xa, x31: x31); - AArch64.X((int)Rn, new Bits(Wn)); - AArch64.X((int)Rm, new Bits(Wm)); - AArch64.X((int)Ra, new Bits(Xa)); - Base.Smsubl(Op[20, 16], Op[14, 10], Op[9, 5], Op[4, 0]); - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); - } + CompareAgainstUnicorn(); } - [Test, Description("UMSUBL , , , ")] - public void Umsubl_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, - [Values(3u, 31u)] uint Ra, + [Test, Pairwise, Description("UMSUBL , , , ")] + public void Umsubl_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(3u, 31u)] uint ra, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(2)] uint Wn, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wn, [Values(0x00000000u, 0x7FFFFFFFu, - 0x80000000u, 0xFFFFFFFFu)] [Random(2)] uint Wm, + 0x80000000u, 0xFFFFFFFFu)] [Random(RndCnt)] uint wm, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(2)] ulong Xa) + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xa) { - uint Opcode = 0x9BA08000; // UMSUBL X0, W0, W0, X0 - Opcode |= ((Rm & 31) << 16) | ((Ra & 31) << 10) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + uint opcode = 0x9BA08000; // UMSUBL X0, W0, W0, X0 + opcode |= ((rm & 31) << 16) | ((ra & 31) << 10) | ((rn & 31) << 5) | ((rd & 31) << 0); - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, X2: Wm, X3: Xa, X31: _X31); + ulong x31 = TestContext.CurrentContext.Random.NextULong(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: wn, x2: wm, x3: xa, x31: x31); - AArch64.X((int)Rn, new Bits(Wn)); - AArch64.X((int)Rm, new Bits(Wm)); - AArch64.X((int)Ra, new Bits(Xa)); - Base.Umsubl(Op[20, 16], Op[14, 10], Op[9, 5], Op[4, 0]); - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); - } + CompareAgainstUnicorn(); } - [Test, Description("SMULH , , ")] - public void Smulh_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("SMULH , , ")] + public void Smulh_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(16)] ulong Xn, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xn, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(16)] ulong Xm) + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xm) { - uint Opcode = 0x9B407C00; // SMULH X0, X0, X0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + uint opcode = 0x9B407C00; // SMULH X0, X0, X0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X31: _X31); + ulong x31 = TestContext.CurrentContext.Random.NextULong(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: xn, x2: xm, x31: x31); - AArch64.X((int)Rn, new Bits(Xn)); - AArch64.X((int)Rm, new Bits(Xm)); - Base.Smulh(Op[20, 16], Op[9, 5], Op[4, 0]); - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); - } + CompareAgainstUnicorn(); } - [Test, Description("UMULH , , ")] - public void Umulh_64bit([Values(0u, 31u)] uint Rd, - [Values(1u, 31u)] uint Rn, - [Values(2u, 31u)] uint Rm, + [Test, Pairwise, Description("UMULH , , ")] + public void Umulh_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(16)] ulong Xn, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xn, [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, - 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(16)] ulong Xm) + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] [Random(RndCnt)] ulong xm) { - uint Opcode = 0x9BC07C00; // UMULH X0, X0, X0 - Opcode |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + uint opcode = 0x9BC07C00; // UMULH X0, X0, X0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); - ulong _X31 = TestContext.CurrentContext.Random.NextULong(); - AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, X2: Xm, X31: _X31); + ulong x31 = TestContext.CurrentContext.Random.NextULong(); - if (Rd != 31) - { - Bits Op = new Bits(Opcode); + SingleOpcode(opcode, x1: xn, x2: xm, x31: x31); - AArch64.X((int)Rn, new Bits(Xn)); - AArch64.X((int)Rm, new Bits(Xm)); - Base.Umulh(Op[20, 16], Op[9, 5], Op[4, 0]); - ulong Xd = AArch64.X(64, (int)Rd).ToUInt64(); - - Assert.That((ulong)ThreadState.X0, Is.EqualTo(Xd)); - } - else - { - Assert.That((ulong)ThreadState.X31, Is.EqualTo(_X31)); - } + CompareAgainstUnicorn(); } #endif } diff --git a/Ryujinx.Tests/Cpu/CpuTestSimd.cs b/Ryujinx.Tests/Cpu/CpuTestSimd.cs index 02c5b25b24..8f7e206972 100644 --- a/Ryujinx.Tests/Cpu/CpuTestSimd.cs +++ b/Ryujinx.Tests/Cpu/CpuTestSimd.cs @@ -1,27 +1,96 @@ #define Simd -using ChocolArm64.State; +using ARMeilleure.State; using NUnit.Framework; -using System.Runtime.Intrinsics; +using System; +using System.Collections.Generic; namespace Ryujinx.Tests.Cpu { - using Tester; - using Tester.Types; - - [Category("Simd")/*, Ignore("Tested: second half of 2018.")*/] + [Category("Simd")] public sealed class CpuTestSimd : CpuTest { #if Simd - [SetUp] - public void SetupTester() + +#region "Helper methods" + private static byte GenLeadingSignsMinus8(int cnt) // 0 <= cnt <= 7 { - AArch64.TakeReset(false); + return (byte)(~(uint)GenLeadingZeros8(cnt + 1)); + } + + private static ushort GenLeadingSignsMinus16(int cnt) // 0 <= cnt <= 15 + { + return (ushort)(~(uint)GenLeadingZeros16(cnt + 1)); + } + + private static uint GenLeadingSignsMinus32(int cnt) // 0 <= cnt <= 31 + { + return ~GenLeadingZeros32(cnt + 1); + } + + private static byte GenLeadingSignsPlus8(int cnt) // 0 <= cnt <= 7 + { + return GenLeadingZeros8(cnt + 1); + } + + private static ushort GenLeadingSignsPlus16(int cnt) // 0 <= cnt <= 15 + { + return GenLeadingZeros16(cnt + 1); + } + + private static uint GenLeadingSignsPlus32(int cnt) // 0 <= cnt <= 31 + { + return GenLeadingZeros32(cnt + 1); + } + + private static byte GenLeadingZeros8(int cnt) // 0 <= cnt <= 8 + { + if (cnt == 8) return (byte)0; + if (cnt == 7) return (byte)1; + + byte rnd = TestContext.CurrentContext.Random.NextByte(); + sbyte mask = sbyte.MinValue; + + return (byte)(((uint)rnd >> (cnt + 1)) | ((uint)((byte)mask) >> cnt)); + } + + private static ushort GenLeadingZeros16(int cnt) // 0 <= cnt <= 16 + { + if (cnt == 16) return (ushort)0; + if (cnt == 15) return (ushort)1; + + ushort rnd = TestContext.CurrentContext.Random.NextUShort(); + short mask = short.MinValue; + + return (ushort)(((uint)rnd >> (cnt + 1)) | ((uint)((ushort)mask) >> cnt)); + } + + private static uint GenLeadingZeros32(int cnt) // 0 <= cnt <= 32 + { + if (cnt == 32) return 0u; + if (cnt == 31) return 1u; + + uint rnd = TestContext.CurrentContext.Random.NextUInt(); + int mask = int.MinValue; + + return (rnd >> (cnt + 1)) | ((uint)mask >> cnt); + } +#endregion + +#region "ValueSource (Types)" + private static ulong[] _1B1H1S1D_() + { + return new ulong[] { 0x0000000000000000ul, 0x000000000000007Ful, + 0x0000000000000080ul, 0x00000000000000FFul, + 0x0000000000007FFFul, 0x0000000000008000ul, + 0x000000000000FFFFul, 0x000000007FFFFFFFul, + 0x0000000080000000ul, 0x00000000FFFFFFFFul, + 0x7FFFFFFFFFFFFFFFul, 0x8000000000000000ul, + 0xFFFFFFFFFFFFFFFFul }; } -#region "ValueSource" private static ulong[] _1D_() { return new ulong[] { 0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, @@ -37,6 +106,24 @@ namespace Ryujinx.Tests.Cpu 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul }; } + private static ulong[] _1S_() + { + return new ulong[] { 0x0000000000000000ul, 0x000000007FFFFFFFul, + 0x0000000080000000ul, 0x00000000FFFFFFFFul }; + } + + private static ulong[] _2S_() + { + return new ulong[] { 0x0000000000000000ul, 0x7FFFFFFF7FFFFFFFul, + 0x8000000080000000ul, 0xFFFFFFFFFFFFFFFFul }; + } + + private static ulong[] _4H_() + { + return new ulong[] { 0x0000000000000000ul, 0x7FFF7FFF7FFF7FFFul, + 0x8000800080008000ul, 0xFFFFFFFFFFFFFFFFul }; + } + private static ulong[] _4H2S1D_() { return new ulong[] { 0x0000000000000000ul, 0x7FFF7FFF7FFF7FFFul, @@ -74,1105 +161,3306 @@ namespace Ryujinx.Tests.Cpu 0x8000000080000000ul, 0x7FFFFFFFFFFFFFFFul, 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul }; } -#endregion - [Test, Description("ABS , ")] - public void Abs_S_D([ValueSource("_1D_")] [Random(1)] ulong A) + private static uint[] _W_() { - uint Opcode = 0x5EE0B820; // ABS D0, D1 - Bits Op = new Bits(Opcode); - - Vector128 V0 = MakeVectorE1(TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0(A); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); - - AArch64.V(1, new Bits(A)); - SimdFp.Abs_S(Op[23, 22], Op[9, 5], Op[4, 0]); - - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); + return new uint[] { 0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu }; } - [Test, Description("ABS ., .")] - public void Abs_V_8B_4H_2S([ValueSource("_8B4H2S_")] [Random(1)] ulong A, - [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + private static ulong[] _X_() { - uint Opcode = 0x0E20B820; // ABS V0.8B, V1.8B - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); + return new ulong[] { 0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul }; + } - Vector128 V0 = MakeVectorE1(TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0(A); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); + private static IEnumerable _1H_F_() + { + yield return 0x000000000000FBFFul; // -Max Normal + yield return 0x0000000000008400ul; // -Min Normal + yield return 0x00000000000083FFul; // -Max Subnormal + yield return 0x0000000000008001ul; // -Min Subnormal + yield return 0x0000000000007BFFul; // +Max Normal + yield return 0x0000000000000400ul; // +Min Normal + yield return 0x00000000000003FFul; // +Max Subnormal + yield return 0x0000000000000001ul; // +Min Subnormal - AArch64.V(1, new Bits(A)); - SimdFp.Abs_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]); - - Assert.Multiple(() => + if (!NoZeros) { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); + yield return 0x0000000000008000ul; // -Zero + yield return 0x0000000000000000ul; // +Zero + } + + if (!NoInfs) + { + yield return 0x000000000000FC00ul; // -Infinity + yield return 0x0000000000007C00ul; // +Infinity + } + + if (!NoNaNs) + { + yield return 0x000000000000FE00ul; // -QNaN (all zeros payload) + yield return 0x000000000000FDFFul; // -SNaN (all ones payload) + yield return 0x0000000000007E00ul; // +QNaN (all zeros payload) (DefaultNaN) + yield return 0x0000000000007DFFul; // +SNaN (all ones payload) + } + + for (int cnt = 1; cnt <= RndCnt; cnt++) + { + ulong grbg = TestContext.CurrentContext.Random.NextUShort(); + ulong rnd1 = GenNormalH(); + ulong rnd2 = GenSubnormalH(); + + yield return (grbg << 48) | (grbg << 32) | (grbg << 16) | rnd1; + yield return (grbg << 48) | (grbg << 32) | (grbg << 16) | rnd2; + } + } + + private static IEnumerable _4H_F_() + { + yield return 0xFBFFFBFFFBFFFBFFul; // -Max Normal + yield return 0x8400840084008400ul; // -Min Normal + yield return 0x83FF83FF83FF83FFul; // -Max Subnormal + yield return 0x8001800180018001ul; // -Min Subnormal + yield return 0x7BFF7BFF7BFF7BFFul; // +Max Normal + yield return 0x0400040004000400ul; // +Min Normal + yield return 0x03FF03FF03FF03FFul; // +Max Subnormal + yield return 0x0001000100010001ul; // +Min Subnormal + + if (!NoZeros) + { + yield return 0x8000800080008000ul; // -Zero + yield return 0x0000000000000000ul; // +Zero + } + + if (!NoInfs) + { + yield return 0xFC00FC00FC00FC00ul; // -Infinity + yield return 0x7C007C007C007C00ul; // +Infinity + } + + if (!NoNaNs) + { + yield return 0xFE00FE00FE00FE00ul; // -QNaN (all zeros payload) + yield return 0xFDFFFDFFFDFFFDFFul; // -SNaN (all ones payload) + yield return 0x7E007E007E007E00ul; // +QNaN (all zeros payload) (DefaultNaN) + yield return 0x7DFF7DFF7DFF7DFFul; // +SNaN (all ones payload) + } + + for (int cnt = 1; cnt <= RndCnt; cnt++) + { + ulong rnd1 = GenNormalH(); + ulong rnd2 = GenSubnormalH(); + + yield return (rnd1 << 48) | (rnd1 << 32) | (rnd1 << 16) | rnd1; + yield return (rnd2 << 48) | (rnd2 << 32) | (rnd2 << 16) | rnd2; + } + } + + private static IEnumerable _1S_F_() + { + yield return 0x00000000FF7FFFFFul; // -Max Normal (float.MinValue) + yield return 0x0000000080800000ul; // -Min Normal + yield return 0x00000000807FFFFFul; // -Max Subnormal + yield return 0x0000000080000001ul; // -Min Subnormal (-float.Epsilon) + yield return 0x000000007F7FFFFFul; // +Max Normal (float.MaxValue) + yield return 0x0000000000800000ul; // +Min Normal + yield return 0x00000000007FFFFFul; // +Max Subnormal + yield return 0x0000000000000001ul; // +Min Subnormal (float.Epsilon) + + if (!NoZeros) + { + yield return 0x0000000080000000ul; // -Zero + yield return 0x0000000000000000ul; // +Zero + } + + if (!NoInfs) + { + yield return 0x00000000FF800000ul; // -Infinity + yield return 0x000000007F800000ul; // +Infinity + } + + if (!NoNaNs) + { + yield return 0x00000000FFC00000ul; // -QNaN (all zeros payload) (float.NaN) + yield return 0x00000000FFBFFFFFul; // -SNaN (all ones payload) + yield return 0x000000007FC00000ul; // +QNaN (all zeros payload) (-float.NaN) (DefaultNaN) + yield return 0x000000007FBFFFFFul; // +SNaN (all ones payload) + } + + for (int cnt = 1; cnt <= RndCnt; cnt++) + { + ulong grbg = TestContext.CurrentContext.Random.NextUInt(); + ulong rnd1 = GenNormalS(); + ulong rnd2 = GenSubnormalS(); + + yield return (grbg << 32) | rnd1; + yield return (grbg << 32) | rnd2; + } + } + + private static IEnumerable _1S_F_W_() + { + // int + yield return 0x00000000CF000001ul; // -2.1474839E9f (-2147483904) + yield return 0x00000000CF000000ul; // -2.14748365E9f (-2147483648) + yield return 0x00000000CEFFFFFFul; // -2.14748352E9f (-2147483520) + yield return 0x000000004F000001ul; // 2.1474839E9f (2147483904) + yield return 0x000000004F000000ul; // 2.14748365E9f (2147483648) + yield return 0x000000004EFFFFFFul; // 2.14748352E9f (2147483520) + + // uint + yield return 0x000000004F800001ul; // 4.2949678E9f (4294967808) + yield return 0x000000004F800000ul; // 4.2949673E9f (4294967296) + yield return 0x000000004F7FFFFFul; // 4.29496704E9f (4294967040) + + yield return 0x00000000FF7FFFFFul; // -Max Normal (float.MinValue) + yield return 0x0000000080800000ul; // -Min Normal + yield return 0x00000000807FFFFFul; // -Max Subnormal + yield return 0x0000000080000001ul; // -Min Subnormal (-float.Epsilon) + yield return 0x000000007F7FFFFFul; // +Max Normal (float.MaxValue) + yield return 0x0000000000800000ul; // +Min Normal + yield return 0x00000000007FFFFFul; // +Max Subnormal + yield return 0x0000000000000001ul; // +Min Subnormal (float.Epsilon) + + if (!NoZeros) + { + yield return 0x0000000080000000ul; // -Zero + yield return 0x0000000000000000ul; // +Zero + } + + if (!NoInfs) + { + yield return 0x00000000FF800000ul; // -Infinity + yield return 0x000000007F800000ul; // +Infinity + } + + if (!NoNaNs) + { + yield return 0x00000000FFC00000ul; // -QNaN (all zeros payload) (float.NaN) + yield return 0x00000000FFBFFFFFul; // -SNaN (all ones payload) + yield return 0x000000007FC00000ul; // +QNaN (all zeros payload) (-float.NaN) (DefaultNaN) + yield return 0x000000007FBFFFFFul; // +SNaN (all ones payload) + } + + for (int cnt = 1; cnt <= RndCnt; cnt++) + { + ulong grbg = TestContext.CurrentContext.Random.NextUInt(); + + ulong rnd1 = (uint)BitConverter.SingleToInt32Bits( + (float)((int)TestContext.CurrentContext.Random.NextUInt())); + ulong rnd2 = (uint)BitConverter.SingleToInt32Bits( + (float)((uint)TestContext.CurrentContext.Random.NextUInt())); + + ulong rnd3 = GenNormalS(); + ulong rnd4 = GenSubnormalS(); + + yield return (grbg << 32) | rnd1; + yield return (grbg << 32) | rnd2; + + yield return (grbg << 32) | rnd3; + yield return (grbg << 32) | rnd4; + } + } + + private static IEnumerable _2S_F_() + { + yield return 0xFF7FFFFFFF7FFFFFul; // -Max Normal (float.MinValue) + yield return 0x8080000080800000ul; // -Min Normal + yield return 0x807FFFFF807FFFFFul; // -Max Subnormal + yield return 0x8000000180000001ul; // -Min Subnormal (-float.Epsilon) + yield return 0x7F7FFFFF7F7FFFFFul; // +Max Normal (float.MaxValue) + yield return 0x0080000000800000ul; // +Min Normal + yield return 0x007FFFFF007FFFFFul; // +Max Subnormal + yield return 0x0000000100000001ul; // +Min Subnormal (float.Epsilon) + + if (!NoZeros) + { + yield return 0x8000000080000000ul; // -Zero + yield return 0x0000000000000000ul; // +Zero + } + + if (!NoInfs) + { + yield return 0xFF800000FF800000ul; // -Infinity + yield return 0x7F8000007F800000ul; // +Infinity + } + + if (!NoNaNs) + { + yield return 0xFFC00000FFC00000ul; // -QNaN (all zeros payload) (float.NaN) + yield return 0xFFBFFFFFFFBFFFFFul; // -SNaN (all ones payload) + yield return 0x7FC000007FC00000ul; // +QNaN (all zeros payload) (-float.NaN) (DefaultNaN) + yield return 0x7FBFFFFF7FBFFFFFul; // +SNaN (all ones payload) + } + + for (int cnt = 1; cnt <= RndCnt; cnt++) + { + ulong rnd1 = GenNormalS(); + ulong rnd2 = GenSubnormalS(); + + yield return (rnd1 << 32) | rnd1; + yield return (rnd2 << 32) | rnd2; + } + } + + private static IEnumerable _2S_F_W_() + { + // int + yield return 0xCF000001CF000001ul; // -2.1474839E9f (-2147483904) + yield return 0xCF000000CF000000ul; // -2.14748365E9f (-2147483648) + yield return 0xCEFFFFFFCEFFFFFFul; // -2.14748352E9f (-2147483520) + yield return 0x4F0000014F000001ul; // 2.1474839E9f (2147483904) + yield return 0x4F0000004F000000ul; // 2.14748365E9f (2147483648) + yield return 0x4EFFFFFF4EFFFFFFul; // 2.14748352E9f (2147483520) + + // uint + yield return 0x4F8000014F800001ul; // 4.2949678E9f (4294967808) + yield return 0x4F8000004F800000ul; // 4.2949673E9f (4294967296) + yield return 0x4F7FFFFF4F7FFFFFul; // 4.29496704E9f (4294967040) + + yield return 0xFF7FFFFFFF7FFFFFul; // -Max Normal (float.MinValue) + yield return 0x8080000080800000ul; // -Min Normal + yield return 0x807FFFFF807FFFFFul; // -Max Subnormal + yield return 0x8000000180000001ul; // -Min Subnormal (-float.Epsilon) + yield return 0x7F7FFFFF7F7FFFFFul; // +Max Normal (float.MaxValue) + yield return 0x0080000000800000ul; // +Min Normal + yield return 0x007FFFFF007FFFFFul; // +Max Subnormal + yield return 0x0000000100000001ul; // +Min Subnormal (float.Epsilon) + + if (!NoZeros) + { + yield return 0x8000000080000000ul; // -Zero + yield return 0x0000000000000000ul; // +Zero + } + + if (!NoInfs) + { + yield return 0xFF800000FF800000ul; // -Infinity + yield return 0x7F8000007F800000ul; // +Infinity + } + + if (!NoNaNs) + { + yield return 0xFFC00000FFC00000ul; // -QNaN (all zeros payload) (float.NaN) + yield return 0xFFBFFFFFFFBFFFFFul; // -SNaN (all ones payload) + yield return 0x7FC000007FC00000ul; // +QNaN (all zeros payload) (-float.NaN) (DefaultNaN) + yield return 0x7FBFFFFF7FBFFFFFul; // +SNaN (all ones payload) + } + + for (int cnt = 1; cnt <= RndCnt; cnt++) + { + ulong rnd1 = (uint)BitConverter.SingleToInt32Bits( + (float)((int)TestContext.CurrentContext.Random.NextUInt())); + ulong rnd2 = (uint)BitConverter.SingleToInt32Bits( + (float)((uint)TestContext.CurrentContext.Random.NextUInt())); + + ulong rnd3 = GenNormalS(); + ulong rnd4 = GenSubnormalS(); + + yield return (rnd1 << 32) | rnd1; + yield return (rnd2 << 32) | rnd2; + + yield return (rnd3 << 32) | rnd3; + yield return (rnd4 << 32) | rnd4; + } + } + + private static IEnumerable _1D_F_() + { + yield return 0xFFEFFFFFFFFFFFFFul; // -Max Normal (double.MinValue) + yield return 0x8010000000000000ul; // -Min Normal + yield return 0x800FFFFFFFFFFFFFul; // -Max Subnormal + yield return 0x8000000000000001ul; // -Min Subnormal (-double.Epsilon) + yield return 0x7FEFFFFFFFFFFFFFul; // +Max Normal (double.MaxValue) + yield return 0x0010000000000000ul; // +Min Normal + yield return 0x000FFFFFFFFFFFFFul; // +Max Subnormal + yield return 0x0000000000000001ul; // +Min Subnormal (double.Epsilon) + + if (!NoZeros) + { + yield return 0x8000000000000000ul; // -Zero + yield return 0x0000000000000000ul; // +Zero + } + + if (!NoInfs) + { + yield return 0xFFF0000000000000ul; // -Infinity + yield return 0x7FF0000000000000ul; // +Infinity + } + + if (!NoNaNs) + { + yield return 0xFFF8000000000000ul; // -QNaN (all zeros payload) (double.NaN) + yield return 0xFFF7FFFFFFFFFFFFul; // -SNaN (all ones payload) + yield return 0x7FF8000000000000ul; // +QNaN (all zeros payload) (-double.NaN) (DefaultNaN) + yield return 0x7FF7FFFFFFFFFFFFul; // +SNaN (all ones payload) + } + + for (int cnt = 1; cnt <= RndCnt; cnt++) + { + ulong rnd1 = GenNormalD(); + ulong rnd2 = GenSubnormalD(); + + yield return rnd1; + yield return rnd2; + } + } + + private static IEnumerable _1D_F_X_() + { + // long + yield return 0xC3E0000000000001ul; // -9.2233720368547780E18d (-9223372036854778000) + yield return 0xC3E0000000000000ul; // -9.2233720368547760E18d (-9223372036854776000) + yield return 0xC3DFFFFFFFFFFFFFul; // -9.2233720368547750E18d (-9223372036854775000) + yield return 0x43E0000000000001ul; // 9.2233720368547780E18d (9223372036854778000) + yield return 0x43E0000000000000ul; // 9.2233720368547760E18d (9223372036854776000) + yield return 0x43DFFFFFFFFFFFFFul; // 9.2233720368547750E18d (9223372036854775000) + + // ulong + yield return 0x43F0000000000001ul; // 1.8446744073709556e19d (18446744073709556000) + yield return 0x43F0000000000000ul; // 1.8446744073709552E19d (18446744073709552000) + yield return 0x43EFFFFFFFFFFFFFul; // 1.8446744073709550e19d (18446744073709550000) + + yield return 0xFFEFFFFFFFFFFFFFul; // -Max Normal (double.MinValue) + yield return 0x8010000000000000ul; // -Min Normal + yield return 0x800FFFFFFFFFFFFFul; // -Max Subnormal + yield return 0x8000000000000001ul; // -Min Subnormal (-double.Epsilon) + yield return 0x7FEFFFFFFFFFFFFFul; // +Max Normal (double.MaxValue) + yield return 0x0010000000000000ul; // +Min Normal + yield return 0x000FFFFFFFFFFFFFul; // +Max Subnormal + yield return 0x0000000000000001ul; // +Min Subnormal (double.Epsilon) + + if (!NoZeros) + { + yield return 0x8000000000000000ul; // -Zero + yield return 0x0000000000000000ul; // +Zero + } + + if (!NoInfs) + { + yield return 0xFFF0000000000000ul; // -Infinity + yield return 0x7FF0000000000000ul; // +Infinity + } + + if (!NoNaNs) + { + yield return 0xFFF8000000000000ul; // -QNaN (all zeros payload) (double.NaN) + yield return 0xFFF7FFFFFFFFFFFFul; // -SNaN (all ones payload) + yield return 0x7FF8000000000000ul; // +QNaN (all zeros payload) (-double.NaN) (DefaultNaN) + yield return 0x7FF7FFFFFFFFFFFFul; // +SNaN (all ones payload) + } + + for (int cnt = 1; cnt <= RndCnt; cnt++) + { + ulong rnd1 = (ulong)BitConverter.DoubleToInt64Bits( + (double)((long)TestContext.CurrentContext.Random.NextULong())); + ulong rnd2 = (ulong)BitConverter.DoubleToInt64Bits( + (double)((ulong)TestContext.CurrentContext.Random.NextULong())); + + ulong rnd3 = GenNormalD(); + ulong rnd4 = GenSubnormalD(); + + yield return rnd1; + yield return rnd2; + + yield return rnd3; + yield return rnd4; + } + } + + private static IEnumerable _GenLeadingSigns8B_() + { + for (int cnt = 0; cnt <= 7; cnt++) + { + ulong rnd1 = GenLeadingSignsMinus8(cnt); + ulong rnd2 = GenLeadingSignsPlus8(cnt); + + yield return (rnd1 << 56) | (rnd1 << 48) | (rnd1 << 40) | (rnd1 << 32) | + (rnd1 << 24) | (rnd1 << 16) | (rnd1 << 08) | rnd1; + yield return (rnd2 << 56) | (rnd2 << 48) | (rnd2 << 40) | (rnd2 << 32) | + (rnd2 << 24) | (rnd2 << 16) | (rnd2 << 08) | rnd2; + } + } + + private static IEnumerable _GenLeadingSigns4H_() + { + for (int cnt = 0; cnt <= 15; cnt++) + { + ulong rnd1 = GenLeadingSignsMinus16(cnt); + ulong rnd2 = GenLeadingSignsPlus16(cnt); + + yield return (rnd1 << 48) | (rnd1 << 32) | (rnd1 << 16) | rnd1; + yield return (rnd2 << 48) | (rnd2 << 32) | (rnd2 << 16) | rnd2; + } + } + + private static IEnumerable _GenLeadingSigns2S_() + { + for (int cnt = 0; cnt <= 31; cnt++) + { + ulong rnd1 = GenLeadingSignsMinus32(cnt); + ulong rnd2 = GenLeadingSignsPlus32(cnt); + + yield return (rnd1 << 32) | rnd1; + yield return (rnd2 << 32) | rnd2; + } + } + + private static IEnumerable _GenLeadingZeros8B_() + { + for (int cnt = 0; cnt <= 8; cnt++) + { + ulong rnd = GenLeadingZeros8(cnt); + + yield return (rnd << 56) | (rnd << 48) | (rnd << 40) | (rnd << 32) | + (rnd << 24) | (rnd << 16) | (rnd << 08) | rnd; + } + } + + private static IEnumerable _GenLeadingZeros4H_() + { + for (int cnt = 0; cnt <= 16; cnt++) + { + ulong rnd = GenLeadingZeros16(cnt); + + yield return (rnd << 48) | (rnd << 32) | (rnd << 16) | rnd; + } + } + + private static IEnumerable _GenLeadingZeros2S_() + { + for (int cnt = 0; cnt <= 32; cnt++) + { + ulong rnd = GenLeadingZeros32(cnt); + + yield return (rnd << 32) | rnd; + } + } + + private static IEnumerable _GenPopCnt8B_() + { + for (ulong cnt = 0ul; cnt <= 255ul; cnt++) + { + yield return (cnt << 56) | (cnt << 48) | (cnt << 40) | (cnt << 32) | + (cnt << 24) | (cnt << 16) | (cnt << 08) | cnt; + } + } +#endregion + +#region "ValueSource (Opcodes)" + private static uint[] _SU_Add_Max_Min_V_V_8BB_4HH_() + { + return new uint[] + { + 0x0E31B800u, // ADDV B0, V0.8B + 0x0E30A800u, // SMAXV B0, V0.8B + 0x0E31A800u, // SMINV B0, V0.8B + 0x2E30A800u, // UMAXV B0, V0.8B + 0x2E31A800u // UMINV B0, V0.8B + }; + } + + private static uint[] _SU_Add_Max_Min_V_V_16BB_8HH_4SS_() + { + return new uint[] + { + 0x4E31B800u, // ADDV B0, V0.16B + 0x4E30A800u, // SMAXV B0, V0.16B + 0x4E31A800u, // SMINV B0, V0.16B + 0x6E30A800u, // UMAXV B0, V0.16B + 0x6E31A800u // UMINV B0, V0.16B + }; + } + + private static uint[] _F_Abs_Neg_Recpx_Sqrt_S_S_() + { + return new uint[] + { + 0x1E20C020u, // FABS S0, S1 + 0x1E214020u, // FNEG S0, S1 + 0x5EA1F820u, // FRECPX S0, S1 + 0x1E21C020u // FSQRT S0, S1 + }; + } + + private static uint[] _F_Abs_Neg_Recpx_Sqrt_S_D_() + { + return new uint[] + { + 0x1E60C020u, // FABS D0, D1 + 0x1E614020u, // FNEG D0, D1 + 0x5EE1F820u, // FRECPX D0, D1 + 0x1E61C020u // FSQRT D0, D1 + }; + } + + private static uint[] _F_Abs_Neg_Sqrt_V_2S_4S_() + { + return new uint[] + { + 0x0EA0F800u, // FABS V0.2S, V0.2S + 0x2EA0F800u, // FNEG V0.2S, V0.2S + 0x2EA1F800u // FSQRT V0.2S, V0.2S + }; + } + + private static uint[] _F_Abs_Neg_Sqrt_V_2D_() + { + return new uint[] + { + 0x4EE0F800u, // FABS V0.2D, V0.2D + 0x6EE0F800u, // FNEG V0.2D, V0.2D + 0x6EE1F800u // FSQRT V0.2D, V0.2D + }; + } + + private static uint[] _F_Add_P_S_2SS_() + { + return new uint[] + { + 0x7E30D820u // FADDP S0, V1.2S + }; + } + + private static uint[] _F_Add_P_S_2DD_() + { + return new uint[] + { + 0x7E70D820u // FADDP D0, V1.2D + }; + } + + private static uint[] _F_Cm_EqGeGtLeLt_S_S_() + { + return new uint[] + { + 0x5EA0D820u, // FCMEQ S0, S1, #0.0 + 0x7EA0C820u, // FCMGE S0, S1, #0.0 + 0x5EA0C820u, // FCMGT S0, S1, #0.0 + 0x7EA0D820u, // FCMLE S0, S1, #0.0 + 0x5EA0E820u // FCMLT S0, S1, #0.0 + }; + } + + private static uint[] _F_Cm_EqGeGtLeLt_S_D_() + { + return new uint[] + { + 0x5EE0D820u, // FCMEQ D0, D1, #0.0 + 0x7EE0C820u, // FCMGE D0, D1, #0.0 + 0x5EE0C820u, // FCMGT D0, D1, #0.0 + 0x7EE0D820u, // FCMLE D0, D1, #0.0 + 0x5EE0E820u // FCMLT D0, D1, #0.0 + }; + } + + private static uint[] _F_Cm_EqGeGtLeLt_V_2S_4S_() + { + return new uint[] + { + 0x0EA0D800u, // FCMEQ V0.2S, V0.2S, #0.0 + 0x2EA0C800u, // FCMGE V0.2S, V0.2S, #0.0 + 0x0EA0C800u, // FCMGT V0.2S, V0.2S, #0.0 + 0x2EA0D800u, // FCMLE V0.2S, V0.2S, #0.0 + 0x0EA0E800u // FCMLT V0.2S, V0.2S, #0.0 + }; + } + + private static uint[] _F_Cm_EqGeGtLeLt_V_2D_() + { + return new uint[] + { + 0x4EE0D800u, // FCMEQ V0.2D, V0.2D, #0.0 + 0x6EE0C800u, // FCMGE V0.2D, V0.2D, #0.0 + 0x4EE0C800u, // FCMGT V0.2D, V0.2D, #0.0 + 0x6EE0D800u, // FCMLE V0.2D, V0.2D, #0.0 + 0x4EE0E800u // FCMLT V0.2D, V0.2D, #0.0 + }; + } + + private static uint[] _F_Cmp_Cmpe_S_S_() + { + return new uint[] + { + 0x1E202028u, // FCMP S1, #0.0 + 0x1E202038u // FCMPE S1, #0.0 + }; + } + + private static uint[] _F_Cmp_Cmpe_S_D_() + { + return new uint[] + { + 0x1E602028u, // FCMP D1, #0.0 + 0x1E602038u // FCMPE D1, #0.0 + }; + } + + private static uint[] _F_Cvt_S_SD_() + { + return new uint[] + { + 0x1E22C020u // FCVT D0, S1 + }; + } + + private static uint[] _F_Cvt_S_DS_() + { + return new uint[] + { + 0x1E624020u // FCVT S0, D1 + }; + } + + private static uint[] _F_Cvt_S_SH_() + { + return new uint[] + { + 0x1E23C020u // FCVT H0, S1 + }; + } + + private static uint[] _F_Cvt_S_HS_() + { + return new uint[] + { + 0x1EE24020u // FCVT S0, H1 + }; + } + + private static uint[] _F_Cvt_NZ_SU_S_S_() + { + return new uint[] + { + 0x5E21A820u, // FCVTNS S0, S1 + 0x7E21A820u, // FCVTNU S0, S1 + 0x5EA1B820u, // FCVTZS S0, S1 + 0x7EA1B820u // FCVTZU S0, S1 + }; + } + + private static uint[] _F_Cvt_NZ_SU_S_D_() + { + return new uint[] + { + 0x5E61A820u, // FCVTNS D0, D1 + 0x7E61A820u, // FCVTNU D0, D1 + 0x5EE1B820u, // FCVTZS D0, D1 + 0x7EE1B820u // FCVTZU D0, D1 + }; + } + + private static uint[] _F_Cvt_NZ_SU_V_2S_4S_() + { + return new uint[] + { + 0x0E21A800u, // FCVTNS V0.2S, V0.2S + 0x2E21A800u, // FCVTNU V0.2S, V0.2S + 0x0EA1B800u, // FCVTZS V0.2S, V0.2S + 0x2EA1B800u // FCVTZU V0.2S, V0.2S + }; + } + + private static uint[] _F_Cvt_NZ_SU_V_2D_() + { + return new uint[] + { + 0x4E61A800u, // FCVTNS V0.2D, V0.2D + 0x6E61A800u, // FCVTNU V0.2D, V0.2D + 0x4EE1B800u, // FCVTZS V0.2D, V0.2D + 0x6EE1B800u // FCVTZU V0.2D, V0.2D + }; + } + + private static uint[] _F_Cvtl_V_4H4S_8H4S_() + { + return new uint[] + { + 0x0E217800u // FCVTL V0.4S, V0.4H + }; + } + + private static uint[] _F_Cvtl_V_2S2D_4S2D_() + { + return new uint[] + { + 0x0E617800u // FCVTL V0.2D, V0.2S + }; + } + + private static uint[] _F_Cvtn_V_4S4H_4S8H_() + { + return new uint[] + { + 0x0E216800u // FCVTN V0.4H, V0.4S + }; + } + + private static uint[] _F_Cvtn_V_2D2S_2D4S_() + { + return new uint[] + { + 0x0E616800u // FCVTN V0.2S, V0.2D + }; + } + + private static uint[] _F_Mov_Ftoi_SW_() + { + return new uint[] + { + 0x1E260000u // FMOV W0, S0 + }; + } + + private static uint[] _F_Mov_Ftoi_DX_() + { + return new uint[] + { + 0x9E660000u // FMOV X0, D0 + }; + } + + private static uint[] _F_Mov_Ftoi1_DX_() + { + return new uint[] + { + 0x9EAE0000u // FMOV X0, V0.D[1] + }; + } + + private static uint[] _F_Mov_Itof_WS_() + { + return new uint[] + { + 0x1E270000u // FMOV S0, W0 + }; + } + + private static uint[] _F_Mov_Itof_XD_() + { + return new uint[] + { + 0x9E670000u // FMOV D0, X0 + }; + } + + private static uint[] _F_Mov_Itof1_XD_() + { + return new uint[] + { + 0x9EAF0000u // FMOV V0.D[1], X0 + }; + } + + private static uint[] _F_Mov_S_S_() + { + return new uint[] + { + 0x1E204020u // FMOV S0, S1 + }; + } + + private static uint[] _F_Mov_S_D_() + { + return new uint[] + { + 0x1E604020u // FMOV D0, D1 + }; + } + + private static uint[] _F_Recpe_Rsqrte_S_S_() + { + return new uint[] + { + 0x5EA1D820u, // FRECPE S0, S1 + 0x7EA1D820u // FRSQRTE S0, S1 + }; + } + + private static uint[] _F_Recpe_Rsqrte_S_D_() + { + return new uint[] + { + 0x5EE1D820u, // FRECPE D0, D1 + 0x7EE1D820u // FRSQRTE D0, D1 + }; + } + + private static uint[] _F_Recpe_Rsqrte_V_2S_4S_() + { + return new uint[] + { + 0x0EA1D800u, // FRECPE V0.2S, V0.2S + 0x2EA1D800u // FRSQRTE V0.2S, V0.2S + }; + } + + private static uint[] _F_Recpe_Rsqrte_V_2D_() + { + return new uint[] + { + 0x4EE1D800u, // FRECPE V0.2D, V0.2D + 0x6EE1D800u // FRSQRTE V0.2D, V0.2D + }; + } + + private static uint[] _F_Rint_AMNPZ_S_S_() + { + return new uint[] + { + 0x1E264020u, // FRINTA S0, S1 + 0x1E254020u, // FRINTM S0, S1 + 0x1E244020u, // FRINTN S0, S1 + 0x1E24C020u, // FRINTP S0, S1 + 0x1E25C020u // FRINTZ S0, S1 + }; + } + + private static uint[] _F_Rint_AMNPZ_S_D_() + { + return new uint[] + { + 0x1E664020u, // FRINTA D0, D1 + 0x1E654020u, // FRINTM D0, D1 + 0x1E644020u, // FRINTN D0, D1 + 0x1E64C020u, // FRINTP D0, D1 + 0x1E65C020u // FRINTZ D0, D1 + }; + } + + private static uint[] _F_Rint_AMNPZ_V_2S_4S_() + { + return new uint[] + { + 0x2E218800u, // FRINTA V0.2S, V0.2S + 0x0E219800u, // FRINTM V0.2S, V0.2S + 0x0E218800u, // FRINTN V0.2S, V0.2S + 0x0EA18800u, // FRINTP V0.2S, V0.2S + 0x0EA19800u // FRINTZ V0.2S, V0.2S + }; + } + + private static uint[] _F_Rint_AMNPZ_V_2D_() + { + return new uint[] + { + 0x6E618800u, // FRINTA V0.2D, V0.2D + 0x4E619800u, // FRINTM V0.2D, V0.2D + 0x4E618800u, // FRINTN V0.2D, V0.2D + 0x4EE18800u, // FRINTP V0.2D, V0.2D + 0x4EE19800u // FRINTZ V0.2D, V0.2D + }; + } + + private static uint[] _F_Rint_IX_S_S_() + { + return new uint[] + { + 0x1E27C020u, // FRINTI S0, S1 + 0x1E274020u // FRINTX S0, S1 + }; + } + + private static uint[] _F_Rint_IX_S_D_() + { + return new uint[] + { + 0x1E67C020u, // FRINTI D0, D1 + 0x1E674020u // FRINTX D0, D1 + }; + } + + private static uint[] _F_Rint_IX_V_2S_4S_() + { + return new uint[] + { + 0x2EA19800u, // FRINTI V0.2S, V0.2S + 0x2E219800u // FRINTX V0.2S, V0.2S + }; + } + + private static uint[] _F_Rint_IX_V_2D_() + { + return new uint[] + { + 0x6EE19800u, // FRINTI V0.2D, V0.2D + 0x6E619800u // FRINTX V0.2D, V0.2D + }; + } + + private static uint[] _SU_Addl_V_V_8BH_4HS_() + { + return new uint[] + { + 0x0E303800u, // SADDLV H0, V0.8B + 0x2E303800u // UADDLV H0, V0.8B + }; + } + + private static uint[] _SU_Addl_V_V_16BH_8HS_4SD_() + { + return new uint[] + { + 0x4E303800u, // SADDLV H0, V0.16B + 0x6E303800u // UADDLV H0, V0.16B + }; + } + + private static uint[] _SU_Cvt_F_S_S_() + { + return new uint[] + { + 0x5E21D820u, // SCVTF S0, S1 + 0x7E21D820u // UCVTF S0, S1 + }; + } + + private static uint[] _SU_Cvt_F_S_D_() + { + return new uint[] + { + 0x5E61D820u, // SCVTF D0, D1 + 0x7E61D820u // UCVTF D0, D1 + }; + } + + private static uint[] _SU_Cvt_F_V_2S_4S_() + { + return new uint[] + { + 0x0E21D800u, // SCVTF V0.2S, V0.2S + 0x2E21D800u // UCVTF V0.2S, V0.2S + }; + } + + private static uint[] _SU_Cvt_F_V_2D_() + { + return new uint[] + { + 0x4E61D800u, // SCVTF V0.2D, V0.2D + 0x6E61D800u // UCVTF V0.2D, V0.2D + }; + } + + private static uint[] _Sha1h_Sha1su1_V_() + { + return new uint[] + { + 0x5E280800u, // SHA1H S0, S0 + 0x5E281800u // SHA1SU1 V0.4S, V0.4S + }; + } + + private static uint[] _Sha256su0_V_() + { + return new uint[] + { + 0x5E282800u // SHA256SU0 V0.4S, V0.4S + }; + } +#endregion + + private const int RndCnt = 2; + + private static readonly bool NoZeros = false; + private static readonly bool NoInfs = false; + private static readonly bool NoNaNs = false; + + [Test, Pairwise, Description("ABS , ")] + public void Abs_S_D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_1D_")] [Random(RndCnt)] ulong a) + { + uint opcode = 0x5EE0B800; // ABS D0, D0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); } [Test, Pairwise, Description("ABS ., .")] - public void Abs_V_16B_8H_4S_2D([ValueSource("_8B4H2S1D_")] [Random(1)] ulong A0, - [ValueSource("_8B4H2S1D_")] [Random(1)] ulong A1, + public void Abs_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x0E20B800; // ABS V0.8B, V0.8B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("ABS ., .")] + public void Abs_V_16B_8H_4S_2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong a, [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> { - uint Opcode = 0x4E20B820; // ABS V0.16B, V1.16B - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); + uint opcode = 0x4E20B800; // ABS V0.16B, V0.16B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); - Vector128 V1 = MakeVectorE0E1(A0, A1); - AThreadState ThreadState = SingleOpcode(Opcode, V1: V1); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); - AArch64.Vpart(1, 0, new Bits(A0)); - AArch64.Vpart(1, 1, new Bits(A1)); - SimdFp.Abs_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); - }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("ADDP , .")] - public void Addp_S_2DD([ValueSource("_1D_")] [Random(1)] ulong A0, - [ValueSource("_1D_")] [Random(1)] ulong A1) + public void Addp_S_2DD([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_1D_")] [Random(RndCnt)] ulong a) { - uint Opcode = 0x5EF1B820; // ADDP D0, V1.2D - Bits Op = new Bits(Opcode); + uint opcode = 0x5EF1B800; // ADDP D0, V0.2D + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); - Vector128 V0 = MakeVectorE1(TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0E1(A0, A1); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); - AArch64.Vpart(1, 0, new Bits(A0)); - AArch64.Vpart(1, 1, new Bits(A1)); - SimdFp.Addp_S(Op[23, 22], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); + CompareAgainstUnicorn(); } - [Test, Description("ADDV , .")] - public void Addv_V_8BB_4HH([ValueSource("_8B4H_")] [Random(1)] ulong A, - [Values(0b00u, 0b01u)] uint size) // <8B, 4H> + [Test, Pairwise] + public void SU_Add_Max_Min_V_V_8BB_4HH([ValueSource("_SU_Add_Max_Min_V_V_8BB_4HH_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_8B4H_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H_")] [Random(RndCnt)] ulong a, + [Values(0b00u, 0b01u)] uint size) // <8BB, 4HH> { - uint Opcode = 0x0E31B820; // ADDV B0, V1.8B - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((size & 3) << 22); - Vector128 V0 = MakeVectorE0E1(TestContext.CurrentContext.Random.NextULong(), - TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0(A); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); - AArch64.Vpart(0, 0, new Bits(TestContext.CurrentContext.Random.NextULong())); - AArch64.V(1, new Bits(A)); - SimdFp.Addv_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]); + SingleOpcode(opcodes, v0: v0, v1: v1); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); + CompareAgainstUnicorn(); } - [Test, Pairwise, Description("ADDV , .")] - public void Addv_V_16BB_8HH_4SS([ValueSource("_8B4H2S_")] [Random(1)] ulong A0, - [ValueSource("_8B4H2S_")] [Random(1)] ulong A1, - [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B, 8H, 4S> + [Test, Pairwise] + public void SU_Add_Max_Min_V_V_16BB_8HH_4SS([ValueSource("_SU_Add_Max_Min_V_V_16BB_8HH_4SS_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16BB, 8HH, 4SS> { - uint Opcode = 0x4E31B820; // ADDV B0, V1.16B - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((size & 3) << 22); - Vector128 V0 = MakeVectorE0E1(TestContext.CurrentContext.Random.NextULong(), - TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0E1(A0, A1); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); - AArch64.Vpart(0, 0, new Bits(TestContext.CurrentContext.Random.NextULong())); - AArch64.Vpart(1, 0, new Bits(A0)); - AArch64.Vpart(1, 1, new Bits(A1)); - SimdFp.Addv_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]); + SingleOpcode(opcodes, v0: v0, v1: v1); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); - } - - [Test, Description("CLS ., .")] - public void Cls_V_8B_4H_2S([ValueSource("_8B4H2S_")] [Random(1)] ulong A, - [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> - { - uint Opcode = 0x0E204820; // CLS V0.8B, V1.8B - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); - - Vector128 V0 = MakeVectorE1(TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0(A); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); - - AArch64.V(1, new Bits(A)); - SimdFp.Cls_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]); - - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("CLS ., .")] - public void Cls_V_16B_8H_4S([ValueSource("_8B4H2S_")] [Random(1)] ulong A0, - [ValueSource("_8B4H2S_")] [Random(1)] ulong A1, - [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B, 8H, 4S> + public void Cls_V_8B_16B([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_8B_")] [Random(RndCnt)] ulong z, + [ValueSource("_GenLeadingSigns8B_")] [Random(RndCnt)] ulong a, + [Values(0b0u, 0b1u)] uint q) // <8B, 16B> { - uint Opcode = 0x4E204820; // CLS V0.16B, V1.16B - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); + uint opcode = 0x0E204800; // CLS V0.8B, V0.8B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((q & 1) << 30); - Vector128 V1 = MakeVectorE0E1(A0, A1); - AThreadState ThreadState = SingleOpcode(Opcode, V1: V1); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); - AArch64.Vpart(1, 0, new Bits(A0)); - AArch64.Vpart(1, 1, new Bits(A1)); - SimdFp.Cls_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); - }); + CompareAgainstUnicorn(); } - [Test, Description("CLZ ., .")] - public void Clz_V_8B_4H_2S([ValueSource("_8B4H2S_")] [Random(1)] ulong A, - [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + [Test, Pairwise, Description("CLS ., .")] + public void Cls_V_4H_8H([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_4H_")] [Random(RndCnt)] ulong z, + [ValueSource("_GenLeadingSigns4H_")] [Random(RndCnt)] ulong a, + [Values(0b0u, 0b1u)] uint q) // <4H, 8H> { - uint Opcode = 0x2E204820; // CLZ V0.8B, V1.8B - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); + uint opcode = 0x0E604800; // CLS V0.4H, V0.4H + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((q & 1) << 30); - Vector128 V0 = MakeVectorE1(TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0(A); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); - AArch64.V(1, new Bits(A)); - SimdFp.Clz_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CLS ., .")] + public void Cls_V_2S_4S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_GenLeadingSigns2S_")] [Random(RndCnt)] ulong a, + [Values(0b0u, 0b1u)] uint q) // <2S, 4S> + { + uint opcode = 0x0EA04800; // CLS V0.2S, V0.2S + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); } [Test, Pairwise, Description("CLZ ., .")] - public void Clz_V_16B_8H_4S([ValueSource("_8B4H2S_")] [Random(1)] ulong A0, - [ValueSource("_8B4H2S_")] [Random(1)] ulong A1, - [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B, 8H, 4S> + public void Clz_V_8B_16B([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_8B_")] [Random(RndCnt)] ulong z, + [ValueSource("_GenLeadingZeros8B_")] [Random(RndCnt)] ulong a, + [Values(0b0u, 0b1u)] uint q) // <8B, 16B> { - uint Opcode = 0x6E204820; // CLZ V0.16B, V1.16B - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); + uint opcode = 0x2E204800; // CLZ V0.8B, V0.8B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((q & 1) << 30); - Vector128 V1 = MakeVectorE0E1(A0, A1); - AThreadState ThreadState = SingleOpcode(Opcode, V1: V1); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); - AArch64.Vpart(1, 0, new Bits(A0)); - AArch64.Vpart(1, 1, new Bits(A1)); - SimdFp.Clz_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); - }); + CompareAgainstUnicorn(); } - [Test, Description("CMEQ , , #0")] - public void Cmeq_S_D([ValueSource("_1D_")] [Random(1)] ulong A) + [Test, Pairwise, Description("CLZ ., .")] + public void Clz_V_4H_8H([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_4H_")] [Random(RndCnt)] ulong z, + [ValueSource("_GenLeadingZeros4H_")] [Random(RndCnt)] ulong a, + [Values(0b0u, 0b1u)] uint q) // <4H, 8H> { - uint Opcode = 0x5EE09820; // CMEQ D0, D1, #0 - Bits Op = new Bits(Opcode); + uint opcode = 0x2E604800; // CLZ V0.4H, V0.4H + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((q & 1) << 30); - Vector128 V0 = MakeVectorE1(TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0(A); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); - AArch64.V(1, new Bits(A)); - SimdFp.Cmeq_Zero_S(Op[23, 22], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); + CompareAgainstUnicorn(); } - [Test, Description("CMEQ ., ., #0")] - public void Cmeq_V_8B_4H_2S([ValueSource("_8B4H2S_")] [Random(1)] ulong A, - [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + [Test, Pairwise, Description("CLZ ., .")] + public void Clz_V_2S_4S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_GenLeadingZeros2S_")] [Random(RndCnt)] ulong a, + [Values(0b0u, 0b1u)] uint q) // <2S, 4S> { - uint Opcode = 0x0E209820; // CMEQ V0.8B, V1.8B, #0 - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); + uint opcode = 0x2EA04800; // CLZ V0.2S, V0.2S + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((q & 1) << 30); - Vector128 V0 = MakeVectorE1(TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0(A); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); - AArch64.V(1, new Bits(A)); - SimdFp.Cmeq_Zero_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CMEQ , , #0")] + public void Cmeq_S_D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_1D_")] [Random(RndCnt)] ulong a) + { + uint opcode = 0x5EE09800; // CMEQ D0, D0, #0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); } [Test, Pairwise, Description("CMEQ ., ., #0")] - public void Cmeq_V_16B_8H_4S_2D([ValueSource("_8B4H2S1D_")] [Random(1)] ulong A0, - [ValueSource("_8B4H2S1D_")] [Random(1)] ulong A1, - [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> - { - uint Opcode = 0x4E209820; // CMEQ V0.16B, V1.16B, #0 - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); - - Vector128 V1 = MakeVectorE0E1(A0, A1); - AThreadState ThreadState = SingleOpcode(Opcode, V1: V1); - - AArch64.Vpart(1, 0, new Bits(A0)); - AArch64.Vpart(1, 1, new Bits(A1)); - SimdFp.Cmeq_Zero_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]); - - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); - }); - } - - [Test, Description("CMGE , , #0")] - public void Cmge_S_D([ValueSource("_1D_")] [Random(1)] ulong A) - { - uint Opcode = 0x7EE08820; // CMGE D0, D1, #0 - Bits Op = new Bits(Opcode); - - Vector128 V0 = MakeVectorE1(TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0(A); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); - - AArch64.V(1, new Bits(A)); - SimdFp.Cmge_Zero_S(Op[23, 22], Op[9, 5], Op[4, 0]); - - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); - } - - [Test, Description("CMGE ., ., #0")] - public void Cmge_V_8B_4H_2S([ValueSource("_8B4H2S_")] [Random(1)] ulong A, + public void Cmeq_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> { - uint Opcode = 0x2E208820; // CMGE V0.8B, V1.8B, #0 - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); + uint opcode = 0x0E209800; // CMEQ V0.8B, V0.8B, #0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); - Vector128 V0 = MakeVectorE1(TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0(A); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); - AArch64.V(1, new Bits(A)); - SimdFp.Cmge_Zero_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CMEQ ., ., #0")] + public void Cmeq_V_16B_8H_4S_2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong a, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint opcode = 0x4E209800; // CMEQ V0.16B, V0.16B, #0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CMGE , , #0")] + public void Cmge_S_D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_1D_")] [Random(RndCnt)] ulong a) + { + uint opcode = 0x7EE08800; // CMGE D0, D0, #0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); } [Test, Pairwise, Description("CMGE ., ., #0")] - public void Cmge_V_16B_8H_4S_2D([ValueSource("_8B4H2S1D_")] [Random(1)] ulong A0, - [ValueSource("_8B4H2S1D_")] [Random(1)] ulong A1, - [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> - { - uint Opcode = 0x6E208820; // CMGE V0.16B, V1.16B, #0 - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); - - Vector128 V1 = MakeVectorE0E1(A0, A1); - AThreadState ThreadState = SingleOpcode(Opcode, V1: V1); - - AArch64.Vpart(1, 0, new Bits(A0)); - AArch64.Vpart(1, 1, new Bits(A1)); - SimdFp.Cmge_Zero_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]); - - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); - }); - } - - [Test, Description("CMGT , , #0")] - public void Cmgt_S_D([ValueSource("_1D_")] [Random(1)] ulong A) - { - uint Opcode = 0x5EE08820; // CMGT D0, D1, #0 - Bits Op = new Bits(Opcode); - - Vector128 V0 = MakeVectorE1(TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0(A); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); - - AArch64.V(1, new Bits(A)); - SimdFp.Cmgt_Zero_S(Op[23, 22], Op[9, 5], Op[4, 0]); - - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); - } - - [Test, Description("CMGT ., ., #0")] - public void Cmgt_V_8B_4H_2S([ValueSource("_8B4H2S_")] [Random(1)] ulong A, + public void Cmge_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> { - uint Opcode = 0x0E208820; // CMGT V0.8B, V1.8B, #0 - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); + uint opcode = 0x2E208800; // CMGE V0.8B, V0.8B, #0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); - Vector128 V0 = MakeVectorE1(TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0(A); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); - AArch64.V(1, new Bits(A)); - SimdFp.Cmgt_Zero_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CMGE ., ., #0")] + public void Cmge_V_16B_8H_4S_2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong a, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint opcode = 0x6E208800; // CMGE V0.16B, V0.16B, #0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CMGT , , #0")] + public void Cmgt_S_D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_1D_")] [Random(RndCnt)] ulong a) + { + uint opcode = 0x5EE08800; // CMGT D0, D0, #0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); } [Test, Pairwise, Description("CMGT ., ., #0")] - public void Cmgt_V_16B_8H_4S_2D([ValueSource("_8B4H2S1D_")] [Random(1)] ulong A0, - [ValueSource("_8B4H2S1D_")] [Random(1)] ulong A1, - [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> - { - uint Opcode = 0x4E208820; // CMGT V0.16B, V1.16B, #0 - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); - - Vector128 V1 = MakeVectorE0E1(A0, A1); - AThreadState ThreadState = SingleOpcode(Opcode, V1: V1); - - AArch64.Vpart(1, 0, new Bits(A0)); - AArch64.Vpart(1, 1, new Bits(A1)); - SimdFp.Cmgt_Zero_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]); - - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); - }); - } - - [Test, Description("CMLE , , #0")] - public void Cmle_S_D([ValueSource("_1D_")] [Random(1)] ulong A) - { - uint Opcode = 0x7EE09820; // CMLE D0, D1, #0 - Bits Op = new Bits(Opcode); - - Vector128 V0 = MakeVectorE1(TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0(A); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); - - AArch64.V(1, new Bits(A)); - SimdFp.Cmle_S(Op[23, 22], Op[9, 5], Op[4, 0]); - - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); - } - - [Test, Description("CMLE ., ., #0")] - public void Cmle_V_8B_4H_2S([ValueSource("_8B4H2S_")] [Random(1)] ulong A, + public void Cmgt_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> { - uint Opcode = 0x2E209820; // CMLE V0.8B, V1.8B, #0 - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); + uint opcode = 0x0E208800; // CMGT V0.8B, V0.8B, #0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); - Vector128 V0 = MakeVectorE1(TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0(A); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); - AArch64.V(1, new Bits(A)); - SimdFp.Cmle_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CMGT ., ., #0")] + public void Cmgt_V_16B_8H_4S_2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong a, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint opcode = 0x4E208800; // CMGT V0.16B, V0.16B, #0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CMLE , , #0")] + public void Cmle_S_D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_1D_")] [Random(RndCnt)] ulong a) + { + uint opcode = 0x7EE09800; // CMLE D0, D0, #0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); } [Test, Pairwise, Description("CMLE ., ., #0")] - public void Cmle_V_16B_8H_4S_2D([ValueSource("_8B4H2S1D_")] [Random(1)] ulong A0, - [ValueSource("_8B4H2S1D_")] [Random(1)] ulong A1, - [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> - { - uint Opcode = 0x6E209820; // CMLE V0.16B, V1.16B, #0 - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); - - Vector128 V1 = MakeVectorE0E1(A0, A1); - AThreadState ThreadState = SingleOpcode(Opcode, V1: V1); - - AArch64.Vpart(1, 0, new Bits(A0)); - AArch64.Vpart(1, 1, new Bits(A1)); - SimdFp.Cmle_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]); - - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); - }); - } - - [Test, Description("CMLT , , #0")] - public void Cmlt_S_D([ValueSource("_1D_")] [Random(1)] ulong A) - { - uint Opcode = 0x5EE0A820; // CMLT D0, D1, #0 - Bits Op = new Bits(Opcode); - - Vector128 V0 = MakeVectorE1(TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0(A); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); - - AArch64.V(1, new Bits(A)); - SimdFp.Cmlt_S(Op[23, 22], Op[9, 5], Op[4, 0]); - - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); - } - - [Test, Description("CMLT ., ., #0")] - public void Cmlt_V_8B_4H_2S([ValueSource("_8B4H2S_")] [Random(1)] ulong A, + public void Cmle_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> { - uint Opcode = 0x0E20A820; // CMLT V0.8B, V1.8B, #0 - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); + uint opcode = 0x2E209800; // CMLE V0.8B, V0.8B, #0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); - Vector128 V0 = MakeVectorE1(TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0(A); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); - AArch64.V(1, new Bits(A)); - SimdFp.Cmlt_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CMLE ., ., #0")] + public void Cmle_V_16B_8H_4S_2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong a, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint opcode = 0x6E209800; // CMLE V0.16B, V0.16B, #0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CMLT , , #0")] + public void Cmlt_S_D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_1D_")] [Random(RndCnt)] ulong a) + { + uint opcode = 0x5EE0A800; // CMLT D0, D0, #0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); } [Test, Pairwise, Description("CMLT ., ., #0")] - public void Cmlt_V_16B_8H_4S_2D([ValueSource("_8B4H2S1D_")] [Random(1)] ulong A0, - [ValueSource("_8B4H2S1D_")] [Random(1)] ulong A1, - [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + public void Cmlt_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> { - uint Opcode = 0x4E20A820; // CMLT V0.16B, V1.16B, #0 - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); + uint opcode = 0x0E20A800; // CMLT V0.8B, V0.8B, #0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); - Vector128 V1 = MakeVectorE0E1(A0, A1); - AThreadState ThreadState = SingleOpcode(Opcode, V1: V1); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); - AArch64.Vpart(1, 0, new Bits(A0)); - AArch64.Vpart(1, 1, new Bits(A1)); - SimdFp.Cmlt_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); - }); + CompareAgainstUnicorn(); } - [Test, Description("CNT ., .")] - public void Cnt_V_8B([ValueSource("_8B_")] [Random(1)] ulong A) + [Test, Pairwise, Description("CMLT ., ., #0")] + public void Cmlt_V_16B_8H_4S_2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong a, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> { - uint Opcode = 0x0E205820; // CNT V0.8B, V1.8B - Bits Op = new Bits(Opcode); + uint opcode = 0x4E20A800; // CMLT V0.16B, V0.16B, #0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); - Vector128 V0 = MakeVectorE1(TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0(A); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); - AArch64.V(1, new Bits(A)); - SimdFp.Cnt_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("CNT ., .")] - public void Cnt_V_16B([ValueSource("_8B_")] [Random(1)] ulong A0, - [ValueSource("_8B_")] [Random(1)] ulong A1) + public void Cnt_V_8B([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_8B_")] [Random(RndCnt)] ulong z, + [ValueSource("_GenPopCnt8B_")] [Random(RndCnt)] ulong a) { - uint Opcode = 0x4E205820; // CNT V0.16B, V1.16B - Bits Op = new Bits(Opcode); + uint opcode = 0x0E205800; // CNT V0.8B, V0.8B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); - Vector128 V1 = MakeVectorE0E1(A0, A1); - AThreadState ThreadState = SingleOpcode(Opcode, V1: V1); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); - AArch64.Vpart(1, 0, new Bits(A0)); - AArch64.Vpart(1, 1, new Bits(A1)); - SimdFp.Cnt_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); - }); + CompareAgainstUnicorn(); } - [Test, Description("NEG , ")] - public void Neg_S_D([ValueSource("_1D_")] [Random(1)] ulong A) + [Test, Pairwise, Description("CNT ., .")] + public void Cnt_V_16B([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_8B_")] [Random(RndCnt)] ulong z, + [ValueSource("_GenPopCnt8B_")] [Random(RndCnt)] ulong a) { - uint Opcode = 0x7EE0B820; // NEG D0, D1 - Bits Op = new Bits(Opcode); + uint opcode = 0x4E205800; // CNT V0.16B, V0.16B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); - Vector128 V0 = MakeVectorE1(TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0(A); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); - AArch64.V(1, new Bits(A)); - SimdFp.Neg_S(Op[23, 22], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); + CompareAgainstUnicorn(); } - [Test, Description("NEG ., .")] - public void Neg_V_8B_4H_2S([ValueSource("_8B4H2S_")] [Random(1)] ulong A, - [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + [Test, Pairwise] [Explicit] + public void F_Abs_Neg_Recpx_Sqrt_S_S([ValueSource("_F_Abs_Neg_Recpx_Sqrt_S_S_")] uint opcodes, + [ValueSource("_1S_F_")] ulong a) { - uint Opcode = 0x2E20B820; // NEG V0.8B, V1.8B - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); - Vector128 V0 = MakeVectorE1(TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0(A); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); - AArch64.V(1, new Bits(A)); - SimdFp.Neg_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]); + int fpcr = rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); + SingleOpcode(opcodes, v0: v0, v1: v1, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Idc); + } + + [Test, Pairwise] [Explicit] + public void F_Abs_Neg_Recpx_Sqrt_S_D([ValueSource("_F_Abs_Neg_Recpx_Sqrt_S_D_")] uint opcodes, + [ValueSource("_1D_F_")] ulong a) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE1(z); + V128 v1 = MakeVectorE0(a); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Idc); + } + + [Test, Pairwise] [Explicit] + public void F_Abs_Neg_Sqrt_V_2S_4S([ValueSource("_F_Abs_Neg_Sqrt_V_2S_4S_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_2S_F_")] ulong z, + [ValueSource("_2S_F_")] ulong a, + [Values(0b0u, 0b1u)] uint q) // <2S, 4S> + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Idc); + } + + [Test, Pairwise] [Explicit] + public void F_Abs_Neg_Sqrt_V_2D([ValueSource("_F_Abs_Neg_Sqrt_V_2D_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_1D_F_")] ulong z, + [ValueSource("_1D_F_")] ulong a) + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Idc); + } + + [Test, Pairwise] [Explicit] + public void F_Add_P_S_2SS([ValueSource("_F_Add_P_S_2SS_")] uint opcodes, + [ValueSource("_2S_F_")] ulong a) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Idc); + } + + [Test, Pairwise] [Explicit] + public void F_Add_P_S_2DD([ValueSource("_F_Add_P_S_2DD_")] uint opcodes, + [ValueSource("_1D_F_")] ulong a) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE1(z); + V128 v1 = MakeVectorE0E1(a, a); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Idc); + } + + [Test, Pairwise] [Explicit] + public void F_Cm_EqGeGtLeLt_S_S([ValueSource("_F_Cm_EqGeGtLeLt_S_S_")] uint opcodes, + [ValueSource("_1S_F_")] ulong a) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + + SingleOpcode(opcodes, v0: v0, v1: v1, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Idc); + } + + [Test, Pairwise] [Explicit] + public void F_Cm_EqGeGtLeLt_S_D([ValueSource("_F_Cm_EqGeGtLeLt_S_D_")] uint opcodes, + [ValueSource("_1D_F_")] ulong a) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE1(z); + V128 v1 = MakeVectorE0(a); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + + SingleOpcode(opcodes, v0: v0, v1: v1, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Idc); + } + + [Test, Pairwise] [Explicit] + public void F_Cm_EqGeGtLeLt_V_2S_4S([ValueSource("_F_Cm_EqGeGtLeLt_V_2S_4S_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_2S_F_")] ulong z, + [ValueSource("_2S_F_")] ulong a, + [Values(0b0u, 0b1u)] uint q) // <2S, 4S> + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + + SingleOpcode(opcodes, v0: v0, v1: v1, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Idc); + } + + [Test, Pairwise] [Explicit] + public void F_Cm_EqGeGtLeLt_V_2D([ValueSource("_F_Cm_EqGeGtLeLt_V_2D_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_1D_F_")] ulong z, + [ValueSource("_1D_F_")] ulong a) + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + + SingleOpcode(opcodes, v0: v0, v1: v1, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Idc); + } + + [Test, Pairwise] [Explicit] + public void F_Cmp_Cmpe_S_S([ValueSource("_F_Cmp_Cmpe_S_S_")] uint opcodes, + [ValueSource("_1S_F_")] ulong a) + { + V128 v1 = MakeVectorE0(a); + + bool v = TestContext.CurrentContext.Random.NextBool(); + bool c = TestContext.CurrentContext.Random.NextBool(); + bool z = TestContext.CurrentContext.Random.NextBool(); + bool n = TestContext.CurrentContext.Random.NextBool(); + + SingleOpcode(opcodes, v1: v1, overflow: v, carry: c, zero: z, negative: n); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc); + } + + [Test, Pairwise] [Explicit] + public void F_Cmp_Cmpe_S_D([ValueSource("_F_Cmp_Cmpe_S_D_")] uint opcodes, + [ValueSource("_1D_F_")] ulong a) + { + V128 v1 = MakeVectorE0(a); + + bool v = TestContext.CurrentContext.Random.NextBool(); + bool c = TestContext.CurrentContext.Random.NextBool(); + bool z = TestContext.CurrentContext.Random.NextBool(); + bool n = TestContext.CurrentContext.Random.NextBool(); + + SingleOpcode(opcodes, v1: v1, overflow: v, carry: c, zero: z, negative: n); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc); + } + + [Test, Pairwise] [Explicit] + public void F_Cvt_S_SD([ValueSource("_F_Cvt_S_SD_")] uint opcodes, + [ValueSource("_1S_F_")] ulong a) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE1(z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] [Explicit] + public void F_Cvt_S_DS([ValueSource("_F_Cvt_S_DS_")] uint opcodes, + [ValueSource("_1D_F_")] ulong a) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] [Explicit] + public void F_Cvt_S_SH([ValueSource("_F_Cvt_S_SH_")] uint opcodes, + [ValueSource("_1S_F_")] ulong a) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] [Explicit] + public void F_Cvt_S_HS([ValueSource("_F_Cvt_S_HS_")] uint opcodes, + [ValueSource("_1H_F_")] ulong a) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] [Explicit] + public void F_Cvt_NZ_SU_S_S([ValueSource("_F_Cvt_NZ_SU_S_S_")] uint opcodes, + [ValueSource("_1S_F_W_")] ulong a) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] [Explicit] + public void F_Cvt_NZ_SU_S_D([ValueSource("_F_Cvt_NZ_SU_S_D_")] uint opcodes, + [ValueSource("_1D_F_X_")] ulong a) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE1(z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] [Explicit] + public void F_Cvt_NZ_SU_V_2S_4S([ValueSource("_F_Cvt_NZ_SU_V_2S_4S_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_2S_F_W_")] ulong z, + [ValueSource("_2S_F_W_")] ulong a, + [Values(0b0u, 0b1u)] uint q) // <2S, 4S> + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] [Explicit] + public void F_Cvt_NZ_SU_V_2D([ValueSource("_F_Cvt_NZ_SU_V_2D_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_1D_F_X_")] ulong z, + [ValueSource("_1D_F_X_")] ulong a) + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] [Explicit] + public void F_Cvtl_V_4H4S_8H4S([ValueSource("_F_Cvtl_V_4H4S_8H4S_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_4H_F_")] ulong z, + [ValueSource("_4H_F_")] ulong a, + [Values(0b0u, 0b1u)] uint q, // <4H4S, 8H4S> + [Values(RMode.Rn)] RMode rMode) + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(q == 0u ? a : 0ul, q == 1u ? a : 0ul); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = (int)rMode << (int)Fpcr.RMode; + fpcr |= rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + fpcr |= rnd & (1 << (int)Fpcr.Ahp); + + SingleOpcode(opcodes, v0: v0, v1: v1, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Ofc | Fpsr.Ufc | Fpsr.Ixc); + } + + [Test, Pairwise] [Explicit] + public void F_Cvtl_V_2S2D_4S2D([ValueSource("_F_Cvtl_V_2S2D_4S2D_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_2S_F_")] ulong z, + [ValueSource("_2S_F_")] ulong a, + [Values(0b0u, 0b1u)] uint q) // <2S2D, 4S2D> + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(q == 0u ? a : 0ul, q == 1u ? a : 0ul); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] [Explicit] // Unicorn seems to default all rounding modes to RMode.Rn. + public void F_Cvtn_V_4S4H_4S8H([ValueSource("_F_Cvtn_V_4S4H_4S8H_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_2S_F_")] ulong z, + [ValueSource("_2S_F_")] ulong a, + [Values(0b0u, 0b1u)] uint q, // <4S4H, 4S8H> + [Values(RMode.Rn)] RMode rMode) + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = (int)rMode << (int)Fpcr.RMode; + fpcr |= rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + fpcr |= rnd & (1 << (int)Fpcr.Ahp); + + SingleOpcode(opcodes, v0: v0, v1: v1, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Ofc | Fpsr.Ufc | Fpsr.Ixc | Fpsr.Idc); + } + + [Test, Pairwise] [Explicit] // Unicorn seems to default all rounding modes to RMode.Rn. + public void F_Cvtn_V_2D2S_2D4S([ValueSource("_F_Cvtn_V_2D2S_2D4S_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_1D_F_")] ulong z, + [ValueSource("_1D_F_")] ulong a, + [Values(0b0u, 0b1u)] uint q) // <2D2S, 2D4S> + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] [Explicit] + public void F_Mov_Ftoi_SW([ValueSource("_F_Mov_Ftoi_SW_")] uint opcodes, + [Values(0u, 31u)] uint rd, + [Values(1u)] uint rn, + [ValueSource("_1S_F_")] ulong a) + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + + ulong x0 = (ulong)TestContext.CurrentContext.Random.NextUInt() << 32; + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, x0: x0, x31: w31, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] [Explicit] + public void F_Mov_Ftoi_DX([ValueSource("_F_Mov_Ftoi_DX_")] uint opcodes, + [Values(0u, 31u)] uint rd, + [Values(1u)] uint rn, + [ValueSource("_1D_F_")] ulong a) + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, x31: x31, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] [Explicit] + public void F_Mov_Ftoi1_DX([ValueSource("_F_Mov_Ftoi1_DX_")] uint opcodes, + [Values(0u, 31u)] uint rd, + [Values(1u)] uint rn, + [ValueSource("_1D_F_")] ulong a) + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + V128 v1 = MakeVectorE1(a); + + SingleOpcode(opcodes, x31: x31, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] [Explicit] + public void F_Mov_Itof_WS([ValueSource("_F_Mov_Itof_WS_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 31u)] uint rn, + [ValueSource("_W_")] [Random(RndCnt)] uint wn) + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + + SingleOpcode(opcodes, x1: wn, x31: w31, v0: v0); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] [Explicit] + public void F_Mov_Itof_XD([ValueSource("_F_Mov_Itof_XD_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 31u)] uint rn, + [ValueSource("_X_")] [Random(RndCnt)] ulong xn) + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE1(z); + + SingleOpcode(opcodes, x1: xn, x31: x31, v0: v0); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] [Explicit] + public void F_Mov_Itof1_XD([ValueSource("_F_Mov_Itof1_XD_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 31u)] uint rn, + [ValueSource("_X_")] [Random(RndCnt)] ulong xn) + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0(z); + + SingleOpcode(opcodes, x1: xn, x31: x31, v0: v0); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] [Explicit] + public void F_Mov_S_S([ValueSource("_F_Mov_S_S_")] uint opcodes, + [ValueSource("_1S_F_")] ulong a) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] [Explicit] + public void F_Mov_S_D([ValueSource("_F_Mov_S_D_")] uint opcodes, + [ValueSource("_1D_F_")] ulong a) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE1(z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] [Explicit] + public void F_Recpe_Rsqrte_S_S([ValueSource("_F_Recpe_Rsqrte_S_S_")] uint opcodes, + [ValueSource("_1S_F_")] ulong a, + [Values(RMode.Rn)] RMode rMode) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = (int)rMode << (int)Fpcr.RMode; + fpcr |= rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Dzc | Fpsr.Ofc | Fpsr.Ufc | Fpsr.Ixc | Fpsr.Idc); + } + + [Test, Pairwise] [Explicit] + public void F_Recpe_Rsqrte_S_D([ValueSource("_F_Recpe_Rsqrte_S_D_")] uint opcodes, + [ValueSource("_1D_F_")] ulong a, + [Values(RMode.Rn)] RMode rMode) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE1(z); + V128 v1 = MakeVectorE0(a); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = (int)rMode << (int)Fpcr.RMode; + fpcr |= rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Dzc | Fpsr.Ofc | Fpsr.Ufc | Fpsr.Ixc | Fpsr.Idc); + } + + [Test, Pairwise] [Explicit] + public void F_Recpe_Rsqrte_V_2S_4S([ValueSource("_F_Recpe_Rsqrte_V_2S_4S_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_2S_F_")] ulong z, + [ValueSource("_2S_F_")] ulong a, + [Values(0b0u, 0b1u)] uint q, // <2S, 4S> + [Values(RMode.Rn)] RMode rMode) + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = (int)rMode << (int)Fpcr.RMode; + fpcr |= rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Dzc | Fpsr.Ofc | Fpsr.Ufc | Fpsr.Ixc | Fpsr.Idc); + } + + [Test, Pairwise] [Explicit] + public void F_Recpe_Rsqrte_V_2D([ValueSource("_F_Recpe_Rsqrte_V_2D_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_1D_F_")] ulong z, + [ValueSource("_1D_F_")] ulong a, + [Values(RMode.Rn)] RMode rMode) + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = (int)rMode << (int)Fpcr.RMode; + fpcr |= rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Dzc | Fpsr.Ofc | Fpsr.Ufc | Fpsr.Ixc | Fpsr.Idc); + } + + [Test, Pairwise] [Explicit] + public void F_Rint_AMNPZ_S_S([ValueSource("_F_Rint_AMNPZ_S_S_")] uint opcodes, + [ValueSource("_1S_F_")] ulong a) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] [Explicit] + public void F_Rint_AMNPZ_S_D([ValueSource("_F_Rint_AMNPZ_S_D_")] uint opcodes, + [ValueSource("_1D_F_")] ulong a) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE1(z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] [Explicit] + public void F_Rint_AMNPZ_V_2S_4S([ValueSource("_F_Rint_AMNPZ_V_2S_4S_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_2S_F_")] ulong z, + [ValueSource("_2S_F_")] ulong a, + [Values(0b0u, 0b1u)] uint q) // <2S, 4S> + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] [Explicit] + public void F_Rint_AMNPZ_V_2D([ValueSource("_F_Rint_AMNPZ_V_2D_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_1D_F_")] ulong z, + [ValueSource("_1D_F_")] ulong a) + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] [Explicit] + public void F_Rint_IX_S_S([ValueSource("_F_Rint_IX_S_S_")] uint opcodes, + [ValueSource("_1S_F_")] ulong a, + [Values] RMode rMode) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + int fpcr = (int)rMode << (int)Fpcr.RMode; + + SingleOpcode(opcodes, v0: v0, v1: v1, fpcr: fpcr); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] [Explicit] + public void F_Rint_IX_S_D([ValueSource("_F_Rint_IX_S_D_")] uint opcodes, + [ValueSource("_1D_F_")] ulong a, + [Values] RMode rMode) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE1(z); + V128 v1 = MakeVectorE0(a); + + int fpcr = (int)rMode << (int)Fpcr.RMode; + + SingleOpcode(opcodes, v0: v0, v1: v1, fpcr: fpcr); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] [Explicit] + public void F_Rint_IX_V_2S_4S([ValueSource("_F_Rint_IX_V_2S_4S_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_2S_F_")] ulong z, + [ValueSource("_2S_F_")] ulong a, + [Values(0b0u, 0b1u)] uint q, // <2S, 4S> + [Values] RMode rMode) + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + + int fpcr = (int)rMode << (int)Fpcr.RMode; + + SingleOpcode(opcodes, v0: v0, v1: v1, fpcr: fpcr); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] [Explicit] + public void F_Rint_IX_V_2D([ValueSource("_F_Rint_IX_V_2D_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_1D_F_")] ulong z, + [ValueSource("_1D_F_")] ulong a, + [Values] RMode rMode) + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + int fpcr = (int)rMode << (int)Fpcr.RMode; + + SingleOpcode(opcodes, v0: v0, v1: v1, fpcr: fpcr); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("NEG , ")] + public void Neg_S_D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_1D_")] [Random(RndCnt)] ulong a) + { + uint opcode = 0x7EE0B800; // NEG D0, D0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); } [Test, Pairwise, Description("NEG ., .")] - public void Neg_V_16B_8H_4S_2D([ValueSource("_8B4H2S1D_")] [Random(1)] ulong A0, - [ValueSource("_8B4H2S1D_")] [Random(1)] ulong A1, - [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + public void Neg_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> { - uint Opcode = 0x6E20B820; // NEG V0.16B, V1.16B - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); + uint opcode = 0x2E20B800; // NEG V0.8B, V0.8B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); - Vector128 V1 = MakeVectorE0E1(A0, A1); - AThreadState ThreadState = SingleOpcode(Opcode, V1: V1); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); - AArch64.Vpart(1, 0, new Bits(A0)); - AArch64.Vpart(1, 1, new Bits(A1)); - SimdFp.Neg_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); - }); + CompareAgainstUnicorn(); } - [Test, Description("NOT ., .")] - public void Not_V_8B([ValueSource("_8B_")] [Random(1)] ulong A) + [Test, Pairwise, Description("NEG ., .")] + public void Neg_V_16B_8H_4S_2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong a, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> { - uint Opcode = 0x2E205820; // NOT V0.8B, V1.8B - Bits Op = new Bits(Opcode); + uint opcode = 0x6E20B800; // NEG V0.16B, V0.16B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); - Vector128 V0 = MakeVectorE1(TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0(A); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); - AArch64.V(1, new Bits(A)); - SimdFp.Not_V(Op[30], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("NOT ., .")] - public void Not_V_16B([ValueSource("_8B_")] [Random(1)] ulong A0, - [ValueSource("_8B_")] [Random(1)] ulong A1) + public void Not_V_8B([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_8B_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B_")] [Random(RndCnt)] ulong a) { - uint Opcode = 0x6E205820; // NOT V0.16B, V1.16B - Bits Op = new Bits(Opcode); + uint opcode = 0x2E205800; // NOT V0.8B, V0.8B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); - Vector128 V1 = MakeVectorE0E1(A0, A1); - AThreadState ThreadState = SingleOpcode(Opcode, V1: V1); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); - AArch64.Vpart(1, 0, new Bits(A0)); - AArch64.Vpart(1, 1, new Bits(A1)); - SimdFp.Not_V(Op[30], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); - }); + CompareAgainstUnicorn(); } - [Test, Description("RBIT ., .")] - public void Rbit_V_8B([ValueSource("_8B_")] [Random(1)] ulong A) + [Test, Pairwise, Description("NOT ., .")] + public void Not_V_16B([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_8B_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B_")] [Random(RndCnt)] ulong a) { - uint Opcode = 0x2E605820; // RBIT V0.8B, V1.8B - Bits Op = new Bits(Opcode); + uint opcode = 0x6E205800; // NOT V0.16B, V0.16B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); - Vector128 V0 = MakeVectorE1(TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0(A); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); - AArch64.V(1, new Bits(A)); - SimdFp.Rbit_V(Op[30], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("RBIT ., .")] - public void Rbit_V_16B([ValueSource("_8B_")] [Random(1)] ulong A0, - [ValueSource("_8B_")] [Random(1)] ulong A1) + public void Rbit_V_8B([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_8B_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B_")] [Random(RndCnt)] ulong a) { - uint Opcode = 0x6E605820; // RBIT V0.16B, V1.16B - Bits Op = new Bits(Opcode); + uint opcode = 0x2E605800; // RBIT V0.8B, V0.8B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); - Vector128 V1 = MakeVectorE0E1(A0, A1); - AThreadState ThreadState = SingleOpcode(Opcode, V1: V1); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); - AArch64.Vpart(1, 0, new Bits(A0)); - AArch64.Vpart(1, 1, new Bits(A1)); - SimdFp.Rbit_V(Op[30], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); - }); + CompareAgainstUnicorn(); } - [Test, Description("REV16 ., .")] - public void Rev16_V_8B([ValueSource("_8B_")] [Random(1)] ulong A) + [Test, Pairwise, Description("RBIT ., .")] + public void Rbit_V_16B([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_8B_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B_")] [Random(RndCnt)] ulong a) { - uint Opcode = 0x0E201820; // REV16 V0.8B, V1.8B - Bits Op = new Bits(Opcode); + uint opcode = 0x6E605800; // RBIT V0.16B, V0.16B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); - Vector128 V0 = MakeVectorE1(TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0(A); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); - AArch64.V(1, new Bits(A)); - SimdFp.Rev16_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("REV16 ., .")] - public void Rev16_V_16B([ValueSource("_8B_")] [Random(1)] ulong A0, - [ValueSource("_8B_")] [Random(1)] ulong A1) + public void Rev16_V_8B([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_8B_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B_")] [Random(RndCnt)] ulong a) { - uint Opcode = 0x4E201820; // REV16 V0.16B, V1.16B - Bits Op = new Bits(Opcode); + uint opcode = 0x0E201800; // REV16 V0.8B, V0.8B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); - Vector128 V1 = MakeVectorE0E1(A0, A1); - AThreadState ThreadState = SingleOpcode(Opcode, V1: V1); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); - AArch64.Vpart(1, 0, new Bits(A0)); - AArch64.Vpart(1, 1, new Bits(A1)); - SimdFp.Rev16_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); - }); + CompareAgainstUnicorn(); } - [Test, Description("REV32 ., .")] - public void Rev32_V_8B_4H([ValueSource("_8B4H_")] [Random(1)] ulong A, - [Values(0b00u, 0b01u)] uint size) // <8B, 4H> + [Test, Pairwise, Description("REV16 ., .")] + public void Rev16_V_16B([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_8B_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B_")] [Random(RndCnt)] ulong a) { - uint Opcode = 0x2E200820; // REV32 V0.8B, V1.8B - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); + uint opcode = 0x4E201800; // REV16 V0.16B, V0.16B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); - Vector128 V0 = MakeVectorE1(TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0(A); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); - AArch64.V(1, new Bits(A)); - SimdFp.Rev32_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("REV32 ., .")] - public void Rev32_V_16B_8H([ValueSource("_8B4H_")] [Random(1)] ulong A0, - [ValueSource("_8B4H_")] [Random(1)] ulong A1, - [Values(0b00u, 0b01u)] uint size) // <16B, 8H> + public void Rev32_V_8B_4H([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_8B4H_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H_")] [Random(RndCnt)] ulong a, + [Values(0b00u, 0b01u)] uint size) // <8B, 4H> { - uint Opcode = 0x6E200820; // REV32 V0.16B, V1.16B - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); + uint opcode = 0x2E200800; // REV32 V0.8B, V0.8B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); - Vector128 V1 = MakeVectorE0E1(A0, A1); - AThreadState ThreadState = SingleOpcode(Opcode, V1: V1); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); - AArch64.Vpart(1, 0, new Bits(A0)); - AArch64.Vpart(1, 1, new Bits(A1)); - SimdFp.Rev32_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); - }); + CompareAgainstUnicorn(); } - [Test, Description("REV64 ., .")] - public void Rev64_V_8B_4H_2S([ValueSource("_8B4H2S_")] [Random(1)] ulong A, - [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + [Test, Pairwise, Description("REV32 ., .")] + public void Rev32_V_16B_8H([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_8B4H_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H_")] [Random(RndCnt)] ulong a, + [Values(0b00u, 0b01u)] uint size) // <16B, 8H> { - uint Opcode = 0x0E200820; // REV64 V0.8B, V1.8B - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); + uint opcode = 0x6E200800; // REV32 V0.16B, V0.16B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); - Vector128 V0 = MakeVectorE1(TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0(A); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); - AArch64.V(1, new Bits(A)); - SimdFp.Rev64_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("REV64 ., .")] - public void Rev64_V_16B_8H_4S([ValueSource("_8B4H2S_")] [Random(1)] ulong A0, - [ValueSource("_8B4H2S_")] [Random(1)] ulong A1, + public void Rev64_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x0E200800; // REV64 V0.8B, V0.8B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("REV64 ., .")] + public void Rev64_V_16B_8H_4S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B, 8H, 4S> { - uint Opcode = 0x4E200820; // REV64 V0.16B, V1.16B - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); + uint opcode = 0x4E200800; // REV64 V0.16B, V0.16B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); - Vector128 V1 = MakeVectorE0E1(A0, A1); - AThreadState ThreadState = SingleOpcode(Opcode, V1: V1); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); - AArch64.Vpart(1, 0, new Bits(A0)); - AArch64.Vpart(1, 1, new Bits(A1)); - SimdFp.Rev64_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); - }); + CompareAgainstUnicorn(); } - [Test, Description("SQXTN , ")] - public void Sqxtn_S_HB_SH_DS([ValueSource("_1H1S1D_")] [Random(1)] ulong A, + [Test, Pairwise, Description("SADALP ., .")] + public void Sadalp_V_8B4H_4H2S_2S1D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B4H, 4H2S, 2S1D> + { + uint opcode = 0x0E206800; // SADALP V0.4H, V0.8B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SADALP ., .")] + public void Sadalp_V_16B8H_8H4S_4S2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B8H, 8H4S, 4S2D> + { + uint opcode = 0x4E206800; // SADALP V0.8H, V0.16B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SADDLP ., .")] + public void Saddlp_V_8B4H_4H2S_2S1D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B4H, 4H2S, 2S1D> + { + uint opcode = 0x0E202800; // SADDLP V0.4H, V0.8B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SADDLP ., .")] + public void Saddlp_V_16B8H_8H4S_4S2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B8H, 8H4S, 4S2D> + { + uint opcode = 0x4E202800; // SADDLP V0.8H, V0.16B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void SU_Addl_V_V_8BH_4HS([ValueSource("_SU_Addl_V_V_8BH_4HS_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_8B4H_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H_")] [Random(RndCnt)] ulong a, + [Values(0b00u, 0b01u)] uint size) // <8BH, 4HS> + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void SU_Addl_V_V_16BH_8HS_4SD([ValueSource("_SU_Addl_V_V_16BH_8HS_4SD_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16BH, 8HS, 4SD> + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] [Explicit] + public void SU_Cvt_F_S_S([ValueSource("_SU_Cvt_F_S_S_")] uint opcodes, + [ValueSource("_1S_")] [Random(RndCnt)] ulong a) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] [Explicit] + public void SU_Cvt_F_S_D([ValueSource("_SU_Cvt_F_S_D_")] uint opcodes, + [ValueSource("_1D_")] [Random(RndCnt)] ulong a) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE1(z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] [Explicit] + public void SU_Cvt_F_V_2S_4S([ValueSource("_SU_Cvt_F_V_2S_4S_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_2S_")] [Random(RndCnt)] ulong a, + [Values(0b0u, 0b1u)] uint q) // <2S, 4S> + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] [Explicit] + public void SU_Cvt_F_V_2D([ValueSource("_SU_Cvt_F_V_2D_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_1D_")] [Random(RndCnt)] ulong a) + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Sha1h_Sha1su1_V([ValueSource("_Sha1h_Sha1su1_V_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Random(RndCnt / 2)] ulong z0, [Random(RndCnt / 2)] ulong z1, + [Random(RndCnt / 2)] ulong a0, [Random(RndCnt / 2)] ulong a1) + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z0, z1); + V128 v1 = MakeVectorE0E1(a0, a1); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Sha256su0_V([ValueSource("_Sha256su0_V_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Random(RndCnt / 2)] ulong z0, [Random(RndCnt / 2)] ulong z1, + [Random(RndCnt / 2)] ulong a0, [Random(RndCnt / 2)] ulong a1) + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z0, z1); + V128 v1 = MakeVectorE0E1(a0, a1); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SHLL{2} ., ., #")] + public void Shll_V([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [Values(0b00u, 0b01u, 0b10u)] uint size, // + [Values(0b0u, 0b1u)] uint q) + { + uint opcode = 0x2E213800; // SHLL V0.8H, V0.8B, #8 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + opcode |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(q == 0u ? a : 0ul, q == 1u ? a : 0ul); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SQABS , ")] + public void Sqabs_S_B_H_S_D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_1B1H1S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_1B1H1S1D_")] [Random(RndCnt)] ulong a, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // + { + uint opcode = 0x5E207800; // SQABS B0, B0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("SQABS ., .")] + public void Sqabs_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x0E207800; // SQABS V0.8B, V0.8B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("SQABS ., .")] + public void Sqabs_V_16B_8H_4S_2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong a, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint opcode = 0x4E207800; // SQABS V0.16B, V0.16B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("SQNEG , ")] + public void Sqneg_S_B_H_S_D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_1B1H1S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_1B1H1S1D_")] [Random(RndCnt)] ulong a, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // + { + uint opcode = 0x7E207800; // SQNEG B0, B0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("SQNEG ., .")] + public void Sqneg_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x2E207800; // SQNEG V0.8B, V0.8B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("SQNEG ., .")] + public void Sqneg_V_16B_8H_4S_2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong a, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint opcode = 0x6E207800; // SQNEG V0.16B, V0.16B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("SQXTN , ")] + public void Sqxtn_S_HB_SH_DS([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_1H1S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_1H1S1D_")] [Random(RndCnt)] ulong a, [Values(0b00u, 0b01u, 0b10u)] uint size) // { - uint Opcode = 0x5E214820; // SQXTN B0, H1 - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); + uint opcode = 0x5E214800; // SQXTN B0, H0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); - Vector128 V0 = MakeVectorE0E1(TestContext.CurrentContext.Random.NextULong(), - TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0(A); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); - AArch64.Vpart(0, 0, new Bits(TestContext.CurrentContext.Random.NextULong())); - AArch64.V(1, new Bits(A)); - SimdFp.Sqxtn_S(Op[23, 22], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); - Assert.That(((ThreadState.Fpsr >> 27) & 1) != 0, Is.EqualTo(Shared.FPSR[27])); + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); } [Test, Pairwise, Description("SQXTN{2} ., .")] - public void Sqxtn_V_8H8B_4S4H_2D2S([ValueSource("_4H2S1D_")] [Random(1)] ulong A0, - [ValueSource("_4H2S1D_")] [Random(1)] ulong A1, + public void Sqxtn_V_8H8B_4S4H_2D2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_4H2S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_4H2S1D_")] [Random(RndCnt)] ulong a, [Values(0b00u, 0b01u, 0b10u)] uint size) // <8H8B, 4S4H, 2D2S> { - uint Opcode = 0x0E214820; // SQXTN V0.8B, V1.8H - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); + uint opcode = 0x0E214800; // SQXTN V0.8B, V0.8H + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); - Vector128 V0 = MakeVectorE1(TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0E1(A0, A1); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); - AArch64.Vpart(1, 0, new Bits(A0)); - AArch64.Vpart(1, 1, new Bits(A1)); - SimdFp.Sqxtn_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); - Assert.That(((ThreadState.Fpsr >> 27) & 1) != 0, Is.EqualTo(Shared.FPSR[27])); + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); } [Test, Pairwise, Description("SQXTN{2} ., .")] - public void Sqxtn_V_8H16B_4S8H_2D4S([ValueSource("_4H2S1D_")] [Random(1)] ulong A0, - [ValueSource("_4H2S1D_")] [Random(1)] ulong A1, + public void Sqxtn_V_8H16B_4S8H_2D4S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_4H2S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_4H2S1D_")] [Random(RndCnt)] ulong a, [Values(0b00u, 0b01u, 0b10u)] uint size) // <8H16B, 4S8H, 2D4S> { - uint Opcode = 0x4E214820; // SQXTN2 V0.16B, V1.8H - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); + uint opcode = 0x4E214800; // SQXTN2 V0.16B, V0.8H + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); - ulong _E0 = TestContext.CurrentContext.Random.NextULong(); - Vector128 V0 = MakeVectorE0(_E0); - Vector128 V1 = MakeVectorE0E1(A0, A1); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); - AArch64.Vpart(1, 0, new Bits(A0)); - AArch64.Vpart(1, 1, new Bits(A1)); - SimdFp.Sqxtn_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(_E0)); - Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); - }); - Assert.That(((ThreadState.Fpsr >> 27) & 1) != 0, Is.EqualTo(Shared.FPSR[27])); + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); } - [Test, Description("SQXTUN , ")] - public void Sqxtun_S_HB_SH_DS([ValueSource("_1H1S1D_")] [Random(1)] ulong A, + [Test, Pairwise, Description("SQXTUN , ")] + public void Sqxtun_S_HB_SH_DS([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_1H1S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_1H1S1D_")] [Random(RndCnt)] ulong a, [Values(0b00u, 0b01u, 0b10u)] uint size) // { - uint Opcode = 0x7E212820; // SQXTUN B0, H1 - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); + uint opcode = 0x7E212800; // SQXTUN B0, H0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); - Vector128 V0 = MakeVectorE0E1(TestContext.CurrentContext.Random.NextULong(), - TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0(A); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); - AArch64.Vpart(0, 0, new Bits(TestContext.CurrentContext.Random.NextULong())); - AArch64.V(1, new Bits(A)); - SimdFp.Sqxtun_S(Op[23, 22], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); - Assert.That(((ThreadState.Fpsr >> 27) & 1) != 0, Is.EqualTo(Shared.FPSR[27])); + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); } [Test, Pairwise, Description("SQXTUN{2} ., .")] - public void Sqxtun_V_8H8B_4S4H_2D2S([ValueSource("_4H2S1D_")] [Random(1)] ulong A0, - [ValueSource("_4H2S1D_")] [Random(1)] ulong A1, + public void Sqxtun_V_8H8B_4S4H_2D2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_4H2S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_4H2S1D_")] [Random(RndCnt)] ulong a, [Values(0b00u, 0b01u, 0b10u)] uint size) // <8H8B, 4S4H, 2D2S> { - uint Opcode = 0x2E212820; // SQXTUN V0.8B, V1.8H - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); + uint opcode = 0x2E212800; // SQXTUN V0.8B, V0.8H + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); - Vector128 V0 = MakeVectorE1(TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0E1(A0, A1); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); - AArch64.Vpart(1, 0, new Bits(A0)); - AArch64.Vpart(1, 1, new Bits(A1)); - SimdFp.Sqxtun_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); - Assert.That(((ThreadState.Fpsr >> 27) & 1) != 0, Is.EqualTo(Shared.FPSR[27])); + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); } [Test, Pairwise, Description("SQXTUN{2} ., .")] - public void Sqxtun_V_8H16B_4S8H_2D4S([ValueSource("_4H2S1D_")] [Random(1)] ulong A0, - [ValueSource("_4H2S1D_")] [Random(1)] ulong A1, + public void Sqxtun_V_8H16B_4S8H_2D4S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_4H2S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_4H2S1D_")] [Random(RndCnt)] ulong a, [Values(0b00u, 0b01u, 0b10u)] uint size) // <8H16B, 4S8H, 2D4S> { - uint Opcode = 0x6E212820; // SQXTUN2 V0.16B, V1.8H - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); + uint opcode = 0x6E212800; // SQXTUN2 V0.16B, V0.8H + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); - ulong _E0 = TestContext.CurrentContext.Random.NextULong(); - Vector128 V0 = MakeVectorE0(_E0); - Vector128 V1 = MakeVectorE0E1(A0, A1); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); - AArch64.Vpart(1, 0, new Bits(A0)); - AArch64.Vpart(1, 1, new Bits(A1)); - SimdFp.Sqxtun_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(_E0)); - Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); - }); - Assert.That(((ThreadState.Fpsr >> 27) & 1) != 0, Is.EqualTo(Shared.FPSR[27])); + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); } - [Test, Description("UQXTN , ")] - public void Uqxtn_S_HB_SH_DS([ValueSource("_1H1S1D_")] [Random(1)] ulong A, + [Test, Pairwise, Description("SUQADD , ")] + public void Suqadd_S_B_H_S_D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_1B1H1S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_1B1H1S1D_")] [Random(RndCnt)] ulong a, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // + { + uint opcode = 0x5E203800; // SUQADD B0, B0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("SUQADD ., .")] + public void Suqadd_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x0E203800; // SUQADD V0.8B, V0.8B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("SUQADD ., .")] + public void Suqadd_V_16B_8H_4S_2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong a, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint opcode = 0x4E203800; // SUQADD V0.16B, V0.16B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("UADALP ., .")] + public void Uadalp_V_8B4H_4H2S_2S1D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B4H, 4H2S, 2S1D> + { + uint opcode = 0x2E206800; // UADALP V0.4H, V0.8B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UADALP ., .")] + public void Uadalp_V_16B8H_8H4S_4S2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B8H, 8H4S, 4S2D> + { + uint opcode = 0x6E206800; // UADALP V0.8H, V0.16B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UADDLP ., .")] + public void Uaddlp_V_8B4H_4H2S_2S1D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B4H, 4H2S, 2S1D> + { + uint opcode = 0x2E202800; // UADDLP V0.4H, V0.8B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UADDLP ., .")] + public void Uaddlp_V_16B8H_8H4S_4S2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B8H, 8H4S, 4S2D> + { + uint opcode = 0x6E202800; // UADDLP V0.8H, V0.16B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UQXTN , ")] + public void Uqxtn_S_HB_SH_DS([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_1H1S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_1H1S1D_")] [Random(RndCnt)] ulong a, [Values(0b00u, 0b01u, 0b10u)] uint size) // { - uint Opcode = 0x7E214820; // UQXTN B0, H1 - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); + uint opcode = 0x7E214800; // UQXTN B0, H0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); - Vector128 V0 = MakeVectorE0E1(TestContext.CurrentContext.Random.NextULong(), - TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0(A); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); - AArch64.Vpart(0, 0, new Bits(TestContext.CurrentContext.Random.NextULong())); - AArch64.V(1, new Bits(A)); - SimdFp.Uqxtn_S(Op[23, 22], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); - Assert.That(((ThreadState.Fpsr >> 27) & 1) != 0, Is.EqualTo(Shared.FPSR[27])); + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); } [Test, Pairwise, Description("UQXTN{2} ., .")] - public void Uqxtn_V_8H8B_4S4H_2D2S([ValueSource("_4H2S1D_")] [Random(1)] ulong A0, - [ValueSource("_4H2S1D_")] [Random(1)] ulong A1, + public void Uqxtn_V_8H8B_4S4H_2D2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_4H2S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_4H2S1D_")] [Random(RndCnt)] ulong a, [Values(0b00u, 0b01u, 0b10u)] uint size) // <8H8B, 4S4H, 2D2S> { - uint Opcode = 0x2E214820; // UQXTN V0.8B, V1.8H - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); + uint opcode = 0x2E214800; // UQXTN V0.8B, V0.8H + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); - Vector128 V0 = MakeVectorE1(TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0E1(A0, A1); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); - AArch64.Vpart(1, 0, new Bits(A0)); - AArch64.Vpart(1, 1, new Bits(A1)); - SimdFp.Uqxtn_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); - Assert.That(((ThreadState.Fpsr >> 27) & 1) != 0, Is.EqualTo(Shared.FPSR[27])); + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); } [Test, Pairwise, Description("UQXTN{2} ., .")] - public void Uqxtn_V_8H16B_4S8H_2D4S([ValueSource("_4H2S1D_")] [Random(1)] ulong A0, - [ValueSource("_4H2S1D_")] [Random(1)] ulong A1, + public void Uqxtn_V_8H16B_4S8H_2D4S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_4H2S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_4H2S1D_")] [Random(RndCnt)] ulong a, [Values(0b00u, 0b01u, 0b10u)] uint size) // <8H16B, 4S8H, 2D4S> { - uint Opcode = 0x6E214820; // UQXTN2 V0.16B, V1.8H - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); + uint opcode = 0x6E214800; // UQXTN2 V0.16B, V0.8H + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); - ulong _E0 = TestContext.CurrentContext.Random.NextULong(); - Vector128 V0 = MakeVectorE0(_E0); - Vector128 V1 = MakeVectorE0E1(A0, A1); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); - AArch64.Vpart(1, 0, new Bits(A0)); - AArch64.Vpart(1, 1, new Bits(A1)); - SimdFp.Uqxtn_V(Op[30], Op[23, 22], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(_E0)); - Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); - }); - Assert.That(((ThreadState.Fpsr >> 27) & 1) != 0, Is.EqualTo(Shared.FPSR[27])); + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("USQADD , ")] + public void Usqadd_S_B_H_S_D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_1B1H1S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_1B1H1S1D_")] [Random(RndCnt)] ulong a, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // + { + uint opcode = 0x7E203800; // USQADD B0, B0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("USQADD ., .")] + public void Usqadd_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x2E203800; // USQADD V0.8B, V0.8B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("USQADD ., .")] + public void Usqadd_V_16B_8H_4S_2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong a, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint opcode = 0x6E203800; // USQADD V0.16B, V0.16B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("XTN{2} ., .")] + public void Xtn_V_8H8B_4S4H_2D2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_4H2S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_4H2S1D_")] [Random(RndCnt)] ulong a, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8H8B, 4S4H, 2D2S> + { + uint opcode = 0x0E212800; // XTN V0.8B, V0.8H + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("XTN{2} ., .")] + public void Xtn_V_8H16B_4S8H_2D4S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_4H2S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_4H2S1D_")] [Random(RndCnt)] ulong a, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8H16B, 4S8H, 2D4S> + { + uint opcode = 0x4E212800; // XTN2 V0.16B, V0.8H + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); } #endif } diff --git a/Ryujinx.Tests/Cpu/CpuTestSimdArithmetic.cs b/Ryujinx.Tests/Cpu/CpuTestSimdArithmetic.cs deleted file mode 100644 index 2a0f5ed919..0000000000 --- a/Ryujinx.Tests/Cpu/CpuTestSimdArithmetic.cs +++ /dev/null @@ -1,789 +0,0 @@ -using ChocolArm64.State; - -using NUnit.Framework; - -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; - -namespace Ryujinx.Tests.Cpu -{ - public class CpuTestSimdArithmetic : CpuTest - { - [TestCase(0xE228420u, 0x0000000000000000ul, 0x0000000000000000ul, 0x0000000000000000ul, 0x0000000000000000ul, 0x0000000000000000ul, 0x0000000000000000ul)] - [TestCase(0xE228420u, 0x00000000FFFFFFFFul, 0x00000000FFFFFFFFul, 0x0000000000000001ul, 0x0000000000000001ul, 0x00000000FFFFFF00ul, 0x0000000000000000ul)] - [TestCase(0xE228420u, 0xFFFFFFFFFFFFFFFFul, 0xFFFFFFFFFFFFFFFFul, 0xFFFFFFFFFFFFFFFFul, 0xFFFFFFFFFFFFFFFFul, 0xFEFEFEFEFEFEFEFEul, 0x0000000000000000ul)] - [TestCase(0xE228420u, 0x0102030405060708ul, 0xAAAAAAAAAAAAAAAAul, 0x0807060504030201ul, 0x2222222222222222ul, 0x0909090909090909ul, 0x0000000000000000ul)] - [TestCase(0x4E228420u, 0x0000000000000000ul, 0x0000000000000000ul, 0x0000000000000000ul, 0x0000000000000000ul, 0x0000000000000000ul, 0x0000000000000000ul)] - [TestCase(0x4E228420u, 0x00000000FFFFFFFFul, 0x00000000FFFFFFFFul, 0x0000000000000001ul, 0x0000000000000001ul, 0x00000000FFFFFF00ul, 0x00000000FFFFFF00ul)] - [TestCase(0x4E228420u, 0xFFFFFFFFFFFFFFFFul, 0xFFFFFFFFFFFFFFFFul, 0xFFFFFFFFFFFFFFFFul, 0xFFFFFFFFFFFFFFFFul, 0xFEFEFEFEFEFEFEFEul, 0xFEFEFEFEFEFEFEFEul)] - [TestCase(0x4E228420u, 0x0102030405060708ul, 0xAAAAAAAAAAAAAAAAul, 0x0807060504030201ul, 0x2222222222222222ul, 0x0909090909090909ul, 0xCCCCCCCCCCCCCCCCul)] - [TestCase(0xE628420u, 0x0000000000000000ul, 0x0000000000000000ul, 0x0000000000000000ul, 0x0000000000000000ul, 0x0000000000000000ul, 0x0000000000000000ul)] - [TestCase(0xE628420u, 0x00000000FFFFFFFFul, 0x00000000FFFFFFFFul, 0x0000000000000001ul, 0x0000000000000001ul, 0x00000000FFFF0000ul, 0x0000000000000000ul)] - [TestCase(0xE628420u, 0xFFFFFFFFFFFFFFFFul, 0xFFFFFFFFFFFFFFFFul, 0xFFFFFFFFFFFFFFFFul, 0xFFFFFFFFFFFFFFFFul, 0xFFFEFFFEFFFEFFFEul, 0x0000000000000000ul)] - [TestCase(0xE628420u, 0x0102030405060708ul, 0xAAAAAAAAAAAAAAAAul, 0x0807060504030201ul, 0x2222222222222222ul, 0x0909090909090909ul, 0x0000000000000000ul)] - [TestCase(0x4E628420u, 0x0000000000000000ul, 0x0000000000000000ul, 0x0000000000000000ul, 0x0000000000000000ul, 0x0000000000000000ul, 0x0000000000000000ul)] - [TestCase(0x4E628420u, 0x00000000FFFFFFFFul, 0x00000000FFFFFFFFul, 0x0000000000000001ul, 0x0000000000000001ul, 0x00000000FFFF0000ul, 0x00000000FFFF0000ul)] - [TestCase(0x4E628420u, 0xFFFFFFFFFFFFFFFFul, 0xFFFFFFFFFFFFFFFFul, 0xFFFFFFFFFFFFFFFFul, 0xFFFFFFFFFFFFFFFFul, 0xFFFEFFFEFFFEFFFEul, 0xFFFEFFFEFFFEFFFEul)] - [TestCase(0x4E628420u, 0x0102030405060708ul, 0xAAAAAAAAAAAAAAAAul, 0x0807060504030201ul, 0x2222222222222222ul, 0x0909090909090909ul, 0xCCCCCCCCCCCCCCCCul)] - [TestCase(0xEA28420u, 0x0000000000000000ul, 0x0000000000000000ul, 0x0000000000000000ul, 0x0000000000000000ul, 0x0000000000000000ul, 0x0000000000000000ul)] - [TestCase(0xEA28420u, 0x00000000FFFFFFFFul, 0x00000000FFFFFFFFul, 0x0000000000000001ul, 0x0000000000000001ul, 0x0000000000000000ul, 0x0000000000000000ul)] - [TestCase(0xEA28420u, 0xFFFFFFFFFFFFFFFFul, 0xFFFFFFFFFFFFFFFFul, 0xFFFFFFFFFFFFFFFFul, 0xFFFFFFFFFFFFFFFFul, 0xFFFFFFFEFFFFFFFEul, 0x0000000000000000ul)] - [TestCase(0xEA28420u, 0x0102030405060708ul, 0xAAAAAAAAAAAAAAAAul, 0x0807060504030201ul, 0x2222222222222222ul, 0x0909090909090909ul, 0x0000000000000000ul)] - [TestCase(0x4EA28420u, 0x0000000000000000ul, 0x0000000000000000ul, 0x0000000000000000ul, 0x0000000000000000ul, 0x0000000000000000ul, 0x0000000000000000ul)] - [TestCase(0x4EA28420u, 0x00000000FFFFFFFFul, 0x00000000FFFFFFFFul, 0x0000000000000001ul, 0x0000000000000001ul, 0x0000000000000000ul, 0x0000000000000000ul)] - [TestCase(0x4EA28420u, 0xFFFFFFFFFFFFFFFFul, 0xFFFFFFFFFFFFFFFFul, 0xFFFFFFFFFFFFFFFFul, 0xFFFFFFFFFFFFFFFFul, 0xFFFFFFFEFFFFFFFEul, 0xFFFFFFFEFFFFFFFEul)] - [TestCase(0x4EA28420u, 0x0102030405060708ul, 0xAAAAAAAAAAAAAAAAul, 0x0807060504030201ul, 0x2222222222222222ul, 0x0909090909090909ul, 0xCCCCCCCCCCCCCCCCul)] - [TestCase(0x4EE28420u, 0x0000000000000000ul, 0x0000000000000000ul, 0x0000000000000000ul, 0x0000000000000000ul, 0x0000000000000000ul, 0x0000000000000000ul)] - [TestCase(0x4EE28420u, 0x00000000FFFFFFFFul, 0x00000000FFFFFFFFul, 0x0000000000000001ul, 0x0000000000000001ul, 0x0000000100000000ul, 0x0000000100000000ul)] - [TestCase(0x4EE28420u, 0xFFFFFFFFFFFFFFFFul, 0xFFFFFFFFFFFFFFFFul, 0xFFFFFFFFFFFFFFFFul, 0xFFFFFFFFFFFFFFFFul, 0xFFFFFFFFFFFFFFFEul, 0xFFFFFFFFFFFFFFFEul)] - [TestCase(0x4EE28420u, 0x0102030405060708ul, 0xAAAAAAAAAAAAAAAAul, 0x0807060504030201ul, 0x2222222222222222ul, 0x0909090909090909ul, 0xCCCCCCCCCCCCCCCCul)] - public void Add_V(uint Opcode, ulong A0, ulong A1, ulong B0, ulong B1, ulong Result0, ulong Result1) - { - Vector128 V1 = MakeVectorE0E1(A0, A1); - Vector128 V2 = MakeVectorE0E1(B0, B1); - AThreadState ThreadState = SingleOpcode(Opcode, V1: V1, V2: V2); - Assert.Multiple(() => - { - Assert.AreEqual(Result0, GetVectorE0(ThreadState.V0)); - Assert.AreEqual(Result1, GetVectorE1(ThreadState.V0)); - }); - } - - [TestCase(0x1E224820u, 0x0000000000000000ul, 0x0000000080000000ul, 0x0000000000000000ul)] - [TestCase(0x1E224820u, 0x0000000080000000ul, 0x0000000000000000ul, 0x0000000000000000ul)] - [TestCase(0x1E224820u, 0x0000000080000000ul, 0x0000000080000000ul, 0x0000000080000000ul)] - [TestCase(0x1E224820u, 0x0000000080000000ul, 0x000000003DCCCCCDul, 0x000000003DCCCCCDul)] - [TestCase(0x1E224820u, 0x000000003DCCCCCDul, 0x000000003C9623B1ul, 0x000000003DCCCCCDul)] - [TestCase(0x1E224820u, 0x000000008BA98D27ul, 0x0000000000000076ul, 0x0000000000000076ul)] - [TestCase(0x1E224820u, 0x00000000807FFFFFul, 0x000000007F7FFFFFul, 0x000000007F7FFFFFul)] - [TestCase(0x1E224820u, 0x000000007F7FFFFFul, 0x00000000807FFFFFul, 0x000000007F7FFFFFul)] - [TestCase(0x1E224820u, 0x000000007FC00000ul, 0x000000003F800000ul, 0x000000007FC00000ul)] - [TestCase(0x1E224820u, 0x000000003F800000ul, 0x000000007FC00000ul, 0x000000007FC00000ul)] - [TestCase(0x1E224820u, 0x000000007F800001ul, 0x000000007FC00042ul, 0x000000007FC00001ul, Ignore = "NaN test.")] - [TestCase(0x1E224820u, 0x000000007FC00042ul, 0x000000007F800001ul, 0x000000007FC00001ul, Ignore = "NaN test.")] - [TestCase(0x1E224820u, 0x000000007FC0000Aul, 0x000000007FC0000Bul, 0x000000007FC0000Aul, Ignore = "NaN test.")] - [TestCase(0x1E624820u, 0x0000000000000000ul, 0x8000000000000000ul, 0x0000000000000000ul)] - [TestCase(0x1E624820u, 0x8000000000000000ul, 0x0000000000000000ul, 0x0000000000000000ul)] - [TestCase(0x1E624820u, 0x8000000000000000ul, 0x8000000000000000ul, 0x8000000000000000ul)] - [TestCase(0x1E624820u, 0x8000000000000000ul, 0x3FF3333333333333ul, 0x3FF3333333333333ul)] - public void Fmax_S(uint Opcode, ulong A, ulong B, ulong Result) - { - // FMAX S0, S1, S2 - AThreadState ThreadState = SingleOpcode(Opcode, - V1: Sse.StaticCast(Sse2.SetVector128(0, A)), - V2: Sse.StaticCast(Sse2.SetVector128(0, B))); - Assert.AreEqual(Result, Sse41.Extract(Sse.StaticCast(ThreadState.V0), 0)); - } - - [TestCase(0x80000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u)] - [TestCase(0x00000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x00000000u, 0x00000000u)] - [TestCase(0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u)] - [TestCase(0x80000000u, 0x80000000u, 0x3DCCCCCDu, 0x3DCCCCCDu, 0x3DCCCCCDu, 0x3DCCCCCDu)] - [TestCase(0x3DCCCCCDu, 0x3DCCCCCDu, 0x3C9623B1u, 0x3C9623B1u, 0x3DCCCCCDu, 0x3DCCCCCDu)] - [TestCase(0x8BA98D27u, 0x8BA98D27u, 0x00000076u, 0x00000076u, 0x00000076u, 0x00000076u)] - [TestCase(0x807FFFFFu, 0x807FFFFFu, 0x7F7FFFFFu, 0x7F7FFFFFu, 0x7F7FFFFFu, 0x7F7FFFFFu)] - [TestCase(0x7F7FFFFFu, 0x7F7FFFFFu, 0x807FFFFFu, 0x807FFFFFu, 0x7F7FFFFFu, 0x7F7FFFFFu)] - [TestCase(0x7FC00000u, 0x7FC00000u, 0x3F800000u, 0x3F800000u, 0x7FC00000u, 0x7FC00000u)] - [TestCase(0x3F800000u, 0x3F800000u, 0x7FC00000u, 0x7FC00000u, 0x7FC00000u, 0x7FC00000u)] - [TestCase(0x7F800001u, 0x7F800001u, 0x7FC00042u, 0x7FC00042u, 0x7FC00001u, 0x7FC00001u, Ignore = "NaN test.")] - [TestCase(0x7FC00042u, 0x7FC00042u, 0x7F800001u, 0x7F800001u, 0x7FC00001u, 0x7FC00001u, Ignore = "NaN test.")] - [TestCase(0x7FC0000Au, 0x7FC0000Au, 0x7FC0000Bu, 0x7FC0000Bu, 0x7FC0000Au, 0x7FC0000Au, Ignore = "NaN test.")] - public void Fmax_V(uint A, uint B, uint C, uint D, uint Result0, uint Result1) - { - uint Opcode = 0x4E22F420; - Vector128 V1 = MakeVectorE0E1(A, B); - Vector128 V2 = MakeVectorE0E1(C, D); - AThreadState ThreadState = SingleOpcode(Opcode, V1: V1, V2: V2); - Assert.Multiple(() => - { - Assert.AreEqual(Result0, GetVectorE0(ThreadState.V0)); - Assert.AreEqual(Result1, GetVectorE1(ThreadState.V0)); - }); - } - - [TestCase(0x1E225820u, 0x0000000000000000ul, 0x0000000080000000ul, 0x0000000080000000ul)] - [TestCase(0x1E225820u, 0x0000000080000000ul, 0x0000000000000000ul, 0x0000000080000000ul)] - [TestCase(0x1E225820u, 0x0000000080000000ul, 0x0000000080000000ul, 0x0000000080000000ul)] - [TestCase(0x1E225820u, 0x0000000080000000ul, 0x000000003DCCCCCDul, 0x0000000080000000ul)] - [TestCase(0x1E225820u, 0x000000003DCCCCCDul, 0x000000003C9623B1ul, 0x000000003C9623B1ul)] - [TestCase(0x1E225820u, 0x000000008BA98D27ul, 0x0000000000000076ul, 0x000000008BA98D27ul)] - [TestCase(0x1E225820u, 0x00000000807FFFFFul, 0x000000007F7FFFFFul, 0x00000000807FFFFFul)] - [TestCase(0x1E225820u, 0x000000007F7FFFFFul, 0x00000000807FFFFFul, 0x00000000807FFFFFul)] - [TestCase(0x1E225820u, 0x000000007FC00000ul, 0x000000003F800000ul, 0x000000007FC00000ul)] - [TestCase(0x1E225820u, 0x000000003F800000ul, 0x000000007FC00000ul, 0x000000007FC00000ul)] - [TestCase(0x1E225820u, 0x000000007F800001ul, 0x000000007FC00042ul, 0x000000007FC00001ul, Ignore = "NaN test.")] - [TestCase(0x1E225820u, 0x000000007FC00042ul, 0x000000007F800001ul, 0x000000007FC00001ul, Ignore = "NaN test.")] - [TestCase(0x1E225820u, 0x000000007FC0000Aul, 0x000000007FC0000Bul, 0x000000007FC0000Aul, Ignore = "NaN test.")] - [TestCase(0x1E625820u, 0x0000000000000000ul, 0x8000000000000000ul, 0x8000000000000000ul)] - [TestCase(0x1E625820u, 0x8000000000000000ul, 0x0000000000000000ul, 0x8000000000000000ul)] - [TestCase(0x1E625820u, 0x8000000000000000ul, 0x8000000000000000ul, 0x8000000000000000ul)] - [TestCase(0x1E625820u, 0x8000000000000000ul, 0x3FF3333333333333ul, 0x8000000000000000ul)] - public void Fmin_S(uint Opcode, ulong A, ulong B, ulong Result) - { - // FMIN S0, S1, S2 - AThreadState ThreadState = SingleOpcode(Opcode, - V1: Sse.StaticCast(Sse2.SetVector128(0, A)), - V2: Sse.StaticCast(Sse2.SetVector128(0, B))); - Assert.AreEqual(Result, Sse41.Extract(Sse.StaticCast(ThreadState.V0), 0)); - } - - [TestCase(0x80000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x80000000u)] - [TestCase(0x00000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u)] - [TestCase(0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u)] - [TestCase(0x80000000u, 0x80000000u, 0x3DCCCCCDu, 0x3DCCCCCDu, 0x80000000u, 0x80000000u)] - [TestCase(0x3DCCCCCDu, 0x3DCCCCCDu, 0x3C9623B1u, 0x3C9623B1u, 0x3C9623B1u, 0x3C9623B1u)] - [TestCase(0x8BA98D27u, 0x8BA98D27u, 0x00000076u, 0x00000076u, 0x8BA98D27u, 0x8BA98D27u)] - [TestCase(0x807FFFFFu, 0x807FFFFFu, 0x7F7FFFFFu, 0x7F7FFFFFu, 0x807FFFFFu, 0x807FFFFFu)] - [TestCase(0x7F7FFFFFu, 0x7F7FFFFFu, 0x807FFFFFu, 0x807FFFFFu, 0x807FFFFFu, 0x807FFFFFu)] - [TestCase(0x7FC00000u, 0x7FC00000u, 0x3F800000u, 0x3F800000u, 0x7FC00000u, 0x7FC00000u)] - [TestCase(0x3F800000u, 0x3F800000u, 0x7FC00000u, 0x7FC00000u, 0x7FC00000u, 0x7FC00000u)] - [TestCase(0x7F800001u, 0x7F800001u, 0x7FC00042u, 0x7FC00042u, 0x7FC00001u, 0x7FC00001u, Ignore = "NaN test.")] - [TestCase(0x7FC00042u, 0x7FC00042u, 0x7F800001u, 0x7F800001u, 0x7FC00001u, 0x7FC00001u, Ignore = "NaN test.")] - [TestCase(0x7FC0000Au, 0x7FC0000Au, 0x7FC0000Bu, 0x7FC0000Bu, 0x7FC0000Au, 0x7FC0000Au, Ignore = "NaN test.")] - public void Fmin_V(uint A, uint B, uint C, uint D, uint Result0, uint Result1) - { - uint Opcode = 0x4EA2F420; - Vector128 V1 = MakeVectorE0E1(A, B); - Vector128 V2 = MakeVectorE0E1(C, D); - AThreadState ThreadState = SingleOpcode(Opcode, V1: V1, V2: V2); - Assert.Multiple(() => - { - Assert.AreEqual(Result0, GetVectorE0(ThreadState.V0)); - Assert.AreEqual(Result1, GetVectorE1(ThreadState.V0)); - }); - } - - [Test, Description("FMUL S6, S1, V0.S[2]")] - public void Fmul_Se([Random(10)] float A, [Random(10)] float B) - { - AThreadState ThreadState = SingleOpcode(0x5F809826, - V1: Sse.SetVector128(0, 0, 0, A), - V0: Sse.SetVector128(0, B, 0, 0)); - - Assert.That(Sse41.Extract(ThreadState.V6, (byte)0), Is.EqualTo(A * B)); - } - - [TestCase(0x00000000u, 0x7F800000u)] - [TestCase(0x80000000u, 0xFF800000u)] - [TestCase(0x00FFF000u, 0x7E000000u)] - [TestCase(0x41200000u, 0x3DCC8000u)] - [TestCase(0xC1200000u, 0xBDCC8000u)] - [TestCase(0x001FFFFFu, 0x7F800000u)] - [TestCase(0x007FF000u, 0x7E800000u)] - public void Frecpe_S(uint A, uint Result) - { - Vector128 V1 = MakeVectorE0(A); - AThreadState ThreadState = SingleOpcode(0x5EA1D820, V1: V1); - Assert.AreEqual(Result, GetVectorE0(ThreadState.V0)); - } - - [Test, Description("FRECPS D0, D1, D2")] - public void Frecps_S([Random(10)] double A, [Random(10)] double B) - { - AThreadState ThreadState = SingleOpcode(0x5E62FC20, - V1: MakeVectorE0(A), - V2: MakeVectorE0(B)); - - Assert.That(VectorExtractDouble(ThreadState.V0, 0), Is.EqualTo(2 - (A * B))); - } - - [Test, Description("FRECPS V4.4S, V2.4S, V0.4S")] - public void Frecps_V([Random(10)] float A, [Random(10)] float B) - { - AThreadState ThreadState = SingleOpcode(0x4E20FC44, - V2: Sse.SetAllVector128(A), - V0: Sse.SetAllVector128(B)); - - float Result = (float)(2 - ((double)A * (double)B)); - Assert.Multiple(() => - { - Assert.That(Sse41.Extract(ThreadState.V4, (byte)0), Is.EqualTo(Result)); - Assert.That(Sse41.Extract(ThreadState.V4, (byte)1), Is.EqualTo(Result)); - Assert.That(Sse41.Extract(ThreadState.V4, (byte)2), Is.EqualTo(Result)); - Assert.That(Sse41.Extract(ThreadState.V4, (byte)3), Is.EqualTo(Result)); - }); - } - - [TestCase(0x3FE66666u, false, 0x40000000u)] - [TestCase(0x3F99999Au, false, 0x3F800000u)] - [TestCase(0x404CCCCDu, false, 0x40400000u)] - [TestCase(0x40733333u, false, 0x40800000u)] - [TestCase(0x3fc00000u, false, 0x40000000u)] - [TestCase(0x40200000u, false, 0x40400000u)] - [TestCase(0x00000000u, false, 0x00000000u)] - [TestCase(0x00000000u, false, 0x00000000u)] - [TestCase(0x00000000u, false, 0x00000000u)] - [TestCase(0x00000000u, false, 0x00000000u)] - [TestCase(0x80000000u, false, 0x80000000u)] - [TestCase(0x80000000u, false, 0x80000000u)] - [TestCase(0x80000000u, false, 0x80000000u)] - [TestCase(0x80000000u, false, 0x80000000u)] - [TestCase(0x7F800000u, false, 0x7F800000u)] - [TestCase(0x7F800000u, false, 0x7F800000u)] - [TestCase(0x7F800000u, false, 0x7F800000u)] - [TestCase(0x7F800000u, false, 0x7F800000u)] - [TestCase(0xFF800000u, false, 0xFF800000u)] - [TestCase(0xFF800000u, false, 0xFF800000u)] - [TestCase(0xFF800000u, false, 0xFF800000u)] - [TestCase(0xFF800000u, false, 0xFF800000u)] - [TestCase(0xFF800001u, false, 0xFFC00001u, Ignore = "NaN test.")] - [TestCase(0xFF800001u, false, 0xFFC00001u, Ignore = "NaN test.")] - [TestCase(0xFF800001u, false, 0xFFC00001u, Ignore = "NaN test.")] - [TestCase(0xFF800001u, false, 0xFFC00001u, Ignore = "NaN test.")] - [TestCase(0xFF800001u, true, 0x7FC00000u, Ignore = "NaN test.")] - [TestCase(0xFF800001u, true, 0x7FC00000u, Ignore = "NaN test.")] - [TestCase(0xFF800001u, true, 0x7FC00000u, Ignore = "NaN test.")] - [TestCase(0xFF800001u, true, 0x7FC00000u, Ignore = "NaN test.")] - [TestCase(0x7FC00002u, false, 0x7FC00002u, Ignore = "NaN test.")] - [TestCase(0x7FC00002u, false, 0x7FC00002u, Ignore = "NaN test.")] - [TestCase(0x7FC00002u, false, 0x7FC00002u, Ignore = "NaN test.")] - [TestCase(0x7FC00002u, false, 0x7FC00002u, Ignore = "NaN test.")] - [TestCase(0x7FC00002u, true, 0x7FC00000u, Ignore = "NaN test.")] - [TestCase(0x7FC00002u, true, 0x7FC00000u, Ignore = "NaN test.")] - [TestCase(0x7FC00002u, true, 0x7FC00000u, Ignore = "NaN test.")] - [TestCase(0x7FC00002u, true, 0x7FC00000u, Ignore = "NaN test.")] - public void Frinta_S(uint A, bool DefaultNaN, uint Result) - { - int FpcrTemp = 0x0; - if(DefaultNaN) - { - FpcrTemp = 0x2000000; - } - Vector128 V1 = MakeVectorE0(A); - AThreadState ThreadState = SingleOpcode(0x1E264020, V1: V1, Fpcr: FpcrTemp); - Assert.AreEqual(Result, GetVectorE0(ThreadState.V0)); - } - - [TestCase(0x6E618820u, 0x3FF3333333333333ul, 0x3FF3333333333333ul, false, 0x3FF0000000000000ul, 0x3FF0000000000000ul)] - [TestCase(0x6E618820u, 0x3FFCCCCCCCCCCCCDul, 0x3FFCCCCCCCCCCCCDul, false, 0x4000000000000000ul, 0x4000000000000000ul)] - [TestCase(0x6E618820u, 0x3FF8000000000000ul, 0x3FF8000000000000ul, false, 0x4000000000000000ul, 0x4000000000000000ul)] - [TestCase(0x6E219820u, 0x3f99999a3fe66666ul, 0x3f99999a3fe66666ul, false, 0x3f80000040000000ul, 0x3f80000040000000ul)] - [TestCase(0x6E219820u, 0x3fc000003fc00000ul, 0x3fc000003fc00000ul, false, 0x4000000040000000ul, 0x4000000040000000ul)] - [TestCase(0x2E219820u, 0x3f99999a3fe66666ul, 0x3f99999a3fe66666ul, false, 0x3f80000040000000ul, 0x0000000000000000ul)] - [TestCase(0x2E219820u, 0x3fc000003fc00000ul, 0x3fc000003fc00000ul, false, 0x4000000040000000ul, 0x0000000000000000ul)] - [TestCase(0x2E218820u, 0x0000000080000000ul, 0x0000000000000000ul, false, 0x0000000080000000ul, 0x0000000000000000ul)] - [TestCase(0x2E218820u, 0x7F800000FF800000ul, 0x0000000000000000ul, false, 0x7F800000FF800000ul, 0x0000000000000000ul)] - [TestCase(0x2E218820u, 0xFF8000017FC00002ul, 0x0000000000000000ul, false, 0xFFC000017FC00002ul, 0x0000000000000000ul, Ignore = "NaN test.")] - [TestCase(0x2E218820u, 0xFF8000017FC00002ul, 0x0000000000000000ul, true, 0x7FC000007FC00000ul, 0x0000000000000000ul, Ignore = "NaN test.")] - public void Frinta_V(uint Opcode, ulong A, ulong B, bool DefaultNaN, ulong Result0, ulong Result1) - { - int FpcrTemp = 0x0; - if(DefaultNaN) - { - FpcrTemp = 0x2000000; - } - Vector128 V1 = MakeVectorE0E1(A, B); - AThreadState ThreadState = SingleOpcode(Opcode, V1: V1, Fpcr: FpcrTemp); - Assert.Multiple(() => - { - Assert.AreEqual(Result0, GetVectorE0(ThreadState.V0)); - Assert.AreEqual(Result1, GetVectorE1(ThreadState.V0)); - }); - } - - [TestCase(0x3FE66666u, 'N', false, 0x40000000u)] - [TestCase(0x3F99999Au, 'N', false, 0x3F800000u)] - [TestCase(0x404CCCCDu, 'P', false, 0x40800000u)] - [TestCase(0x40733333u, 'P', false, 0x40800000u)] - [TestCase(0x404CCCCDu, 'M', false, 0x40400000u)] - [TestCase(0x40733333u, 'M', false, 0x40400000u)] - [TestCase(0x3F99999Au, 'Z', false, 0x3F800000u)] - [TestCase(0x3FE66666u, 'Z', false, 0x3F800000u)] - [TestCase(0x00000000u, 'N', false, 0x00000000u)] - [TestCase(0x00000000u, 'P', false, 0x00000000u)] - [TestCase(0x00000000u, 'M', false, 0x00000000u)] - [TestCase(0x00000000u, 'Z', false, 0x00000000u)] - [TestCase(0x80000000u, 'N', false, 0x80000000u)] - [TestCase(0x80000000u, 'P', false, 0x80000000u)] - [TestCase(0x80000000u, 'M', false, 0x80000000u)] - [TestCase(0x80000000u, 'Z', false, 0x80000000u)] - [TestCase(0x7F800000u, 'N', false, 0x7F800000u)] - [TestCase(0x7F800000u, 'P', false, 0x7F800000u)] - [TestCase(0x7F800000u, 'M', false, 0x7F800000u)] - [TestCase(0x7F800000u, 'Z', false, 0x7F800000u)] - [TestCase(0xFF800000u, 'N', false, 0xFF800000u)] - [TestCase(0xFF800000u, 'P', false, 0xFF800000u)] - [TestCase(0xFF800000u, 'M', false, 0xFF800000u)] - [TestCase(0xFF800000u, 'Z', false, 0xFF800000u)] - [TestCase(0xFF800001u, 'N', false, 0xFFC00001u, Ignore = "NaN test.")] - [TestCase(0xFF800001u, 'P', false, 0xFFC00001u, Ignore = "NaN test.")] - [TestCase(0xFF800001u, 'M', false, 0xFFC00001u, Ignore = "NaN test.")] - [TestCase(0xFF800001u, 'Z', false, 0xFFC00001u, Ignore = "NaN test.")] - [TestCase(0xFF800001u, 'N', true, 0x7FC00000u, Ignore = "NaN test.")] - [TestCase(0xFF800001u, 'P', true, 0x7FC00000u, Ignore = "NaN test.")] - [TestCase(0xFF800001u, 'M', true, 0x7FC00000u, Ignore = "NaN test.")] - [TestCase(0xFF800001u, 'Z', true, 0x7FC00000u, Ignore = "NaN test.")] - [TestCase(0x7FC00002u, 'N', false, 0x7FC00002u, Ignore = "NaN test.")] - [TestCase(0x7FC00002u, 'P', false, 0x7FC00002u, Ignore = "NaN test.")] - [TestCase(0x7FC00002u, 'M', false, 0x7FC00002u, Ignore = "NaN test.")] - [TestCase(0x7FC00002u, 'Z', false, 0x7FC00002u, Ignore = "NaN test.")] - [TestCase(0x7FC00002u, 'N', true, 0x7FC00000u, Ignore = "NaN test.")] - [TestCase(0x7FC00002u, 'P', true, 0x7FC00000u, Ignore = "NaN test.")] - [TestCase(0x7FC00002u, 'M', true, 0x7FC00000u, Ignore = "NaN test.")] - [TestCase(0x7FC00002u, 'Z', true, 0x7FC00000u, Ignore = "NaN test.")] - public void Frinti_S(uint A, char RoundType, bool DefaultNaN, uint Result) - { - int FpcrTemp = 0x0; - switch(RoundType) - { - case 'N': - FpcrTemp = 0x0; - break; - - case 'P': - FpcrTemp = 0x400000; - break; - - case 'M': - FpcrTemp = 0x800000; - break; - - case 'Z': - FpcrTemp = 0xC00000; - break; - } - if(DefaultNaN) - { - FpcrTemp |= 1 << 25; - } - Vector128 V1 = MakeVectorE0(A); - AThreadState ThreadState = SingleOpcode(0x1E27C020, V1: V1, Fpcr: FpcrTemp); - Assert.AreEqual(Result, GetVectorE0(ThreadState.V0)); - } - - [TestCase(0x6EE19820u, 0x3FF3333333333333ul, 0x3FF3333333333333ul, 'N', false, 0x3FF0000000000000ul, 0x3FF0000000000000ul)] - [TestCase(0x6EE19820u, 0x3FFCCCCCCCCCCCCDul, 0x3FFCCCCCCCCCCCCDul, 'N', false, 0x4000000000000000ul, 0x4000000000000000ul)] - [TestCase(0x6EE19820u, 0x3FF3333333333333ul, 0x3FF3333333333333ul, 'P', false, 0x4000000000000000ul, 0x4000000000000000ul)] - [TestCase(0x6EE19820u, 0x3FFCCCCCCCCCCCCDul, 0x3FFCCCCCCCCCCCCDul, 'P', false, 0x4000000000000000ul, 0x4000000000000000ul)] - [TestCase(0x6EE19820u, 0x3FF3333333333333ul, 0x3FF3333333333333ul, 'M', false, 0x3FF0000000000000ul, 0x3FF0000000000000ul)] - [TestCase(0x6EE19820u, 0x3FFCCCCCCCCCCCCDul, 0x3FFCCCCCCCCCCCCDul, 'M', false, 0x3FF0000000000000ul, 0x3FF0000000000000ul)] - [TestCase(0x6EE19820u, 0x3FF3333333333333ul, 0x3FF3333333333333ul, 'Z', false, 0x3FF0000000000000ul, 0x3FF0000000000000ul)] - [TestCase(0x6EE19820u, 0x3FFCCCCCCCCCCCCDul, 0x3FFCCCCCCCCCCCCDul, 'Z', false, 0x3FF0000000000000ul, 0x3FF0000000000000ul)] - [TestCase(0x6EA19820u, 0x3f99999a3fe66666ul, 0x3f99999a3fe66666ul, 'N', false, 0x3f80000040000000ul, 0x3f80000040000000ul)] - [TestCase(0x6EA19820u, 0x3f99999a3fe66666ul, 0x3f99999a3fe66666ul, 'P', false, 0x4000000040000000ul, 0x4000000040000000ul)] - [TestCase(0x6EA19820u, 0x3f99999a3fe66666ul, 0x3f99999a3fe66666ul, 'M', false, 0x3f8000003f800000ul, 0x3f8000003f800000ul)] - [TestCase(0x6EA19820u, 0x3f99999a3fe66666ul, 0x3f99999a3fe66666ul, 'Z', false, 0x3f8000003f800000ul, 0x3f8000003f800000ul)] - [TestCase(0x2EA19820u, 0x3f99999a3fe66666ul, 0x3f99999a3fe66666ul, 'N', false, 0x3f80000040000000ul, 0x0000000000000000ul)] - [TestCase(0x2EA19820u, 0x3f99999a3fe66666ul, 0x3f99999a3fe66666ul, 'P', false, 0x4000000040000000ul, 0x0000000000000000ul)] - [TestCase(0x2EA19820u, 0x3f99999a3fe66666ul, 0x3f99999a3fe66666ul, 'M', false, 0x3f8000003f800000ul, 0x0000000000000000ul)] - [TestCase(0x2EA19820u, 0x3f99999a3fe66666ul, 0x3f99999a3fe66666ul, 'Z', false, 0x3f8000003f800000ul, 0x0000000000000000ul)] - [TestCase(0x2EA19820u, 0x0000000080000000ul, 0x0000000000000000ul, 'N', false, 0x0000000080000000ul, 0x0000000000000000ul)] - [TestCase(0x2EA19820u, 0x0000000080000000ul, 0x0000000000000000ul, 'P', false, 0x0000000080000000ul, 0x0000000000000000ul)] - [TestCase(0x2EA19820u, 0x0000000080000000ul, 0x0000000000000000ul, 'M', false, 0x0000000080000000ul, 0x0000000000000000ul)] - [TestCase(0x2EA19820u, 0x0000000080000000ul, 0x0000000000000000ul, 'Z', false, 0x0000000080000000ul, 0x0000000000000000ul)] - [TestCase(0x2EA19820u, 0x7F800000FF800000ul, 0x0000000000000000ul, 'N', false, 0x7F800000FF800000ul, 0x0000000000000000ul)] - [TestCase(0x2EA19820u, 0x7F800000FF800000ul, 0x0000000000000000ul, 'P', false, 0x7F800000FF800000ul, 0x0000000000000000ul)] - [TestCase(0x2EA19820u, 0x7F800000FF800000ul, 0x0000000000000000ul, 'M', false, 0x7F800000FF800000ul, 0x0000000000000000ul)] - [TestCase(0x2EA19820u, 0x7F800000FF800000ul, 0x0000000000000000ul, 'Z', false, 0x7F800000FF800000ul, 0x0000000000000000ul)] - [TestCase(0x2EA19820u, 0xFF8000017FC00002ul, 0x0000000000000000ul, 'N', false, 0xFFC000017FC00002ul, 0x0000000000000000ul, Ignore = "NaN test.")] - [TestCase(0x2EA19820u, 0xFF8000017FC00002ul, 0x0000000000000000ul, 'P', false, 0xFFC000017FC00002ul, 0x0000000000000000ul, Ignore = "NaN test.")] - [TestCase(0x2EA19820u, 0xFF8000017FC00002ul, 0x0000000000000000ul, 'M', false, 0xFFC000017FC00002ul, 0x0000000000000000ul, Ignore = "NaN test.")] - [TestCase(0x2EA19820u, 0xFF8000017FC00002ul, 0x0000000000000000ul, 'Z', false, 0xFFC000017FC00002ul, 0x0000000000000000ul, Ignore = "NaN test.")] - [TestCase(0x2EA19820u, 0xFF8000017FC00002ul, 0x0000000000000000ul, 'N', true, 0x7FC000007FC00000ul, 0x0000000000000000ul, Ignore = "NaN test.")] - [TestCase(0x2EA19820u, 0xFF8000017FC00002ul, 0x0000000000000000ul, 'P', true, 0x7FC000007FC00000ul, 0x0000000000000000ul, Ignore = "NaN test.")] - [TestCase(0x2EA19820u, 0xFF8000017FC00002ul, 0x0000000000000000ul, 'M', true, 0x7FC000007FC00000ul, 0x0000000000000000ul, Ignore = "NaN test.")] - [TestCase(0x2EA19820u, 0xFF8000017FC00002ul, 0x0000000000000000ul, 'Z', true, 0x7FC000007FC00000ul, 0x0000000000000000ul, Ignore = "NaN test.")] - public void Frinti_V(uint Opcode, ulong A, ulong B, char RoundType, bool DefaultNaN, ulong Result0, ulong Result1) - { - int FpcrTemp = 0x0; - switch(RoundType) - { - case 'N': - FpcrTemp = 0x0; - break; - - case 'P': - FpcrTemp = 0x400000; - break; - - case 'M': - FpcrTemp = 0x800000; - break; - - case 'Z': - FpcrTemp = 0xC00000; - break; - } - if(DefaultNaN) - { - FpcrTemp |= 1 << 25; - } - Vector128 V1 = MakeVectorE0E1(A, B); - AThreadState ThreadState = SingleOpcode(Opcode, V1: V1, Fpcr: FpcrTemp); - Assert.Multiple(() => - { - Assert.AreEqual(Result0, GetVectorE0(ThreadState.V0)); - Assert.AreEqual(Result1, GetVectorE1(ThreadState.V0)); - }); - } - - [TestCase(0x3FE66666u, false, 0x3F800000u)] - [TestCase(0x3F99999Au, false, 0x3F800000u)] - [TestCase(0x404CCCCDu, false, 0x40400000u)] - [TestCase(0x40733333u, false, 0x40400000u)] - [TestCase(0x3fc00000u, false, 0x3F800000u)] - [TestCase(0x40200000u, false, 0x40000000u)] - [TestCase(0x00000000u, false, 0x00000000u)] - [TestCase(0x00000000u, false, 0x00000000u)] - [TestCase(0x00000000u, false, 0x00000000u)] - [TestCase(0x00000000u, false, 0x00000000u)] - [TestCase(0x80000000u, false, 0x80000000u)] - [TestCase(0x80000000u, false, 0x80000000u)] - [TestCase(0x80000000u, false, 0x80000000u)] - [TestCase(0x80000000u, false, 0x80000000u)] - [TestCase(0x7F800000u, false, 0x7F800000u)] - [TestCase(0x7F800000u, false, 0x7F800000u)] - [TestCase(0x7F800000u, false, 0x7F800000u)] - [TestCase(0x7F800000u, false, 0x7F800000u)] - [TestCase(0xFF800000u, false, 0xFF800000u)] - [TestCase(0xFF800000u, false, 0xFF800000u)] - [TestCase(0xFF800000u, false, 0xFF800000u)] - [TestCase(0xFF800000u, false, 0xFF800000u)] - [TestCase(0xFF800001u, false, 0xFFC00001u, Ignore = "NaN test.")] - [TestCase(0xFF800001u, false, 0xFFC00001u, Ignore = "NaN test.")] - [TestCase(0xFF800001u, false, 0xFFC00001u, Ignore = "NaN test.")] - [TestCase(0xFF800001u, false, 0xFFC00001u, Ignore = "NaN test.")] - [TestCase(0xFF800001u, true, 0x7FC00000u, Ignore = "NaN test.")] - [TestCase(0xFF800001u, true, 0x7FC00000u, Ignore = "NaN test.")] - [TestCase(0xFF800001u, true, 0x7FC00000u, Ignore = "NaN test.")] - [TestCase(0xFF800001u, true, 0x7FC00000u, Ignore = "NaN test.")] - [TestCase(0x7FC00002u, false, 0x7FC00002u, Ignore = "NaN test.")] - [TestCase(0x7FC00002u, false, 0x7FC00002u, Ignore = "NaN test.")] - [TestCase(0x7FC00002u, false, 0x7FC00002u, Ignore = "NaN test.")] - [TestCase(0x7FC00002u, false, 0x7FC00002u, Ignore = "NaN test.")] - [TestCase(0x7FC00002u, true, 0x7FC00000u, Ignore = "NaN test.")] - [TestCase(0x7FC00002u, true, 0x7FC00000u, Ignore = "NaN test.")] - [TestCase(0x7FC00002u, true, 0x7FC00000u, Ignore = "NaN test.")] - [TestCase(0x7FC00002u, true, 0x7FC00000u, Ignore = "NaN test.")] - public void Frintm_S(uint A, bool DefaultNaN, uint Result) - { - int FpcrTemp = 0x0; - if(DefaultNaN) - { - FpcrTemp = 0x2000000; - } - Vector128 V1 = MakeVectorE0(A); - AThreadState ThreadState = SingleOpcode(0x1E254020, V1: V1, Fpcr: FpcrTemp); - Assert.AreEqual(Result, GetVectorE0(ThreadState.V0)); - } - - [TestCase(0x4E619820u, 0x3FF3333333333333ul, 0x3FF3333333333333ul, false, 0x3FF0000000000000ul, 0x3FF0000000000000ul)] - [TestCase(0x4E619820u, 0x3FFCCCCCCCCCCCCDul, 0x3FFCCCCCCCCCCCCDul, false, 0x3FF0000000000000ul, 0x3FF0000000000000ul)] - [TestCase(0x4E219820u, 0x3f99999a3fe66666ul, 0x3f99999a3fe66666ul, false, 0x3f8000003f800000ul, 0x3f8000003f800000ul)] - [TestCase(0xE219820u, 0x3f99999a3fe66666ul, 0x3f99999a3fe66666ul, false, 0x3f8000003f800000ul, 0x0000000000000000ul)] - [TestCase(0xE219820u, 0x0000000080000000ul, 0x0000000000000000ul, false, 0x0000000080000000ul, 0x0000000000000000ul)] - [TestCase(0xE219820u, 0x7F800000FF800000ul, 0x0000000000000000ul, false, 0x7F800000FF800000ul, 0x0000000000000000ul)] - [TestCase(0xE219820u, 0xFF8000017FC00002ul, 0x0000000000000000ul, false, 0xFFC000017FC00002ul, 0x0000000000000000ul, Ignore = "NaN test.")] - [TestCase(0xE219820u, 0xFF8000017FC00002ul, 0x0000000000000000ul, true, 0x7FC000007FC00000ul, 0x0000000000000000ul, Ignore = "NaN test.")] - public void Frintm_V(uint Opcode, ulong A, ulong B, bool DefaultNaN, ulong Result0, ulong Result1) - { - int FpcrTemp = 0x0; - if(DefaultNaN) - { - FpcrTemp = 0x2000000; - } - Vector128 V1 = MakeVectorE0E1(A, B); - AThreadState ThreadState = SingleOpcode(Opcode, V1: V1, Fpcr: FpcrTemp); - Assert.Multiple(() => - { - Assert.AreEqual(Result0, GetVectorE0(ThreadState.V0)); - Assert.AreEqual(Result1, GetVectorE1(ThreadState.V0)); - }); - } - - [TestCase(0x3FE66666u, false, 0x40000000u)] - [TestCase(0x3F99999Au, false, 0x3F800000u)] - [TestCase(0x404CCCCDu, false, 0x40400000u)] - [TestCase(0x40733333u, false, 0x40800000u)] - [TestCase(0x3fc00000u, false, 0x40000000u)] - [TestCase(0x40200000u, false, 0x40400000u)] - [TestCase(0x00000000u, false, 0x00000000u)] - [TestCase(0x00000000u, false, 0x00000000u)] - [TestCase(0x00000000u, false, 0x00000000u)] - [TestCase(0x00000000u, false, 0x00000000u)] - [TestCase(0x80000000u, false, 0x80000000u)] - [TestCase(0x80000000u, false, 0x80000000u)] - [TestCase(0x80000000u, false, 0x80000000u)] - [TestCase(0x80000000u, false, 0x80000000u)] - [TestCase(0x7F800000u, false, 0x7F800000u)] - [TestCase(0x7F800000u, false, 0x7F800000u)] - [TestCase(0x7F800000u, false, 0x7F800000u)] - [TestCase(0x7F800000u, false, 0x7F800000u)] - [TestCase(0xFF800000u, false, 0xFF800000u)] - [TestCase(0xFF800000u, false, 0xFF800000u)] - [TestCase(0xFF800000u, false, 0xFF800000u)] - [TestCase(0xFF800000u, false, 0xFF800000u)] - [TestCase(0xFF800001u, false, 0xFFC00001u, Ignore = "NaN test.")] - [TestCase(0xFF800001u, false, 0xFFC00001u, Ignore = "NaN test.")] - [TestCase(0xFF800001u, false, 0xFFC00001u, Ignore = "NaN test.")] - [TestCase(0xFF800001u, false, 0xFFC00001u, Ignore = "NaN test.")] - [TestCase(0xFF800001u, true, 0x7FC00000u, Ignore = "NaN test.")] - [TestCase(0xFF800001u, true, 0x7FC00000u, Ignore = "NaN test.")] - [TestCase(0xFF800001u, true, 0x7FC00000u, Ignore = "NaN test.")] - [TestCase(0xFF800001u, true, 0x7FC00000u, Ignore = "NaN test.")] - [TestCase(0x7FC00002u, false, 0x7FC00002u, Ignore = "NaN test.")] - [TestCase(0x7FC00002u, false, 0x7FC00002u, Ignore = "NaN test.")] - [TestCase(0x7FC00002u, false, 0x7FC00002u, Ignore = "NaN test.")] - [TestCase(0x7FC00002u, false, 0x7FC00002u, Ignore = "NaN test.")] - [TestCase(0x7FC00002u, true, 0x7FC00000u, Ignore = "NaN test.")] - [TestCase(0x7FC00002u, true, 0x7FC00000u, Ignore = "NaN test.")] - [TestCase(0x7FC00002u, true, 0x7FC00000u, Ignore = "NaN test.")] - [TestCase(0x7FC00002u, true, 0x7FC00000u, Ignore = "NaN test.")] - public void Frintn_S(uint A, bool DefaultNaN, uint Result) - { - int FpcrTemp = 0x0; - if(DefaultNaN) - { - FpcrTemp = 0x2000000; - } - Vector128 V1 = MakeVectorE0(A); - AThreadState ThreadState = SingleOpcode(0x1E264020, V1: V1, Fpcr: FpcrTemp); - Assert.AreEqual(Result, GetVectorE0(ThreadState.V0)); - } - - [TestCase(0x4E618820u, 0x3FF3333333333333ul, 0x3FF3333333333333ul, false, 0x3FF0000000000000ul, 0x3FF0000000000000ul)] - [TestCase(0x4E618820u, 0x3FFCCCCCCCCCCCCDul, 0x3FFCCCCCCCCCCCCDul, false, 0x4000000000000000ul, 0x4000000000000000ul)] - [TestCase(0x4E618820u, 0x3FF8000000000000ul, 0x3FF8000000000000ul, false, 0x4000000000000000ul, 0x4000000000000000ul)] - [TestCase(0x4E218820u, 0x3f99999a3fe66666ul, 0x3f99999a3fe66666ul, false, 0x3f80000040000000ul, 0x3f80000040000000ul)] - [TestCase(0x4E218820u, 0x3fc000003fc00000ul, 0x3fc000003fc00000ul, false, 0x4000000040000000ul, 0x4000000040000000ul)] - [TestCase(0xE218820u, 0x3f99999a3fe66666ul, 0x3f99999a3fe66666ul, false, 0x3f80000040000000ul, 0x0000000000000000ul)] - [TestCase(0xE218820u, 0x3fc000003fc00000ul, 0x3fc000003fc00000ul, false, 0x4000000040000000ul, 0x0000000000000000ul)] - [TestCase(0xE218820u, 0x0000000080000000ul, 0x0000000000000000ul, false, 0x0000000080000000ul, 0x0000000000000000ul)] - [TestCase(0xE218820u, 0x7F800000FF800000ul, 0x0000000000000000ul, false, 0x7F800000FF800000ul, 0x0000000000000000ul)] - [TestCase(0xE218820u, 0xFF8000017FC00002ul, 0x0000000000000000ul, false, 0xFFC000017FC00002ul, 0x0000000000000000ul, Ignore = "NaN test.")] - [TestCase(0xE218820u, 0xFF8000017FC00002ul, 0x0000000000000000ul, true, 0x7FC000007FC00000ul, 0x0000000000000000ul, Ignore = "NaN test.")] - public void Frintn_V(uint Opcode, ulong A, ulong B, bool DefaultNaN, ulong Result0, ulong Result1) - { - int FpcrTemp = 0x0; - if(DefaultNaN) - { - FpcrTemp = 0x2000000; - } - Vector128 V1 = MakeVectorE0E1(A, B); - AThreadState ThreadState = SingleOpcode(Opcode, V1: V1, Fpcr: FpcrTemp); - Assert.Multiple(() => - { - Assert.AreEqual(Result0, GetVectorE0(ThreadState.V0)); - Assert.AreEqual(Result1, GetVectorE1(ThreadState.V0)); - }); - } - - [TestCase(0x3FE66666u, false, 0x40000000u)] - [TestCase(0x3F99999Au, false, 0x40000000u)] - [TestCase(0x404CCCCDu, false, 0x40800000u)] - [TestCase(0x40733333u, false, 0x40800000u)] - [TestCase(0x3fc00000u, false, 0x40000000u)] - [TestCase(0x40200000u, false, 0x40400000u)] - [TestCase(0x00000000u, false, 0x00000000u)] - [TestCase(0x00000000u, false, 0x00000000u)] - [TestCase(0x00000000u, false, 0x00000000u)] - [TestCase(0x00000000u, false, 0x00000000u)] - [TestCase(0x80000000u, false, 0x80000000u)] - [TestCase(0x80000000u, false, 0x80000000u)] - [TestCase(0x80000000u, false, 0x80000000u)] - [TestCase(0x80000000u, false, 0x80000000u)] - [TestCase(0x7F800000u, false, 0x7F800000u)] - [TestCase(0x7F800000u, false, 0x7F800000u)] - [TestCase(0x7F800000u, false, 0x7F800000u)] - [TestCase(0x7F800000u, false, 0x7F800000u)] - [TestCase(0xFF800000u, false, 0xFF800000u)] - [TestCase(0xFF800000u, false, 0xFF800000u)] - [TestCase(0xFF800000u, false, 0xFF800000u)] - [TestCase(0xFF800000u, false, 0xFF800000u)] - [TestCase(0xFF800001u, false, 0xFFC00001u, Ignore = "NaN test.")] - [TestCase(0xFF800001u, false, 0xFFC00001u, Ignore = "NaN test.")] - [TestCase(0xFF800001u, false, 0xFFC00001u, Ignore = "NaN test.")] - [TestCase(0xFF800001u, false, 0xFFC00001u, Ignore = "NaN test.")] - [TestCase(0xFF800001u, true, 0x7FC00000u, Ignore = "NaN test.")] - [TestCase(0xFF800001u, true, 0x7FC00000u, Ignore = "NaN test.")] - [TestCase(0xFF800001u, true, 0x7FC00000u, Ignore = "NaN test.")] - [TestCase(0xFF800001u, true, 0x7FC00000u, Ignore = "NaN test.")] - [TestCase(0x7FC00002u, false, 0x7FC00002u, Ignore = "NaN test.")] - [TestCase(0x7FC00002u, false, 0x7FC00002u, Ignore = "NaN test.")] - [TestCase(0x7FC00002u, false, 0x7FC00002u, Ignore = "NaN test.")] - [TestCase(0x7FC00002u, false, 0x7FC00002u, Ignore = "NaN test.")] - [TestCase(0x7FC00002u, true, 0x7FC00000u, Ignore = "NaN test.")] - [TestCase(0x7FC00002u, true, 0x7FC00000u, Ignore = "NaN test.")] - [TestCase(0x7FC00002u, true, 0x7FC00000u, Ignore = "NaN test.")] - [TestCase(0x7FC00002u, true, 0x7FC00000u, Ignore = "NaN test.")] - public void Frintp_S(uint A, bool DefaultNaN, uint Result) - { - int FpcrTemp = 0x0; - if(DefaultNaN) - { - FpcrTemp = 0x2000000; - } - Vector128 V1 = MakeVectorE0(A); - AThreadState ThreadState = SingleOpcode(0x1E24C020, V1: V1, Fpcr: FpcrTemp); - Assert.AreEqual(Result, GetVectorE0(ThreadState.V0)); - } - - [TestCase(0x4EE18820u, 0x3FF3333333333333ul, 0x3FF3333333333333ul, false, 0x4000000000000000ul, 0x4000000000000000ul)] - [TestCase(0x4EE18820u, 0x3FFCCCCCCCCCCCCDul, 0x3FFCCCCCCCCCCCCDul, false, 0x4000000000000000ul, 0x4000000000000000ul)] - [TestCase(0x4EA18820u, 0x3f99999a3fe66666ul, 0x3f99999a3fe66666ul, false, 0x4000000040000000ul, 0x4000000040000000ul)] - [TestCase(0xEA18820u, 0x3f99999a3fe66666ul, 0x3f99999a3fe66666ul, false, 0x4000000040000000ul, 0x0000000000000000ul)] - [TestCase(0xEA18820u, 0x0000000080000000ul, 0x0000000000000000ul, false, 0x0000000080000000ul, 0x0000000000000000ul)] - [TestCase(0xEA18820u, 0x7F800000FF800000ul, 0x0000000000000000ul, false, 0x7F800000FF800000ul, 0x0000000000000000ul)] - [TestCase(0xEA18820u, 0xFF8000017FC00002ul, 0x0000000000000000ul, false, 0xFFC000017FC00002ul, 0x0000000000000000ul, Ignore = "NaN test.")] - [TestCase(0xEA18820u, 0xFF8000017FC00002ul, 0x0000000000000000ul, true, 0x7FC000007FC00000ul, 0x0000000000000000ul, Ignore = "NaN test.")] - public void Frintp_V(uint Opcode, ulong A, ulong B, bool DefaultNaN, ulong Result0, ulong Result1) - { - int FpcrTemp = 0x0; - if(DefaultNaN) - { - FpcrTemp = 0x2000000; - } - Vector128 V1 = MakeVectorE0E1(A, B); - AThreadState ThreadState = SingleOpcode(Opcode, V1: V1, Fpcr: FpcrTemp); - Assert.Multiple(() => - { - Assert.AreEqual(Result0, GetVectorE0(ThreadState.V0)); - Assert.AreEqual(Result1, GetVectorE1(ThreadState.V0)); - }); - } - - [TestCase(0x3FE66666u, 'N', false, 0x40000000u)] - [TestCase(0x3F99999Au, 'N', false, 0x3F800000u)] - [TestCase(0x404CCCCDu, 'P', false, 0x40800000u)] - [TestCase(0x40733333u, 'P', false, 0x40800000u)] - [TestCase(0x404CCCCDu, 'M', false, 0x40400000u)] - [TestCase(0x40733333u, 'M', false, 0x40400000u)] - [TestCase(0x3F99999Au, 'Z', false, 0x3F800000u)] - [TestCase(0x3FE66666u, 'Z', false, 0x3F800000u)] - [TestCase(0x00000000u, 'N', false, 0x00000000u)] - [TestCase(0x00000000u, 'P', false, 0x00000000u)] - [TestCase(0x00000000u, 'M', false, 0x00000000u)] - [TestCase(0x00000000u, 'Z', false, 0x00000000u)] - [TestCase(0x80000000u, 'N', false, 0x80000000u)] - [TestCase(0x80000000u, 'P', false, 0x80000000u)] - [TestCase(0x80000000u, 'M', false, 0x80000000u)] - [TestCase(0x80000000u, 'Z', false, 0x80000000u)] - [TestCase(0x7F800000u, 'N', false, 0x7F800000u)] - [TestCase(0x7F800000u, 'P', false, 0x7F800000u)] - [TestCase(0x7F800000u, 'M', false, 0x7F800000u)] - [TestCase(0x7F800000u, 'Z', false, 0x7F800000u)] - [TestCase(0xFF800000u, 'N', false, 0xFF800000u)] - [TestCase(0xFF800000u, 'P', false, 0xFF800000u)] - [TestCase(0xFF800000u, 'M', false, 0xFF800000u)] - [TestCase(0xFF800000u, 'Z', false, 0xFF800000u)] - [TestCase(0xFF800001u, 'N', false, 0xFFC00001u, Ignore = "NaN test.")] - [TestCase(0xFF800001u, 'P', false, 0xFFC00001u, Ignore = "NaN test.")] - [TestCase(0xFF800001u, 'M', false, 0xFFC00001u, Ignore = "NaN test.")] - [TestCase(0xFF800001u, 'Z', false, 0xFFC00001u, Ignore = "NaN test.")] - [TestCase(0xFF800001u, 'N', true, 0x7FC00000u, Ignore = "NaN test.")] - [TestCase(0xFF800001u, 'P', true, 0x7FC00000u, Ignore = "NaN test.")] - [TestCase(0xFF800001u, 'M', true, 0x7FC00000u, Ignore = "NaN test.")] - [TestCase(0xFF800001u, 'Z', true, 0x7FC00000u, Ignore = "NaN test.")] - [TestCase(0x7FC00002u, 'N', false, 0x7FC00002u, Ignore = "NaN test.")] - [TestCase(0x7FC00002u, 'P', false, 0x7FC00002u, Ignore = "NaN test.")] - [TestCase(0x7FC00002u, 'M', false, 0x7FC00002u, Ignore = "NaN test.")] - [TestCase(0x7FC00002u, 'Z', false, 0x7FC00002u, Ignore = "NaN test.")] - [TestCase(0x7FC00002u, 'N', true, 0x7FC00000u, Ignore = "NaN test.")] - [TestCase(0x7FC00002u, 'P', true, 0x7FC00000u, Ignore = "NaN test.")] - [TestCase(0x7FC00002u, 'M', true, 0x7FC00000u, Ignore = "NaN test.")] - [TestCase(0x7FC00002u, 'Z', true, 0x7FC00000u, Ignore = "NaN test.")] - public void Frintx_S(uint A, char RoundType, bool DefaultNaN, uint Result) - { - int FpcrTemp = 0x0; - switch(RoundType) - { - case 'N': - FpcrTemp = 0x0; - break; - - case 'P': - FpcrTemp = 0x400000; - break; - - case 'M': - FpcrTemp = 0x800000; - break; - - case 'Z': - FpcrTemp = 0xC00000; - break; - } - if(DefaultNaN) - { - FpcrTemp |= 1 << 25; - } - Vector128 V1 = MakeVectorE0(A); - AThreadState ThreadState = SingleOpcode(0x1E274020, V1: V1, Fpcr: FpcrTemp); - Assert.AreEqual(Result, GetVectorE0(ThreadState.V0)); - } - - [TestCase(0x6E619820u, 0x3FF3333333333333ul, 0x3FF3333333333333ul, 'N', false, 0x3FF0000000000000ul, 0x3FF0000000000000ul)] - [TestCase(0x6E619820u, 0x3FFCCCCCCCCCCCCDul, 0x3FFCCCCCCCCCCCCDul, 'N', false, 0x4000000000000000ul, 0x4000000000000000ul)] - [TestCase(0x6E619820u, 0x3FF3333333333333ul, 0x3FF3333333333333ul, 'P', false, 0x4000000000000000ul, 0x4000000000000000ul)] - [TestCase(0x6E619820u, 0x3FFCCCCCCCCCCCCDul, 0x3FFCCCCCCCCCCCCDul, 'P', false, 0x4000000000000000ul, 0x4000000000000000ul)] - [TestCase(0x6E619820u, 0x3FF3333333333333ul, 0x3FF3333333333333ul, 'M', false, 0x3FF0000000000000ul, 0x3FF0000000000000ul)] - [TestCase(0x6E619820u, 0x3FFCCCCCCCCCCCCDul, 0x3FFCCCCCCCCCCCCDul, 'M', false, 0x3FF0000000000000ul, 0x3FF0000000000000ul)] - [TestCase(0x6E619820u, 0x3FF3333333333333ul, 0x3FF3333333333333ul, 'Z', false, 0x3FF0000000000000ul, 0x3FF0000000000000ul)] - [TestCase(0x6E619820u, 0x3FFCCCCCCCCCCCCDul, 0x3FFCCCCCCCCCCCCDul, 'Z', false, 0x3FF0000000000000ul, 0x3FF0000000000000ul)] - [TestCase(0x6E219820u, 0x3f99999a3fe66666ul, 0x3f99999a3fe66666ul, 'N', false, 0x3f80000040000000ul, 0x3f80000040000000ul)] - [TestCase(0x6E219820u, 0x3f99999a3fe66666ul, 0x3f99999a3fe66666ul, 'P', false, 0x4000000040000000ul, 0x4000000040000000ul)] - [TestCase(0x6E219820u, 0x3f99999a3fe66666ul, 0x3f99999a3fe66666ul, 'M', false, 0x3f8000003f800000ul, 0x3f8000003f800000ul)] - [TestCase(0x6E219820u, 0x3f99999a3fe66666ul, 0x3f99999a3fe66666ul, 'Z', false, 0x3f8000003f800000ul, 0x3f8000003f800000ul)] - [TestCase(0x2E219820u, 0x3f99999a3fe66666ul, 0x3f99999a3fe66666ul, 'N', false, 0x3f80000040000000ul, 0x0000000000000000ul)] - [TestCase(0x2E219820u, 0x3f99999a3fe66666ul, 0x3f99999a3fe66666ul, 'P', false, 0x4000000040000000ul, 0x0000000000000000ul)] - [TestCase(0x2E219820u, 0x3f99999a3fe66666ul, 0x3f99999a3fe66666ul, 'M', false, 0x3f8000003f800000ul, 0x0000000000000000ul)] - [TestCase(0x2E219820u, 0x3f99999a3fe66666ul, 0x3f99999a3fe66666ul, 'Z', false, 0x3f8000003f800000ul, 0x0000000000000000ul)] - [TestCase(0x2E219820u, 0x0000000080000000ul, 0x0000000000000000ul, 'N', false, 0x0000000080000000ul, 0x0000000000000000ul)] - [TestCase(0x2E219820u, 0x0000000080000000ul, 0x0000000000000000ul, 'P', false, 0x0000000080000000ul, 0x0000000000000000ul)] - [TestCase(0x2E219820u, 0x0000000080000000ul, 0x0000000000000000ul, 'M', false, 0x0000000080000000ul, 0x0000000000000000ul)] - [TestCase(0x2E219820u, 0x0000000080000000ul, 0x0000000000000000ul, 'Z', false, 0x0000000080000000ul, 0x0000000000000000ul)] - [TestCase(0x2E219820u, 0x7F800000FF800000ul, 0x0000000000000000ul, 'N', false, 0x7F800000FF800000ul, 0x0000000000000000ul)] - [TestCase(0x2E219820u, 0x7F800000FF800000ul, 0x0000000000000000ul, 'P', false, 0x7F800000FF800000ul, 0x0000000000000000ul)] - [TestCase(0x2E219820u, 0x7F800000FF800000ul, 0x0000000000000000ul, 'M', false, 0x7F800000FF800000ul, 0x0000000000000000ul)] - [TestCase(0x2E219820u, 0x7F800000FF800000ul, 0x0000000000000000ul, 'Z', false, 0x7F800000FF800000ul, 0x0000000000000000ul)] - [TestCase(0x2E219820u, 0xFF8000017FC00002ul, 0x0000000000000000ul, 'N', false, 0xFFC000017FC00002ul, 0x0000000000000000ul, Ignore = "NaN test.")] - [TestCase(0x2E219820u, 0xFF8000017FC00002ul, 0x0000000000000000ul, 'P', false, 0xFFC000017FC00002ul, 0x0000000000000000ul, Ignore = "NaN test.")] - [TestCase(0x2E219820u, 0xFF8000017FC00002ul, 0x0000000000000000ul, 'M', false, 0xFFC000017FC00002ul, 0x0000000000000000ul, Ignore = "NaN test.")] - [TestCase(0x2E219820u, 0xFF8000017FC00002ul, 0x0000000000000000ul, 'Z', false, 0xFFC000017FC00002ul, 0x0000000000000000ul, Ignore = "NaN test.")] - [TestCase(0x2E219820u, 0xFF8000017FC00002ul, 0x0000000000000000ul, 'N', true, 0x7FC000007FC00000ul, 0x0000000000000000ul, Ignore = "NaN test.")] - [TestCase(0x2E219820u, 0xFF8000017FC00002ul, 0x0000000000000000ul, 'P', true, 0x7FC000007FC00000ul, 0x0000000000000000ul, Ignore = "NaN test.")] - [TestCase(0x2E219820u, 0xFF8000017FC00002ul, 0x0000000000000000ul, 'M', true, 0x7FC000007FC00000ul, 0x0000000000000000ul, Ignore = "NaN test.")] - [TestCase(0x2E219820u, 0xFF8000017FC00002ul, 0x0000000000000000ul, 'Z', true, 0x7FC000007FC00000ul, 0x0000000000000000ul, Ignore = "NaN test.")] - public void Frintx_V(uint Opcode, ulong A, ulong B, char RoundType, bool DefaultNaN, ulong Result0, ulong Result1) - { - int FpcrTemp = 0x0; - switch(RoundType) - { - case 'N': - FpcrTemp = 0x0; - break; - - case 'P': - FpcrTemp = 0x400000; - break; - - case 'M': - FpcrTemp = 0x800000; - break; - - case 'Z': - FpcrTemp = 0xC00000; - break; - } - if(DefaultNaN) - { - FpcrTemp |= 1 << 25; - } - Vector128 V1 = MakeVectorE0E1(A, B); - AThreadState ThreadState = SingleOpcode(Opcode, V1: V1, Fpcr: FpcrTemp); - Assert.Multiple(() => - { - Assert.AreEqual(Result0, GetVectorE0(ThreadState.V0)); - Assert.AreEqual(Result1, GetVectorE1(ThreadState.V0)); - }); - } - - [TestCase(0x41200000u, 0x3EA18000u)] - public void Frsqrte_S(uint A, uint Result) - { - Vector128 V1 = MakeVectorE0(A); - AThreadState ThreadState = SingleOpcode(0x7EA1D820, V1: V1); - Assert.AreEqual(Result, GetVectorE0(ThreadState.V0)); - } - } -} diff --git a/Ryujinx.Tests/Cpu/CpuTestSimdCmp.cs b/Ryujinx.Tests/Cpu/CpuTestSimdCmp.cs deleted file mode 100644 index 41f5113d6e..0000000000 --- a/Ryujinx.Tests/Cpu/CpuTestSimdCmp.cs +++ /dev/null @@ -1,375 +0,0 @@ -using ChocolArm64.State; - -using NUnit.Framework; - -using System; -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; - -namespace Ryujinx.Tests.Cpu -{ - public class CpuTestSimdCmp : CpuTest - { -#region "ValueSource" - private static float[] _floats_() - { - return new float[] { float.NegativeInfinity, float.MinValue, -1f, -0f, - +0f, +1f, float.MaxValue, float.PositiveInfinity }; - } - - private static double[] _doubles_() - { - return new double[] { double.NegativeInfinity, double.MinValue, -1d, -0d, - +0d, +1d, double.MaxValue, double.PositiveInfinity }; - } -#endregion - - [Test, Description("FCMEQ D0, D1, D2 | FCMGE D0, D1, D2 | FCMGT D0, D1, D2")] - public void Fcmeq_Fcmge_Fcmgt_Reg_S_D([ValueSource("_doubles_")] [Random(8)] double A, - [ValueSource("_doubles_")] [Random(8)] double B, - [Values(0u, 1u, 3u)] uint EU) // EQ, GE, GT - { - uint Opcode = 0x5E62E420 | ((EU & 1) << 29) | ((EU >> 1) << 23); - Vector128 V0 = Sse.StaticCast(Sse2.SetAllVector128(TestContext.CurrentContext.Random.NextDouble())); - Vector128 V1 = Sse.StaticCast(Sse2.SetScalarVector128(A)); - Vector128 V2 = Sse.StaticCast(Sse2.SetScalarVector128(B)); - - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); - - byte[] Exp = default(byte[]); - byte[] Ones = new byte[] {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; - byte[] Zeros = new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; - - switch (EU) - { - case 0: Exp = (A == B ? Ones : Zeros); break; - case 1: Exp = (A >= B ? Ones : Zeros); break; - case 3: Exp = (A > B ? Ones : Zeros); break; - } - - Assert.Multiple(() => - { - Assert.That(BitConverter.GetBytes(VectorExtractDouble(ThreadState.V0, (byte)0)), Is.EquivalentTo(Exp)); - Assert.That(VectorExtractDouble(ThreadState.V0, (byte)1), Is.Zero); - }); - } - - [Test, Description("FCMEQ S0, S1, S2 | FCMGE S0, S1, S2 | FCMGT S0, S1, S2")] - public void Fcmeq_Fcmge_Fcmgt_Reg_S_S([ValueSource("_floats_")] [Random(8)] float A, - [ValueSource("_floats_")] [Random(8)] float B, - [Values(0u, 1u, 3u)] uint EU) // EQ, GE, GT - { - uint Opcode = 0x5E22E420 | ((EU & 1) << 29) | ((EU >> 1) << 23); - Vector128 V0 = Sse.SetAllVector128(TestContext.CurrentContext.Random.NextFloat()); - Vector128 V1 = Sse.SetScalarVector128(A); - Vector128 V2 = Sse.SetScalarVector128(B); - - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); - - byte[] Exp = default(byte[]); - byte[] Ones = new byte[] {0xFF, 0xFF, 0xFF, 0xFF}; - byte[] Zeros = new byte[] {0x00, 0x00, 0x00, 0x00}; - - switch (EU) - { - case 0: Exp = (A == B ? Ones : Zeros); break; - case 1: Exp = (A >= B ? Ones : Zeros); break; - case 3: Exp = (A > B ? Ones : Zeros); break; - } - - Assert.Multiple(() => - { - Assert.That(BitConverter.GetBytes(Sse41.Extract(ThreadState.V0, (byte)0)), Is.EquivalentTo(Exp)); - Assert.That(Sse41.Extract(ThreadState.V0, (byte)1), Is.Zero); - Assert.That(Sse41.Extract(ThreadState.V0, (byte)2), Is.Zero); - Assert.That(Sse41.Extract(ThreadState.V0, (byte)3), Is.Zero); - }); - } - - [Test, Description("FCMEQ V0.2D, V1.2D, V2.2D | FCMGE V0.2D, V1.2D, V2.2D | FCMGT V0.2D, V1.2D, V2.2D")] - public void Fcmeq_Fcmge_Fcmgt_Reg_V_2D([ValueSource("_doubles_")] [Random(8)] double A, - [ValueSource("_doubles_")] [Random(8)] double B, - [Values(0u, 1u, 3u)] uint EU) // EQ, GE, GT - { - uint Opcode = 0x4E62E420 | ((EU & 1) << 29) | ((EU >> 1) << 23); - Vector128 V1 = Sse.StaticCast(Sse2.SetAllVector128(A)); - Vector128 V2 = Sse.StaticCast(Sse2.SetAllVector128(B)); - - AThreadState ThreadState = SingleOpcode(Opcode, V1: V1, V2: V2); - - byte[] Exp = default(byte[]); - byte[] Ones = new byte[] {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; - byte[] Zeros = new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; - - switch (EU) - { - case 0: Exp = (A == B ? Ones : Zeros); break; - case 1: Exp = (A >= B ? Ones : Zeros); break; - case 3: Exp = (A > B ? Ones : Zeros); break; - } - - Assert.Multiple(() => - { - Assert.That(BitConverter.GetBytes(VectorExtractDouble(ThreadState.V0, (byte)0)), Is.EquivalentTo(Exp)); - Assert.That(BitConverter.GetBytes(VectorExtractDouble(ThreadState.V0, (byte)1)), Is.EquivalentTo(Exp)); - }); - } - - [Test, Description("FCMEQ V0.2S, V1.2S, V2.2S | FCMGE V0.2S, V1.2S, V2.2S | FCMGT V0.2S, V1.2S, V2.2S")] - public void Fcmeq_Fcmge_Fcmgt_Reg_V_2S([ValueSource("_floats_")] [Random(8)] float A, - [ValueSource("_floats_")] [Random(8)] float B, - [Values(0u, 1u, 3u)] uint EU) // EQ, GE, GT - { - uint Opcode = 0x0E22E420 | ((EU & 1) << 29) | ((EU >> 1) << 23); - Vector128 V0 = Sse.SetAllVector128(TestContext.CurrentContext.Random.NextFloat()); - Vector128 V1 = Sse.SetVector128(0, 0, A, A); - Vector128 V2 = Sse.SetVector128(0, 0, B, B); - - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); - - byte[] Exp = default(byte[]); - byte[] Ones = new byte[] {0xFF, 0xFF, 0xFF, 0xFF}; - byte[] Zeros = new byte[] {0x00, 0x00, 0x00, 0x00}; - - switch (EU) - { - case 0: Exp = (A == B ? Ones : Zeros); break; - case 1: Exp = (A >= B ? Ones : Zeros); break; - case 3: Exp = (A > B ? Ones : Zeros); break; - } - - Assert.Multiple(() => - { - Assert.That(BitConverter.GetBytes(Sse41.Extract(ThreadState.V0, (byte)0)), Is.EquivalentTo(Exp)); - Assert.That(BitConverter.GetBytes(Sse41.Extract(ThreadState.V0, (byte)1)), Is.EquivalentTo(Exp)); - Assert.That(Sse41.Extract(ThreadState.V0, (byte)2), Is.Zero); - Assert.That(Sse41.Extract(ThreadState.V0, (byte)3), Is.Zero); - }); - } - - [Test, Description("FCMEQ V0.4S, V1.4S, V2.4S | FCMGE V0.4S, V1.4S, V2.4S | FCMGT V0.4S, V1.4S, V2.4S")] - public void Fcmeq_Fcmge_Fcmgt_Reg_V_4S([ValueSource("_floats_")] [Random(8)] float A, - [ValueSource("_floats_")] [Random(8)] float B, - [Values(0u, 1u, 3u)] uint EU) // EQ, GE, GT - { - uint Opcode = 0x4E22E420 | ((EU & 1) << 29) | ((EU >> 1) << 23); - Vector128 V1 = Sse.SetAllVector128(A); - Vector128 V2 = Sse.SetAllVector128(B); - - AThreadState ThreadState = SingleOpcode(Opcode, V1: V1, V2: V2); - - byte[] Exp = default(byte[]); - byte[] Ones = new byte[] {0xFF, 0xFF, 0xFF, 0xFF}; - byte[] Zeros = new byte[] {0x00, 0x00, 0x00, 0x00}; - - switch (EU) - { - case 0: Exp = (A == B ? Ones : Zeros); break; - case 1: Exp = (A >= B ? Ones : Zeros); break; - case 3: Exp = (A > B ? Ones : Zeros); break; - } - - Assert.Multiple(() => - { - Assert.That(BitConverter.GetBytes(Sse41.Extract(ThreadState.V0, (byte)0)), Is.EquivalentTo(Exp)); - Assert.That(BitConverter.GetBytes(Sse41.Extract(ThreadState.V0, (byte)1)), Is.EquivalentTo(Exp)); - Assert.That(BitConverter.GetBytes(Sse41.Extract(ThreadState.V0, (byte)2)), Is.EquivalentTo(Exp)); - Assert.That(BitConverter.GetBytes(Sse41.Extract(ThreadState.V0, (byte)3)), Is.EquivalentTo(Exp)); - }); - } - - [Test, Description("FCMGT D0, D1, #0.0 | FCMGE D0, D1, #0.0 | FCMEQ D0, D1, #0.0 | FCMLE D0, D1, #0.0 | FCMLT D0, D1, #0.0")] - public void Fcmgt_Fcmge_Fcmeq_Fcmle_Fcmlt_Zero_S_D([ValueSource("_doubles_")] [Random(8)] double A, - [Values(0u, 1u, 2u, 3u)] uint opU, // GT, GE, EQ, LE - [Values(0u, 1u)] uint bit13) // "LT" - { - uint Opcode = 0x5EE0C820 | (((opU & 1) & ~bit13) << 29) | (bit13 << 13) | (((opU >> 1) & ~bit13) << 12); - Vector128 V0 = Sse.StaticCast(Sse2.SetAllVector128(TestContext.CurrentContext.Random.NextDouble())); - Vector128 V1 = Sse.StaticCast(Sse2.SetScalarVector128(A)); - - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); - - double Zero = +0d; - byte[] Exp = default(byte[]); - byte[] Ones = new byte[] {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; - byte[] Zeros = new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; - - if (bit13 == 0) - { - switch (opU) - { - case 0: Exp = (A > Zero ? Ones : Zeros); break; - case 1: Exp = (A >= Zero ? Ones : Zeros); break; - case 2: Exp = (A == Zero ? Ones : Zeros); break; - case 3: Exp = (Zero >= A ? Ones : Zeros); break; - } - } - else - { - Exp = (Zero > A ? Ones : Zeros); - } - - Assert.Multiple(() => - { - Assert.That(BitConverter.GetBytes(VectorExtractDouble(ThreadState.V0, (byte)0)), Is.EquivalentTo(Exp)); - Assert.That(VectorExtractDouble(ThreadState.V0, (byte)1), Is.Zero); - }); - } - - [Test, Description("FCMGT S0, S1, #0.0 | FCMGE S0, S1, #0.0 | FCMEQ S0, S1, #0.0 | FCMLE S0, S1, #0.0 | FCMLT S0, S1, #0.0")] - public void Fcmgt_Fcmge_Fcmeq_Fcmle_Fcmlt_Zero_S_S([ValueSource("_floats_")] [Random(8)] float A, - [Values(0u, 1u, 2u, 3u)] uint opU, // GT, GE, EQ, LE - [Values(0u, 1u)] uint bit13) // "LT" - { - uint Opcode = 0x5EA0C820 | (((opU & 1) & ~bit13) << 29) | (bit13 << 13) | (((opU >> 1) & ~bit13) << 12); - Vector128 V0 = Sse.SetAllVector128(TestContext.CurrentContext.Random.NextFloat()); - Vector128 V1 = Sse.SetScalarVector128(A); - - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); - - float Zero = +0f; - byte[] Exp = default(byte[]); - byte[] Ones = new byte[] {0xFF, 0xFF, 0xFF, 0xFF}; - byte[] Zeros = new byte[] {0x00, 0x00, 0x00, 0x00}; - - if (bit13 == 0) - { - switch (opU) - { - case 0: Exp = (A > Zero ? Ones : Zeros); break; - case 1: Exp = (A >= Zero ? Ones : Zeros); break; - case 2: Exp = (A == Zero ? Ones : Zeros); break; - case 3: Exp = (Zero >= A ? Ones : Zeros); break; - } - } - else - { - Exp = (Zero > A ? Ones : Zeros); - } - - Assert.Multiple(() => - { - Assert.That(BitConverter.GetBytes(Sse41.Extract(ThreadState.V0, (byte)0)), Is.EquivalentTo(Exp)); - Assert.That(Sse41.Extract(ThreadState.V0, (byte)1), Is.Zero); - Assert.That(Sse41.Extract(ThreadState.V0, (byte)2), Is.Zero); - Assert.That(Sse41.Extract(ThreadState.V0, (byte)3), Is.Zero); - }); - } - - [Test, Description("FCMGT V0.2D, V1.2D, #0.0 | FCMGE V0.2D, V1.2D, #0.0 | FCMEQ V0.2D, V1.2D, #0.0 | FCMLE V0.2D, V1.2D, #0.0 | FCMLT V0.2D, V1.2D, #0.0")] - public void Fcmgt_Fcmge_Fcmeq_Fcmle_Fcmlt_Zero_V_2D([ValueSource("_doubles_")] [Random(8)] double A, - [Values(0u, 1u, 2u, 3u)] uint opU, // GT, GE, EQ, LE - [Values(0u, 1u)] uint bit13) // "LT" - { - uint Opcode = 0x4EE0C820 | (((opU & 1) & ~bit13) << 29) | (bit13 << 13) | (((opU >> 1) & ~bit13) << 12); - Vector128 V1 = Sse.StaticCast(Sse2.SetAllVector128(A)); - - AThreadState ThreadState = SingleOpcode(Opcode, V1: V1); - - double Zero = +0d; - byte[] Exp = default(byte[]); - byte[] Ones = new byte[] {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; - byte[] Zeros = new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; - - if (bit13 == 0) - { - switch (opU) - { - case 0: Exp = (A > Zero ? Ones : Zeros); break; - case 1: Exp = (A >= Zero ? Ones : Zeros); break; - case 2: Exp = (A == Zero ? Ones : Zeros); break; - case 3: Exp = (Zero >= A ? Ones : Zeros); break; - } - } - else - { - Exp = (Zero > A ? Ones : Zeros); - } - - Assert.Multiple(() => - { - Assert.That(BitConverter.GetBytes(VectorExtractDouble(ThreadState.V0, (byte)0)), Is.EquivalentTo(Exp)); - Assert.That(BitConverter.GetBytes(VectorExtractDouble(ThreadState.V0, (byte)1)), Is.EquivalentTo(Exp)); - }); - } - - [Test, Description("FCMGT V0.2S, V1.2S, #0.0 | FCMGE V0.2S, V1.2S, #0.0 | FCMEQ V0.2S, V1.2S, #0.0 | FCMLE V0.2S, V1.2S, #0.0 | FCMLT V0.2S, V1.2S, #0.0")] - public void Fcmgt_Fcmge_Fcmeq_Fcmle_Fcmlt_Zero_V_2S([ValueSource("_floats_")] [Random(8)] float A, - [Values(0u, 1u, 2u, 3u)] uint opU, // GT, GE, EQ, LE - [Values(0u, 1u)] uint bit13) // "LT" - { - uint Opcode = 0x0EA0C820 | (((opU & 1) & ~bit13) << 29) | (bit13 << 13) | (((opU >> 1) & ~bit13) << 12); - Vector128 V0 = Sse.SetAllVector128(TestContext.CurrentContext.Random.NextFloat()); - Vector128 V1 = Sse.SetVector128(0, 0, A, A); - - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); - - float Zero = +0f; - byte[] Exp = default(byte[]); - byte[] Ones = new byte[] {0xFF, 0xFF, 0xFF, 0xFF}; - byte[] Zeros = new byte[] {0x00, 0x00, 0x00, 0x00}; - - if (bit13 == 0) - { - switch (opU) - { - case 0: Exp = (A > Zero ? Ones : Zeros); break; - case 1: Exp = (A >= Zero ? Ones : Zeros); break; - case 2: Exp = (A == Zero ? Ones : Zeros); break; - case 3: Exp = (Zero >= A ? Ones : Zeros); break; - } - } - else - { - Exp = (Zero > A ? Ones : Zeros); - } - - Assert.Multiple(() => - { - Assert.That(BitConverter.GetBytes(Sse41.Extract(ThreadState.V0, (byte)0)), Is.EquivalentTo(Exp)); - Assert.That(BitConverter.GetBytes(Sse41.Extract(ThreadState.V0, (byte)1)), Is.EquivalentTo(Exp)); - Assert.That(Sse41.Extract(ThreadState.V0, (byte)2), Is.Zero); - Assert.That(Sse41.Extract(ThreadState.V0, (byte)3), Is.Zero); - }); - } - - [Test, Description("FCMGT V0.4S, V1.4S, #0.0 | FCMGE V0.4S, V1.4S, #0.0 | FCMEQ V0.4S, V1.4S, #0.0 | FCMLE V0.4S, V1.4S, #0.0 | FCMLT V0.4S, V1.4S, #0.0")] - public void Fcmgt_Fcmge_Fcmeq_Fcmle_Fcmlt_Zero_V_4S([ValueSource("_floats_")] [Random(8)] float A, - [Values(0u, 1u, 2u, 3u)] uint opU, // GT, GE, EQ, LE - [Values(0u, 1u)] uint bit13) // "LT" - { - uint Opcode = 0x4EA0C820 | (((opU & 1) & ~bit13) << 29) | (bit13 << 13) | (((opU >> 1) & ~bit13) << 12); - Vector128 V1 = Sse.SetAllVector128(A); - - AThreadState ThreadState = SingleOpcode(Opcode, V1: V1); - - float Zero = +0f; - byte[] Exp = default(byte[]); - byte[] Ones = new byte[] {0xFF, 0xFF, 0xFF, 0xFF}; - byte[] Zeros = new byte[] {0x00, 0x00, 0x00, 0x00}; - - if (bit13 == 0) - { - switch (opU) - { - case 0: Exp = (A > Zero ? Ones : Zeros); break; - case 1: Exp = (A >= Zero ? Ones : Zeros); break; - case 2: Exp = (A == Zero ? Ones : Zeros); break; - case 3: Exp = (Zero >= A ? Ones : Zeros); break; - } - } - else - { - Exp = (Zero > A ? Ones : Zeros); - } - - Assert.Multiple(() => - { - Assert.That(BitConverter.GetBytes(Sse41.Extract(ThreadState.V0, (byte)0)), Is.EquivalentTo(Exp)); - Assert.That(BitConverter.GetBytes(Sse41.Extract(ThreadState.V0, (byte)1)), Is.EquivalentTo(Exp)); - Assert.That(BitConverter.GetBytes(Sse41.Extract(ThreadState.V0, (byte)2)), Is.EquivalentTo(Exp)); - Assert.That(BitConverter.GetBytes(Sse41.Extract(ThreadState.V0, (byte)3)), Is.EquivalentTo(Exp)); - }); - } - } -} diff --git a/Ryujinx.Tests/Cpu/CpuTestSimdCrypto.cs b/Ryujinx.Tests/Cpu/CpuTestSimdCrypto.cs new file mode 100644 index 0000000000..fd8ec9c570 --- /dev/null +++ b/Ryujinx.Tests/Cpu/CpuTestSimdCrypto.cs @@ -0,0 +1,145 @@ +// https://www.intel.com/content/dam/doc/white-paper/advanced-encryption-standard-new-instructions-set-paper.pdf + +using ARMeilleure.State; + +using NUnit.Framework; + +namespace Ryujinx.Tests.Cpu +{ + public class CpuTestSimdCrypto : CpuTest + { + [Test, Description("AESD .16B, .16B")] + public void Aesd_V([Values(0u)] uint rd, + [Values(1u)] uint rn, + [Values(0x7B5B546573745665ul)] ulong valueH, + [Values(0x63746F725D53475Dul)] ulong valueL, + [Random(2)] ulong roundKeyH, + [Random(2)] ulong roundKeyL, + [Values(0x8DCAB9BC035006BCul)] ulong resultH, + [Values(0x8F57161E00CAFD8Dul)] ulong resultL) + { + uint opcode = 0x4E285800; // AESD V0.16B, V0.16B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(roundKeyL ^ valueL, roundKeyH ^ valueH); + V128 v1 = MakeVectorE0E1(roundKeyL, roundKeyH); + + ExecutionContext context = SingleOpcode(opcode, v0: v0, v1: v1); + + Assert.Multiple(() => + { + Assert.That(GetVectorE0(context.GetV(0)), Is.EqualTo(resultL)); + Assert.That(GetVectorE1(context.GetV(0)), Is.EqualTo(resultH)); + }); + Assert.Multiple(() => + { + Assert.That(GetVectorE0(context.GetV(1)), Is.EqualTo(roundKeyL)); + Assert.That(GetVectorE1(context.GetV(1)), Is.EqualTo(roundKeyH)); + }); + + CompareAgainstUnicorn(); + } + + [Test, Description("AESE .16B, .16B")] + public void Aese_V([Values(0u)] uint rd, + [Values(1u)] uint rn, + [Values(0x7B5B546573745665ul)] ulong valueH, + [Values(0x63746F725D53475Dul)] ulong valueL, + [Random(2)] ulong roundKeyH, + [Random(2)] ulong roundKeyL, + [Values(0x8F92A04DFBED204Dul)] ulong resultH, + [Values(0x4C39B1402192A84Cul)] ulong resultL) + { + uint opcode = 0x4E284800; // AESE V0.16B, V0.16B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(roundKeyL ^ valueL, roundKeyH ^ valueH); + V128 v1 = MakeVectorE0E1(roundKeyL, roundKeyH); + + ExecutionContext context = SingleOpcode(opcode, v0: v0, v1: v1); + + Assert.Multiple(() => + { + Assert.That(GetVectorE0(context.GetV(0)), Is.EqualTo(resultL)); + Assert.That(GetVectorE1(context.GetV(0)), Is.EqualTo(resultH)); + }); + Assert.Multiple(() => + { + Assert.That(GetVectorE0(context.GetV(1)), Is.EqualTo(roundKeyL)); + Assert.That(GetVectorE1(context.GetV(1)), Is.EqualTo(roundKeyH)); + }); + + CompareAgainstUnicorn(); + } + + [Test, Description("AESIMC .16B, .16B")] + public void Aesimc_V([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(0x8DCAB9DC035006BCul)] ulong valueH, + [Values(0x8F57161E00CAFD8Dul)] ulong valueL, + [Values(0xD635A667928B5EAEul)] ulong resultH, + [Values(0xEEC9CC3BC55F5777ul)] ulong resultL) + { + uint opcode = 0x4E287800; // AESIMC V0.16B, V0.16B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v = MakeVectorE0E1(valueL, valueH); + + ExecutionContext context = SingleOpcode( + opcode, + v0: rn == 0u ? v : default(V128), + v1: rn == 1u ? v : default(V128)); + + Assert.Multiple(() => + { + Assert.That(GetVectorE0(context.GetV(0)), Is.EqualTo(resultL)); + Assert.That(GetVectorE1(context.GetV(0)), Is.EqualTo(resultH)); + }); + if (rn == 1u) + { + Assert.Multiple(() => + { + Assert.That(GetVectorE0(context.GetV(1)), Is.EqualTo(valueL)); + Assert.That(GetVectorE1(context.GetV(1)), Is.EqualTo(valueH)); + }); + } + + CompareAgainstUnicorn(); + } + + [Test, Description("AESMC .16B, .16B")] + public void Aesmc_V([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(0x627A6F6644B109C8ul)] ulong valueH, + [Values(0x2B18330A81C3B3E5ul)] ulong valueL, + [Values(0x7B5B546573745665ul)] ulong resultH, + [Values(0x63746F725D53475Dul)] ulong resultL) + { + uint opcode = 0x4E286800; // AESMC V0.16B, V0.16B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v = MakeVectorE0E1(valueL, valueH); + + ExecutionContext context = SingleOpcode( + opcode, + v0: rn == 0u ? v : default(V128), + v1: rn == 1u ? v : default(V128)); + + Assert.Multiple(() => + { + Assert.That(GetVectorE0(context.GetV(0)), Is.EqualTo(resultL)); + Assert.That(GetVectorE1(context.GetV(0)), Is.EqualTo(resultH)); + }); + if (rn == 1u) + { + Assert.Multiple(() => + { + Assert.That(GetVectorE0(context.GetV(1)), Is.EqualTo(valueL)); + Assert.That(GetVectorE1(context.GetV(1)), Is.EqualTo(valueH)); + }); + } + + CompareAgainstUnicorn(); + } + } +} diff --git a/Ryujinx.Tests/Cpu/CpuTestSimdCvt.cs b/Ryujinx.Tests/Cpu/CpuTestSimdCvt.cs new file mode 100644 index 0000000000..2d5c82318c --- /dev/null +++ b/Ryujinx.Tests/Cpu/CpuTestSimdCvt.cs @@ -0,0 +1,673 @@ +#define SimdCvt + +using ARMeilleure.State; + +using NUnit.Framework; + +using System; +using System.Collections.Generic; + +namespace Ryujinx.Tests.Cpu +{ + [Category("SimdCvt")] + public sealed class CpuTestSimdCvt : CpuTest + { +#if SimdCvt + +#region "ValueSource (Types)" + private static uint[] _W_() + { + return new uint[] { 0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu }; + } + + private static ulong[] _X_() + { + return new ulong[] { 0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul }; + } + + private static IEnumerable _1S_F_WX_() + { + // int + yield return 0x00000000CF000001ul; // -2.1474839E9f (-2147483904) + yield return 0x00000000CF000000ul; // -2.14748365E9f (-2147483648) + yield return 0x00000000CEFFFFFFul; // -2.14748352E9f (-2147483520) + yield return 0x000000004F000001ul; // 2.1474839E9f (2147483904) + yield return 0x000000004F000000ul; // 2.14748365E9f (2147483648) + yield return 0x000000004EFFFFFFul; // 2.14748352E9f (2147483520) + + // long + yield return 0x00000000DF000001ul; // -9.223373E18f (-9223373136366403584) + yield return 0x00000000DF000000ul; // -9.223372E18f (-9223372036854775808) + yield return 0x00000000DEFFFFFFul; // -9.2233715E18f (-9223371487098961920) + yield return 0x000000005F000001ul; // 9.223373E18f (9223373136366403584) + yield return 0x000000005F000000ul; // 9.223372E18f (9223372036854775808) + yield return 0x000000005EFFFFFFul; // 9.2233715E18f (9223371487098961920) + + // uint + yield return 0x000000004F800001ul; // 4.2949678E9f (4294967808) + yield return 0x000000004F800000ul; // 4.2949673E9f (4294967296) + yield return 0x000000004F7FFFFFul; // 4.29496704E9f (4294967040) + + // ulong + yield return 0x000000005F800001ul; // 1.8446746E19f (18446746272732807168) + yield return 0x000000005F800000ul; // 1.8446744E19f (18446744073709551616) + yield return 0x000000005F7FFFFFul; // 1.8446743E19f (18446742974197923840) + + yield return 0x00000000FF7FFFFFul; // -Max Normal (float.MinValue) + yield return 0x0000000080800000ul; // -Min Normal + yield return 0x00000000807FFFFFul; // -Max Subnormal + yield return 0x0000000080000001ul; // -Min Subnormal (-float.Epsilon) + yield return 0x000000007F7FFFFFul; // +Max Normal (float.MaxValue) + yield return 0x0000000000800000ul; // +Min Normal + yield return 0x00000000007FFFFFul; // +Max Subnormal + yield return 0x0000000000000001ul; // +Min Subnormal (float.Epsilon) + + if (!NoZeros) + { + yield return 0x0000000080000000ul; // -Zero + yield return 0x0000000000000000ul; // +Zero + } + + if (!NoInfs) + { + yield return 0x00000000FF800000ul; // -Infinity + yield return 0x000000007F800000ul; // +Infinity + } + + if (!NoNaNs) + { + yield return 0x00000000FFC00000ul; // -QNaN (all zeros payload) (float.NaN) + yield return 0x00000000FFBFFFFFul; // -SNaN (all ones payload) + yield return 0x000000007FC00000ul; // +QNaN (all zeros payload) (-float.NaN) (DefaultNaN) + yield return 0x000000007FBFFFFFul; // +SNaN (all ones payload) + } + + for (int cnt = 1; cnt <= RndCnt; cnt++) + { + ulong grbg = TestContext.CurrentContext.Random.NextUInt(); + + ulong rnd1 = (uint)BitConverter.SingleToInt32Bits( + (float)((int)TestContext.CurrentContext.Random.NextUInt())); + ulong rnd2 = (uint)BitConverter.SingleToInt32Bits( + (float)((long)TestContext.CurrentContext.Random.NextULong())); + ulong rnd3 = (uint)BitConverter.SingleToInt32Bits( + (float)((uint)TestContext.CurrentContext.Random.NextUInt())); + ulong rnd4 = (uint)BitConverter.SingleToInt32Bits( + (float)((ulong)TestContext.CurrentContext.Random.NextULong())); + + ulong rnd5 = GenNormalS(); + ulong rnd6 = GenSubnormalS(); + + yield return (grbg << 32) | rnd1; + yield return (grbg << 32) | rnd2; + yield return (grbg << 32) | rnd3; + yield return (grbg << 32) | rnd4; + + yield return (grbg << 32) | rnd5; + yield return (grbg << 32) | rnd6; + } + } + + private static IEnumerable _1D_F_WX_() + { + // int + yield return 0xC1E0000000200000ul; // -2147483649.0000000d (-2147483649) + yield return 0xC1E0000000000000ul; // -2147483648.0000000d (-2147483648) + yield return 0xC1DFFFFFFFC00000ul; // -2147483647.0000000d (-2147483647) + yield return 0x41E0000000200000ul; // 2147483649.0000000d (2147483649) + yield return 0x41E0000000000000ul; // 2147483648.0000000d (2147483648) + yield return 0x41DFFFFFFFC00000ul; // 2147483647.0000000d (2147483647) + + // long + yield return 0xC3E0000000000001ul; // -9.2233720368547780E18d (-9223372036854778000) + yield return 0xC3E0000000000000ul; // -9.2233720368547760E18d (-9223372036854776000) + yield return 0xC3DFFFFFFFFFFFFFul; // -9.2233720368547750E18d (-9223372036854775000) + yield return 0x43E0000000000001ul; // 9.2233720368547780E18d (9223372036854778000) + yield return 0x43E0000000000000ul; // 9.2233720368547760E18d (9223372036854776000) + yield return 0x43DFFFFFFFFFFFFFul; // 9.2233720368547750E18d (9223372036854775000) + + // uint + yield return 0x41F0000000100000ul; // 4294967297.0000000d (4294967297) + yield return 0x41F0000000000000ul; // 4294967296.0000000d (4294967296) + yield return 0x41EFFFFFFFE00000ul; // 4294967295.0000000d (4294967295) + + // ulong + yield return 0x43F0000000000001ul; // 1.8446744073709556e19d (18446744073709556000) + yield return 0x43F0000000000000ul; // 1.8446744073709552E19d (18446744073709552000) + yield return 0x43EFFFFFFFFFFFFFul; // 1.8446744073709550e19d (18446744073709550000) + + yield return 0xFFEFFFFFFFFFFFFFul; // -Max Normal (double.MinValue) + yield return 0x8010000000000000ul; // -Min Normal + yield return 0x800FFFFFFFFFFFFFul; // -Max Subnormal + yield return 0x8000000000000001ul; // -Min Subnormal (-double.Epsilon) + yield return 0x7FEFFFFFFFFFFFFFul; // +Max Normal (double.MaxValue) + yield return 0x0010000000000000ul; // +Min Normal + yield return 0x000FFFFFFFFFFFFFul; // +Max Subnormal + yield return 0x0000000000000001ul; // +Min Subnormal (double.Epsilon) + + if (!NoZeros) + { + yield return 0x8000000000000000ul; // -Zero + yield return 0x0000000000000000ul; // +Zero + } + + if (!NoInfs) + { + yield return 0xFFF0000000000000ul; // -Infinity + yield return 0x7FF0000000000000ul; // +Infinity + } + + if (!NoNaNs) + { + yield return 0xFFF8000000000000ul; // -QNaN (all zeros payload) (double.NaN) + yield return 0xFFF7FFFFFFFFFFFFul; // -SNaN (all ones payload) + yield return 0x7FF8000000000000ul; // +QNaN (all zeros payload) (-double.NaN) (DefaultNaN) + yield return 0x7FF7FFFFFFFFFFFFul; // +SNaN (all ones payload) + } + + for (int cnt = 1; cnt <= RndCnt; cnt++) + { + ulong rnd1 = (ulong)BitConverter.DoubleToInt64Bits( + (double)((int)TestContext.CurrentContext.Random.NextUInt())); + ulong rnd2 = (ulong)BitConverter.DoubleToInt64Bits( + (double)((long)TestContext.CurrentContext.Random.NextULong())); + ulong rnd3 = (ulong)BitConverter.DoubleToInt64Bits( + (double)((uint)TestContext.CurrentContext.Random.NextUInt())); + ulong rnd4 = (ulong)BitConverter.DoubleToInt64Bits( + (double)((ulong)TestContext.CurrentContext.Random.NextULong())); + + ulong rnd5 = GenNormalD(); + ulong rnd6 = GenSubnormalD(); + + yield return rnd1; + yield return rnd2; + yield return rnd3; + yield return rnd4; + + yield return rnd5; + yield return rnd6; + } + } +#endregion + +#region "ValueSource (Opcodes)" + private static uint[] _F_Cvt_AMPZ_SU_Gp_SW_() + { + return new uint[] + { + 0x1E240000u, // FCVTAS W0, S0 + 0x1E250000u, // FCVTAU W0, S0 + 0x1E300000u, // FCVTMS W0, S0 + 0x1E310000u, // FCVTMU W0, S0 + 0x1E280000u, // FCVTPS W0, S0 + 0x1E290000u, // FCVTPU W0, S0 + 0x1E380000u, // FCVTZS W0, S0 + 0x1E390000u // FCVTZU W0, S0 + }; + } + + private static uint[] _F_Cvt_AMPZ_SU_Gp_SX_() + { + return new uint[] + { + 0x9E240000u, // FCVTAS X0, S0 + 0x9E250000u, // FCVTAU X0, S0 + 0x9E300000u, // FCVTMS X0, S0 + 0x9E310000u, // FCVTMU X0, S0 + 0x9E280000u, // FCVTPS X0, S0 + 0x9E290000u, // FCVTPU X0, S0 + 0x9E380000u, // FCVTZS X0, S0 + 0x9E390000u // FCVTZU X0, S0 + }; + } + + private static uint[] _F_Cvt_AMPZ_SU_Gp_DW_() + { + return new uint[] + { + 0x1E640000u, // FCVTAS W0, D0 + 0x1E650000u, // FCVTAU W0, D0 + 0x1E700000u, // FCVTMS W0, D0 + 0x1E710000u, // FCVTMU W0, D0 + 0x1E680000u, // FCVTPS W0, D0 + 0x1E690000u, // FCVTPU W0, D0 + 0x1E780000u, // FCVTZS W0, D0 + 0x1E790000u // FCVTZU W0, D0 + }; + } + + private static uint[] _F_Cvt_AMPZ_SU_Gp_DX_() + { + return new uint[] + { + 0x9E640000u, // FCVTAS X0, D0 + 0x9E650000u, // FCVTAU X0, D0 + 0x9E700000u, // FCVTMS X0, D0 + 0x9E710000u, // FCVTMU X0, D0 + 0x9E680000u, // FCVTPS X0, D0 + 0x9E690000u, // FCVTPU X0, D0 + 0x9E780000u, // FCVTZS X0, D0 + 0x9E790000u // FCVTZU X0, D0 + }; + } + + private static uint[] _F_Cvt_Z_SU_Gp_Fixed_SW_() + { + return new uint[] + { + 0x1E188000u, // FCVTZS W0, S0, #32 + 0x1E198000u // FCVTZU W0, S0, #32 + }; + } + + private static uint[] _F_Cvt_Z_SU_Gp_Fixed_SX_() + { + return new uint[] + { + 0x9E180000u, // FCVTZS X0, S0, #64 + 0x9E190000u // FCVTZU X0, S0, #64 + }; + } + + private static uint[] _F_Cvt_Z_SU_Gp_Fixed_DW_() + { + return new uint[] + { + 0x1E588000u, // FCVTZS W0, D0, #32 + 0x1E598000u // FCVTZU W0, D0, #32 + }; + } + + private static uint[] _F_Cvt_Z_SU_Gp_Fixed_DX_() + { + return new uint[] + { + 0x9E580000u, // FCVTZS X0, D0, #64 + 0x9E590000u // FCVTZU X0, D0, #64 + }; + } + + private static uint[] _SU_Cvt_F_Gp_WS_() + { + return new uint[] + { + 0x1E220000u, // SCVTF S0, W0 + 0x1E230000u // UCVTF S0, W0 + }; + } + + private static uint[] _SU_Cvt_F_Gp_WD_() + { + return new uint[] + { + 0x1E620000u, // SCVTF D0, W0 + 0x1E630000u // UCVTF D0, W0 + }; + } + + private static uint[] _SU_Cvt_F_Gp_XS_() + { + return new uint[] + { + 0x9E220000u, // SCVTF S0, X0 + 0x9E230000u // UCVTF S0, X0 + }; + } + + private static uint[] _SU_Cvt_F_Gp_XD_() + { + return new uint[] + { + 0x9E620000u, // SCVTF D0, X0 + 0x9E630000u // UCVTF D0, X0 + }; + } + + private static uint[] _SU_Cvt_F_Gp_Fixed_WS_() + { + return new uint[] + { + 0x1E028000u, // SCVTF S0, W0, #32 + 0x1E038000u // UCVTF S0, W0, #32 + }; + } + + private static uint[] _SU_Cvt_F_Gp_Fixed_WD_() + { + return new uint[] + { + 0x1E428000u, // SCVTF D0, W0, #32 + 0x1E438000u // UCVTF D0, W0, #32 + }; + } + + private static uint[] _SU_Cvt_F_Gp_Fixed_XS_() + { + return new uint[] + { + 0x9E020000u, // SCVTF S0, X0, #64 + 0x9E030000u // UCVTF S0, X0, #64 + }; + } + + private static uint[] _SU_Cvt_F_Gp_Fixed_XD_() + { + return new uint[] + { + 0x9E420000u, // SCVTF D0, X0, #64 + 0x9E430000u // UCVTF D0, X0, #64 + }; + } +#endregion + + private const int RndCnt = 2; + private const int RndCntFBits = 2; + + private static readonly bool NoZeros = false; + private static readonly bool NoInfs = false; + private static readonly bool NoNaNs = false; + + [Test, Pairwise] [Explicit] + public void F_Cvt_AMPZ_SU_Gp_SW([ValueSource("_F_Cvt_AMPZ_SU_Gp_SW_")] uint opcodes, + [Values(0u, 31u)] uint rd, + [Values(1u)] uint rn, + [ValueSource("_1S_F_WX_")] ulong a) + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + + ulong x0 = (ulong)TestContext.CurrentContext.Random.NextUInt() << 32; + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, x0: x0, x31: w31, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] [Explicit] + public void F_Cvt_AMPZ_SU_Gp_SX([ValueSource("_F_Cvt_AMPZ_SU_Gp_SX_")] uint opcodes, + [Values(0u, 31u)] uint rd, + [Values(1u)] uint rn, + [ValueSource("_1S_F_WX_")] ulong a) + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, x31: x31, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] [Explicit] + public void F_Cvt_AMPZ_SU_Gp_DW([ValueSource("_F_Cvt_AMPZ_SU_Gp_DW_")] uint opcodes, + [Values(0u, 31u)] uint rd, + [Values(1u)] uint rn, + [ValueSource("_1D_F_WX_")] ulong a) + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + + ulong x0 = (ulong)TestContext.CurrentContext.Random.NextUInt() << 32; + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, x0: x0, x31: w31, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] [Explicit] + public void F_Cvt_AMPZ_SU_Gp_DX([ValueSource("_F_Cvt_AMPZ_SU_Gp_DX_")] uint opcodes, + [Values(0u, 31u)] uint rd, + [Values(1u)] uint rn, + [ValueSource("_1D_F_WX_")] ulong a) + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, x31: x31, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] [Explicit] + public void F_Cvt_Z_SU_Gp_Fixed_SW([ValueSource("_F_Cvt_Z_SU_Gp_Fixed_SW_")] uint opcodes, + [Values(0u, 31u)] uint rd, + [Values(1u)] uint rn, + [ValueSource("_1S_F_WX_")] ulong a, + [Values(1u, 32u)] [Random(2u, 31u, RndCntFBits)] uint fBits) + { + uint scale = (64u - fBits) & 0x3Fu; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (scale << 10); + + ulong x0 = (ulong)TestContext.CurrentContext.Random.NextUInt() << 32; + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, x0: x0, x31: w31, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] [Explicit] + public void F_Cvt_Z_SU_Gp_Fixed_SX([ValueSource("_F_Cvt_Z_SU_Gp_Fixed_SX_")] uint opcodes, + [Values(0u, 31u)] uint rd, + [Values(1u)] uint rn, + [ValueSource("_1S_F_WX_")] ulong a, + [Values(1u, 64u)] [Random(2u, 63u, RndCntFBits)] uint fBits) + { + uint scale = (64u - fBits) & 0x3Fu; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (scale << 10); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, x31: x31, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] [Explicit] + public void F_Cvt_Z_SU_Gp_Fixed_DW([ValueSource("_F_Cvt_Z_SU_Gp_Fixed_DW_")] uint opcodes, + [Values(0u, 31u)] uint rd, + [Values(1u)] uint rn, + [ValueSource("_1D_F_WX_")] ulong a, + [Values(1u, 32u)] [Random(2u, 31u, RndCntFBits)] uint fBits) + { + uint scale = (64u - fBits) & 0x3Fu; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (scale << 10); + + ulong x0 = (ulong)TestContext.CurrentContext.Random.NextUInt() << 32; + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, x0: x0, x31: w31, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] [Explicit] + public void F_Cvt_Z_SU_Gp_Fixed_DX([ValueSource("_F_Cvt_Z_SU_Gp_Fixed_DX_")] uint opcodes, + [Values(0u, 31u)] uint rd, + [Values(1u)] uint rn, + [ValueSource("_1D_F_WX_")] ulong a, + [Values(1u, 64u)] [Random(2u, 63u, RndCntFBits)] uint fBits) + { + uint scale = (64u - fBits) & 0x3Fu; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (scale << 10); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, x31: x31, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] [Explicit] + public void SU_Cvt_F_Gp_WS([ValueSource("_SU_Cvt_F_Gp_WS_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 31u)] uint rn, + [ValueSource("_W_")] [Random(RndCnt)] uint wn) + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + + SingleOpcode(opcodes, x1: wn, x31: w31, v0: v0); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] [Explicit] + public void SU_Cvt_F_Gp_WD([ValueSource("_SU_Cvt_F_Gp_WD_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 31u)] uint rn, + [ValueSource("_W_")] [Random(RndCnt)] uint wn) + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE1(z); + + SingleOpcode(opcodes, x1: wn, x31: w31, v0: v0); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] [Explicit] + public void SU_Cvt_F_Gp_XS([ValueSource("_SU_Cvt_F_Gp_XS_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 31u)] uint rn, + [ValueSource("_X_")] [Random(RndCnt)] ulong xn) + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + + SingleOpcode(opcodes, x1: xn, x31: x31, v0: v0); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] [Explicit] + public void SU_Cvt_F_Gp_XD([ValueSource("_SU_Cvt_F_Gp_XD_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 31u)] uint rn, + [ValueSource("_X_")] [Random(RndCnt)] ulong xn) + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE1(z); + + SingleOpcode(opcodes, x1: xn, x31: x31, v0: v0); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] [Explicit] + public void SU_Cvt_F_Gp_Fixed_WS([ValueSource("_SU_Cvt_F_Gp_Fixed_WS_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 31u)] uint rn, + [ValueSource("_W_")] [Random(RndCnt)] uint wn, + [Values(1u, 32u)] [Random(2u, 31u, RndCntFBits)] uint fBits) + { + uint scale = (64u - fBits) & 0x3Fu; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (scale << 10); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + + SingleOpcode(opcodes, x1: wn, x31: w31, v0: v0); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] [Explicit] + public void SU_Cvt_F_Gp_Fixed_WD([ValueSource("_SU_Cvt_F_Gp_Fixed_WD_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 31u)] uint rn, + [ValueSource("_W_")] [Random(RndCnt)] uint wn, + [Values(1u, 32u)] [Random(2u, 31u, RndCntFBits)] uint fBits) + { + uint scale = (64u - fBits) & 0x3Fu; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (scale << 10); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE1(z); + + SingleOpcode(opcodes, x1: wn, x31: w31, v0: v0); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] [Explicit] + public void SU_Cvt_F_Gp_Fixed_XS([ValueSource("_SU_Cvt_F_Gp_Fixed_XS_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 31u)] uint rn, + [ValueSource("_X_")] [Random(RndCnt)] ulong xn, + [Values(1u, 64u)] [Random(2u, 63u, RndCntFBits)] uint fBits) + { + uint scale = (64u - fBits) & 0x3Fu; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (scale << 10); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + + SingleOpcode(opcodes, x1: xn, x31: x31, v0: v0); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] [Explicit] + public void SU_Cvt_F_Gp_Fixed_XD([ValueSource("_SU_Cvt_F_Gp_Fixed_XD_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 31u)] uint rn, + [ValueSource("_X_")] [Random(RndCnt)] ulong xn, + [Values(1u, 64u)] [Random(2u, 63u, RndCntFBits)] uint fBits) + { + uint scale = (64u - fBits) & 0x3Fu; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (scale << 10); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE1(z); + + SingleOpcode(opcodes, x1: xn, x31: x31, v0: v0); + + CompareAgainstUnicorn(); + } +#endif + } +} diff --git a/Ryujinx.Tests/Cpu/CpuTestSimdExt.cs b/Ryujinx.Tests/Cpu/CpuTestSimdExt.cs new file mode 100644 index 0000000000..0ab40cad23 --- /dev/null +++ b/Ryujinx.Tests/Cpu/CpuTestSimdExt.cs @@ -0,0 +1,74 @@ +#define SimdExt + +using ARMeilleure.State; + +using NUnit.Framework; + +namespace Ryujinx.Tests.Cpu +{ + [Category("SimdExt")] + public sealed class CpuTestSimdExt : CpuTest + { +#if SimdExt + +#region "ValueSource" + private static ulong[] _8B_() + { + return new ulong[] { 0x0000000000000000ul, 0x7F7F7F7F7F7F7F7Ful, + 0x8080808080808080ul, 0xFFFFFFFFFFFFFFFFul }; + } +#endregion + + private const int RndCnt = 2; + private const int RndCntIndex = 2; + + [Test, Pairwise, Description("EXT .8B, .8B, .8B, #")] + public void Ext_V_8B([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B_")] [Random(RndCnt)] ulong b, + [Values(0u, 7u)] [Random(1u, 6u, RndCntIndex)] uint index) + { + uint imm4 = index & 0x7u; + + uint opcode = 0x2E000000; // EXT V0.8B, V0.8B, V0.8B, #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= (imm4 << 11); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("EXT .16B, .16B, .16B, #")] + public void Ext_V_16B([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B_")] [Random(RndCnt)] ulong b, + [Values(0u, 15u)] [Random(1u, 14u, RndCntIndex)] uint index) + { + uint imm4 = index & 0xFu; + + uint opcode = 0x6E000000; // EXT V0.16B, V0.16B, V0.16B, #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= (imm4 << 11); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } +#endif + } +} diff --git a/Ryujinx.Tests/Cpu/CpuTestSimdFcond.cs b/Ryujinx.Tests/Cpu/CpuTestSimdFcond.cs new file mode 100644 index 0000000000..825a1c78ca --- /dev/null +++ b/Ryujinx.Tests/Cpu/CpuTestSimdFcond.cs @@ -0,0 +1,237 @@ +#define SimdFcond + +using ARMeilleure.State; + +using NUnit.Framework; + +using System.Collections.Generic; + +namespace Ryujinx.Tests.Cpu +{ + [Category("SimdFcond")] + public sealed class CpuTestSimdFcond : CpuTest + { +#if SimdFcond + +#region "ValueSource (Types)" + private static IEnumerable _1S_F_() + { + yield return 0x00000000FF7FFFFFul; // -Max Normal (float.MinValue) + yield return 0x0000000080800000ul; // -Min Normal + yield return 0x00000000807FFFFFul; // -Max Subnormal + yield return 0x0000000080000001ul; // -Min Subnormal (-float.Epsilon) + yield return 0x000000007F7FFFFFul; // +Max Normal (float.MaxValue) + yield return 0x0000000000800000ul; // +Min Normal + yield return 0x00000000007FFFFFul; // +Max Subnormal + yield return 0x0000000000000001ul; // +Min Subnormal (float.Epsilon) + + if (!NoZeros) + { + yield return 0x0000000080000000ul; // -Zero + yield return 0x0000000000000000ul; // +Zero + } + + if (!NoInfs) + { + yield return 0x00000000FF800000ul; // -Infinity + yield return 0x000000007F800000ul; // +Infinity + } + + if (!NoNaNs) + { + yield return 0x00000000FFC00000ul; // -QNaN (all zeros payload) (float.NaN) + yield return 0x00000000FFBFFFFFul; // -SNaN (all ones payload) + yield return 0x000000007FC00000ul; // +QNaN (all zeros payload) (-float.NaN) (DefaultNaN) + yield return 0x000000007FBFFFFFul; // +SNaN (all ones payload) + } + + for (int cnt = 1; cnt <= RndCnt; cnt++) + { + ulong grbg = TestContext.CurrentContext.Random.NextUInt(); + ulong rnd1 = GenNormalS(); + ulong rnd2 = GenSubnormalS(); + + yield return (grbg << 32) | rnd1; + yield return (grbg << 32) | rnd2; + } + } + + private static IEnumerable _1D_F_() + { + yield return 0xFFEFFFFFFFFFFFFFul; // -Max Normal (double.MinValue) + yield return 0x8010000000000000ul; // -Min Normal + yield return 0x800FFFFFFFFFFFFFul; // -Max Subnormal + yield return 0x8000000000000001ul; // -Min Subnormal (-double.Epsilon) + yield return 0x7FEFFFFFFFFFFFFFul; // +Max Normal (double.MaxValue) + yield return 0x0010000000000000ul; // +Min Normal + yield return 0x000FFFFFFFFFFFFFul; // +Max Subnormal + yield return 0x0000000000000001ul; // +Min Subnormal (double.Epsilon) + + if (!NoZeros) + { + yield return 0x8000000000000000ul; // -Zero + yield return 0x0000000000000000ul; // +Zero + } + + if (!NoInfs) + { + yield return 0xFFF0000000000000ul; // -Infinity + yield return 0x7FF0000000000000ul; // +Infinity + } + + if (!NoNaNs) + { + yield return 0xFFF8000000000000ul; // -QNaN (all zeros payload) (double.NaN) + yield return 0xFFF7FFFFFFFFFFFFul; // -SNaN (all ones payload) + yield return 0x7FF8000000000000ul; // +QNaN (all zeros payload) (-double.NaN) (DefaultNaN) + yield return 0x7FF7FFFFFFFFFFFFul; // +SNaN (all ones payload) + } + + for (int cnt = 1; cnt <= RndCnt; cnt++) + { + ulong rnd1 = GenNormalD(); + ulong rnd2 = GenSubnormalD(); + + yield return rnd1; + yield return rnd2; + } + } +#endregion + +#region "ValueSource (Opcodes)" + private static uint[] _F_Ccmp_Ccmpe_S_S_() + { + return new uint[] + { + 0x1E220420u, // FCCMP S1, S2, #0, EQ + 0x1E220430u // FCCMPE S1, S2, #0, EQ + }; + } + + private static uint[] _F_Ccmp_Ccmpe_S_D_() + { + return new uint[] + { + 0x1E620420u, // FCCMP D1, D2, #0, EQ + 0x1E620430u // FCCMPE D1, D2, #0, EQ + }; + } + + private static uint[] _F_Csel_S_S_() + { + return new uint[] + { + 0x1E220C20u // FCSEL S0, S1, S2, EQ + }; + } + + private static uint[] _F_Csel_S_D_() + { + return new uint[] + { + 0x1E620C20u // FCSEL D0, D1, D2, EQ + }; + } +#endregion + + private const int RndCnt = 2; + private const int RndCntNzcv = 2; + + private static readonly bool NoZeros = false; + private static readonly bool NoInfs = false; + private static readonly bool NoNaNs = false; + + [Test, Pairwise] [Explicit] + public void F_Ccmp_Ccmpe_S_S([ValueSource("_F_Ccmp_Ccmpe_S_S_")] uint opcodes, + [ValueSource("_1S_F_")] ulong a, + [ValueSource("_1S_F_")] ulong b, + [Random(0u, 15u, RndCntNzcv)] uint nzcv, + [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // + { + opcodes |= ((cond & 15) << 12) | ((nzcv & 15) << 0); + + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + bool v = TestContext.CurrentContext.Random.NextBool(); + bool c = TestContext.CurrentContext.Random.NextBool(); + bool z = TestContext.CurrentContext.Random.NextBool(); + bool n = TestContext.CurrentContext.Random.NextBool(); + + SingleOpcode(opcodes, v1: v1, v2: v2, overflow: v, carry: c, zero: z, negative: n); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc); + } + + [Test, Pairwise] [Explicit] + public void F_Ccmp_Ccmpe_S_D([ValueSource("_F_Ccmp_Ccmpe_S_D_")] uint opcodes, + [ValueSource("_1D_F_")] ulong a, + [ValueSource("_1D_F_")] ulong b, + [Random(0u, 15u, RndCntNzcv)] uint nzcv, + [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // + { + opcodes |= ((cond & 15) << 12) | ((nzcv & 15) << 0); + + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + bool v = TestContext.CurrentContext.Random.NextBool(); + bool c = TestContext.CurrentContext.Random.NextBool(); + bool z = TestContext.CurrentContext.Random.NextBool(); + bool n = TestContext.CurrentContext.Random.NextBool(); + + SingleOpcode(opcodes, v1: v1, v2: v2, overflow: v, carry: c, zero: z, negative: n); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc); + } + + [Test, Pairwise] [Explicit] + public void F_Csel_S_S([ValueSource("_F_Csel_S_S_")] uint opcodes, + [ValueSource("_1S_F_")] ulong a, + [ValueSource("_1S_F_")] ulong b, + [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // + { + opcodes |= ((cond & 15) << 12); + + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] [Explicit] + public void F_Csel_S_D([ValueSource("_F_Csel_S_D_")] uint opcodes, + [ValueSource("_1D_F_")] ulong a, + [ValueSource("_1D_F_")] ulong b, + [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // + { + opcodes |= ((cond & 15) << 12); + + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE1(z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } +#endif + } +} diff --git a/Ryujinx.Tests/Cpu/CpuTestSimdFmov.cs b/Ryujinx.Tests/Cpu/CpuTestSimdFmov.cs new file mode 100644 index 0000000000..534dba57d1 --- /dev/null +++ b/Ryujinx.Tests/Cpu/CpuTestSimdFmov.cs @@ -0,0 +1,61 @@ +#define SimdFmov + +using ARMeilleure.State; + +using NUnit.Framework; + +namespace Ryujinx.Tests.Cpu +{ + [Category("SimdFmov")] + public sealed class CpuTestSimdFmov : CpuTest + { +#if SimdFmov + +#region "ValueSource" + private static uint[] _F_Mov_Si_S_() + { + return new uint[] + { + 0x1E201000u // FMOV S0, #2.0 + }; + } + + private static uint[] _F_Mov_Si_D_() + { + return new uint[] + { + 0x1E601000u // FMOV D0, #2.0 + }; + } +#endregion + + [Test, Pairwise] [Explicit] + public void F_Mov_Si_S([ValueSource("_F_Mov_Si_S_")] uint opcodes, + [Range(0u, 255u, 1u)] uint imm8) + { + opcodes |= ((imm8 & 0xFFu) << 13); + + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + + SingleOpcode(opcodes, v0: v0); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] [Explicit] + public void F_Mov_Si_D([ValueSource("_F_Mov_Si_D_")] uint opcodes, + [Range(0u, 255u, 1u)] uint imm8) + { + opcodes |= ((imm8 & 0xFFu) << 13); + + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE1(z); + + SingleOpcode(opcodes, v0: v0); + + CompareAgainstUnicorn(); + } +#endif + } +} diff --git a/Ryujinx.Tests/Cpu/CpuTestSimdImm.cs b/Ryujinx.Tests/Cpu/CpuTestSimdImm.cs new file mode 100644 index 0000000000..1ea74a112f --- /dev/null +++ b/Ryujinx.Tests/Cpu/CpuTestSimdImm.cs @@ -0,0 +1,398 @@ +#define SimdImm + +using ARMeilleure.State; + +using NUnit.Framework; + +using System.Collections.Generic; + +namespace Ryujinx.Tests.Cpu +{ + [Category("SimdImm")] + public sealed class CpuTestSimdImm : CpuTest + { +#if SimdImm + +#region "Helper methods" + // abcdefgh -> aaaaaaaabbbbbbbbccccccccddddddddeeeeeeeeffffffffgggggggghhhhhhhh + private static ulong ExpandImm8(byte imm8) + { + ulong imm64 = 0ul; + + for (int i = 0, j = 0; i < 8; i++, j += 8) + { + if (((imm8 >> i) & 0b1) != 0) + { + imm64 |= 0b11111111ul << j; + } + } + + return imm64; + } + + // aaaaaaaabbbbbbbbccccccccddddddddeeeeeeeeffffffffgggggggghhhhhhhh -> abcdefgh + private static byte ShrinkImm64(ulong imm64) + { + byte imm8 = 0; + + for (int i = 0, j = 0; i < 8; i++, j += 8) + { + if (((imm64 >> j) & 0b11111111ul) != 0ul) // Note: no format check. + { + imm8 |= (byte)(0b1 << i); + } + } + + return imm8; + } +#endregion + +#region "ValueSource (Types)" + private static ulong[] _2S_() + { + return new ulong[] { 0x0000000000000000ul, 0x7FFFFFFF7FFFFFFFul, + 0x8000000080000000ul, 0xFFFFFFFFFFFFFFFFul }; + } + + private static ulong[] _4H_() + { + return new ulong[] { 0x0000000000000000ul, 0x7FFF7FFF7FFF7FFFul, + 0x8000800080008000ul, 0xFFFFFFFFFFFFFFFFul }; + } + + private static IEnumerable _8BIT_IMM_() + { + yield return 0x00; + yield return 0x7F; + yield return 0x80; + yield return 0xFF; + + for (int cnt = 1; cnt <= RndCntImm8; cnt++) + { + byte imm8 = TestContext.CurrentContext.Random.NextByte(); + + yield return imm8; + } + } + + private static IEnumerable _64BIT_IMM_() + { + yield return ExpandImm8(0x00); + yield return ExpandImm8(0x7F); + yield return ExpandImm8(0x80); + yield return ExpandImm8(0xFF); + + for (int cnt = 1; cnt <= RndCntImm64; cnt++) + { + byte imm8 = TestContext.CurrentContext.Random.NextByte(); + + yield return ExpandImm8(imm8); + } + } +#endregion + +#region "ValueSource (Opcodes)" + private static uint[] _Bic_Orr_Vi_16bit_() + { + return new uint[] + { + 0x2F009400u, // BIC V0.4H, #0 + 0x0F009400u // ORR V0.4H, #0 + }; + } + + private static uint[] _Bic_Orr_Vi_32bit_() + { + return new uint[] + { + 0x2F001400u, // BIC V0.2S, #0 + 0x0F001400u // ORR V0.2S, #0 + }; + } + + private static uint[] _F_Mov_Vi_2S_() + { + return new uint[] + { + 0x0F00F400u // FMOV V0.2S, #2.0 + }; + } + + private static uint[] _F_Mov_Vi_4S_() + { + return new uint[] + { + 0x4F00F400u // FMOV V0.4S, #2.0 + }; + } + + private static uint[] _F_Mov_Vi_2D_() + { + return new uint[] + { + 0x6F00F400u // FMOV V0.2D, #2.0 + }; + } + + private static uint[] _Movi_V_8bit_() + { + return new uint[] + { + 0x0F00E400u // MOVI V0.8B, #0 + }; + } + + private static uint[] _Movi_Mvni_V_16bit_shifted_imm_() + { + return new uint[] + { + 0x0F008400u, // MOVI V0.4H, #0 + 0x2F008400u // MVNI V0.4H, #0 + }; + } + + private static uint[] _Movi_Mvni_V_32bit_shifted_imm_() + { + return new uint[] + { + 0x0F000400u, // MOVI V0.2S, #0 + 0x2F000400u // MVNI V0.2S, #0 + }; + } + + private static uint[] _Movi_Mvni_V_32bit_shifting_ones_() + { + return new uint[] + { + 0x0F00C400u, // MOVI V0.2S, #0, MSL #8 + 0x2F00C400u // MVNI V0.2S, #0, MSL #8 + }; + } + + private static uint[] _Movi_V_64bit_scalar_() + { + return new uint[] + { + 0x2F00E400u // MOVI D0, #0 + }; + } + + private static uint[] _Movi_V_64bit_vector_() + { + return new uint[] + { + 0x6F00E400u // MOVI V0.2D, #0 + }; + } +#endregion + + private const int RndCnt = 2; + private const int RndCntImm8 = 2; + private const int RndCntImm64 = 2; + + [Test, Pairwise] + public void Bic_Orr_Vi_16bit([ValueSource("_Bic_Orr_Vi_16bit_")] uint opcodes, + [ValueSource("_4H_")] [Random(RndCnt)] ulong z, + [ValueSource("_8BIT_IMM_")] byte imm8, + [Values(0b0u, 0b1u)] uint amount, // <0, 8> + [Values(0b0u, 0b1u)] uint q) // <4H, 8H> + { + uint abc = (imm8 & 0xE0u) >> 5; + uint defgh = (imm8 & 0x1Fu); + + opcodes |= (abc << 16) | (defgh << 5); + opcodes |= ((amount & 1) << 13); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + + SingleOpcode(opcodes, v0: v0); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Bic_Orr_Vi_32bit([ValueSource("_Bic_Orr_Vi_32bit_")] uint opcodes, + [ValueSource("_2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8BIT_IMM_")] byte imm8, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint amount, // <0, 8, 16, 24> + [Values(0b0u, 0b1u)] uint q) // <2S, 4S> + { + uint abc = (imm8 & 0xE0u) >> 5; + uint defgh = (imm8 & 0x1Fu); + + opcodes |= (abc << 16) | (defgh << 5); + opcodes |= ((amount & 3) << 13); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + + SingleOpcode(opcodes, v0: v0); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] [Explicit] + public void F_Mov_Vi_2S([ValueSource("_F_Mov_Vi_2S_")] uint opcodes, + [Range(0u, 255u, 1u)] uint abcdefgh) + { + uint abc = (abcdefgh & 0xE0u) >> 5; + uint defgh = (abcdefgh & 0x1Fu); + + opcodes |= (abc << 16) | (defgh << 5); + + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE1(z); + + SingleOpcode(opcodes, v0: v0); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] [Explicit] + public void F_Mov_Vi_4S([ValueSource("_F_Mov_Vi_4S_")] uint opcodes, + [Range(0u, 255u, 1u)] uint abcdefgh) + { + uint abc = (abcdefgh & 0xE0u) >> 5; + uint defgh = (abcdefgh & 0x1Fu); + + opcodes |= (abc << 16) | (defgh << 5); + + SingleOpcode(opcodes); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] [Explicit] + public void F_Mov_Vi_2D([ValueSource("_F_Mov_Vi_2D_")] uint opcodes, + [Range(0u, 255u, 1u)] uint abcdefgh) + { + uint abc = (abcdefgh & 0xE0u) >> 5; + uint defgh = (abcdefgh & 0x1Fu); + + opcodes |= (abc << 16) | (defgh << 5); + + SingleOpcode(opcodes); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Movi_V_8bit([ValueSource("_Movi_V_8bit_")] uint opcodes, + [ValueSource("_8BIT_IMM_")] byte imm8, + [Values(0b0u, 0b1u)] uint q) // <8B, 16B> + { + uint abc = (imm8 & 0xE0u) >> 5; + uint defgh = (imm8 & 0x1Fu); + + opcodes |= (abc << 16) | (defgh << 5); + opcodes |= ((q & 1) << 30); + + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE1(q == 0u ? z : 0ul); + + SingleOpcode(opcodes, v0: v0); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Movi_Mvni_V_16bit_shifted_imm([ValueSource("_Movi_Mvni_V_16bit_shifted_imm_")] uint opcodes, + [ValueSource("_8BIT_IMM_")] byte imm8, + [Values(0b0u, 0b1u)] uint amount, // <0, 8> + [Values(0b0u, 0b1u)] uint q) // <4H, 8H> + { + uint abc = (imm8 & 0xE0u) >> 5; + uint defgh = (imm8 & 0x1Fu); + + opcodes |= (abc << 16) | (defgh << 5); + opcodes |= ((amount & 1) << 13); + opcodes |= ((q & 1) << 30); + + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE1(q == 0u ? z : 0ul); + + SingleOpcode(opcodes, v0: v0); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Movi_Mvni_V_32bit_shifted_imm([ValueSource("_Movi_Mvni_V_32bit_shifted_imm_")] uint opcodes, + [ValueSource("_8BIT_IMM_")] byte imm8, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint amount, // <0, 8, 16, 24> + [Values(0b0u, 0b1u)] uint q) // <2S, 4S> + { + uint abc = (imm8 & 0xE0u) >> 5; + uint defgh = (imm8 & 0x1Fu); + + opcodes |= (abc << 16) | (defgh << 5); + opcodes |= ((amount & 3) << 13); + opcodes |= ((q & 1) << 30); + + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE1(q == 0u ? z : 0ul); + + SingleOpcode(opcodes, v0: v0); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Movi_Mvni_V_32bit_shifting_ones([ValueSource("_Movi_Mvni_V_32bit_shifting_ones_")] uint opcodes, + [ValueSource("_8BIT_IMM_")] byte imm8, + [Values(0b0u, 0b1u)] uint amount, // <8, 16> + [Values(0b0u, 0b1u)] uint q) // <2S, 4S> + { + uint abc = (imm8 & 0xE0u) >> 5; + uint defgh = (imm8 & 0x1Fu); + + opcodes |= (abc << 16) | (defgh << 5); + opcodes |= ((amount & 1) << 12); + opcodes |= ((q & 1) << 30); + + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE1(q == 0u ? z : 0ul); + + SingleOpcode(opcodes, v0: v0); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Movi_V_64bit_scalar([ValueSource("_Movi_V_64bit_scalar_")] uint opcodes, + [ValueSource("_64BIT_IMM_")] ulong imm) + { + byte imm8 = ShrinkImm64(imm); + + uint abc = (imm8 & 0xE0u) >> 5; + uint defgh = (imm8 & 0x1Fu); + + opcodes |= (abc << 16) | (defgh << 5); + + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE1(z); + + SingleOpcode(opcodes, v0: v0); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Movi_V_64bit_vector([ValueSource("_Movi_V_64bit_vector_")] uint opcodes, + [ValueSource("_64BIT_IMM_")] ulong imm) + { + byte imm8 = ShrinkImm64(imm); + + uint abc = (imm8 & 0xE0u) >> 5; + uint defgh = (imm8 & 0x1Fu); + + opcodes |= (abc << 16) | (defgh << 5); + + SingleOpcode(opcodes); + + CompareAgainstUnicorn(); + } +#endif + } +} diff --git a/Ryujinx.Tests/Cpu/CpuTestSimdIns.cs b/Ryujinx.Tests/Cpu/CpuTestSimdIns.cs new file mode 100644 index 0000000000..031ed0f2c1 --- /dev/null +++ b/Ryujinx.Tests/Cpu/CpuTestSimdIns.cs @@ -0,0 +1,693 @@ +#define SimdIns + +using ARMeilleure.State; + +using NUnit.Framework; + +namespace Ryujinx.Tests.Cpu +{ + [Category("SimdIns")] + public sealed class CpuTestSimdIns : CpuTest + { +#if SimdIns + +#region "ValueSource" + private static ulong[] _1D_() + { + return new ulong[] { 0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul }; + } + + private static ulong[] _2S_() + { + return new ulong[] { 0x0000000000000000ul, 0x7FFFFFFF7FFFFFFFul, + 0x8000000080000000ul, 0xFFFFFFFFFFFFFFFFul }; + } + + private static ulong[] _4H_() + { + return new ulong[] { 0x0000000000000000ul, 0x7FFF7FFF7FFF7FFFul, + 0x8000800080008000ul, 0xFFFFFFFFFFFFFFFFul }; + } + + private static ulong[] _8B_() + { + return new ulong[] { 0x0000000000000000ul, 0x7F7F7F7F7F7F7F7Ful, + 0x8080808080808080ul, 0xFFFFFFFFFFFFFFFFul }; + } + + private static ulong[] _8B4H_() + { + return new ulong[] { 0x0000000000000000ul, 0x7F7F7F7F7F7F7F7Ful, + 0x8080808080808080ul, 0x7FFF7FFF7FFF7FFFul, + 0x8000800080008000ul, 0xFFFFFFFFFFFFFFFFul }; + } + + private static ulong[] _8B4H2S_() + { + return new ulong[] { 0x0000000000000000ul, 0x7F7F7F7F7F7F7F7Ful, + 0x8080808080808080ul, 0x7FFF7FFF7FFF7FFFul, + 0x8000800080008000ul, 0x7FFFFFFF7FFFFFFFul, + 0x8000000080000000ul, 0xFFFFFFFFFFFFFFFFul }; + } + + private static uint[] _W_() + { + return new uint[] { 0x00000000u, 0x0000007Fu, + 0x00000080u, 0x000000FFu, + 0x00007FFFu, 0x00008000u, + 0x0000FFFFu, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu }; + } + + private static ulong[] _X_() + { + return new ulong[] { 0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul }; + } +#endregion + + private const int RndCnt = 2; + private const int RndCntIndex = 2; + + [Test, Pairwise, Description("DUP ., W")] + public void Dup_Gp_W([Values(0u)] uint rd, + [Values(1u, 31u)] uint rn, + [ValueSource("_W_")] [Random(RndCnt)] uint wn, + [Values(0, 1, 2)] int size, // Q0: <8B, 4H, 2S> + [Values(0b0u, 0b1u)] uint q) // Q1: <16B, 8H, 4S> + { + uint imm5 = (1u << size) & 0x1Fu; + + uint opcode = 0x0E000C00; // RESERVED + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= (imm5 << 16); + opcode |= ((q & 1) << 30); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + + SingleOpcode(opcode, x1: wn, x31: w31, v0: v0); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("DUP ., X")] + public void Dup_Gp_X([Values(0u)] uint rd, + [Values(1u, 31u)] uint rn, + [ValueSource("_X_")] [Random(RndCnt)] ulong xn) + { + uint opcode = 0x4E080C00; // DUP V0.2D, X0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + + SingleOpcode(opcode, x1: xn, x31: x31, v0: v0); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("DUP B0, V1.B[]")] + public void Dup_S_B([ValueSource("_8B_")] [Random(RndCnt)] ulong a, + [Values(0u, 15u)] [Random(1u, 14u, RndCntIndex)] uint index) + { + const int size = 0; + + uint imm5 = (index << (size + 1) | 1u << size) & 0x1Fu; + + uint opcode = 0x5E000420; // RESERVED + opcode |= (imm5 << 16); + + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("DUP H0, V1.H[]")] + public void Dup_S_H([ValueSource("_4H_")] [Random(RndCnt)] ulong a, + [Values(0u, 7u)] [Random(1u, 6u, RndCntIndex)] uint index) + { + const int size = 1; + + uint imm5 = (index << (size + 1) | 1u << size) & 0x1Fu; + + uint opcode = 0x5E000420; // RESERVED + opcode |= (imm5 << 16); + + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("DUP S0, V1.S[]")] + public void Dup_S_S([ValueSource("_2S_")] [Random(RndCnt)] ulong a, + [Values(0u, 1u, 2u, 3u)] uint index) + { + const int size = 2; + + uint imm5 = (index << (size + 1) | 1u << size) & 0x1Fu; + + uint opcode = 0x5E000420; // RESERVED + opcode |= (imm5 << 16); + + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("DUP D0, V1.D[]")] + public void Dup_S_D([ValueSource("_1D_")] [Random(RndCnt)] ulong a, + [Values(0u, 1u)] uint index) + { + const int size = 3; + + uint imm5 = (index << (size + 1) | 1u << size) & 0x1Fu; + + uint opcode = 0x5E000420; // RESERVED + opcode |= (imm5 << 16); + + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("DUP ., .B[]")] + public void Dup_V_8B_16B([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_8B_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B_")] [Random(RndCnt)] ulong a, + [Values(0u, 15u)] [Random(1u, 14u, RndCntIndex)] uint index, + [Values(0b0u, 0b1u)] uint q) // <8B, 16B> + { + const int size = 0; + + uint imm5 = (index << (size + 1) | 1u << size) & 0x1Fu; + + uint opcode = 0x0E000400; // RESERVED + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= (imm5 << 16); + opcode |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("DUP ., .H[]")] + public void Dup_V_4H_8H([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_4H_")] [Random(RndCnt)] ulong z, + [ValueSource("_4H_")] [Random(RndCnt)] ulong a, + [Values(0u, 7u)] [Random(1u, 6u, RndCntIndex)] uint index, + [Values(0b0u, 0b1u)] uint q) // <4H, 8H> + { + const int size = 1; + + uint imm5 = (index << (size + 1) | 1u << size) & 0x1Fu; + + uint opcode = 0x0E000400; // RESERVED + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= (imm5 << 16); + opcode |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("DUP ., .S[]")] + public void Dup_V_2S_4S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_2S_")] [Random(RndCnt)] ulong a, + [Values(0u, 1u, 2u, 3u)] uint index, + [Values(0b0u, 0b1u)] uint q) // <2S, 4S> + { + const int size = 2; + + uint imm5 = (index << (size + 1) | 1u << size) & 0x1Fu; + + uint opcode = 0x0E000400; // RESERVED + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= (imm5 << 16); + opcode |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("DUP ., .D[]")] + public void Dup_V_2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_1D_")] [Random(RndCnt)] ulong a, + [Values(0u, 1u)] uint index, + [Values(0b1u)] uint q) // <2D> + { + const int size = 3; + + uint imm5 = (index << (size + 1) | 1u << size) & 0x1Fu; + + uint opcode = 0x0E000400; // RESERVED + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= (imm5 << 16); + opcode |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("INS .B[], W")] + public void Ins_Gp_WB([Values(0u)] uint rd, + [Values(1u, 31u)] uint rn, + [ValueSource("_8B_")] [Random(RndCnt)] ulong z, + [ValueSource("_W_")] [Random(RndCnt)] uint wn, + [Values(0u, 15u)] [Random(1u, 14u, RndCntIndex)] uint index) + { + const int size = 0; + + uint imm5 = (index << (size + 1) | 1u << size) & 0x1Fu; + + uint opcode = 0x4E001C00; // RESERVED + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= (imm5 << 16); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + V128 v0 = MakeVectorE0E1(z, z); + + SingleOpcode(opcode, x1: wn, x31: w31, v0: v0); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("INS .H[], W")] + public void Ins_Gp_WH([Values(0u)] uint rd, + [Values(1u, 31u)] uint rn, + [ValueSource("_4H_")] [Random(RndCnt)] ulong z, + [ValueSource("_W_")] [Random(RndCnt)] uint wn, + [Values(0u, 7u)] [Random(1u, 6u, RndCntIndex)] uint index) + { + const int size = 1; + + uint imm5 = (index << (size + 1) | 1u << size) & 0x1Fu; + + uint opcode = 0x4E001C00; // RESERVED + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= (imm5 << 16); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + V128 v0 = MakeVectorE0E1(z, z); + + SingleOpcode(opcode, x1: wn, x31: w31, v0: v0); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("INS .S[], W")] + public void Ins_Gp_WS([Values(0u)] uint rd, + [Values(1u, 31u)] uint rn, + [ValueSource("_2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_W_")] [Random(RndCnt)] uint wn, + [Values(0u, 1u, 2u, 3u)] uint index) + { + const int size = 2; + + uint imm5 = (index << (size + 1) | 1u << size) & 0x1Fu; + + uint opcode = 0x4E001C00; // RESERVED + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= (imm5 << 16); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + V128 v0 = MakeVectorE0E1(z, z); + + SingleOpcode(opcode, x1: wn, x31: w31, v0: v0); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("INS .D[], X")] + public void Ins_Gp_XD([Values(0u)] uint rd, + [Values(1u, 31u)] uint rn, + [ValueSource("_1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_X_")] [Random(RndCnt)] ulong xn, + [Values(0u, 1u)] uint index) + { + const int size = 3; + + uint imm5 = (index << (size + 1) | 1u << size) & 0x1Fu; + + uint opcode = 0x4E001C00; // RESERVED + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= (imm5 << 16); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + + SingleOpcode(opcode, x1: xn, x31: x31, v0: v0); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("INS .B[], .B[]")] + public void Ins_V_BB([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_8B_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B_")] [Random(RndCnt)] ulong a, + [Values(0u, 15u)] [Random(1u, 14u, RndCntIndex)] uint dstIndex, + [Values(0u, 15u)] [Random(1u, 14u, RndCntIndex)] uint srcIndex) + { + const int size = 0; + + uint imm5 = (dstIndex << (size + 1) | 1u << size) & 0x1Fu; + uint imm4 = (srcIndex << size) & 0xFu; + + uint opcode = 0x6E000400; // RESERVED + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= (imm5 << 16); + opcode |= (imm4 << 11); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("INS .H[], .H[]")] + public void Ins_V_HH([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_4H_")] [Random(RndCnt)] ulong z, + [ValueSource("_4H_")] [Random(RndCnt)] ulong a, + [Values(0u, 7u)] [Random(1u, 6u, RndCntIndex)] uint dstIndex, + [Values(0u, 7u)] [Random(1u, 6u, RndCntIndex)] uint srcIndex) + { + const int size = 1; + + uint imm5 = (dstIndex << (size + 1) | 1u << size) & 0x1Fu; + uint imm4 = (srcIndex << size) & 0xFu; + + uint opcode = 0x6E000400; // RESERVED + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= (imm5 << 16); + opcode |= (imm4 << 11); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("INS .S[], .S[]")] + public void Ins_V_SS([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_2S_")] [Random(RndCnt)] ulong a, + [Values(0u, 1u, 2u, 3u)] uint dstIndex, + [Values(0u, 1u, 2u, 3u)] uint srcIndex) + { + const int size = 2; + + uint imm5 = (dstIndex << (size + 1) | 1u << size) & 0x1Fu; + uint imm4 = (srcIndex << size) & 0xFu; + + uint opcode = 0x6E000400; // RESERVED + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= (imm5 << 16); + opcode |= (imm4 << 11); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("INS .D[], .D[]")] + public void Ins_V_DD([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_1D_")] [Random(RndCnt)] ulong a, + [Values(0u, 1u)] uint dstIndex, + [Values(0u, 1u)] uint srcIndex) + { + const int size = 3; + + uint imm5 = (dstIndex << (size + 1) | 1u << size) & 0x1Fu; + uint imm4 = (srcIndex << size) & 0xFu; + + uint opcode = 0x6E000400; // RESERVED + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= (imm5 << 16); + opcode |= (imm4 << 11); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SMOV , .B[]")] + public void Smov_S_BW([Values(0u, 31u)] uint rd, + [Values(1u)] uint rn, + [ValueSource("_8B_")] [Random(RndCnt)] ulong a, + [Values(0u, 15u)] [Random(1u, 14u, RndCntIndex)] uint index) + { + const int size = 0; + + uint imm5 = (index << (size + 1) | 1u << size) & 0x1Fu; + + uint opcode = 0x0E002C00; // RESERVED + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= (imm5 << 16); + + ulong x0 = (ulong)TestContext.CurrentContext.Random.NextUInt() << 32; + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, x0: x0, x31: w31, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SMOV , .H[]")] + public void Smov_S_HW([Values(0u, 31u)] uint rd, + [Values(1u)] uint rn, + [ValueSource("_4H_")] [Random(RndCnt)] ulong a, + [Values(0u, 7u)] [Random(1u, 6u, RndCntIndex)] uint index) + { + const int size = 1; + + uint imm5 = (index << (size + 1) | 1u << size) & 0x1Fu; + + uint opcode = 0x0E002C00; // RESERVED + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= (imm5 << 16); + + ulong x0 = (ulong)TestContext.CurrentContext.Random.NextUInt() << 32; + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, x0: x0, x31: w31, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SMOV , .B[]")] + public void Smov_S_BX([Values(0u, 31u)] uint rd, + [Values(1u)] uint rn, + [ValueSource("_8B_")] [Random(RndCnt)] ulong a, + [Values(0u, 15u)] [Random(1u, 14u, RndCntIndex)] uint index) + { + const int size = 0; + + uint imm5 = (index << (size + 1) | 1u << size) & 0x1Fu; + + uint opcode = 0x4E002C00; // RESERVED + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= (imm5 << 16); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, x31: x31, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SMOV , .H[]")] + public void Smov_S_HX([Values(0u, 31u)] uint rd, + [Values(1u)] uint rn, + [ValueSource("_4H_")] [Random(RndCnt)] ulong a, + [Values(0u, 7u)] [Random(1u, 6u, RndCntIndex)] uint index) + { + const int size = 1; + + uint imm5 = (index << (size + 1) | 1u << size) & 0x1Fu; + + uint opcode = 0x4E002C00; // RESERVED + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= (imm5 << 16); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, x31: x31, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SMOV , .S[]")] + public void Smov_S_SX([Values(0u, 31u)] uint rd, + [Values(1u)] uint rn, + [ValueSource("_2S_")] [Random(RndCnt)] ulong a, + [Values(0u, 1u, 2u, 3u)] uint index) + { + const int size = 2; + + uint imm5 = (index << (size + 1) | 1u << size) & 0x1Fu; + + uint opcode = 0x4E002C00; // RESERVED + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= (imm5 << 16); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, x31: x31, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UMOV , .B[]")] + public void Umov_S_BW([Values(0u, 31u)] uint rd, + [Values(1u)] uint rn, + [ValueSource("_8B_")] [Random(RndCnt)] ulong a, + [Values(0u, 15u)] [Random(1u, 14u, RndCntIndex)] uint index) + { + const int size = 0; + + uint imm5 = (index << (size + 1) | 1u << size) & 0x1Fu; + + uint opcode = 0x0E003C00; // RESERVED + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= (imm5 << 16); + + ulong x0 = (ulong)TestContext.CurrentContext.Random.NextUInt() << 32; + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, x0: x0, x31: w31, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UMOV , .H[]")] + public void Umov_S_HW([Values(0u, 31u)] uint rd, + [Values(1u)] uint rn, + [ValueSource("_4H_")] [Random(RndCnt)] ulong a, + [Values(0u, 7u)] [Random(1u, 6u, RndCntIndex)] uint index) + { + const int size = 1; + + uint imm5 = (index << (size + 1) | 1u << size) & 0x1Fu; + + uint opcode = 0x0E003C00; // RESERVED + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= (imm5 << 16); + + ulong x0 = (ulong)TestContext.CurrentContext.Random.NextUInt() << 32; + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, x0: x0, x31: w31, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UMOV , .S[]")] + public void Umov_S_SW([Values(0u, 31u)] uint rd, + [Values(1u)] uint rn, + [ValueSource("_2S_")] [Random(RndCnt)] ulong a, + [Values(0u, 1u, 2u, 3u)] uint index) + { + const int size = 2; + + uint imm5 = (index << (size + 1) | 1u << size) & 0x1Fu; + + uint opcode = 0x0E003C00; // RESERVED + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= (imm5 << 16); + + ulong x0 = (ulong)TestContext.CurrentContext.Random.NextUInt() << 32; + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, x0: x0, x31: w31, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UMOV , .D[]")] + public void Umov_S_DX([Values(0u, 31u)] uint rd, + [Values(1u)] uint rn, + [ValueSource("_1D_")] [Random(RndCnt)] ulong a, + [Values(0u, 1u)] uint index) + { + const int size = 3; + + uint imm5 = (index << (size + 1) | 1u << size) & 0x1Fu; + + uint opcode = 0x4E003C00; // RESERVED + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= (imm5 << 16); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, x31: x31, v1: v1); + + CompareAgainstUnicorn(); + } +#endif + } +} diff --git a/Ryujinx.Tests/Cpu/CpuTestSimdMove.cs b/Ryujinx.Tests/Cpu/CpuTestSimdMove.cs deleted file mode 100644 index 055e08689c..0000000000 --- a/Ryujinx.Tests/Cpu/CpuTestSimdMove.cs +++ /dev/null @@ -1,136 +0,0 @@ -using ChocolArm64.State; - -using NUnit.Framework; - -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; - -namespace Ryujinx.Tests.Cpu -{ - public class CpuTestSimdMove : CpuTest - { - [Test, Description("TRN1 V0.4S, V1.4S, V2.4S")] - public void Trn1_V_4S([Random(2)] uint A0, [Random(2)] uint A1, [Random(2)] uint A2, [Random(2)] uint A3, - [Random(2)] uint B0, [Random(2)] uint B1, [Random(2)] uint B2, [Random(2)] uint B3) - { - uint Opcode = 0x4E822820; - Vector128 V1 = Sse.StaticCast(Sse2.SetVector128(A3, A2, A1, A0)); - Vector128 V2 = Sse.StaticCast(Sse2.SetVector128(B3, B2, B1, B0)); - - AThreadState ThreadState = SingleOpcode(Opcode, V1: V1, V2: V2); - - Assert.Multiple(() => - { - Assert.That(Sse41.Extract(Sse.StaticCast(ThreadState.V0), (byte)0), Is.EqualTo(A0)); - Assert.That(Sse41.Extract(Sse.StaticCast(ThreadState.V0), (byte)1), Is.EqualTo(B0)); - Assert.That(Sse41.Extract(Sse.StaticCast(ThreadState.V0), (byte)2), Is.EqualTo(A2)); - Assert.That(Sse41.Extract(Sse.StaticCast(ThreadState.V0), (byte)3), Is.EqualTo(B2)); - }); - } - - [Test, Description("TRN1 V0.8B, V1.8B, V2.8B")] - public void Trn1_V_8B([Random(2)] byte A0, [Random(1)] byte A1, [Random(2)] byte A2, [Random(1)] byte A3, - [Random(2)] byte A4, [Random(1)] byte A5, [Random(2)] byte A6, [Random(1)] byte A7, - [Random(2)] byte B0, [Random(1)] byte B1, [Random(2)] byte B2, [Random(1)] byte B3, - [Random(2)] byte B4, [Random(1)] byte B5, [Random(2)] byte B6, [Random(1)] byte B7) - { - uint Opcode = 0x0E022820; - Vector128 V1 = Sse.StaticCast(Sse2.SetVector128(0, 0, 0, 0, 0, 0, 0, 0, A7, A6, A5, A4, A3, A2, A1, A0)); - Vector128 V2 = Sse.StaticCast(Sse2.SetVector128(0, 0, 0, 0, 0, 0, 0, 0, B7, B6, B5, B4, B3, B2, B1, B0)); - - AThreadState ThreadState = SingleOpcode(Opcode, V1: V1, V2: V2); - - Assert.Multiple(() => - { - Assert.That(Sse41.Extract(Sse.StaticCast(ThreadState.V0), (byte)0), Is.EqualTo(A0)); - Assert.That(Sse41.Extract(Sse.StaticCast(ThreadState.V0), (byte)1), Is.EqualTo(B0)); - Assert.That(Sse41.Extract(Sse.StaticCast(ThreadState.V0), (byte)2), Is.EqualTo(A2)); - Assert.That(Sse41.Extract(Sse.StaticCast(ThreadState.V0), (byte)3), Is.EqualTo(B2)); - Assert.That(Sse41.Extract(Sse.StaticCast(ThreadState.V0), (byte)4), Is.EqualTo(A4)); - Assert.That(Sse41.Extract(Sse.StaticCast(ThreadState.V0), (byte)5), Is.EqualTo(B4)); - Assert.That(Sse41.Extract(Sse.StaticCast(ThreadState.V0), (byte)6), Is.EqualTo(A6)); - Assert.That(Sse41.Extract(Sse.StaticCast(ThreadState.V0), (byte)7), Is.EqualTo(B6)); - }); - } - - [Test, Description("TRN2 V0.4S, V1.4S, V2.4S")] - public void Trn2_V_4S([Random(2)] uint A0, [Random(2)] uint A1, [Random(2)] uint A2, [Random(2)] uint A3, - [Random(2)] uint B0, [Random(2)] uint B1, [Random(2)] uint B2, [Random(2)] uint B3) - { - uint Opcode = 0x4E826820; - Vector128 V1 = Sse.StaticCast(Sse2.SetVector128(A3, A2, A1, A0)); - Vector128 V2 = Sse.StaticCast(Sse2.SetVector128(B3, B2, B1, B0)); - - AThreadState ThreadState = SingleOpcode(Opcode, V1: V1, V2: V2); - - Assert.Multiple(() => - { - Assert.That(Sse41.Extract(Sse.StaticCast(ThreadState.V0), (byte)0), Is.EqualTo(A1)); - Assert.That(Sse41.Extract(Sse.StaticCast(ThreadState.V0), (byte)1), Is.EqualTo(B1)); - Assert.That(Sse41.Extract(Sse.StaticCast(ThreadState.V0), (byte)2), Is.EqualTo(A3)); - Assert.That(Sse41.Extract(Sse.StaticCast(ThreadState.V0), (byte)3), Is.EqualTo(B3)); - }); - } - - [Test, Description("TRN2 V0.8B, V1.8B, V2.8B")] - public void Trn2_V_8B([Random(1)] byte A0, [Random(2)] byte A1, [Random(1)] byte A2, [Random(2)] byte A3, - [Random(1)] byte A4, [Random(2)] byte A5, [Random(1)] byte A6, [Random(2)] byte A7, - [Random(1)] byte B0, [Random(2)] byte B1, [Random(1)] byte B2, [Random(2)] byte B3, - [Random(1)] byte B4, [Random(2)] byte B5, [Random(1)] byte B6, [Random(2)] byte B7) - { - uint Opcode = 0x0E026820; - Vector128 V1 = Sse.StaticCast(Sse2.SetVector128(0, 0, 0, 0, 0, 0, 0, 0, A7, A6, A5, A4, A3, A2, A1, A0)); - Vector128 V2 = Sse.StaticCast(Sse2.SetVector128(0, 0, 0, 0, 0, 0, 0, 0, B7, B6, B5, B4, B3, B2, B1, B0)); - - AThreadState ThreadState = SingleOpcode(Opcode, V1: V1, V2: V2); - - Assert.Multiple(() => - { - Assert.That(Sse41.Extract(Sse.StaticCast(ThreadState.V0), (byte)0), Is.EqualTo(A1)); - Assert.That(Sse41.Extract(Sse.StaticCast(ThreadState.V0), (byte)1), Is.EqualTo(B1)); - Assert.That(Sse41.Extract(Sse.StaticCast(ThreadState.V0), (byte)2), Is.EqualTo(A3)); - Assert.That(Sse41.Extract(Sse.StaticCast(ThreadState.V0), (byte)3), Is.EqualTo(B3)); - Assert.That(Sse41.Extract(Sse.StaticCast(ThreadState.V0), (byte)4), Is.EqualTo(A5)); - Assert.That(Sse41.Extract(Sse.StaticCast(ThreadState.V0), (byte)5), Is.EqualTo(B5)); - Assert.That(Sse41.Extract(Sse.StaticCast(ThreadState.V0), (byte)6), Is.EqualTo(A7)); - Assert.That(Sse41.Extract(Sse.StaticCast(ThreadState.V0), (byte)7), Is.EqualTo(B7)); - }); - } - - [TestCase(0u, 0u, 0x2313221221112010ul, 0x0000000000000000ul)] - [TestCase(1u, 0u, 0x2313221221112010ul, 0x2717261625152414ul)] - [TestCase(0u, 1u, 0x2322131221201110ul, 0x0000000000000000ul)] - [TestCase(1u, 1u, 0x2322131221201110ul, 0x2726171625241514ul)] - [TestCase(0u, 2u, 0x2322212013121110ul, 0x0000000000000000ul)] - [TestCase(1u, 2u, 0x2322212013121110ul, 0x2726252417161514ul)] - [TestCase(1u, 3u, 0x1716151413121110ul, 0x2726252423222120ul)] - public void Zip1_V(uint Q, uint size, ulong Result_0, ulong Result_1) - { - // ZIP1 V0., V1., V2. - uint Opcode = 0x0E023820 | (Q << 30) | (size << 22); - Vector128 V1 = MakeVectorE0E1(0x1716151413121110, 0x1F1E1D1C1B1A1918); - Vector128 V2 = MakeVectorE0E1(0x2726252423222120, 0x2F2E2D2C2B2A2928); - AThreadState ThreadState = SingleOpcode(Opcode, V1: V1, V2: V2); - Assert.AreEqual(Result_0, GetVectorE0(ThreadState.V0)); - Assert.AreEqual(Result_1, GetVectorE1(ThreadState.V0)); - } - - [TestCase(0u, 0u, 0x2717261625152414ul, 0x0000000000000000ul)] - [TestCase(1u, 0u, 0x2B1B2A1A29192818ul, 0x2F1F2E1E2D1D2C1Cul)] - [TestCase(0u, 1u, 0x2726171625241514ul, 0x0000000000000000ul)] - [TestCase(1u, 1u, 0x2B2A1B1A29281918ul, 0x2F2E1F1E2D2C1D1Cul)] - [TestCase(0u, 2u, 0x2726252417161514ul, 0x0000000000000000ul)] - [TestCase(1u, 2u, 0x2B2A29281B1A1918ul, 0x2F2E2D2C1F1E1D1Cul)] - [TestCase(1u, 3u, 0x1F1E1D1C1B1A1918ul, 0x2F2E2D2C2B2A2928ul)] - public void Zip2_V(uint Q, uint size, ulong Result_0, ulong Result_1) - { - // ZIP2 V0., V1., V2. - uint Opcode = 0x0E027820 | (Q << 30) | (size << 22); - Vector128 V1 = MakeVectorE0E1(0x1716151413121110, 0x1F1E1D1C1B1A1918); - Vector128 V2 = MakeVectorE0E1(0x2726252423222120, 0x2F2E2D2C2B2A2928); - AThreadState ThreadState = SingleOpcode(Opcode, V1: V1, V2: V2); - Assert.AreEqual(Result_0, GetVectorE0(ThreadState.V0)); - Assert.AreEqual(Result_1, GetVectorE1(ThreadState.V0)); - } - } -} diff --git a/Ryujinx.Tests/Cpu/CpuTestSimdReg.cs b/Ryujinx.Tests/Cpu/CpuTestSimdReg.cs index 5e14f55d36..9b767db408 100644 --- a/Ryujinx.Tests/Cpu/CpuTestSimdReg.cs +++ b/Ryujinx.Tests/Cpu/CpuTestSimdReg.cs @@ -1,33 +1,51 @@ #define SimdReg -using ChocolArm64.State; +using ARMeilleure.State; using NUnit.Framework; -using System.Runtime.Intrinsics; +using System.Collections.Generic; namespace Ryujinx.Tests.Cpu { - using Tester; - using Tester.Types; - - [Category("SimdReg")/*, Ignore("Tested: second half of 2018.")*/] + [Category("SimdReg")] public sealed class CpuTestSimdReg : CpuTest { #if SimdReg - [SetUp] - public void SetupTester() + +#region "ValueSource (Types)" + private static ulong[] _1B1H1S1D_() { - AArch64.TakeReset(false); + return new ulong[] { 0x0000000000000000ul, 0x000000000000007Ful, + 0x0000000000000080ul, 0x00000000000000FFul, + 0x0000000000007FFFul, 0x0000000000008000ul, + 0x000000000000FFFFul, 0x000000007FFFFFFFul, + 0x0000000080000000ul, 0x00000000FFFFFFFFul, + 0x7FFFFFFFFFFFFFFFul, 0x8000000000000000ul, + 0xFFFFFFFFFFFFFFFFul }; } -#region "ValueSource" private static ulong[] _1D_() { return new ulong[] { 0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul }; } + private static ulong[] _1H1S_() + { + return new ulong[] { 0x0000000000000000ul, 0x0000000000007FFFul, + 0x0000000000008000ul, 0x000000000000FFFFul, + 0x000000007FFFFFFFul, 0x0000000080000000ul, + 0x00000000FFFFFFFFul }; + } + + private static ulong[] _4H2S_() + { + return new ulong[] { 0x0000000000000000ul, 0x7FFF7FFF7FFF7FFFul, + 0x8000800080008000ul, 0x7FFFFFFF7FFFFFFFul, + 0x8000000080000000ul, 0xFFFFFFFFFFFFFFFFul }; + } + private static ulong[] _4H2S1D_() { return new ulong[] { 0x0000000000000000ul, 0x7FFF7FFF7FFF7FFFul, @@ -58,1768 +76,3860 @@ namespace Ryujinx.Tests.Cpu 0x8000000080000000ul, 0x7FFFFFFFFFFFFFFFul, 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul }; } -#endregion - [Test, Description("ADD , , ")] - public void Add_S_D([ValueSource("_1D_")] [Random(1)] ulong A, - [ValueSource("_1D_")] [Random(1)] ulong B) + private static IEnumerable _1S_F_() { - uint Opcode = 0x5EE28420; // ADD D0, D1, D2 - Bits Op = new Bits(Opcode); + yield return 0x00000000FF7FFFFFul; // -Max Normal (float.MinValue) + yield return 0x0000000080800000ul; // -Min Normal + yield return 0x00000000807FFFFFul; // -Max Subnormal + yield return 0x0000000080000001ul; // -Min Subnormal (-float.Epsilon) + yield return 0x000000007F7FFFFFul; // +Max Normal (float.MaxValue) + yield return 0x0000000000800000ul; // +Min Normal + yield return 0x00000000007FFFFFul; // +Max Subnormal + yield return 0x0000000000000001ul; // +Min Subnormal (float.Epsilon) - Vector128 V0 = MakeVectorE1(TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0(A); - Vector128 V2 = MakeVectorE0(B); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); - - AArch64.V(1, new Bits(A)); - AArch64.V(2, new Bits(B)); - SimdFp.Add_S(Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); - - Assert.Multiple(() => + if (!NoZeros) { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); + yield return 0x0000000080000000ul; // -Zero + yield return 0x0000000000000000ul; // +Zero + } + + if (!NoInfs) + { + yield return 0x00000000FF800000ul; // -Infinity + yield return 0x000000007F800000ul; // +Infinity + } + + if (!NoNaNs) + { + yield return 0x00000000FFC00000ul; // -QNaN (all zeros payload) (float.NaN) + yield return 0x00000000FFBFFFFFul; // -SNaN (all ones payload) + yield return 0x000000007FC00000ul; // +QNaN (all zeros payload) (-float.NaN) (DefaultNaN) + yield return 0x000000007FBFFFFFul; // +SNaN (all ones payload) + } + + for (int cnt = 1; cnt <= RndCnt; cnt++) + { + ulong grbg = TestContext.CurrentContext.Random.NextUInt(); + ulong rnd1 = GenNormalS(); + ulong rnd2 = GenSubnormalS(); + + yield return (grbg << 32) | rnd1; + yield return (grbg << 32) | rnd2; + } } - [Test, Description("ADD ., ., .")] - public void Add_V_8B_4H_2S([ValueSource("_8B4H2S_")] [Random(1)] ulong A, - [ValueSource("_8B4H2S_")] [Random(1)] ulong B, - [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + private static IEnumerable _2S_F_() { - uint Opcode = 0x0E228420; // ADD V0.8B, V1.8B, V2.8B - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); + yield return 0xFF7FFFFFFF7FFFFFul; // -Max Normal (float.MinValue) + yield return 0x8080000080800000ul; // -Min Normal + yield return 0x807FFFFF807FFFFFul; // -Max Subnormal + yield return 0x8000000180000001ul; // -Min Subnormal (-float.Epsilon) + yield return 0x7F7FFFFF7F7FFFFFul; // +Max Normal (float.MaxValue) + yield return 0x0080000000800000ul; // +Min Normal + yield return 0x007FFFFF007FFFFFul; // +Max Subnormal + yield return 0x0000000100000001ul; // +Min Subnormal (float.Epsilon) - Vector128 V0 = MakeVectorE1(TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0(A); - Vector128 V2 = MakeVectorE0(B); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); - - AArch64.V(1, new Bits(A)); - AArch64.V(2, new Bits(B)); - SimdFp.Add_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); - - Assert.Multiple(() => + if (!NoZeros) { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); + yield return 0x8000000080000000ul; // -Zero + yield return 0x0000000000000000ul; // +Zero + } + + if (!NoInfs) + { + yield return 0xFF800000FF800000ul; // -Infinity + yield return 0x7F8000007F800000ul; // +Infinity + } + + if (!NoNaNs) + { + yield return 0xFFC00000FFC00000ul; // -QNaN (all zeros payload) (float.NaN) + yield return 0xFFBFFFFFFFBFFFFFul; // -SNaN (all ones payload) + yield return 0x7FC000007FC00000ul; // +QNaN (all zeros payload) (-float.NaN) (DefaultNaN) + yield return 0x7FBFFFFF7FBFFFFFul; // +SNaN (all ones payload) + } + + for (int cnt = 1; cnt <= RndCnt; cnt++) + { + ulong rnd1 = GenNormalS(); + ulong rnd2 = GenSubnormalS(); + + yield return (rnd1 << 32) | rnd1; + yield return (rnd2 << 32) | rnd2; + } + } + + private static IEnumerable _1D_F_() + { + yield return 0xFFEFFFFFFFFFFFFFul; // -Max Normal (double.MinValue) + yield return 0x8010000000000000ul; // -Min Normal + yield return 0x800FFFFFFFFFFFFFul; // -Max Subnormal + yield return 0x8000000000000001ul; // -Min Subnormal (-double.Epsilon) + yield return 0x7FEFFFFFFFFFFFFFul; // +Max Normal (double.MaxValue) + yield return 0x0010000000000000ul; // +Min Normal + yield return 0x000FFFFFFFFFFFFFul; // +Max Subnormal + yield return 0x0000000000000001ul; // +Min Subnormal (double.Epsilon) + + if (!NoZeros) + { + yield return 0x8000000000000000ul; // -Zero + yield return 0x0000000000000000ul; // +Zero + } + + if (!NoInfs) + { + yield return 0xFFF0000000000000ul; // -Infinity + yield return 0x7FF0000000000000ul; // +Infinity + } + + if (!NoNaNs) + { + yield return 0xFFF8000000000000ul; // -QNaN (all zeros payload) (double.NaN) + yield return 0xFFF7FFFFFFFFFFFFul; // -SNaN (all ones payload) + yield return 0x7FF8000000000000ul; // +QNaN (all zeros payload) (-double.NaN) (DefaultNaN) + yield return 0x7FF7FFFFFFFFFFFFul; // +SNaN (all ones payload) + } + + for (int cnt = 1; cnt <= RndCnt; cnt++) + { + ulong rnd1 = GenNormalD(); + ulong rnd2 = GenSubnormalD(); + + yield return rnd1; + yield return rnd2; + } + } +#endregion + +#region "ValueSource (Opcodes)" + private static uint[] _F_Abd_Add_Div_Mul_Mulx_Nmul_Sub_S_S_() + { + return new uint[] + { + 0x7EA2D420u, // FABD S0, S1, S2 + 0x1E222820u, // FADD S0, S1, S2 + 0x1E221820u, // FDIV S0, S1, S2 + 0x1E220820u, // FMUL S0, S1, S2 + 0x5E22DC20u, // FMULX S0, S1, S2 + 0x1E228820u, // FNMUL S0, S1, S2 + 0x1E223820u // FSUB S0, S1, S2 + }; + } + + private static uint[] _F_Abd_Add_Div_Mul_Mulx_Nmul_Sub_S_D_() + { + return new uint[] + { + 0x7EE2D420u, // FABD D0, D1, D2 + 0x1E622820u, // FADD D0, D1, D2 + 0x1E621820u, // FDIV D0, D1, D2 + 0x1E620820u, // FMUL D0, D1, D2 + 0x5E62DC20u, // FMULX D0, D1, D2 + 0x1E628820u, // FNMUL D0, D1, D2 + 0x1E623820u // FSUB D0, D1, D2 + }; + } + + private static uint[] _F_Abd_Add_Div_Mul_Mulx_Sub_P_V_2S_4S_() + { + return new uint[] + { + 0x2EA0D400u, // FABD V0.2S, V0.2S, V0.2S + 0x0E20D400u, // FADD V0.2S, V0.2S, V0.2S + 0x2E20D400u, // FADDP V0.2S, V0.2S, V0.2S + 0x2E20FC00u, // FDIV V0.2S, V0.2S, V0.2S + 0x2E20DC00u, // FMUL V0.2S, V0.2S, V0.2S + 0x0E20DC00u, // FMULX V0.2S, V0.2S, V0.2S + 0x0EA0D400u // FSUB V0.2S, V0.2S, V0.2S + }; + } + + private static uint[] _F_Abd_Add_Div_Mul_Mulx_Sub_P_V_2D_() + { + return new uint[] + { + 0x6EE0D400u, // FABD V0.2D, V0.2D, V0.2D + 0x4E60D400u, // FADD V0.2D, V0.2D, V0.2D + 0x6E60D400u, // FADDP V0.2D, V0.2D, V0.2D + 0x6E60FC00u, // FDIV V0.2D, V0.2D, V0.2D + 0x6E60DC00u, // FMUL V0.2D, V0.2D, V0.2D + 0x4E60DC00u, // FMULX V0.2D, V0.2D, V0.2D + 0x4EE0D400u // FSUB V0.2D, V0.2D, V0.2D + }; + } + + private static uint[] _F_Cm_EqGeGt_S_S_() + { + return new uint[] + { + 0x5E22E420u, // FCMEQ S0, S1, S2 + 0x7E22E420u, // FCMGE S0, S1, S2 + 0x7EA2E420u // FCMGT S0, S1, S2 + }; + } + + private static uint[] _F_Cm_EqGeGt_S_D_() + { + return new uint[] + { + 0x5E62E420u, // FCMEQ D0, D1, D2 + 0x7E62E420u, // FCMGE D0, D1, D2 + 0x7EE2E420u // FCMGT D0, D1, D2 + }; + } + + private static uint[] _F_Cm_EqGeGt_V_2S_4S_() + { + return new uint[] + { + 0x0E20E400u, // FCMEQ V0.2S, V0.2S, V0.2S + 0x2E20E400u, // FCMGE V0.2S, V0.2S, V0.2S + 0x2EA0E400u // FCMGT V0.2S, V0.2S, V0.2S + }; + } + + private static uint[] _F_Cm_EqGeGt_V_2D_() + { + return new uint[] + { + 0x4E60E400u, // FCMEQ V0.2D, V0.2D, V0.2D + 0x6E60E400u, // FCMGE V0.2D, V0.2D, V0.2D + 0x6EE0E400u // FCMGT V0.2D, V0.2D, V0.2D + }; + } + + private static uint[] _F_Cmp_Cmpe_S_S_() + { + return new uint[] + { + 0x1E222020u, // FCMP S1, S2 + 0x1E222030u // FCMPE S1, S2 + }; + } + + private static uint[] _F_Cmp_Cmpe_S_D_() + { + return new uint[] + { + 0x1E622020u, // FCMP D1, D2 + 0x1E622030u // FCMPE D1, D2 + }; + } + + private static uint[] _F_Madd_Msub_Nmadd_Nmsub_S_S_() + { + return new uint[] + { + 0x1F020C20u, // FMADD S0, S1, S2, S3 + 0x1F028C20u, // FMSUB S0, S1, S2, S3 + 0x1F220C20u, // FNMADD S0, S1, S2, S3 + 0x1F228C20u // FNMSUB S0, S1, S2, S3 + }; + } + + private static uint[] _F_Madd_Msub_Nmadd_Nmsub_S_D_() + { + return new uint[] + { + 0x1F420C20u, // FMADD D0, D1, D2, D3 + 0x1F428C20u, // FMSUB D0, D1, D2, D3 + 0x1F620C20u, // FNMADD D0, D1, D2, D3 + 0x1F628C20u // FNMSUB D0, D1, D2, D3 + }; + } + + private static uint[] _F_Max_Min_Nm_S_S_() + { + return new uint[] + { + 0x1E224820u, // FMAX S0, S1, S2 + 0x1E226820u, // FMAXNM S0, S1, S2 + 0x1E225820u, // FMIN S0, S1, S2 + 0x1E227820u // FMINNM S0, S1, S2 + }; + } + + private static uint[] _F_Max_Min_Nm_S_D_() + { + return new uint[] + { + 0x1E624820u, // FMAX D0, D1, D2 + 0x1E626820u, // FMAXNM D0, D1, D2 + 0x1E625820u, // FMIN D0, D1, D2 + 0x1E627820u // FMINNM D0, D1, D2 + }; + } + + private static uint[] _F_Max_Min_Nm_P_V_2S_4S_() + { + return new uint[] + { + 0x0E20F400u, // FMAX V0.2S, V0.2S, V0.2S + 0x0E20C400u, // FMAXNM V0.2S, V0.2S, V0.2S + 0x2E20F400u, // FMAXP V0.2S, V0.2S, V0.2S + 0x0EA0F400u, // FMIN V0.2S, V0.2S, V0.2S + 0x0EA0C400u, // FMINNM V0.2S, V0.2S, V0.2S + 0x2EA0F400u // FMINP V0.2S, V0.2S, V0.2S + }; + } + + private static uint[] _F_Max_Min_Nm_P_V_2D_() + { + return new uint[] + { + 0x4E60F400u, // FMAX V0.2D, V0.2D, V0.2D + 0x4E60C400u, // FMAXNM V0.2D, V0.2D, V0.2D + 0x6E60F400u, // FMAXP V0.2D, V0.2D, V0.2D + 0x4EE0F400u, // FMIN V0.2D, V0.2D, V0.2D + 0x4EE0C400u, // FMINNM V0.2D, V0.2D, V0.2D + 0x6EE0F400u // FMINP V0.2D, V0.2D, V0.2D + }; + } + + private static uint[] _F_Mla_Mls_V_2S_4S_() + { + return new uint[] + { + 0x0E20CC00u, // FMLA V0.2S, V0.2S, V0.2S + 0x0EA0CC00u // FMLS V0.2S, V0.2S, V0.2S + }; + } + + private static uint[] _F_Mla_Mls_V_2D_() + { + return new uint[] + { + 0x4E60CC00u, // FMLA V0.2D, V0.2D, V0.2D + 0x4EE0CC00u // FMLS V0.2D, V0.2D, V0.2D + }; + } + + private static uint[] _F_Recps_Rsqrts_S_S_() + { + return new uint[] + { + 0x5E22FC20u, // FRECPS S0, S1, S2 + 0x5EA2FC20u // FRSQRTS S0, S1, S2 + }; + } + + private static uint[] _F_Recps_Rsqrts_S_D_() + { + return new uint[] + { + 0x5E62FC20u, // FRECPS D0, D1, D2 + 0x5EE2FC20u // FRSQRTS D0, D1, D2 + }; + } + + private static uint[] _F_Recps_Rsqrts_V_2S_4S_() + { + return new uint[] + { + 0x0E20FC00u, // FRECPS V0.2S, V0.2S, V0.2S + 0x0EA0FC00u // FRSQRTS V0.2S, V0.2S, V0.2S + }; + } + + private static uint[] _F_Recps_Rsqrts_V_2D_() + { + return new uint[] + { + 0x4E60FC00u, // FRECPS V0.2D, V0.2D, V0.2D + 0x4EE0FC00u // FRSQRTS V0.2D, V0.2D, V0.2D + }; + } + + private static uint[] _Mla_Mls_Mul_V_8B_4H_2S_() + { + return new uint[] + { + 0x0E209400u, // MLA V0.8B, V0.8B, V0.8B + 0x2E209400u, // MLS V0.8B, V0.8B, V0.8B + 0x0E209C00u // MUL V0.8B, V0.8B, V0.8B + }; + } + + private static uint[] _Mla_Mls_Mul_V_16B_8H_4S_() + { + return new uint[] + { + 0x4E209400u, // MLA V0.16B, V0.16B, V0.16B + 0x6E209400u, // MLS V0.16B, V0.16B, V0.16B + 0x4E209C00u // MUL V0.16B, V0.16B, V0.16B + }; + } + + private static uint[] _Sha1c_Sha1m_Sha1p_Sha1su0_V_() + { + return new uint[] + { + 0x5E000000u, // SHA1C Q0, S0, V0.4S + 0x5E002000u, // SHA1M Q0, S0, V0.4S + 0x5E001000u, // SHA1P Q0, S0, V0.4S + 0x5E003000u // SHA1SU0 V0.4S, V0.4S, V0.4S + }; + } + + private static uint[] _Sha256h_Sha256h2_Sha256su1_V_() + { + return new uint[] + { + 0x5E004000u, // SHA256H Q0, Q0, V0.4S + 0x5E005000u, // SHA256H2 Q0, Q0, V0.4S + 0x5E006000u // SHA256SU1 V0.4S, V0.4S, V0.4S + }; + } + + private static uint[] _SU_Max_Min_P_V_() + { + return new uint[] + { + 0x0E206400u, // SMAX V0.8B, V0.8B, V0.8B + 0x0E20A400u, // SMAXP V0.8B, V0.8B, V0.8B + 0x0E206C00u, // SMIN V0.8B, V0.8B, V0.8B + 0x0E20AC00u, // SMINP V0.8B, V0.8B, V0.8B + 0x2E206400u, // UMAX V0.8B, V0.8B, V0.8B + 0x2E20A400u, // UMAXP V0.8B, V0.8B, V0.8B + 0x2E206C00u, // UMIN V0.8B, V0.8B, V0.8B + 0x2E20AC00u // UMINP V0.8B, V0.8B, V0.8B + }; + } + + private static uint[] _SU_Mlal_Mlsl_Mull_V_8B8H_4H4S_2S2D_() + { + return new uint[] + { + 0x0E208000u, // SMLAL V0.8H, V0.8B, V0.8B + 0x0E20A000u, // SMLSL V0.8H, V0.8B, V0.8B + 0x0E20C000u, // SMULL V0.8H, V0.8B, V0.8B + 0x2E208000u, // UMLAL V0.8H, V0.8B, V0.8B + 0x2E20A000u, // UMLSL V0.8H, V0.8B, V0.8B + 0x2E20C000u // UMULL V0.8H, V0.8B, V0.8B + }; + } + + private static uint[] _SU_Mlal_Mlsl_Mull_V_16B8H_8H4S_4S2D_() + { + return new uint[] + { + 0x4E208000u, // SMLAL2 V0.8H, V0.16B, V0.16B + 0x4E20A000u, // SMLSL2 V0.8H, V0.16B, V0.16B + 0x4E20C000u, // SMULL2 V0.8H, V0.16B, V0.16B + 0x6E208000u, // UMLAL2 V0.8H, V0.16B, V0.16B + 0x6E20A000u, // UMLSL2 V0.8H, V0.16B, V0.16B + 0x6E20C000u // UMULL2 V0.8H, V0.16B, V0.16B + }; + } + + private static uint[] _ShlReg_V_8B_4H_2S_() + { + return new uint[] + { + 0x0E205C00u, // SQRSHL V0.8B, V0.8B, V0.8B + 0x0E204C00u, // SQSHL V0.8B, V0.8B, V0.8B + 0x0E205400u, // SRSHL V0.8B, V0.8B, V0.8B + 0x0E204400u, // SSHL V0.8B, V0.8B, V0.8B + 0x2E205C00u, // UQRSHL V0.8B, V0.8B, V0.8B + 0x2E204C00u, // UQSHL V0.8B, V0.8B, V0.8B + 0x2E205400u, // URSHL V0.8B, V0.8B, V0.8B + 0x2E204400u // USHL V0.8B, V0.8B, V0.8B + }; + } + + private static uint[] _ShlReg_V_16B_8H_4S_2D_() + { + return new uint[] + { + 0x4E205C00u, // SQRSHL V0.16B, V0.16B, V0.16B + 0x4E204C00u, // SQSHL V0.16B, V0.16B, V0.16B + 0x4E205400u, // SRSHL V0.16B, V0.16B, V0.16B + 0x4E204400u, // SSHL V0.16B, V0.16B, V0.16B + 0x6E205C00u, // UQRSHL V0.16B, V0.16B, V0.16B + 0x6E204C00u, // UQSHL V0.16B, V0.16B, V0.16B + 0x6E205400u, // URSHL V0.16B, V0.16B, V0.16B + 0x6E204400u // USHL V0.16B, V0.16B, V0.16B + }; + } +#endregion + + private const int RndCnt = 2; + + private static readonly bool NoZeros = false; + private static readonly bool NoInfs = false; + private static readonly bool NoNaNs = false; + + [Test, Pairwise, Description("ADD , , ")] + public void Add_S_D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_1D_")] [Random(RndCnt)] ulong a, + [ValueSource("_1D_")] [Random(RndCnt)] ulong b) + { + uint opcode = 0x5EE08400; // ADD D0, D0, D0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); } [Test, Pairwise, Description("ADD ., ., .")] - public void Add_V_16B_8H_4S_2D([ValueSource("_8B4H2S1D_")] [Random(1)] ulong A0, - [ValueSource("_8B4H2S1D_")] [Random(1)] ulong A1, - [ValueSource("_8B4H2S1D_")] [Random(1)] ulong B0, - [ValueSource("_8B4H2S1D_")] [Random(1)] ulong B1, + public void Add_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x0E208400; // ADD V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("ADD ., ., .")] + public void Add_V_16B_8H_4S_2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong b, [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> { - uint Opcode = 0x4E228420; // ADD V0.16B, V1.16B, V2.16B - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); + uint opcode = 0x4E208400; // ADD V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); - Vector128 V1 = MakeVectorE0E1(A0, A1); - Vector128 V2 = MakeVectorE0E1(B0, B1); - AThreadState ThreadState = SingleOpcode(Opcode, V1: V1, V2: V2); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); - AArch64.Vpart(1, 0, new Bits(A0)); - AArch64.Vpart(1, 1, new Bits(A1)); - AArch64.Vpart(2, 0, new Bits(B0)); - AArch64.Vpart(2, 1, new Bits(B1)); - SimdFp.Add_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); - }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("ADDHN{2} ., ., .")] - public void Addhn_V_8H8B_4S4H_2D2S([ValueSource("_4H2S1D_")] [Random(1)] ulong A0, - [ValueSource("_4H2S1D_")] [Random(1)] ulong A1, - [ValueSource("_4H2S1D_")] [Random(1)] ulong B0, - [ValueSource("_4H2S1D_")] [Random(1)] ulong B1, + public void Addhn_V_8H8B_4S4H_2D2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_4H2S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_4H2S1D_")] [Random(RndCnt)] ulong a, + [ValueSource("_4H2S1D_")] [Random(RndCnt)] ulong b, [Values(0b00u, 0b01u, 0b10u)] uint size) // <8H8B, 4S4H, 2D2S> { - uint Opcode = 0x0E224020; // ADDHN V0.8B, V1.8H, V2.8H - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); + uint opcode = 0x0E204000; // ADDHN V0.8B, V0.8H, V0.8H + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); - Vector128 V0 = MakeVectorE1(TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0E1(A0, A1); - Vector128 V2 = MakeVectorE0E1(B0, B1); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); - AArch64.Vpart(1, 0, new Bits(A0)); - AArch64.Vpart(1, 1, new Bits(A1)); - AArch64.Vpart(2, 0, new Bits(B0)); - AArch64.Vpart(2, 1, new Bits(B1)); - SimdFp.Addhn_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("ADDHN{2} ., ., .")] - public void Addhn_V_8H16B_4S8H_2D4S([ValueSource("_4H2S1D_")] [Random(1)] ulong A0, - [ValueSource("_4H2S1D_")] [Random(1)] ulong A1, - [ValueSource("_4H2S1D_")] [Random(1)] ulong B0, - [ValueSource("_4H2S1D_")] [Random(1)] ulong B1, + public void Addhn_V_8H16B_4S8H_2D4S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_4H2S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_4H2S1D_")] [Random(RndCnt)] ulong a, + [ValueSource("_4H2S1D_")] [Random(RndCnt)] ulong b, [Values(0b00u, 0b01u, 0b10u)] uint size) // <8H16B, 4S8H, 2D4S> { - uint Opcode = 0x4E224020; // ADDHN2 V0.16B, V1.8H, V2.8H - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); + uint opcode = 0x4E204000; // ADDHN2 V0.16B, V0.8H, V0.8H + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); - ulong _E0 = TestContext.CurrentContext.Random.NextULong(); - Vector128 V0 = MakeVectorE0(_E0); - Vector128 V1 = MakeVectorE0E1(A0, A1); - Vector128 V2 = MakeVectorE0E1(B0, B1); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); - AArch64.Vpart(1, 0, new Bits(A0)); - AArch64.Vpart(1, 1, new Bits(A1)); - AArch64.Vpart(2, 0, new Bits(B0)); - AArch64.Vpart(2, 1, new Bits(B1)); - SimdFp.Addhn_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(_E0)); - Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); - }); - } - - [Test, Description("ADDP ., ., .")] - public void Addp_V_8B_4H_2S([ValueSource("_8B4H2S_")] [Random(1)] ulong A, - [ValueSource("_8B4H2S_")] [Random(1)] ulong B, - [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> - { - uint Opcode = 0x0E22BC20; // ADDP V0.8B, V1.8B, V2.8B - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); - - Vector128 V0 = MakeVectorE1(TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0(A); - Vector128 V2 = MakeVectorE0(B); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); - - AArch64.V(1, new Bits(A)); - AArch64.V(2, new Bits(B)); - SimdFp.Addp_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); - - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("ADDP ., ., .")] - public void Addp_V_16B_8H_4S_2D([ValueSource("_8B4H2S1D_")] [Random(1)] ulong A0, - [ValueSource("_8B4H2S1D_")] [Random(1)] ulong A1, - [ValueSource("_8B4H2S1D_")] [Random(1)] ulong B0, - [ValueSource("_8B4H2S1D_")] [Random(1)] ulong B1, - [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + public void Addp_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> { - uint Opcode = 0x4E22BC20; // ADDP V0.16B, V1.16B, V2.16B - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); + uint opcode = 0x0E20BC00; // ADDP V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); - Vector128 V1 = MakeVectorE0E1(A0, A1); - Vector128 V2 = MakeVectorE0E1(B0, B1); - AThreadState ThreadState = SingleOpcode(Opcode, V1: V1, V2: V2); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); - AArch64.Vpart(1, 0, new Bits(A0)); - AArch64.Vpart(1, 1, new Bits(A1)); - AArch64.Vpart(2, 0, new Bits(B0)); - AArch64.Vpart(2, 1, new Bits(B1)); - SimdFp.Addp_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); - }); + CompareAgainstUnicorn(); } - [Test, Description("AND ., ., .")] - public void And_V_8B([ValueSource("_8B_")] [Random(1)] ulong A, - [ValueSource("_8B_")] [Random(1)] ulong B) + [Test, Pairwise, Description("ADDP ., ., .")] + public void Addp_V_16B_8H_4S_2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> { - uint Opcode = 0x0E221C20; // AND V0.8B, V1.8B, V2.8B - Bits Op = new Bits(Opcode); + uint opcode = 0x4E20BC00; // ADDP V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); - Vector128 V0 = MakeVectorE1(TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0(A); - Vector128 V2 = MakeVectorE0(B); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); - AArch64.V(1, new Bits(A)); - AArch64.V(2, new Bits(B)); - SimdFp.And_V(Op[30], Op[20, 16], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("AND ., ., .")] - public void And_V_16B([ValueSource("_8B_")] [Random(1)] ulong A0, - [ValueSource("_8B_")] [Random(1)] ulong A1, - [ValueSource("_8B_")] [Random(1)] ulong B0, - [ValueSource("_8B_")] [Random(1)] ulong B1) + public void And_V_8B([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B_")] [Random(RndCnt)] ulong b) { - uint Opcode = 0x4E221C20; // AND V0.16B, V1.16B, V2.16B - Bits Op = new Bits(Opcode); + uint opcode = 0x0E201C00; // AND V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); - Vector128 V1 = MakeVectorE0E1(A0, A1); - Vector128 V2 = MakeVectorE0E1(B0, B1); - AThreadState ThreadState = SingleOpcode(Opcode, V1: V1, V2: V2); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); - AArch64.Vpart(1, 0, new Bits(A0)); - AArch64.Vpart(1, 1, new Bits(A1)); - AArch64.Vpart(2, 0, new Bits(B0)); - AArch64.Vpart(2, 1, new Bits(B1)); - SimdFp.And_V(Op[30], Op[20, 16], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); - }); + CompareAgainstUnicorn(); } - [Test, Description("BIC ., ., .")] - public void Bic_V_8B([ValueSource("_8B_")] [Random(1)] ulong A, - [ValueSource("_8B_")] [Random(1)] ulong B) + [Test, Pairwise, Description("AND ., ., .")] + public void And_V_16B([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B_")] [Random(RndCnt)] ulong b) { - uint Opcode = 0x0E621C20; // BIC V0.8B, V1.8B, V2.8B - Bits Op = new Bits(Opcode); + uint opcode = 0x4E201C00; // AND V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); - Vector128 V0 = MakeVectorE1(TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0(A); - Vector128 V2 = MakeVectorE0(B); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); - AArch64.V(1, new Bits(A)); - AArch64.V(2, new Bits(B)); - SimdFp.Bic_V(Op[30], Op[20, 16], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("BIC ., ., .")] - public void Bic_V_16B([ValueSource("_8B_")] [Random(1)] ulong A0, - [ValueSource("_8B_")] [Random(1)] ulong A1, - [ValueSource("_8B_")] [Random(1)] ulong B0, - [ValueSource("_8B_")] [Random(1)] ulong B1) + public void Bic_V_8B([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B_")] [Random(RndCnt)] ulong b) { - uint Opcode = 0x4E621C20; // BIC V0.16B, V1.16B, V2.16B - Bits Op = new Bits(Opcode); + uint opcode = 0x0E601C00; // BIC V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); - Vector128 V1 = MakeVectorE0E1(A0, A1); - Vector128 V2 = MakeVectorE0E1(B0, B1); - AThreadState ThreadState = SingleOpcode(Opcode, V1: V1, V2: V2); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); - AArch64.Vpart(1, 0, new Bits(A0)); - AArch64.Vpart(1, 1, new Bits(A1)); - AArch64.Vpart(2, 0, new Bits(B0)); - AArch64.Vpart(2, 1, new Bits(B1)); - SimdFp.Bic_V(Op[30], Op[20, 16], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); - }); + CompareAgainstUnicorn(); } - [Test, Description("BIF ., ., .")] - public void Bif_V_8B([ValueSource("_8B_")] [Random(1)] ulong _Z, - [ValueSource("_8B_")] [Random(1)] ulong A, - [ValueSource("_8B_")] [Random(1)] ulong B) + [Test, Pairwise, Description("BIC ., ., .")] + public void Bic_V_16B([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B_")] [Random(RndCnt)] ulong b) { - uint Opcode = 0x2EE21C20; // BIF V0.8B, V1.8B, V2.8B - Bits Op = new Bits(Opcode); + uint opcode = 0x4E601C00; // BIC V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); - Vector128 V0 = MakeVectorE0E1(_Z, TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0(A); - Vector128 V2 = MakeVectorE0(B); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); - AArch64.Vpart(0, 0, new Bits(_Z)); - AArch64.V(1, new Bits(A)); - AArch64.V(2, new Bits(B)); - SimdFp.Bif_V(Op[30], Op[20, 16], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("BIF ., ., .")] - public void Bif_V_16B([ValueSource("_8B_")] [Random(1)] ulong _Z0, - [ValueSource("_8B_")] [Random(1)] ulong _Z1, - [ValueSource("_8B_")] [Random(1)] ulong A0, - [ValueSource("_8B_")] [Random(1)] ulong A1, - [ValueSource("_8B_")] [Random(1)] ulong B0, - [ValueSource("_8B_")] [Random(1)] ulong B1) + public void Bif_V_8B([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B_")] [Random(RndCnt)] ulong b) { - uint Opcode = 0x6EE21C20; // BIF V0.16B, V1.16B, V2.16B - Bits Op = new Bits(Opcode); + uint opcode = 0x2EE01C00; // BIF V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); - Vector128 V0 = MakeVectorE0E1(_Z0, _Z1); - Vector128 V1 = MakeVectorE0E1(A0, A1); - Vector128 V2 = MakeVectorE0E1(B0, B1); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); - AArch64.Vpart(0, 0, new Bits(_Z0)); - AArch64.Vpart(0, 1, new Bits(_Z1)); - AArch64.Vpart(1, 0, new Bits(A0)); - AArch64.Vpart(1, 1, new Bits(A1)); - AArch64.Vpart(2, 0, new Bits(B0)); - AArch64.Vpart(2, 1, new Bits(B1)); - SimdFp.Bif_V(Op[30], Op[20, 16], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); - }); + CompareAgainstUnicorn(); } - [Test, Description("BIT ., ., .")] - public void Bit_V_8B([ValueSource("_8B_")] [Random(1)] ulong _Z, - [ValueSource("_8B_")] [Random(1)] ulong A, - [ValueSource("_8B_")] [Random(1)] ulong B) + [Test, Pairwise, Description("BIF ., ., .")] + public void Bif_V_16B([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B_")] [Random(RndCnt)] ulong b) { - uint Opcode = 0x2EA21C20; // BIT V0.8B, V1.8B, V2.8B - Bits Op = new Bits(Opcode); + uint opcode = 0x6EE01C00; // BIF V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); - Vector128 V0 = MakeVectorE0E1(_Z, TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0(A); - Vector128 V2 = MakeVectorE0(B); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); - AArch64.Vpart(0, 0, new Bits(_Z)); - AArch64.V(1, new Bits(A)); - AArch64.V(2, new Bits(B)); - SimdFp.Bit_V(Op[30], Op[20, 16], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("BIT ., ., .")] - public void Bit_V_16B([ValueSource("_8B_")] [Random(1)] ulong _Z0, - [ValueSource("_8B_")] [Random(1)] ulong _Z1, - [ValueSource("_8B_")] [Random(1)] ulong A0, - [ValueSource("_8B_")] [Random(1)] ulong A1, - [ValueSource("_8B_")] [Random(1)] ulong B0, - [ValueSource("_8B_")] [Random(1)] ulong B1) + public void Bit_V_8B([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B_")] [Random(RndCnt)] ulong b) { - uint Opcode = 0x6EA21C20; // BIT V0.16B, V1.16B, V2.16B - Bits Op = new Bits(Opcode); + uint opcode = 0x2EA01C00; // BIT V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); - Vector128 V0 = MakeVectorE0E1(_Z0, _Z1); - Vector128 V1 = MakeVectorE0E1(A0, A1); - Vector128 V2 = MakeVectorE0E1(B0, B1); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); - AArch64.Vpart(0, 0, new Bits(_Z0)); - AArch64.Vpart(0, 1, new Bits(_Z1)); - AArch64.Vpart(1, 0, new Bits(A0)); - AArch64.Vpart(1, 1, new Bits(A1)); - AArch64.Vpart(2, 0, new Bits(B0)); - AArch64.Vpart(2, 1, new Bits(B1)); - SimdFp.Bit_V(Op[30], Op[20, 16], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); - }); + CompareAgainstUnicorn(); } - [Test, Description("BSL ., ., .")] - public void Bsl_V_8B([ValueSource("_8B_")] [Random(1)] ulong _Z, - [ValueSource("_8B_")] [Random(1)] ulong A, - [ValueSource("_8B_")] [Random(1)] ulong B) + [Test, Pairwise, Description("BIT ., ., .")] + public void Bit_V_16B([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B_")] [Random(RndCnt)] ulong b) { - uint Opcode = 0x2E621C20; // BSL V0.8B, V1.8B, V2.8B - Bits Op = new Bits(Opcode); + uint opcode = 0x6EA01C00; // BIT V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); - Vector128 V0 = MakeVectorE0E1(_Z, TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0(A); - Vector128 V2 = MakeVectorE0(B); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); - AArch64.Vpart(0, 0, new Bits(_Z)); - AArch64.V(1, new Bits(A)); - AArch64.V(2, new Bits(B)); - SimdFp.Bsl_V(Op[30], Op[20, 16], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("BSL ., ., .")] - public void Bsl_V_16B([ValueSource("_8B_")] [Random(1)] ulong _Z0, - [ValueSource("_8B_")] [Random(1)] ulong _Z1, - [ValueSource("_8B_")] [Random(1)] ulong A0, - [ValueSource("_8B_")] [Random(1)] ulong A1, - [ValueSource("_8B_")] [Random(1)] ulong B0, - [ValueSource("_8B_")] [Random(1)] ulong B1) + public void Bsl_V_8B([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B_")] [Random(RndCnt)] ulong b) { - uint Opcode = 0x6E621C20; // BSL V0.16B, V1.16B, V2.16B - Bits Op = new Bits(Opcode); + uint opcode = 0x2E601C00; // BSL V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); - Vector128 V0 = MakeVectorE0E1(_Z0, _Z1); - Vector128 V1 = MakeVectorE0E1(A0, A1); - Vector128 V2 = MakeVectorE0E1(B0, B1); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); - AArch64.Vpart(0, 0, new Bits(_Z0)); - AArch64.Vpart(0, 1, new Bits(_Z1)); - AArch64.Vpart(1, 0, new Bits(A0)); - AArch64.Vpart(1, 1, new Bits(A1)); - AArch64.Vpart(2, 0, new Bits(B0)); - AArch64.Vpart(2, 1, new Bits(B1)); - SimdFp.Bsl_V(Op[30], Op[20, 16], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); - }); + CompareAgainstUnicorn(); } - [Test, Description("CMEQ , , ")] - public void Cmeq_S_D([ValueSource("_1D_")] [Random(1)] ulong A, - [ValueSource("_1D_")] [Random(1)] ulong B) + [Test, Pairwise, Description("BSL ., ., .")] + public void Bsl_V_16B([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B_")] [Random(RndCnt)] ulong b) { - uint Opcode = 0x7EE28C20; // CMEQ D0, D1, D2 - Bits Op = new Bits(Opcode); + uint opcode = 0x6E601C00; // BSL V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); - Vector128 V0 = MakeVectorE1(TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0(A); - Vector128 V2 = MakeVectorE0(B); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); - AArch64.V(1, new Bits(A)); - AArch64.V(2, new Bits(B)); - SimdFp.Cmeq_Reg_S(Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); + CompareAgainstUnicorn(); } - [Test, Description("CMEQ ., ., .")] - public void Cmeq_V_8B_4H_2S([ValueSource("_8B4H2S_")] [Random(1)] ulong A, - [ValueSource("_8B4H2S_")] [Random(1)] ulong B, - [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + [Test, Pairwise, Description("CMEQ , , ")] + public void Cmeq_S_D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_1D_")] [Random(RndCnt)] ulong a, + [ValueSource("_1D_")] [Random(RndCnt)] ulong b) { - uint Opcode = 0x2E228C20; // CMEQ V0.8B, V1.8B, V2.8B - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); + uint opcode = 0x7EE08C00; // CMEQ D0, D0, D0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); - Vector128 V0 = MakeVectorE1(TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0(A); - Vector128 V2 = MakeVectorE0(B); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); - AArch64.V(1, new Bits(A)); - AArch64.V(2, new Bits(B)); - SimdFp.Cmeq_Reg_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("CMEQ ., ., .")] - public void Cmeq_V_16B_8H_4S_2D([ValueSource("_8B4H2S1D_")] [Random(1)] ulong A0, - [ValueSource("_8B4H2S1D_")] [Random(1)] ulong A1, - [ValueSource("_8B4H2S1D_")] [Random(1)] ulong B0, - [ValueSource("_8B4H2S1D_")] [Random(1)] ulong B1, - [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> - { - uint Opcode = 0x6E228C20; // CMEQ V0.16B, V1.16B, V2.16B - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); - - Vector128 V1 = MakeVectorE0E1(A0, A1); - Vector128 V2 = MakeVectorE0E1(B0, B1); - AThreadState ThreadState = SingleOpcode(Opcode, V1: V1, V2: V2); - - AArch64.Vpart(1, 0, new Bits(A0)); - AArch64.Vpart(1, 1, new Bits(A1)); - AArch64.Vpart(2, 0, new Bits(B0)); - AArch64.Vpart(2, 1, new Bits(B1)); - SimdFp.Cmeq_Reg_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); - - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); - }); - } - - [Test, Description("CMGE , , ")] - public void Cmge_S_D([ValueSource("_1D_")] [Random(1)] ulong A, - [ValueSource("_1D_")] [Random(1)] ulong B) - { - uint Opcode = 0x5EE23C20; // CMGE D0, D1, D2 - Bits Op = new Bits(Opcode); - - Vector128 V0 = MakeVectorE1(TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0(A); - Vector128 V2 = MakeVectorE0(B); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); - - AArch64.V(1, new Bits(A)); - AArch64.V(2, new Bits(B)); - SimdFp.Cmge_Reg_S(Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); - - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); - } - - [Test, Description("CMGE ., ., .")] - public void Cmge_V_8B_4H_2S([ValueSource("_8B4H2S_")] [Random(1)] ulong A, - [ValueSource("_8B4H2S_")] [Random(1)] ulong B, + public void Cmeq_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> { - uint Opcode = 0x0E223C20; // CMGE V0.8B, V1.8B, V2.8B - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); + uint opcode = 0x2E208C00; // CMEQ V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); - Vector128 V0 = MakeVectorE1(TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0(A); - Vector128 V2 = MakeVectorE0(B); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); - AArch64.V(1, new Bits(A)); - AArch64.V(2, new Bits(B)); - SimdFp.Cmge_Reg_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CMEQ ., ., .")] + public void Cmeq_V_16B_8H_4S_2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint opcode = 0x6E208C00; // CMEQ V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CMGE , , ")] + public void Cmge_S_D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_1D_")] [Random(RndCnt)] ulong a, + [ValueSource("_1D_")] [Random(RndCnt)] ulong b) + { + uint opcode = 0x5EE03C00; // CMGE D0, D0, D0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); } [Test, Pairwise, Description("CMGE ., ., .")] - public void Cmge_V_16B_8H_4S_2D([ValueSource("_8B4H2S1D_")] [Random(1)] ulong A0, - [ValueSource("_8B4H2S1D_")] [Random(1)] ulong A1, - [ValueSource("_8B4H2S1D_")] [Random(1)] ulong B0, - [ValueSource("_8B4H2S1D_")] [Random(1)] ulong B1, - [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> - { - uint Opcode = 0x4E223C20; // CMGE V0.16B, V1.16B, V2.16B - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); - - Vector128 V1 = MakeVectorE0E1(A0, A1); - Vector128 V2 = MakeVectorE0E1(B0, B1); - AThreadState ThreadState = SingleOpcode(Opcode, V1: V1, V2: V2); - - AArch64.Vpart(1, 0, new Bits(A0)); - AArch64.Vpart(1, 1, new Bits(A1)); - AArch64.Vpart(2, 0, new Bits(B0)); - AArch64.Vpart(2, 1, new Bits(B1)); - SimdFp.Cmge_Reg_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); - - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); - }); - } - - [Test, Description("CMGT , , ")] - public void Cmgt_S_D([ValueSource("_1D_")] [Random(1)] ulong A, - [ValueSource("_1D_")] [Random(1)] ulong B) - { - uint Opcode = 0x5EE23420; // CMGT D0, D1, D2 - Bits Op = new Bits(Opcode); - - Vector128 V0 = MakeVectorE1(TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0(A); - Vector128 V2 = MakeVectorE0(B); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); - - AArch64.V(1, new Bits(A)); - AArch64.V(2, new Bits(B)); - SimdFp.Cmgt_Reg_S(Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); - - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); - } - - [Test, Description("CMGT ., ., .")] - public void Cmgt_V_8B_4H_2S([ValueSource("_8B4H2S_")] [Random(1)] ulong A, - [ValueSource("_8B4H2S_")] [Random(1)] ulong B, + public void Cmge_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> { - uint Opcode = 0x0E223420; // CMGT V0.8B, V1.8B, V2.8B - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); + uint opcode = 0x0E203C00; // CMGE V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); - Vector128 V0 = MakeVectorE1(TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0(A); - Vector128 V2 = MakeVectorE0(B); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); - AArch64.V(1, new Bits(A)); - AArch64.V(2, new Bits(B)); - SimdFp.Cmgt_Reg_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CMGE ., ., .")] + public void Cmge_V_16B_8H_4S_2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint opcode = 0x4E203C00; // CMGE V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CMGT , , ")] + public void Cmgt_S_D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_1D_")] [Random(RndCnt)] ulong a, + [ValueSource("_1D_")] [Random(RndCnt)] ulong b) + { + uint opcode = 0x5EE03400; // CMGT D0, D0, D0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); } [Test, Pairwise, Description("CMGT ., ., .")] - public void Cmgt_V_16B_8H_4S_2D([ValueSource("_8B4H2S1D_")] [Random(1)] ulong A0, - [ValueSource("_8B4H2S1D_")] [Random(1)] ulong A1, - [ValueSource("_8B4H2S1D_")] [Random(1)] ulong B0, - [ValueSource("_8B4H2S1D_")] [Random(1)] ulong B1, - [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> - { - uint Opcode = 0x4E223420; // CMGT V0.16B, V1.16B, V2.16B - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); - - Vector128 V1 = MakeVectorE0E1(A0, A1); - Vector128 V2 = MakeVectorE0E1(B0, B1); - AThreadState ThreadState = SingleOpcode(Opcode, V1: V1, V2: V2); - - AArch64.Vpart(1, 0, new Bits(A0)); - AArch64.Vpart(1, 1, new Bits(A1)); - AArch64.Vpart(2, 0, new Bits(B0)); - AArch64.Vpart(2, 1, new Bits(B1)); - SimdFp.Cmgt_Reg_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); - - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); - }); - } - - [Test, Description("CMHI , , ")] - public void Cmhi_S_D([ValueSource("_1D_")] [Random(1)] ulong A, - [ValueSource("_1D_")] [Random(1)] ulong B) - { - uint Opcode = 0x7EE23420; // CMHI D0, D1, D2 - Bits Op = new Bits(Opcode); - - Vector128 V0 = MakeVectorE1(TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0(A); - Vector128 V2 = MakeVectorE0(B); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); - - AArch64.V(1, new Bits(A)); - AArch64.V(2, new Bits(B)); - SimdFp.Cmhi_S(Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); - - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); - } - - [Test, Description("CMHI ., ., .")] - public void Cmhi_V_8B_4H_2S([ValueSource("_8B4H2S_")] [Random(1)] ulong A, - [ValueSource("_8B4H2S_")] [Random(1)] ulong B, + public void Cmgt_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> { - uint Opcode = 0x2E223420; // CMHI V0.8B, V1.8B, V2.8B - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); + uint opcode = 0x0E203400; // CMGT V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); - Vector128 V0 = MakeVectorE1(TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0(A); - Vector128 V2 = MakeVectorE0(B); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); - AArch64.V(1, new Bits(A)); - AArch64.V(2, new Bits(B)); - SimdFp.Cmhi_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CMGT ., ., .")] + public void Cmgt_V_16B_8H_4S_2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint opcode = 0x4E203400; // CMGT V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CMHI , , ")] + public void Cmhi_S_D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_1D_")] [Random(RndCnt)] ulong a, + [ValueSource("_1D_")] [Random(RndCnt)] ulong b) + { + uint opcode = 0x7EE03400; // CMHI D0, D0, D0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); } [Test, Pairwise, Description("CMHI ., ., .")] - public void Cmhi_V_16B_8H_4S_2D([ValueSource("_8B4H2S1D_")] [Random(1)] ulong A0, - [ValueSource("_8B4H2S1D_")] [Random(1)] ulong A1, - [ValueSource("_8B4H2S1D_")] [Random(1)] ulong B0, - [ValueSource("_8B4H2S1D_")] [Random(1)] ulong B1, - [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> - { - uint Opcode = 0x6E223420; // CMHI V0.16B, V1.16B, V2.16B - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); - - Vector128 V1 = MakeVectorE0E1(A0, A1); - Vector128 V2 = MakeVectorE0E1(B0, B1); - AThreadState ThreadState = SingleOpcode(Opcode, V1: V1, V2: V2); - - AArch64.Vpart(1, 0, new Bits(A0)); - AArch64.Vpart(1, 1, new Bits(A1)); - AArch64.Vpart(2, 0, new Bits(B0)); - AArch64.Vpart(2, 1, new Bits(B1)); - SimdFp.Cmhi_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); - - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); - }); - } - - [Test, Description("CMHS , , ")] - public void Cmhs_S_D([ValueSource("_1D_")] [Random(1)] ulong A, - [ValueSource("_1D_")] [Random(1)] ulong B) - { - uint Opcode = 0x7EE23C20; // CMHS D0, D1, D2 - Bits Op = new Bits(Opcode); - - Vector128 V0 = MakeVectorE1(TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0(A); - Vector128 V2 = MakeVectorE0(B); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); - - AArch64.V(1, new Bits(A)); - AArch64.V(2, new Bits(B)); - SimdFp.Cmhs_S(Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); - - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); - } - - [Test, Description("CMHS ., ., .")] - public void Cmhs_V_8B_4H_2S([ValueSource("_8B4H2S_")] [Random(1)] ulong A, - [ValueSource("_8B4H2S_")] [Random(1)] ulong B, + public void Cmhi_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> { - uint Opcode = 0x2E223C20; // CMHS V0.8B, V1.8B, V2.8B - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); + uint opcode = 0x2E203400; // CMHI V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); - Vector128 V0 = MakeVectorE1(TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0(A); - Vector128 V2 = MakeVectorE0(B); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); - AArch64.V(1, new Bits(A)); - AArch64.V(2, new Bits(B)); - SimdFp.Cmhs_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CMHI ., ., .")] + public void Cmhi_V_16B_8H_4S_2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint opcode = 0x6E203400; // CMHI V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CMHS , , ")] + public void Cmhs_S_D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_1D_")] [Random(RndCnt)] ulong a, + [ValueSource("_1D_")] [Random(RndCnt)] ulong b) + { + uint opcode = 0x7EE03C00; // CMHS D0, D0, D0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); } [Test, Pairwise, Description("CMHS ., ., .")] - public void Cmhs_V_16B_8H_4S_2D([ValueSource("_8B4H2S1D_")] [Random(1)] ulong A0, - [ValueSource("_8B4H2S1D_")] [Random(1)] ulong A1, - [ValueSource("_8B4H2S1D_")] [Random(1)] ulong B0, - [ValueSource("_8B4H2S1D_")] [Random(1)] ulong B1, + public void Cmhs_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x2E203C00; // CMHS V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CMHS ., ., .")] + public void Cmhs_V_16B_8H_4S_2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong b, [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> { - uint Opcode = 0x6E223C20; // CMHS V0.16B, V1.16B, V2.16B - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); + uint opcode = 0x6E203C00; // CMHS V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); - Vector128 V1 = MakeVectorE0E1(A0, A1); - Vector128 V2 = MakeVectorE0E1(B0, B1); - AThreadState ThreadState = SingleOpcode(Opcode, V1: V1, V2: V2); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); - AArch64.Vpart(1, 0, new Bits(A0)); - AArch64.Vpart(1, 1, new Bits(A1)); - AArch64.Vpart(2, 0, new Bits(B0)); - AArch64.Vpart(2, 1, new Bits(B1)); - SimdFp.Cmhs_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); - }); + CompareAgainstUnicorn(); } - [Test, Description("CMTST , , ")] - public void Cmtst_S_D([ValueSource("_1D_")] [Random(1)] ulong A, - [ValueSource("_1D_")] [Random(1)] ulong B) + [Test, Pairwise, Description("CMTST , , ")] + public void Cmtst_S_D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_1D_")] [Random(RndCnt)] ulong a, + [ValueSource("_1D_")] [Random(RndCnt)] ulong b) { - uint Opcode = 0x5EE28C20; // CMTST D0, D1, D2 - Bits Op = new Bits(Opcode); + uint opcode = 0x5EE08C00; // CMTST D0, D0, D0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); - Vector128 V0 = MakeVectorE1(TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0(A); - Vector128 V2 = MakeVectorE0(B); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); - AArch64.V(1, new Bits(A)); - AArch64.V(2, new Bits(B)); - SimdFp.Cmtst_S(Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); - } - - [Test, Description("CMTST ., ., .")] - public void Cmtst_V_8B_4H_2S([ValueSource("_8B4H2S_")] [Random(1)] ulong A, - [ValueSource("_8B4H2S_")] [Random(1)] ulong B, - [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> - { - uint Opcode = 0x0E228C20; // CMTST V0.8B, V1.8B, V2.8B - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); - - Vector128 V0 = MakeVectorE1(TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0(A); - Vector128 V2 = MakeVectorE0(B); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); - - AArch64.V(1, new Bits(A)); - AArch64.V(2, new Bits(B)); - SimdFp.Cmtst_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); - - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("CMTST ., ., .")] - public void Cmtst_V_16B_8H_4S_2D([ValueSource("_8B4H2S1D_")] [Random(1)] ulong A0, - [ValueSource("_8B4H2S1D_")] [Random(1)] ulong A1, - [ValueSource("_8B4H2S1D_")] [Random(1)] ulong B0, - [ValueSource("_8B4H2S1D_")] [Random(1)] ulong B1, - [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + public void Cmtst_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> { - uint Opcode = 0x4E228C20; // CMTST V0.16B, V1.16B, V2.16B - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); + uint opcode = 0x0E208C00; // CMTST V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); - Vector128 V1 = MakeVectorE0E1(A0, A1); - Vector128 V2 = MakeVectorE0E1(B0, B1); - AThreadState ThreadState = SingleOpcode(Opcode, V1: V1, V2: V2); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); - AArch64.Vpart(1, 0, new Bits(A0)); - AArch64.Vpart(1, 1, new Bits(A1)); - AArch64.Vpart(2, 0, new Bits(B0)); - AArch64.Vpart(2, 1, new Bits(B1)); - SimdFp.Cmtst_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); - }); + CompareAgainstUnicorn(); } - [Test, Description("EOR ., ., .")] - public void Eor_V_8B([ValueSource("_8B_")] [Random(1)] ulong A, - [ValueSource("_8B_")] [Random(1)] ulong B) + [Test, Pairwise, Description("CMTST ., ., .")] + public void Cmtst_V_16B_8H_4S_2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> { - uint Opcode = 0x2E221C20; // EOR V0.8B, V1.8B, V2.8B - Bits Op = new Bits(Opcode); + uint opcode = 0x4E208C00; // CMTST V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); - Vector128 V0 = MakeVectorE1(TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0(A); - Vector128 V2 = MakeVectorE0(B); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); - AArch64.V(1, new Bits(A)); - AArch64.V(2, new Bits(B)); - SimdFp.Eor_V(Op[30], Op[20, 16], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("EOR ., ., .")] - public void Eor_V_16B([ValueSource("_8B_")] [Random(1)] ulong A0, - [ValueSource("_8B_")] [Random(1)] ulong A1, - [ValueSource("_8B_")] [Random(1)] ulong B0, - [ValueSource("_8B_")] [Random(1)] ulong B1) + public void Eor_V_8B([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B_")] [Random(RndCnt)] ulong b) { - uint Opcode = 0x6E221C20; // EOR V0.16B, V1.16B, V2.16B - Bits Op = new Bits(Opcode); + uint opcode = 0x2E201C00; // EOR V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); - Vector128 V1 = MakeVectorE0E1(A0, A1); - Vector128 V2 = MakeVectorE0E1(B0, B1); - AThreadState ThreadState = SingleOpcode(Opcode, V1: V1, V2: V2); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); - AArch64.Vpart(1, 0, new Bits(A0)); - AArch64.Vpart(1, 1, new Bits(A1)); - AArch64.Vpart(2, 0, new Bits(B0)); - AArch64.Vpart(2, 1, new Bits(B1)); - SimdFp.Eor_V(Op[30], Op[20, 16], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); - }); + CompareAgainstUnicorn(); } - [Test, Description("ORN ., ., .")] - public void Orn_V_8B([ValueSource("_8B_")] [Random(1)] ulong A, - [ValueSource("_8B_")] [Random(1)] ulong B) + [Test, Pairwise, Description("EOR ., ., .")] + public void Eor_V_16B([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B_")] [Random(RndCnt)] ulong b) { - uint Opcode = 0x0EE21C20; // ORN V0.8B, V1.8B, V2.8B - Bits Op = new Bits(Opcode); + uint opcode = 0x6E201C00; // EOR V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); - Vector128 V0 = MakeVectorE1(TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0(A); - Vector128 V2 = MakeVectorE0(B); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); - AArch64.V(1, new Bits(A)); - AArch64.V(2, new Bits(B)); - SimdFp.Orn_V(Op[30], Op[20, 16], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); + CompareAgainstUnicorn(); + } + + [Test, Pairwise] [Explicit] + public void F_Abd_Add_Div_Mul_Mulx_Nmul_Sub_S_S([ValueSource("_F_Abd_Add_Div_Mul_Mulx_Nmul_Sub_S_S_")] uint opcodes, + [ValueSource("_1S_F_")] ulong a, + [ValueSource("_1S_F_")] ulong b) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Dzc | Fpsr.Idc); + } + + [Test, Pairwise] [Explicit] + public void F_Abd_Add_Div_Mul_Mulx_Nmul_Sub_S_D([ValueSource("_F_Abd_Add_Div_Mul_Mulx_Nmul_Sub_S_D_")] uint opcodes, + [ValueSource("_1D_F_")] ulong a, + [ValueSource("_1D_F_")] ulong b) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE1(z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Dzc | Fpsr.Idc); + } + + [Test, Pairwise] [Explicit] + public void F_Abd_Add_Div_Mul_Mulx_Sub_P_V_2S_4S([ValueSource("_F_Abd_Add_Div_Mul_Mulx_Sub_P_V_2S_4S_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_2S_F_")] ulong z, + [ValueSource("_2S_F_")] ulong a, + [ValueSource("_2S_F_")] ulong b, + [Values(0b0u, 0b1u)] uint q) // <2S, 4S> + { + opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + V128 v2 = MakeVectorE0E1(b, b * q); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Dzc | Fpsr.Idc); + } + + [Test, Pairwise] [Explicit] + public void F_Abd_Add_Div_Mul_Mulx_Sub_P_V_2D([ValueSource("_F_Abd_Add_Div_Mul_Mulx_Sub_P_V_2D_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_1D_F_")] ulong z, + [ValueSource("_1D_F_")] ulong a, + [ValueSource("_1D_F_")] ulong b) + { + opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Dzc | Fpsr.Idc); + } + + [Test, Pairwise] [Explicit] + public void F_Cm_EqGeGt_S_S([ValueSource("_F_Cm_EqGeGt_S_S_")] uint opcodes, + [ValueSource("_1S_F_")] ulong a, + [ValueSource("_1S_F_")] ulong b) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Idc); + } + + [Test, Pairwise] [Explicit] + public void F_Cm_EqGeGt_S_D([ValueSource("_F_Cm_EqGeGt_S_D_")] uint opcodes, + [ValueSource("_1D_F_")] ulong a, + [ValueSource("_1D_F_")] ulong b) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE1(z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Idc); + } + + [Test, Pairwise] [Explicit] + public void F_Cm_EqGeGt_V_2S_4S([ValueSource("_F_Cm_EqGeGt_V_2S_4S_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_2S_F_")] ulong z, + [ValueSource("_2S_F_")] ulong a, + [ValueSource("_2S_F_")] ulong b, + [Values(0b0u, 0b1u)] uint q) // <2S, 4S> + { + opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + V128 v2 = MakeVectorE0E1(b, b * q); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Idc); + } + + [Test, Pairwise] [Explicit] + public void F_Cm_EqGeGt_V_2D([ValueSource("_F_Cm_EqGeGt_V_2D_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_1D_F_")] ulong z, + [ValueSource("_1D_F_")] ulong a, + [ValueSource("_1D_F_")] ulong b) + { + opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Idc); + } + + [Test, Pairwise] [Explicit] + public void F_Cmp_Cmpe_S_S([ValueSource("_F_Cmp_Cmpe_S_S_")] uint opcodes, + [ValueSource("_1S_F_")] ulong a, + [ValueSource("_1S_F_")] ulong b) + { + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + bool v = TestContext.CurrentContext.Random.NextBool(); + bool c = TestContext.CurrentContext.Random.NextBool(); + bool z = TestContext.CurrentContext.Random.NextBool(); + bool n = TestContext.CurrentContext.Random.NextBool(); + + SingleOpcode(opcodes, v1: v1, v2: v2, overflow: v, carry: c, zero: z, negative: n); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc); + } + + [Test, Pairwise] [Explicit] + public void F_Cmp_Cmpe_S_D([ValueSource("_F_Cmp_Cmpe_S_D_")] uint opcodes, + [ValueSource("_1D_F_")] ulong a, + [ValueSource("_1D_F_")] ulong b) + { + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + bool v = TestContext.CurrentContext.Random.NextBool(); + bool c = TestContext.CurrentContext.Random.NextBool(); + bool z = TestContext.CurrentContext.Random.NextBool(); + bool n = TestContext.CurrentContext.Random.NextBool(); + + SingleOpcode(opcodes, v1: v1, v2: v2, overflow: v, carry: c, zero: z, negative: n); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc); + } + + [Test, Pairwise] [Explicit] // Fused. + public void F_Madd_Msub_Nmadd_Nmsub_S_S([ValueSource("_F_Madd_Msub_Nmadd_Nmsub_S_S_")] uint opcodes, + [ValueSource("_1S_F_")] ulong a, + [ValueSource("_1S_F_")] ulong b, + [ValueSource("_1S_F_")] ulong c) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + V128 v3 = MakeVectorE0(c); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, v3: v3, fpcr: fpcr); + + CompareAgainstUnicorn(Fpsr.Ioc | Fpsr.Idc, FpSkips.IfUnderflow, FpTolerances.UpToOneUlpsS); + } + + [Test, Pairwise] [Explicit] // Fused. + public void F_Madd_Msub_Nmadd_Nmsub_S_D([ValueSource("_F_Madd_Msub_Nmadd_Nmsub_S_D_")] uint opcodes, + [ValueSource("_1D_F_")] ulong a, + [ValueSource("_1D_F_")] ulong b, + [ValueSource("_1D_F_")] ulong c) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE1(z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + V128 v3 = MakeVectorE0(c); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, v3: v3, fpcr: fpcr); + + CompareAgainstUnicorn(Fpsr.Ioc | Fpsr.Idc, FpSkips.IfUnderflow, FpTolerances.UpToOneUlpsD); + } + + [Test, Pairwise] [Explicit] + public void F_Max_Min_Nm_S_S([ValueSource("_F_Max_Min_Nm_S_S_")] uint opcodes, + [ValueSource("_1S_F_")] ulong a, + [ValueSource("_1S_F_")] ulong b) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Idc); + } + + [Test, Pairwise] [Explicit] + public void F_Max_Min_Nm_S_D([ValueSource("_F_Max_Min_Nm_S_D_")] uint opcodes, + [ValueSource("_1D_F_")] ulong a, + [ValueSource("_1D_F_")] ulong b) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE1(z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Idc); + } + + [Test, Pairwise] [Explicit] + public void F_Max_Min_Nm_P_V_2S_4S([ValueSource("_F_Max_Min_Nm_P_V_2S_4S_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_2S_F_")] ulong z, + [ValueSource("_2S_F_")] ulong a, + [ValueSource("_2S_F_")] ulong b, + [Values(0b0u, 0b1u)] uint q) // <2S, 4S> + { + opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + V128 v2 = MakeVectorE0E1(b, b * q); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Idc); + } + + [Test, Pairwise] [Explicit] + public void F_Max_Min_Nm_P_V_2D([ValueSource("_F_Max_Min_Nm_P_V_2D_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_1D_F_")] ulong z, + [ValueSource("_1D_F_")] ulong a, + [ValueSource("_1D_F_")] ulong b) + { + opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Idc); + } + + [Test, Pairwise] [Explicit] // Fused. + public void F_Mla_Mls_V_2S_4S([ValueSource("_F_Mla_Mls_V_2S_4S_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_2S_F_")] ulong z, + [ValueSource("_2S_F_")] ulong a, + [ValueSource("_2S_F_")] ulong b, + [Values(0b0u, 0b1u)] uint q) // <2S, 4S> + { + opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + V128 v2 = MakeVectorE0E1(b, b * q); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, fpcr: fpcr); + + CompareAgainstUnicorn(Fpsr.Ioc | Fpsr.Idc, FpSkips.IfUnderflow, FpTolerances.UpToOneUlpsS); + } + + [Test, Pairwise] [Explicit] // Fused. + public void F_Mla_Mls_V_2D([ValueSource("_F_Mla_Mls_V_2D_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_1D_F_")] ulong z, + [ValueSource("_1D_F_")] ulong a, + [ValueSource("_1D_F_")] ulong b) + { + opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, fpcr: fpcr); + + CompareAgainstUnicorn(Fpsr.Ioc | Fpsr.Idc, FpSkips.IfUnderflow, FpTolerances.UpToOneUlpsD); + } + + [Test, Pairwise] [Explicit] // Fused. + public void F_Recps_Rsqrts_S_S([ValueSource("_F_Recps_Rsqrts_S_S_")] uint opcodes, + [ValueSource("_1S_F_")] ulong a, + [ValueSource("_1S_F_")] ulong b) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, fpcr: fpcr); + + CompareAgainstUnicorn(Fpsr.Ioc | Fpsr.Idc, FpSkips.IfUnderflow, FpTolerances.UpToOneUlpsS); + } + + [Test, Pairwise] [Explicit] // Fused. + public void F_Recps_Rsqrts_S_D([ValueSource("_F_Recps_Rsqrts_S_D_")] uint opcodes, + [ValueSource("_1D_F_")] ulong a, + [ValueSource("_1D_F_")] ulong b) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE1(z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, fpcr: fpcr); + + CompareAgainstUnicorn(Fpsr.Ioc | Fpsr.Idc, FpSkips.IfUnderflow, FpTolerances.UpToOneUlpsD); + } + + [Test, Pairwise] [Explicit] // Fused. + public void F_Recps_Rsqrts_V_2S_4S([ValueSource("_F_Recps_Rsqrts_V_2S_4S_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_2S_F_")] ulong z, + [ValueSource("_2S_F_")] ulong a, + [ValueSource("_2S_F_")] ulong b, + [Values(0b0u, 0b1u)] uint q) // <2S, 4S> + { + opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + V128 v2 = MakeVectorE0E1(b, b * q); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, fpcr: fpcr); + + CompareAgainstUnicorn(Fpsr.Ioc | Fpsr.Idc, FpSkips.IfUnderflow, FpTolerances.UpToOneUlpsS); + } + + [Test, Pairwise] [Explicit] // Fused. + public void F_Recps_Rsqrts_V_2D([ValueSource("_F_Recps_Rsqrts_V_2D_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_1D_F_")] ulong z, + [ValueSource("_1D_F_")] ulong a, + [ValueSource("_1D_F_")] ulong b) + { + opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, fpcr: fpcr); + + CompareAgainstUnicorn(Fpsr.Ioc | Fpsr.Idc, FpSkips.IfUnderflow, FpTolerances.UpToOneUlpsD); + } + + [Test, Pairwise] + public void Mla_Mls_Mul_V_8B_4H_2S([ValueSource("_Mla_Mls_Mul_V_8B_4H_2S_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Mla_Mls_Mul_V_16B_8H_4S([ValueSource("_Mla_Mls_Mul_V_16B_8H_4S_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B, 8H, 4S> + { + opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); } [Test, Pairwise, Description("ORN ., ., .")] - public void Orn_V_16B([ValueSource("_8B_")] [Random(1)] ulong A0, - [ValueSource("_8B_")] [Random(1)] ulong A1, - [ValueSource("_8B_")] [Random(1)] ulong B0, - [ValueSource("_8B_")] [Random(1)] ulong B1) + public void Orn_V_8B([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B_")] [Random(RndCnt)] ulong b) { - uint Opcode = 0x4EE21C20; // ORN V0.16B, V1.16B, V2.16B - Bits Op = new Bits(Opcode); + uint opcode = 0x0EE01C00; // ORN V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); - Vector128 V1 = MakeVectorE0E1(A0, A1); - Vector128 V2 = MakeVectorE0E1(B0, B1); - AThreadState ThreadState = SingleOpcode(Opcode, V1: V1, V2: V2); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); - AArch64.Vpart(1, 0, new Bits(A0)); - AArch64.Vpart(1, 1, new Bits(A1)); - AArch64.Vpart(2, 0, new Bits(B0)); - AArch64.Vpart(2, 1, new Bits(B1)); - SimdFp.Orn_V(Op[30], Op[20, 16], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); - }); + CompareAgainstUnicorn(); } - [Test, Description("ORR ., ., .")] - public void Orr_V_8B([ValueSource("_8B_")] [Random(1)] ulong A, - [ValueSource("_8B_")] [Random(1)] ulong B) + [Test, Pairwise, Description("ORN ., ., .")] + public void Orn_V_16B([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B_")] [Random(RndCnt)] ulong b) { - uint Opcode = 0x0EA21C20; // ORR V0.8B, V1.8B, V2.8B - Bits Op = new Bits(Opcode); + uint opcode = 0x4EE01C00; // ORN V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); - Vector128 V0 = MakeVectorE1(TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0(A); - Vector128 V2 = MakeVectorE0(B); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); - AArch64.V(1, new Bits(A)); - AArch64.V(2, new Bits(B)); - SimdFp.Orr_V(Op[30], Op[20, 16], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("ORR ., ., .")] - public void Orr_V_16B([ValueSource("_8B_")] [Random(1)] ulong A0, - [ValueSource("_8B_")] [Random(1)] ulong A1, - [ValueSource("_8B_")] [Random(1)] ulong B0, - [ValueSource("_8B_")] [Random(1)] ulong B1) + public void Orr_V_8B([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B_")] [Random(RndCnt)] ulong b) { - uint Opcode = 0x4EA21C20; // ORR V0.16B, V1.16B, V2.16B - Bits Op = new Bits(Opcode); + uint opcode = 0x0EA01C00; // ORR V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); - Vector128 V1 = MakeVectorE0E1(A0, A1); - Vector128 V2 = MakeVectorE0E1(B0, B1); - AThreadState ThreadState = SingleOpcode(Opcode, V1: V1, V2: V2); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); - AArch64.Vpart(1, 0, new Bits(A0)); - AArch64.Vpart(1, 1, new Bits(A1)); - AArch64.Vpart(2, 0, new Bits(B0)); - AArch64.Vpart(2, 1, new Bits(B1)); - SimdFp.Orr_V(Op[30], Op[20, 16], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); - }); + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("ORR ., ., .")] + public void Orr_V_16B([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B_")] [Random(RndCnt)] ulong b) + { + uint opcode = 0x4EA01C00; // ORR V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); } [Test, Pairwise, Description("RADDHN{2} ., ., .")] - public void Raddhn_V_8H8B_4S4H_2D2S([ValueSource("_4H2S1D_")] [Random(1)] ulong A0, - [ValueSource("_4H2S1D_")] [Random(1)] ulong A1, - [ValueSource("_4H2S1D_")] [Random(1)] ulong B0, - [ValueSource("_4H2S1D_")] [Random(1)] ulong B1, + public void Raddhn_V_8H8B_4S4H_2D2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_4H2S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_4H2S1D_")] [Random(RndCnt)] ulong a, + [ValueSource("_4H2S1D_")] [Random(RndCnt)] ulong b, [Values(0b00u, 0b01u, 0b10u)] uint size) // <8H8B, 4S4H, 2D2S> { - uint Opcode = 0x2E224020; // RADDHN V0.8B, V1.8H, V2.8H - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); + uint opcode = 0x2E204000; // RADDHN V0.8B, V0.8H, V0.8H + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); - Vector128 V0 = MakeVectorE1(TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0E1(A0, A1); - Vector128 V2 = MakeVectorE0E1(B0, B1); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); - AArch64.Vpart(1, 0, new Bits(A0)); - AArch64.Vpart(1, 1, new Bits(A1)); - AArch64.Vpart(2, 0, new Bits(B0)); - AArch64.Vpart(2, 1, new Bits(B1)); - SimdFp.Raddhn_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("RADDHN{2} ., ., .")] - public void Raddhn_V_8H16B_4S8H_2D4S([ValueSource("_4H2S1D_")] [Random(1)] ulong A0, - [ValueSource("_4H2S1D_")] [Random(1)] ulong A1, - [ValueSource("_4H2S1D_")] [Random(1)] ulong B0, - [ValueSource("_4H2S1D_")] [Random(1)] ulong B1, + public void Raddhn_V_8H16B_4S8H_2D4S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_4H2S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_4H2S1D_")] [Random(RndCnt)] ulong a, + [ValueSource("_4H2S1D_")] [Random(RndCnt)] ulong b, [Values(0b00u, 0b01u, 0b10u)] uint size) // <8H16B, 4S8H, 2D4S> { - uint Opcode = 0x6E224020; // RADDHN2 V0.16B, V1.8H, V2.8H - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); + uint opcode = 0x6E204000; // RADDHN2 V0.16B, V0.8H, V0.8H + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); - ulong _E0 = TestContext.CurrentContext.Random.NextULong(); - Vector128 V0 = MakeVectorE0(_E0); - Vector128 V1 = MakeVectorE0E1(A0, A1); - Vector128 V2 = MakeVectorE0E1(B0, B1); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); - AArch64.Vpart(1, 0, new Bits(A0)); - AArch64.Vpart(1, 1, new Bits(A1)); - AArch64.Vpart(2, 0, new Bits(B0)); - AArch64.Vpart(2, 1, new Bits(B1)); - SimdFp.Raddhn_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(_E0)); - Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); - }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("RSUBHN{2} ., ., .")] - public void Rsubhn_V_8H8B_4S4H_2D2S([ValueSource("_4H2S1D_")] [Random(1)] ulong A0, - [ValueSource("_4H2S1D_")] [Random(1)] ulong A1, - [ValueSource("_4H2S1D_")] [Random(1)] ulong B0, - [ValueSource("_4H2S1D_")] [Random(1)] ulong B1, + public void Rsubhn_V_8H8B_4S4H_2D2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_4H2S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_4H2S1D_")] [Random(RndCnt)] ulong a, + [ValueSource("_4H2S1D_")] [Random(RndCnt)] ulong b, [Values(0b00u, 0b01u, 0b10u)] uint size) // <8H8B, 4S4H, 2D2S> { - uint Opcode = 0x2E226020; // RSUBHN V0.8B, V1.8H, V2.8H - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); + uint opcode = 0x2E206000; // RSUBHN V0.8B, V0.8H, V0.8H + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); - Vector128 V0 = MakeVectorE1(TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0E1(A0, A1); - Vector128 V2 = MakeVectorE0E1(B0, B1); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); - AArch64.Vpart(1, 0, new Bits(A0)); - AArch64.Vpart(1, 1, new Bits(A1)); - AArch64.Vpart(2, 0, new Bits(B0)); - AArch64.Vpart(2, 1, new Bits(B1)); - SimdFp.Rsubhn_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("RSUBHN{2} ., ., .")] - public void Rsubhn_V_8H16B_4S8H_2D4S([ValueSource("_4H2S1D_")] [Random(1)] ulong A0, - [ValueSource("_4H2S1D_")] [Random(1)] ulong A1, - [ValueSource("_4H2S1D_")] [Random(1)] ulong B0, - [ValueSource("_4H2S1D_")] [Random(1)] ulong B1, + public void Rsubhn_V_8H16B_4S8H_2D4S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_4H2S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_4H2S1D_")] [Random(RndCnt)] ulong a, + [ValueSource("_4H2S1D_")] [Random(RndCnt)] ulong b, [Values(0b00u, 0b01u, 0b10u)] uint size) // <8H16B, 4S8H, 2D4S> { - uint Opcode = 0x6E226020; // RSUBHN2 V0.16B, V1.8H, V2.8H - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); + uint opcode = 0x6E206000; // RSUBHN2 V0.16B, V0.8H, V0.8H + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); - ulong _E0 = TestContext.CurrentContext.Random.NextULong(); - Vector128 V0 = MakeVectorE0(_E0); - Vector128 V1 = MakeVectorE0E1(A0, A1); - Vector128 V2 = MakeVectorE0E1(B0, B1); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); - AArch64.Vpart(1, 0, new Bits(A0)); - AArch64.Vpart(1, 1, new Bits(A1)); - AArch64.Vpart(2, 0, new Bits(B0)); - AArch64.Vpart(2, 1, new Bits(B1)); - SimdFp.Rsubhn_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(_E0)); - Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); - }); - } - - [Test, Description("SABA ., ., .")] - public void Saba_V_8B_4H_2S([ValueSource("_8B4H2S_")] [Random(1)] ulong _Z, - [ValueSource("_8B4H2S_")] [Random(1)] ulong A, - [ValueSource("_8B4H2S_")] [Random(1)] ulong B, - [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> - { - uint Opcode = 0x0E227C20; // SABA V0.8B, V1.8B, V2.8B - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); - - Vector128 V0 = MakeVectorE0E1(_Z, TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0(A); - Vector128 V2 = MakeVectorE0(B); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); - - AArch64.Vpart(0, 0, new Bits(_Z)); - AArch64.V(1, new Bits(A)); - AArch64.V(2, new Bits(B)); - SimdFp.Saba_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); - - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("SABA ., ., .")] - public void Saba_V_16B_8H_4S([ValueSource("_8B4H2S_")] [Random(1)] ulong _Z0, - [ValueSource("_8B4H2S_")] [Random(1)] ulong _Z1, - [ValueSource("_8B4H2S_")] [Random(1)] ulong A0, - [ValueSource("_8B4H2S_")] [Random(1)] ulong A1, - [ValueSource("_8B4H2S_")] [Random(1)] ulong B0, - [ValueSource("_8B4H2S_")] [Random(1)] ulong B1, - [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B, 8H, 4S> - { - uint Opcode = 0x4E227C20; // SABA V0.16B, V1.16B, V2.16B - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); - - Vector128 V0 = MakeVectorE0E1(_Z0, _Z1); - Vector128 V1 = MakeVectorE0E1(A0, A1); - Vector128 V2 = MakeVectorE0E1(B0, B1); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); - - AArch64.Vpart(0, 0, new Bits(_Z0)); - AArch64.Vpart(0, 1, new Bits(_Z1)); - AArch64.Vpart(1, 0, new Bits(A0)); - AArch64.Vpart(1, 1, new Bits(A1)); - AArch64.Vpart(2, 0, new Bits(B0)); - AArch64.Vpart(2, 1, new Bits(B1)); - SimdFp.Saba_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); - - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); - }); - } - - [Test, Pairwise, Description("SABAL{2} ., ., .")] - public void Sabal_V_8B8H_4H4S_2S2D([ValueSource("_8B4H2S_")] [Random(1)] ulong _Z0, - [ValueSource("_8B4H2S_")] [Random(1)] ulong _Z1, - [ValueSource("_8B4H2S_")] [Random(1)] ulong A0, - [ValueSource("_8B4H2S_")] [Random(1)] ulong B0, - [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> - { - uint Opcode = 0x0E225020; // SABAL V0.8H, V1.8B, V2.8B - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); - - Vector128 V0 = MakeVectorE0E1(_Z0, _Z1); - Vector128 V1 = MakeVectorE0(A0); - Vector128 V2 = MakeVectorE0(B0); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); - - AArch64.Vpart(0, 0, new Bits(_Z0)); - AArch64.Vpart(0, 1, new Bits(_Z1)); - AArch64.Vpart(1, 0, new Bits(A0)); - AArch64.Vpart(2, 0, new Bits(B0)); - SimdFp.Sabal_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); - - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); - }); - } - - [Test, Pairwise, Description("SABAL{2} ., ., .")] - public void Sabal_V_16B8H_8H4S_4S2D([ValueSource("_8B4H2S_")] [Random(1)] ulong _Z0, - [ValueSource("_8B4H2S_")] [Random(1)] ulong _Z1, - [ValueSource("_8B4H2S_")] [Random(1)] ulong A1, - [ValueSource("_8B4H2S_")] [Random(1)] ulong B1, - [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B, 8H, 4S> - { - uint Opcode = 0x4E225020; // SABAL2 V0.8H, V1.16B, V2.16B - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); - - Vector128 V0 = MakeVectorE0E1(_Z0, _Z1); - Vector128 V1 = MakeVectorE1(A1); - Vector128 V2 = MakeVectorE1(B1); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); - - AArch64.Vpart(0, 0, new Bits(_Z0)); - AArch64.Vpart(0, 1, new Bits(_Z1)); - AArch64.Vpart(1, 1, new Bits(A1)); - AArch64.Vpart(2, 1, new Bits(B1)); - SimdFp.Sabal_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); - - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); - }); - } - - [Test, Description("SABD ., ., .")] - public void Sabd_V_8B_4H_2S([ValueSource("_8B4H2S_")] [Random(1)] ulong A, - [ValueSource("_8B4H2S_")] [Random(1)] ulong B, + public void Saba_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> { - uint Opcode = 0x0E227420; // SABD V0.8B, V1.8B, V2.8B - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); + uint opcode = 0x0E207C00; // SABA V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); - Vector128 V0 = MakeVectorE0E1(TestContext.CurrentContext.Random.NextULong(), - TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0(A); - Vector128 V2 = MakeVectorE0(B); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); - AArch64.V(1, new Bits(A)); - AArch64.V(2, new Bits(B)); - SimdFp.Sabd_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SABA ., ., .")] + public void Saba_V_16B_8H_4S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B, 8H, 4S> + { + uint opcode = 0x4E207C00; // SABA V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SABAL{2} ., ., .")] + public void Sabal_V_8B8H_4H4S_2S2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B8H, 4H4S, 2S2D> + { + uint opcode = 0x0E205000; // SABAL V0.8H, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SABAL{2} ., ., .")] + public void Sabal_V_16B8H_8H4S_4S2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B8H, 8H4S, 4S2D> + { + uint opcode = 0x4E205000; // SABAL2 V0.8H, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE1(a); + V128 v2 = MakeVectorE1(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); } [Test, Pairwise, Description("SABD ., ., .")] - public void Sabd_V_16B_8H_4S([ValueSource("_8B4H2S_")] [Random(1)] ulong A0, - [ValueSource("_8B4H2S_")] [Random(1)] ulong A1, - [ValueSource("_8B4H2S_")] [Random(1)] ulong B0, - [ValueSource("_8B4H2S_")] [Random(1)] ulong B1, + public void Sabd_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x0E207400; // SABD V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SABD ., ., .")] + public void Sabd_V_16B_8H_4S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B, 8H, 4S> { - uint Opcode = 0x4E227420; // SABD V0.16B, V1.16B, V2.16B - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); + uint opcode = 0x4E207400; // SABD V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); - Vector128 V0 = MakeVectorE0E1(TestContext.CurrentContext.Random.NextULong(), - TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0E1(A0, A1); - Vector128 V2 = MakeVectorE0E1(B0, B1); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); - AArch64.Vpart(1, 0, new Bits(A0)); - AArch64.Vpart(1, 1, new Bits(A1)); - AArch64.Vpart(2, 0, new Bits(B0)); - AArch64.Vpart(2, 1, new Bits(B1)); - SimdFp.Sabd_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); - }); + CompareAgainstUnicorn(); } - [Test, Description("SABDL{2} ., ., .")] - public void Sabdl_V_8B8H_4H4S_2S2D([ValueSource("_8B4H2S_")] [Random(1)] ulong A0, - [ValueSource("_8B4H2S_")] [Random(1)] ulong B0, - [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + [Test, Pairwise, Description("SABDL{2} ., ., .")] + public void Sabdl_V_8B8H_4H4S_2S2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B8H, 4H4S, 2S2D> { - uint Opcode = 0x0E227020; // SABDL V0.8H, V1.8B, V2.8B - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); + uint opcode = 0x0E207000; // SABDL V0.8H, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); - Vector128 V0 = MakeVectorE0E1(TestContext.CurrentContext.Random.NextULong(), - TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0(A0); - Vector128 V2 = MakeVectorE0(B0); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); - AArch64.Vpart(1, 0, new Bits(A0)); - AArch64.Vpart(2, 0, new Bits(B0)); - SimdFp.Sabdl_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); - }); + CompareAgainstUnicorn(); } - [Test, Description("SABDL{2} ., ., .")] - public void Sabdl_V_16B8H_8H4S_4S2D([ValueSource("_8B4H2S_")] [Random(1)] ulong A1, - [ValueSource("_8B4H2S_")] [Random(1)] ulong B1, - [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B, 8H, 4S> + [Test, Pairwise, Description("SABDL{2} ., ., .")] + public void Sabdl_V_16B8H_8H4S_4S2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B8H, 8H4S, 4S2D> { - uint Opcode = 0x4E227020; // SABDL2 V0.8H, V1.16B, V2.16B - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); + uint opcode = 0x4E207000; // SABDL2 V0.8H, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); - Vector128 V0 = MakeVectorE0E1(TestContext.CurrentContext.Random.NextULong(), - TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE1(A1); - Vector128 V2 = MakeVectorE1(B1); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE1(a); + V128 v2 = MakeVectorE1(b); - AArch64.Vpart(1, 1, new Bits(A1)); - AArch64.Vpart(2, 1, new Bits(B1)); - SimdFp.Sabdl_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); - }); + CompareAgainstUnicorn(); } - [Test, Description("SUB , , ")] - public void Sub_S_D([ValueSource("_1D_")] [Random(1)] ulong A, - [ValueSource("_1D_")] [Random(1)] ulong B) + [Test, Pairwise, Description("SADDL{2} ., ., .")] + public void Saddl_V_8B8H_4H4S_2S2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B8H, 4H4S, 2S2D> { - uint Opcode = 0x7EE28420; // SUB D0, D1, D2 - Bits Op = new Bits(Opcode); + uint opcode = 0x0E200000; // SADDL V0.8H, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); - Vector128 V0 = MakeVectorE1(TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0(A); - Vector128 V2 = MakeVectorE0(B); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); - AArch64.V(1, new Bits(A)); - AArch64.V(2, new Bits(B)); - SimdFp.Sub_S(Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); + CompareAgainstUnicorn(); } - [Test, Description("SUB ., ., .")] - public void Sub_V_8B_4H_2S([ValueSource("_8B4H2S_")] [Random(1)] ulong A, - [ValueSource("_8B4H2S_")] [Random(1)] ulong B, - [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + [Test, Pairwise, Description("SADDL{2} ., ., .")] + public void Saddl_V_16B8H_8H4S_4S2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B8H, 8H4S, 4S2D> { - uint Opcode = 0x2E228420; // SUB V0.8B, V1.8B, V2.8B - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); + uint opcode = 0x4E200000; // SADDL2 V0.8H, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); - Vector128 V0 = MakeVectorE1(TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0(A); - Vector128 V2 = MakeVectorE0(B); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE1(a); + V128 v2 = MakeVectorE1(b); - AArch64.V(1, new Bits(A)); - AArch64.V(2, new Bits(B)); - SimdFp.Sub_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SADDW{2} ., ., .")] + public void Saddw_V_8B8H8H_4H4S4S_2S2D2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_4H2S1D_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B8H8H, 4H4S4S, 2S2D2D> + { + uint opcode = 0x0E201000; // SADDW V0.8H, V0.8H, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SADDW{2} ., ., .")] + public void Saddw_V_16B8H8H_8H4S4S_4S2D2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_4H2S1D_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B8H8H, 8H4S4S, 4S2D2D> + { + uint opcode = 0x4E201000; // SADDW2 V0.8H, V0.8H, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE1(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Sha1c_Sha1m_Sha1p_Sha1su0_V([ValueSource("_Sha1c_Sha1m_Sha1p_Sha1su0_V_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [Random(RndCnt / 2)] ulong z0, [Random(RndCnt / 2)] ulong z1, + [Random(RndCnt / 2)] ulong a0, [Random(RndCnt / 2)] ulong a1, + [Random(RndCnt / 2)] ulong b0, [Random(RndCnt / 2)] ulong b1) + { + opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z0, z1); + V128 v1 = MakeVectorE0E1(a0, a1); + V128 v2 = MakeVectorE0E1(b0, b1); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Sha256h_Sha256h2_Sha256su1_V([ValueSource("_Sha256h_Sha256h2_Sha256su1_V_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [Random(RndCnt / 2)] ulong z0, [Random(RndCnt / 2)] ulong z1, + [Random(RndCnt / 2)] ulong a0, [Random(RndCnt / 2)] ulong a1, + [Random(RndCnt / 2)] ulong b0, [Random(RndCnt / 2)] ulong b1) + { + opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z0, z1); + V128 v1 = MakeVectorE0E1(a0, a1); + V128 v2 = MakeVectorE0E1(b0, b1); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SHADD ., ., .")] + public void Shadd_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x0E200400; // SHADD V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SHADD ., ., .")] + public void Shadd_V_16B_8H_4S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B, 8H, 4S> + { + uint opcode = 0x4E200400; // SHADD V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SHSUB ., ., .")] + public void Shsub_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x0E202400; // SHSUB V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SHSUB ., ., .")] + public void Shsub_V_16B_8H_4S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B, 8H, 4S> + { + uint opcode = 0x4E202400; // SHSUB V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void SU_Max_Min_P_V([ValueSource("_SU_Max_Min_P_V_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size, // Q0: <8B, 4H, 2S> + [Values(0b0u, 0b1u)] uint q) // Q1: <16B, 8H, 4S> + { + opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((size & 3) << 22); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + V128 v2 = MakeVectorE0E1(b, b * q); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void SU_Mlal_Mlsl_Mull_V_8B8H_4H4S_2S2D([ValueSource("_SU_Mlal_Mlsl_Mull_V_8B8H_4H4S_2S2D_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B8H, 4H4S, 2S2D> + { + opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void SU_Mlal_Mlsl_Mull_V_16B8H_8H4S_4S2D([ValueSource("_SU_Mlal_Mlsl_Mull_V_16B8H_8H4S_4S2D_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B8H, 8H4S, 4S2D> + { + opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE1(a); + V128 v2 = MakeVectorE1(b); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SQADD , , ")] + public void Sqadd_S_B_H_S_D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_1B1H1S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_1B1H1S1D_")] [Random(RndCnt)] ulong a, + [ValueSource("_1B1H1S1D_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // + { + uint opcode = 0x5E200C00; // SQADD B0, B0, B0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("SQADD ., ., .")] + public void Sqadd_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x0E200C00; // SQADD V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("SQADD ., ., .")] + public void Sqadd_V_16B_8H_4S_2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint opcode = 0x4E200C00; // SQADD V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("SQDMULH , , ")] + public void Sqdmulh_S_H_S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_1H1S_")] [Random(RndCnt)] ulong z, + [ValueSource("_1H1S_")] [Random(RndCnt)] ulong a, + [ValueSource("_1H1S_")] [Random(RndCnt)] ulong b, + [Values(0b01u, 0b10u)] uint size) // + { + uint opcode = 0x5E20B400; // SQDMULH B0, B0, B0 (RESERVED) + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("SQDMULH ., ., .")] + public void Sqdmulh_V_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b01u, 0b10u)] uint size) // <4H, 2S> + { + uint opcode = 0x0E20B400; // SQDMULH V0.8B, V0.8B, V0.8B (RESERVED) + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("SQDMULH ., ., .")] + public void Sqdmulh_V_8H_4S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b01u, 0b10u)] uint size) // <8H, 4S> + { + uint opcode = 0x4E20B400; // SQDMULH V0.16B, V0.16B, V0.16B (RESERVED) + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("SQRDMULH , , ")] + public void Sqrdmulh_S_H_S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_1H1S_")] [Random(RndCnt)] ulong z, + [ValueSource("_1H1S_")] [Random(RndCnt)] ulong a, + [ValueSource("_1H1S_")] [Random(RndCnt)] ulong b, + [Values(0b01u, 0b10u)] uint size) // + { + uint opcode = 0x7E20B400; // SQRDMULH B0, B0, B0 (RESERVED) + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("SQRDMULH ., ., .")] + public void Sqrdmulh_V_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b01u, 0b10u)] uint size) // <4H, 2S> + { + uint opcode = 0x2E20B400; // SQRDMULH V0.8B, V0.8B, V0.8B (RESERVED) + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("SQRDMULH ., ., .")] + public void Sqrdmulh_V_8H_4S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b01u, 0b10u)] uint size) // <8H, 4S> + { + uint opcode = 0x6E20B400; // SQRDMULH V0.16B, V0.16B, V0.16B (RESERVED) + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("SQSUB , , ")] + public void Sqsub_S_B_H_S_D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_1B1H1S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_1B1H1S1D_")] [Random(RndCnt)] ulong a, + [ValueSource("_1B1H1S1D_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // + { + uint opcode = 0x5E202C00; // SQSUB B0, B0, B0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("SQSUB ., ., .")] + public void Sqsub_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x0E202C00; // SQSUB V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("SQSUB ., ., .")] + public void Sqsub_V_16B_8H_4S_2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint opcode = 0x4E202C00; // SQSUB V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("SRHADD ., ., .")] + public void Srhadd_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x0E201400; // SRHADD V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SRHADD ., ., .")] + public void Srhadd_V_16B_8H_4S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B, 8H, 4S> + { + uint opcode = 0x4E201400; // SRHADD V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void ShlReg_V_8B_4H_2S([ValueSource("_ShlReg_V_8B_4H_2S_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(0ul, 255ul, RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise] + public void ShlReg_V_16B_8H_4S_2D([ValueSource("_ShlReg_V_16B_8H_4S_2D_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S1D_")] [Random(0ul, 255ul, RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("SSUBL{2} ., ., .")] + public void Ssubl_V_8B8H_4H4S_2S2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B8H, 4H4S, 2S2D> + { + uint opcode = 0x0E202000; // SSUBL V0.8H, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SSUBL{2} ., ., .")] + public void Ssubl_V_16B8H_8H4S_4S2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B8H, 8H4S, 4S2D> + { + uint opcode = 0x4E202000; // SSUBL2 V0.8H, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE1(a); + V128 v2 = MakeVectorE1(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SSUBW{2} ., ., .")] + public void Ssubw_V_8B8H8H_4H4S4S_2S2D2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_4H2S1D_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B8H8H, 4H4S4S, 2S2D2D> + { + uint opcode = 0x0E203000; // SSUBW V0.8H, V0.8H, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SSUBW{2} ., ., .")] + public void Ssubw_V_16B8H8H_8H4S4S_4S2D2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_4H2S1D_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B8H8H, 8H4S4S, 4S2D2D> + { + uint opcode = 0x4E203000; // SSUBW2 V0.8H, V0.8H, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE1(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SUB , , ")] + public void Sub_S_D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_1D_")] [Random(RndCnt)] ulong a, + [ValueSource("_1D_")] [Random(RndCnt)] ulong b) + { + uint opcode = 0x7EE08400; // SUB D0, D0, D0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); } [Test, Pairwise, Description("SUB ., ., .")] - public void Sub_V_16B_8H_4S_2D([ValueSource("_8B4H2S1D_")] [Random(1)] ulong A0, - [ValueSource("_8B4H2S1D_")] [Random(1)] ulong A1, - [ValueSource("_8B4H2S1D_")] [Random(1)] ulong B0, - [ValueSource("_8B4H2S1D_")] [Random(1)] ulong B1, + public void Sub_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x2E208400; // SUB V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SUB ., ., .")] + public void Sub_V_16B_8H_4S_2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong b, [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> { - uint Opcode = 0x6E228420; // SUB V0.16B, V1.16B, V2.16B - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); + uint opcode = 0x6E208400; // SUB V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); - Vector128 V1 = MakeVectorE0E1(A0, A1); - Vector128 V2 = MakeVectorE0E1(B0, B1); - AThreadState ThreadState = SingleOpcode(Opcode, V1: V1, V2: V2); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); - AArch64.Vpart(1, 0, new Bits(A0)); - AArch64.Vpart(1, 1, new Bits(A1)); - AArch64.Vpart(2, 0, new Bits(B0)); - AArch64.Vpart(2, 1, new Bits(B1)); - SimdFp.Sub_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); - }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("SUBHN{2} ., ., .")] - public void Subhn_V_8H8B_4S4H_2D2S([ValueSource("_4H2S1D_")] [Random(1)] ulong A0, - [ValueSource("_4H2S1D_")] [Random(1)] ulong A1, - [ValueSource("_4H2S1D_")] [Random(1)] ulong B0, - [ValueSource("_4H2S1D_")] [Random(1)] ulong B1, + public void Subhn_V_8H8B_4S4H_2D2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_4H2S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_4H2S1D_")] [Random(RndCnt)] ulong a, + [ValueSource("_4H2S1D_")] [Random(RndCnt)] ulong b, [Values(0b00u, 0b01u, 0b10u)] uint size) // <8H8B, 4S4H, 2D2S> { - uint Opcode = 0x0E226020; // SUBHN V0.8B, V1.8H, V2.8H - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); + uint opcode = 0x0E206000; // SUBHN V0.8B, V0.8H, V0.8H + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); - Vector128 V0 = MakeVectorE1(TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0E1(A0, A1); - Vector128 V2 = MakeVectorE0E1(B0, B1); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); - AArch64.Vpart(1, 0, new Bits(A0)); - AArch64.Vpart(1, 1, new Bits(A1)); - AArch64.Vpart(2, 0, new Bits(B0)); - AArch64.Vpart(2, 1, new Bits(B1)); - SimdFp.Subhn_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("SUBHN{2} ., ., .")] - public void Subhn_V_8H16B_4S8H_2D4S([ValueSource("_4H2S1D_")] [Random(1)] ulong A0, - [ValueSource("_4H2S1D_")] [Random(1)] ulong A1, - [ValueSource("_4H2S1D_")] [Random(1)] ulong B0, - [ValueSource("_4H2S1D_")] [Random(1)] ulong B1, + public void Subhn_V_8H16B_4S8H_2D4S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_4H2S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_4H2S1D_")] [Random(RndCnt)] ulong a, + [ValueSource("_4H2S1D_")] [Random(RndCnt)] ulong b, [Values(0b00u, 0b01u, 0b10u)] uint size) // <8H16B, 4S8H, 2D4S> { - uint Opcode = 0x4E226020; // SUBHN2 V0.16B, V1.8H, V2.8H - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); + uint opcode = 0x4E206000; // SUBHN2 V0.16B, V0.8H, V0.8H + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); - ulong _E0 = TestContext.CurrentContext.Random.NextULong(); - Vector128 V0 = MakeVectorE0(_E0); - Vector128 V1 = MakeVectorE0E1(A0, A1); - Vector128 V2 = MakeVectorE0E1(B0, B1); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); - AArch64.Vpart(1, 0, new Bits(A0)); - AArch64.Vpart(1, 1, new Bits(A1)); - AArch64.Vpart(2, 0, new Bits(B0)); - AArch64.Vpart(2, 1, new Bits(B1)); - SimdFp.Subhn_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(_E0)); - Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); - }); + CompareAgainstUnicorn(); } - [Test, Description("UABA ., ., .")] - public void Uaba_V_8B_4H_2S([ValueSource("_8B4H2S_")] [Random(1)] ulong _Z, - [ValueSource("_8B4H2S_")] [Random(1)] ulong A, - [ValueSource("_8B4H2S_")] [Random(1)] ulong B, + [Test, Pairwise, Description("TRN1 ., ., .")] + public void Trn1_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> { - uint Opcode = 0x2E227C20; // UABA V0.8B, V1.8B, V2.8B - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); + uint opcode = 0x0E002800; // TRN1 V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); - Vector128 V0 = MakeVectorE0E1(_Z, TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0(A); - Vector128 V2 = MakeVectorE0(B); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); - AArch64.Vpart(0, 0, new Bits(_Z)); - AArch64.V(1, new Bits(A)); - AArch64.V(2, new Bits(B)); - SimdFp.Uaba_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("TRN1 ., ., .")] + public void Trn1_V_16B_8H_4S_2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint opcode = 0x4E002800; // TRN1 V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, ~z); + V128 v1 = MakeVectorE0E1(a, ~a); + V128 v2 = MakeVectorE0E1(b, ~b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("TRN2 ., ., .")] + public void Trn2_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x0E006800; // TRN2 V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("TRN2 ., ., .")] + public void Trn2_V_16B_8H_4S_2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint opcode = 0x4E006800; // TRN2 V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, ~z); + V128 v1 = MakeVectorE0E1(a, ~a); + V128 v2 = MakeVectorE0E1(b, ~b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); } [Test, Pairwise, Description("UABA ., ., .")] - public void Uaba_V_16B_8H_4S([ValueSource("_8B4H2S_")] [Random(1)] ulong _Z0, - [ValueSource("_8B4H2S_")] [Random(1)] ulong _Z1, - [ValueSource("_8B4H2S_")] [Random(1)] ulong A0, - [ValueSource("_8B4H2S_")] [Random(1)] ulong A1, - [ValueSource("_8B4H2S_")] [Random(1)] ulong B0, - [ValueSource("_8B4H2S_")] [Random(1)] ulong B1, - [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B, 8H, 4S> - { - uint Opcode = 0x6E227C20; // UABA V0.16B, V1.16B, V2.16B - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); - - Vector128 V0 = MakeVectorE0E1(_Z0, _Z1); - Vector128 V1 = MakeVectorE0E1(A0, A1); - Vector128 V2 = MakeVectorE0E1(B0, B1); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); - - AArch64.Vpart(0, 0, new Bits(_Z0)); - AArch64.Vpart(0, 1, new Bits(_Z1)); - AArch64.Vpart(1, 0, new Bits(A0)); - AArch64.Vpart(1, 1, new Bits(A1)); - AArch64.Vpart(2, 0, new Bits(B0)); - AArch64.Vpart(2, 1, new Bits(B1)); - SimdFp.Uaba_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); - - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); - }); - } - - [Test, Pairwise, Description("UABAL{2} ., ., .")] - public void Uabal_V_8B8H_4H4S_2S2D([ValueSource("_8B4H2S_")] [Random(1)] ulong _Z0, - [ValueSource("_8B4H2S_")] [Random(1)] ulong _Z1, - [ValueSource("_8B4H2S_")] [Random(1)] ulong A0, - [ValueSource("_8B4H2S_")] [Random(1)] ulong B0, - [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> - { - uint Opcode = 0x2E225020; // UABAL V0.8H, V1.8B, V2.8B - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); - - Vector128 V0 = MakeVectorE0E1(_Z0, _Z1); - Vector128 V1 = MakeVectorE0(A0); - Vector128 V2 = MakeVectorE0(B0); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); - - AArch64.Vpart(0, 0, new Bits(_Z0)); - AArch64.Vpart(0, 1, new Bits(_Z1)); - AArch64.Vpart(1, 0, new Bits(A0)); - AArch64.Vpart(2, 0, new Bits(B0)); - SimdFp.Uabal_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); - - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); - }); - } - - [Test, Pairwise, Description("UABAL{2} ., ., .")] - public void Uabal_V_16B8H_8H4S_4S2D([ValueSource("_8B4H2S_")] [Random(1)] ulong _Z0, - [ValueSource("_8B4H2S_")] [Random(1)] ulong _Z1, - [ValueSource("_8B4H2S_")] [Random(1)] ulong A1, - [ValueSource("_8B4H2S_")] [Random(1)] ulong B1, - [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B, 8H, 4S> - { - uint Opcode = 0x6E225020; // UABAL2 V0.8H, V1.16B, V2.16B - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); - - Vector128 V0 = MakeVectorE0E1(_Z0, _Z1); - Vector128 V1 = MakeVectorE1(A1); - Vector128 V2 = MakeVectorE1(B1); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); - - AArch64.Vpart(0, 0, new Bits(_Z0)); - AArch64.Vpart(0, 1, new Bits(_Z1)); - AArch64.Vpart(1, 1, new Bits(A1)); - AArch64.Vpart(2, 1, new Bits(B1)); - SimdFp.Uabal_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); - - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); - }); - } - - [Test, Description("UABD ., ., .")] - public void Uabd_V_8B_4H_2S([ValueSource("_8B4H2S_")] [Random(1)] ulong A, - [ValueSource("_8B4H2S_")] [Random(1)] ulong B, + public void Uaba_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> { - uint Opcode = 0x2E227420; // UABD V0.8B, V1.8B, V2.8B - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); + uint opcode = 0x2E207C00; // UABA V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); - Vector128 V0 = MakeVectorE0E1(TestContext.CurrentContext.Random.NextULong(), - TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0(A); - Vector128 V2 = MakeVectorE0(B); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); - AArch64.V(1, new Bits(A)); - AArch64.V(2, new Bits(B)); - SimdFp.Uabd_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.V(64, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.Zero); - }); + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UABA ., ., .")] + public void Uaba_V_16B_8H_4S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B, 8H, 4S> + { + uint opcode = 0x6E207C00; // UABA V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UABAL{2} ., ., .")] + public void Uabal_V_8B8H_4H4S_2S2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B8H, 4H4S, 2S2D> + { + uint opcode = 0x2E205000; // UABAL V0.8H, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UABAL{2} ., ., .")] + public void Uabal_V_16B8H_8H4S_4S2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B8H, 8H4S, 4S2D> + { + uint opcode = 0x6E205000; // UABAL2 V0.8H, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE1(a); + V128 v2 = MakeVectorE1(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); } [Test, Pairwise, Description("UABD ., ., .")] - public void Uabd_V_16B_8H_4S([ValueSource("_8B4H2S_")] [Random(1)] ulong A0, - [ValueSource("_8B4H2S_")] [Random(1)] ulong A1, - [ValueSource("_8B4H2S_")] [Random(1)] ulong B0, - [ValueSource("_8B4H2S_")] [Random(1)] ulong B1, + public void Uabd_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x2E207400; // UABD V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UABD ., ., .")] + public void Uabd_V_16B_8H_4S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B, 8H, 4S> { - uint Opcode = 0x6E227420; // UABD V0.16B, V1.16B, V2.16B - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); + uint opcode = 0x6E207400; // UABD V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); - Vector128 V0 = MakeVectorE0E1(TestContext.CurrentContext.Random.NextULong(), - TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0E1(A0, A1); - Vector128 V2 = MakeVectorE0E1(B0, B1); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); - AArch64.Vpart(1, 0, new Bits(A0)); - AArch64.Vpart(1, 1, new Bits(A1)); - AArch64.Vpart(2, 0, new Bits(B0)); - AArch64.Vpart(2, 1, new Bits(B1)); - SimdFp.Uabd_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); - }); + CompareAgainstUnicorn(); } - [Test, Description("UABDL{2} ., ., .")] - public void Uabdl_V_8B8H_4H4S_2S2D([ValueSource("_8B4H2S_")] [Random(1)] ulong A0, - [ValueSource("_8B4H2S_")] [Random(1)] ulong B0, - [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + [Test, Pairwise, Description("UABDL{2} ., ., .")] + public void Uabdl_V_8B8H_4H4S_2S2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B8H, 4H4S, 2S2D> { - uint Opcode = 0x2E227020; // UABDL V0.8H, V1.8B, V2.8B - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); + uint opcode = 0x2E207000; // UABDL V0.8H, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); - Vector128 V0 = MakeVectorE0E1(TestContext.CurrentContext.Random.NextULong(), - TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE0(A0); - Vector128 V2 = MakeVectorE0(B0); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); - AArch64.Vpart(1, 0, new Bits(A0)); - AArch64.Vpart(2, 0, new Bits(B0)); - SimdFp.Uabdl_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); - }); + CompareAgainstUnicorn(); } - [Test, Description("UABDL{2} ., ., .")] - public void Uabdl_V_16B8H_8H4S_4S2D([ValueSource("_8B4H2S_")] [Random(1)] ulong A1, - [ValueSource("_8B4H2S_")] [Random(1)] ulong B1, - [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B, 8H, 4S> + [Test, Pairwise, Description("UABDL{2} ., ., .")] + public void Uabdl_V_16B8H_8H4S_4S2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B8H, 8H4S, 4S2D> { - uint Opcode = 0x6E227020; // UABDL2 V0.8H, V1.16B, V2.16B - Opcode |= ((size & 3) << 22); - Bits Op = new Bits(Opcode); + uint opcode = 0x6E207000; // UABDL2 V0.8H, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); - Vector128 V0 = MakeVectorE0E1(TestContext.CurrentContext.Random.NextULong(), - TestContext.CurrentContext.Random.NextULong()); - Vector128 V1 = MakeVectorE1(A1); - Vector128 V2 = MakeVectorE1(B1); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE1(a); + V128 v2 = MakeVectorE1(b); - AArch64.Vpart(1, 1, new Bits(A1)); - AArch64.Vpart(2, 1, new Bits(B1)); - SimdFp.Uabdl_V(Op[30], Op[23, 22], Op[20, 16], Op[9, 5], Op[4, 0]); + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); - Assert.Multiple(() => - { - Assert.That(GetVectorE0(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 0).ToUInt64())); - Assert.That(GetVectorE1(ThreadState.V0), Is.EqualTo(AArch64.Vpart(64, 0, 1).ToUInt64())); - }); + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UADDL{2} ., ., .")] + public void Uaddl_V_8B8H_4H4S_2S2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B8H, 4H4S, 2S2D> + { + uint opcode = 0x2E200000; // UADDL V0.8H, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UADDL{2} ., ., .")] + public void Uaddl_V_16B8H_8H4S_4S2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B8H, 8H4S, 4S2D> + { + uint opcode = 0x6E200000; // UADDL2 V0.8H, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE1(a); + V128 v2 = MakeVectorE1(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UADDW{2} ., ., .")] + public void Uaddw_V_8B8H8H_4H4S4S_2S2D2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_4H2S1D_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B8H8H, 4H4S4S, 2S2D2D> + { + uint opcode = 0x2E201000; // UADDW V0.8H, V0.8H, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UADDW{2} ., ., .")] + public void Uaddw_V_16B8H8H_8H4S4S_4S2D2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_4H2S1D_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B8H8H, 8H4S4S, 4S2D2D> + { + uint opcode = 0x6E201000; // UADDW2 V0.8H, V0.8H, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE1(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UHADD ., ., .")] + public void Uhadd_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x2E200400; // UHADD V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UHADD ., ., .")] + public void Uhadd_V_16B_8H_4S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B, 8H, 4S> + { + uint opcode = 0x6E200400; // UHADD V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UHSUB ., ., .")] + public void Uhsub_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x2E202400; // UHSUB V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UHSUB ., ., .")] + public void Uhsub_V_16B_8H_4S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B, 8H, 4S> + { + uint opcode = 0x6E202400; // UHSUB V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UQADD , , ")] + public void Uqadd_S_B_H_S_D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_1B1H1S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_1B1H1S1D_")] [Random(RndCnt)] ulong a, + [ValueSource("_1B1H1S1D_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // + { + uint opcode = 0x7E200C00; // UQADD B0, B0, B0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("UQADD ., ., .")] + public void Uqadd_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x2E200C00; // UQADD V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("UQADD ., ., .")] + public void Uqadd_V_16B_8H_4S_2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint opcode = 0x6E200C00; // UQADD V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("UQSUB , , ")] + public void Uqsub_S_B_H_S_D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_1B1H1S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_1B1H1S1D_")] [Random(RndCnt)] ulong a, + [ValueSource("_1B1H1S1D_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // + { + uint opcode = 0x7E202C00; // UQSUB B0, B0, B0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("UQSUB ., ., .")] + public void Uqsub_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x2E202C00; // UQSUB V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("UQSUB ., ., .")] + public void Uqsub_V_16B_8H_4S_2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint opcode = 0x6E202C00; // UQSUB V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("URHADD ., ., .")] + public void Urhadd_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x2E201400; // URHADD V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("URHADD ., ., .")] + public void Urhadd_V_16B_8H_4S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B, 8H, 4S> + { + uint opcode = 0x6E201400; // URHADD V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("USUBL{2} ., ., .")] + public void Usubl_V_8B8H_4H4S_2S2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B8H, 4H4S, 2S2D> + { + uint opcode = 0x2E202000; // USUBL V0.8H, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("USUBL{2} ., ., .")] + public void Usubl_V_16B8H_8H4S_4S2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B8H, 8H4S, 4S2D> + { + uint opcode = 0x6E202000; // USUBL2 V0.8H, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE1(a); + V128 v2 = MakeVectorE1(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("USUBW{2} ., ., .")] + public void Usubw_V_8B8H8H_4H4S4S_2S2D2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_4H2S1D_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B8H8H, 4H4S4S, 2S2D2D> + { + uint opcode = 0x2E203000; // USUBW V0.8H, V0.8H, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("USUBW{2} ., ., .")] + public void Usubw_V_16B8H8H_8H4S4S_4S2D2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_4H2S1D_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B8H8H, 8H4S4S, 4S2D2D> + { + uint opcode = 0x6E203000; // USUBW2 V0.8H, V0.8H, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE1(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UZP1 ., ., .")] + public void Uzp1_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x0E001800; // UZP1 V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UZP1 ., ., .")] + public void Uzp1_V_16B_8H_4S_2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint opcode = 0x4E001800; // UZP1 V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, ~z); + V128 v1 = MakeVectorE0E1(a, ~a); + V128 v2 = MakeVectorE0E1(b, ~b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UZP2 ., ., .")] + public void Uzp2_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x0E005800; // UZP2 V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UZP2 ., ., .")] + public void Uzp2_V_16B_8H_4S_2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint opcode = 0x4E005800; // UZP2 V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, ~z); + V128 v1 = MakeVectorE0E1(a, ~a); + V128 v2 = MakeVectorE0E1(b, ~b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("ZIP1 ., ., .")] + public void Zip1_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x0E003800; // ZIP1 V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("ZIP1 ., ., .")] + public void Zip1_V_16B_8H_4S_2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint opcode = 0x4E003800; // ZIP1 V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, ~z); + V128 v1 = MakeVectorE0E1(a, ~a); + V128 v2 = MakeVectorE0E1(b, ~b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("ZIP2 ., ., .")] + public void Zip2_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x0E007800; // ZIP2 V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("ZIP2 ., ., .")] + public void Zip2_V_16B_8H_4S_2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong a, + [ValueSource("_8B4H2S1D_")] [Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint opcode = 0x4E007800; // ZIP2 V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, ~z); + V128 v1 = MakeVectorE0E1(a, ~a); + V128 v2 = MakeVectorE0E1(b, ~b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); } #endif } diff --git a/Ryujinx.Tests/Cpu/CpuTestSimdRegElem.cs b/Ryujinx.Tests/Cpu/CpuTestSimdRegElem.cs new file mode 100644 index 0000000000..23e0e36465 --- /dev/null +++ b/Ryujinx.Tests/Cpu/CpuTestSimdRegElem.cs @@ -0,0 +1,190 @@ +#define SimdRegElem + +using ARMeilleure.State; + +using NUnit.Framework; + +namespace Ryujinx.Tests.Cpu +{ + [Category("SimdRegElem")] + public sealed class CpuTestSimdRegElem : CpuTest + { +#if SimdRegElem + +#region "ValueSource (Types)" + private static ulong[] _2S_() + { + return new ulong[] { 0x0000000000000000ul, 0x7FFFFFFF7FFFFFFFul, + 0x8000000080000000ul, 0xFFFFFFFFFFFFFFFFul }; + } + + private static ulong[] _4H_() + { + return new ulong[] { 0x0000000000000000ul, 0x7FFF7FFF7FFF7FFFul, + 0x8000800080008000ul, 0xFFFFFFFFFFFFFFFFul }; + } +#endregion + +#region "ValueSource (Opcodes)" + private static uint[] _Mla_Mls_Mul_Ve_4H_8H_() + { + return new uint[] + { + 0x2F400000u, // MLA V0.4H, V0.4H, V0.H[0] + 0x2F404000u, // MLS V0.4H, V0.4H, V0.H[0] + 0x0F408000u // MUL V0.4H, V0.4H, V0.H[0] + }; + } + + private static uint[] _Mla_Mls_Mul_Ve_2S_4S_() + { + return new uint[] + { + 0x2F800000u, // MLA V0.2S, V0.2S, V0.S[0] + 0x2F804000u, // MLS V0.2S, V0.2S, V0.S[0] + 0x0F808000u // MUL V0.2S, V0.2S, V0.S[0] + }; + } + + private static uint[] _SU_Mlal_Mlsl_Mull_Ve_4H4S_8H4S_() + { + return new uint[] + { + 0x0F402000u, // SMLAL V0.4S, V0.4H, V0.H[0] + 0x0F406000u, // SMLSL V0.4S, V0.4H, V0.H[0] + 0x0F40A000u, // SMULL V0.4S, V0.4H, V0.H[0] + 0x2F402000u, // UMLAL V0.4S, V0.4H, V0.H[0] + 0x2F406000u, // UMLSL V0.4S, V0.4H, V0.H[0] + 0x2F40A000u // UMULL V0.4S, V0.4H, V0.H[0] + }; + } + + private static uint[] _SU_Mlal_Mlsl_Mull_Ve_2S2D_4S2D_() + { + return new uint[] + { + 0x0F802000u, // SMLAL V0.2D, V0.2S, V0.S[0] + 0x0F806000u, // SMLSL V0.2D, V0.2S, V0.S[0] + 0x0F80A000u, // SMULL V0.2D, V0.2S, V0.S[0] + 0x2F802000u, // UMLAL V0.2D, V0.2S, V0.S[0] + 0x2F806000u, // UMLSL V0.2D, V0.2S, V0.S[0] + 0x2F80A000u // UMULL V0.2D, V0.2S, V0.S[0] + }; + } +#endregion + + private const int RndCnt = 2; + private const int RndCntIndex = 2; + + [Test, Pairwise] + public void Mla_Mls_Mul_Ve_4H_8H([ValueSource("_Mla_Mls_Mul_Ve_4H_8H_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_4H_")] [Random(RndCnt)] ulong z, + [ValueSource("_4H_")] [Random(RndCnt)] ulong a, + [ValueSource("_4H_")] [Random(RndCnt)] ulong b, + [Values(0u, 7u)] [Random(1u, 6u, RndCntIndex)] uint index, + [Values(0b0u, 0b1u)] uint q) // <4H, 8H> + { + uint h = (index >> 2) & 1; + uint l = (index >> 1) & 1; + uint m = index & 1; + + opcodes |= ((rm & 15) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (l << 21) | (m << 20) | (h << 11); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + V128 v2 = MakeVectorE0E1(b, b * h); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Mla_Mls_Mul_Ve_2S_4S([ValueSource("_Mla_Mls_Mul_Ve_2S_4S_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_2S_")] [Random(RndCnt)] ulong b, + [Values(0u, 1u, 2u, 3u)] uint index, + [Values(0b0u, 0b1u)] uint q) // <2S, 4S> + { + uint h = (index >> 1) & 1; + uint l = index & 1; + + opcodes |= ((rm & 15) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (l << 21) | (h << 11); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + V128 v2 = MakeVectorE0E1(b, b * h); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void SU_Mlal_Mlsl_Mull_Ve_4H4S_8H4S([ValueSource("_SU_Mlal_Mlsl_Mull_Ve_4H4S_8H4S_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_4H_")] [Random(RndCnt)] ulong z, + [ValueSource("_4H_")] [Random(RndCnt)] ulong a, + [ValueSource("_4H_")] [Random(RndCnt)] ulong b, + [Values(0u, 7u)] [Random(1u, 6u, RndCntIndex)] uint index, + [Values(0b0u, 0b1u)] uint q) // <4H4S, 8H4S> + { + uint h = (index >> 2) & 1; + uint l = (index >> 1) & 1; + uint m = index & 1; + + opcodes |= ((rm & 15) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (l << 21) | (m << 20) | (h << 11); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(q == 0u ? a : 0ul, q == 1u ? a : 0ul); + V128 v2 = MakeVectorE0E1(b, b * h); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void SU_Mlal_Mlsl_Mull_Ve_2S2D_4S2D([ValueSource("_SU_Mlal_Mlsl_Mull_Ve_2S2D_4S2D_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_2S_")] [Random(RndCnt)] ulong a, + [ValueSource("_2S_")] [Random(RndCnt)] ulong b, + [Values(0u, 1u, 2u, 3u)] uint index, + [Values(0b0u, 0b1u)] uint q) // <2S2D, 4S2D> + { + uint h = (index >> 1) & 1; + uint l = index & 1; + + opcodes |= ((rm & 15) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (l << 21) | (h << 11); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(q == 0u ? a : 0ul, q == 1u ? a : 0ul); + V128 v2 = MakeVectorE0E1(b, b * h); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } +#endif + } +} diff --git a/Ryujinx.Tests/Cpu/CpuTestSimdRegElemF.cs b/Ryujinx.Tests/Cpu/CpuTestSimdRegElemF.cs new file mode 100644 index 0000000000..38197fd5f4 --- /dev/null +++ b/Ryujinx.Tests/Cpu/CpuTestSimdRegElemF.cs @@ -0,0 +1,447 @@ +#define SimdRegElemF + +using ARMeilleure.State; + +using NUnit.Framework; + +using System.Collections.Generic; + +namespace Ryujinx.Tests.Cpu +{ + [Category("SimdRegElemF")] + public sealed class CpuTestSimdRegElemF : CpuTest + { +#if SimdRegElemF + +#region "ValueSource (Types)" + private static IEnumerable _1S_F_() + { + yield return 0x00000000FF7FFFFFul; // -Max Normal (float.MinValue) + yield return 0x0000000080800000ul; // -Min Normal + yield return 0x00000000807FFFFFul; // -Max Subnormal + yield return 0x0000000080000001ul; // -Min Subnormal (-float.Epsilon) + yield return 0x000000007F7FFFFFul; // +Max Normal (float.MaxValue) + yield return 0x0000000000800000ul; // +Min Normal + yield return 0x00000000007FFFFFul; // +Max Subnormal + yield return 0x0000000000000001ul; // +Min Subnormal (float.Epsilon) + + if (!NoZeros) + { + yield return 0x0000000080000000ul; // -Zero + yield return 0x0000000000000000ul; // +Zero + } + + if (!NoInfs) + { + yield return 0x00000000FF800000ul; // -Infinity + yield return 0x000000007F800000ul; // +Infinity + } + + if (!NoNaNs) + { + yield return 0x00000000FFC00000ul; // -QNaN (all zeros payload) (float.NaN) + yield return 0x00000000FFBFFFFFul; // -SNaN (all ones payload) + yield return 0x000000007FC00000ul; // +QNaN (all zeros payload) (-float.NaN) (DefaultNaN) + yield return 0x000000007FBFFFFFul; // +SNaN (all ones payload) + } + + for (int cnt = 1; cnt <= RndCnt; cnt++) + { + ulong grbg = TestContext.CurrentContext.Random.NextUInt(); + ulong rnd1 = GenNormalS(); + ulong rnd2 = GenSubnormalS(); + + yield return (grbg << 32) | rnd1; + yield return (grbg << 32) | rnd2; + } + } + + private static IEnumerable _2S_F_() + { + yield return 0xFF7FFFFFFF7FFFFFul; // -Max Normal (float.MinValue) + yield return 0x8080000080800000ul; // -Min Normal + yield return 0x807FFFFF807FFFFFul; // -Max Subnormal + yield return 0x8000000180000001ul; // -Min Subnormal (-float.Epsilon) + yield return 0x7F7FFFFF7F7FFFFFul; // +Max Normal (float.MaxValue) + yield return 0x0080000000800000ul; // +Min Normal + yield return 0x007FFFFF007FFFFFul; // +Max Subnormal + yield return 0x0000000100000001ul; // +Min Subnormal (float.Epsilon) + + if (!NoZeros) + { + yield return 0x8000000080000000ul; // -Zero + yield return 0x0000000000000000ul; // +Zero + } + + if (!NoInfs) + { + yield return 0xFF800000FF800000ul; // -Infinity + yield return 0x7F8000007F800000ul; // +Infinity + } + + if (!NoNaNs) + { + yield return 0xFFC00000FFC00000ul; // -QNaN (all zeros payload) (float.NaN) + yield return 0xFFBFFFFFFFBFFFFFul; // -SNaN (all ones payload) + yield return 0x7FC000007FC00000ul; // +QNaN (all zeros payload) (-float.NaN) (DefaultNaN) + yield return 0x7FBFFFFF7FBFFFFFul; // +SNaN (all ones payload) + } + + for (int cnt = 1; cnt <= RndCnt; cnt++) + { + ulong rnd1 = GenNormalS(); + ulong rnd2 = GenSubnormalS(); + + yield return (rnd1 << 32) | rnd1; + yield return (rnd2 << 32) | rnd2; + } + } + + private static IEnumerable _1D_F_() + { + yield return 0xFFEFFFFFFFFFFFFFul; // -Max Normal (double.MinValue) + yield return 0x8010000000000000ul; // -Min Normal + yield return 0x800FFFFFFFFFFFFFul; // -Max Subnormal + yield return 0x8000000000000001ul; // -Min Subnormal (-double.Epsilon) + yield return 0x7FEFFFFFFFFFFFFFul; // +Max Normal (double.MaxValue) + yield return 0x0010000000000000ul; // +Min Normal + yield return 0x000FFFFFFFFFFFFFul; // +Max Subnormal + yield return 0x0000000000000001ul; // +Min Subnormal (double.Epsilon) + + if (!NoZeros) + { + yield return 0x8000000000000000ul; // -Zero + yield return 0x0000000000000000ul; // +Zero + } + + if (!NoInfs) + { + yield return 0xFFF0000000000000ul; // -Infinity + yield return 0x7FF0000000000000ul; // +Infinity + } + + if (!NoNaNs) + { + yield return 0xFFF8000000000000ul; // -QNaN (all zeros payload) (double.NaN) + yield return 0xFFF7FFFFFFFFFFFFul; // -SNaN (all ones payload) + yield return 0x7FF8000000000000ul; // +QNaN (all zeros payload) (-double.NaN) (DefaultNaN) + yield return 0x7FF7FFFFFFFFFFFFul; // +SNaN (all ones payload) + } + + for (int cnt = 1; cnt <= RndCnt; cnt++) + { + ulong rnd1 = GenNormalD(); + ulong rnd2 = GenSubnormalD(); + + yield return rnd1; + yield return rnd2; + } + } +#endregion + +#region "ValueSource (Opcodes)" + private static uint[] _F_Mla_Mls_Se_S_() + { + return new uint[] + { + 0x5F821020u, // FMLA S0, S1, V2.S[0] + 0x5F825020u // FMLS S0, S1, V2.S[0] + }; + } + + private static uint[] _F_Mla_Mls_Se_D_() + { + return new uint[] + { + 0x5FC21020u, // FMLA D0, D1, V2.D[0] + 0x5FC25020u // FMLS D0, D1, V2.D[0] + }; + } + + private static uint[] _F_Mla_Mls_Ve_2S_4S_() + { + return new uint[] + { + 0x0F801000u, // FMLA V0.2S, V0.2S, V0.S[0] + 0x0F805000u // FMLS V0.2S, V0.2S, V0.S[0] + }; + } + + private static uint[] _F_Mla_Mls_Ve_2D_() + { + return new uint[] + { + 0x4FC01000u, // FMLA V0.2D, V0.2D, V0.D[0] + 0x4FC05000u // FMLS V0.2D, V0.2D, V0.D[0] + }; + } + + private static uint[] _F_Mul_Mulx_Se_S_() + { + return new uint[] + { + 0x5F829020u, // FMUL S0, S1, V2.S[0] + 0x7F829020u // FMULX S0, S1, V2.S[0] + }; + } + + private static uint[] _F_Mul_Mulx_Se_D_() + { + return new uint[] + { + 0x5FC29020u, // FMUL D0, D1, V2.D[0] + 0x7FC29020u // FMULX D0, D1, V2.D[0] + }; + } + + private static uint[] _F_Mul_Mulx_Ve_2S_4S_() + { + return new uint[] + { + 0x0F809000u, // FMUL V0.2S, V0.2S, V0.S[0] + 0x2F809000u // FMULX V0.2S, V0.2S, V0.S[0] + }; + } + + private static uint[] _F_Mul_Mulx_Ve_2D_() + { + return new uint[] + { + 0x4FC09000u, // FMUL V0.2D, V0.2D, V0.D[0] + 0x6FC09000u // FMULX V0.2D, V0.2D, V0.D[0] + }; + } +#endregion + + private const int RndCnt = 2; + + private static readonly bool NoZeros = false; + private static readonly bool NoInfs = false; + private static readonly bool NoNaNs = false; + + [Test, Pairwise] [Explicit] // Fused. + public void F_Mla_Mls_Se_S([ValueSource("_F_Mla_Mls_Se_S_")] uint opcodes, + [ValueSource("_1S_F_")] ulong z, + [ValueSource("_1S_F_")] ulong a, + [ValueSource("_2S_F_")] ulong b, + [Values(0u, 1u, 2u, 3u)] uint index) + { + uint h = (index >> 1) & 1; + uint l = index & 1; + + opcodes |= (l << 21) | (h << 11); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0E1(b, b * h); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, fpcr: fpcr); + + CompareAgainstUnicorn(Fpsr.Ioc | Fpsr.Idc, FpSkips.IfUnderflow, FpTolerances.UpToOneUlpsS); + } + + [Test, Pairwise] [Explicit] // Fused. + public void F_Mla_Mls_Se_D([ValueSource("_F_Mla_Mls_Se_D_")] uint opcodes, + [ValueSource("_1D_F_")] ulong z, + [ValueSource("_1D_F_")] ulong a, + [ValueSource("_1D_F_")] ulong b, + [Values(0u, 1u)] uint index) + { + uint h = index & 1; + + opcodes |= h << 11; + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0E1(b, b * h); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, fpcr: fpcr); + + CompareAgainstUnicorn(Fpsr.Ioc | Fpsr.Idc, FpSkips.IfUnderflow, FpTolerances.UpToOneUlpsD); + } + + [Test, Pairwise] [Explicit] // Fused. + public void F_Mla_Mls_Ve_2S_4S([ValueSource("_F_Mla_Mls_Ve_2S_4S_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_2S_F_")] ulong z, + [ValueSource("_2S_F_")] ulong a, + [ValueSource("_2S_F_")] ulong b, + [Values(0u, 1u, 2u, 3u)] uint index, + [Values(0b0u, 0b1u)] uint q) // <2S, 4S> + { + uint h = (index >> 1) & 1; + uint l = index & 1; + + opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (l << 21) | (h << 11); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + V128 v2 = MakeVectorE0E1(b, b * h); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, fpcr: fpcr); + + CompareAgainstUnicorn(Fpsr.Ioc | Fpsr.Idc, FpSkips.IfUnderflow, FpTolerances.UpToOneUlpsS); + } + + [Test, Pairwise] [Explicit] // Fused. + public void F_Mla_Mls_Ve_2D([ValueSource("_F_Mla_Mls_Ve_2D_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_1D_F_")] ulong z, + [ValueSource("_1D_F_")] ulong a, + [ValueSource("_1D_F_")] ulong b, + [Values(0u, 1u)] uint index) + { + uint h = index & 1; + + opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= h << 11; + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b * h); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, fpcr: fpcr); + + CompareAgainstUnicorn(Fpsr.Ioc | Fpsr.Idc, FpSkips.IfUnderflow, FpTolerances.UpToOneUlpsD); + } + + [Test, Pairwise] [Explicit] + public void F_Mul_Mulx_Se_S([ValueSource("_F_Mul_Mulx_Se_S_")] uint opcodes, + [ValueSource("_1S_F_")] ulong a, + [ValueSource("_2S_F_")] ulong b, + [Values(0u, 1u, 2u, 3u)] uint index) + { + uint h = (index >> 1) & 1; + uint l = index & 1; + + opcodes |= (l << 21) | (h << 11); + + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0E1(b, b * h); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Idc); + } + + [Test, Pairwise] [Explicit] + public void F_Mul_Mulx_Se_D([ValueSource("_F_Mul_Mulx_Se_D_")] uint opcodes, + [ValueSource("_1D_F_")] ulong a, + [ValueSource("_1D_F_")] ulong b, + [Values(0u, 1u)] uint index) + { + uint h = index & 1; + + opcodes |= h << 11; + + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE1(z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0E1(b, b * h); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Idc); + } + + [Test, Pairwise] [Explicit] + public void F_Mul_Mulx_Ve_2S_4S([ValueSource("_F_Mul_Mulx_Ve_2S_4S_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_2S_F_")] ulong z, + [ValueSource("_2S_F_")] ulong a, + [ValueSource("_2S_F_")] ulong b, + [Values(0u, 1u, 2u, 3u)] uint index, + [Values(0b0u, 0b1u)] uint q) // <2S, 4S> + { + uint h = (index >> 1) & 1; + uint l = index & 1; + + opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (l << 21) | (h << 11); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + V128 v2 = MakeVectorE0E1(b, b * h); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Idc); + } + + [Test, Pairwise] [Explicit] + public void F_Mul_Mulx_Ve_2D([ValueSource("_F_Mul_Mulx_Ve_2D_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource("_1D_F_")] ulong z, + [ValueSource("_1D_F_")] ulong a, + [ValueSource("_1D_F_")] ulong b, + [Values(0u, 1u)] uint index) + { + uint h = index & 1; + + opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= h << 11; + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b * h); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Idc); + } +#endif + } +} diff --git a/Ryujinx.Tests/Cpu/CpuTestSimdShImm.cs b/Ryujinx.Tests/Cpu/CpuTestSimdShImm.cs new file mode 100644 index 0000000000..1d208d6958 --- /dev/null +++ b/Ryujinx.Tests/Cpu/CpuTestSimdShImm.cs @@ -0,0 +1,1063 @@ +#define SimdShImm + +using ARMeilleure.State; + +using NUnit.Framework; + +using System; +using System.Collections.Generic; + +namespace Ryujinx.Tests.Cpu +{ + [Category("SimdShImm")] + public sealed class CpuTestSimdShImm : CpuTest + { +#if SimdShImm + +#region "ValueSource (Types)" + private static ulong[] _1D_() + { + return new ulong[] { 0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul }; + } + + private static ulong[] _1H_() + { + return new ulong[] { 0x0000000000000000ul, 0x0000000000007FFFul, + 0x0000000000008000ul, 0x000000000000FFFFul }; + } + + private static ulong[] _1S_() + { + return new ulong[] { 0x0000000000000000ul, 0x000000007FFFFFFFul, + 0x0000000080000000ul, 0x00000000FFFFFFFFul }; + } + + private static ulong[] _2S_() + { + return new ulong[] { 0x0000000000000000ul, 0x7FFFFFFF7FFFFFFFul, + 0x8000000080000000ul, 0xFFFFFFFFFFFFFFFFul }; + } + + private static ulong[] _4H_() + { + return new ulong[] { 0x0000000000000000ul, 0x7FFF7FFF7FFF7FFFul, + 0x8000800080008000ul, 0xFFFFFFFFFFFFFFFFul }; + } + + private static ulong[] _8B_() + { + return new ulong[] { 0x0000000000000000ul, 0x7F7F7F7F7F7F7F7Ful, + 0x8080808080808080ul, 0xFFFFFFFFFFFFFFFFul }; + } + + private static IEnumerable _2S_F_W_() + { + // int + yield return 0xCF000001CF000001ul; // -2.1474839E9f (-2147483904) + yield return 0xCF000000CF000000ul; // -2.14748365E9f (-2147483648) + yield return 0xCEFFFFFFCEFFFFFFul; // -2.14748352E9f (-2147483520) + yield return 0x4F0000014F000001ul; // 2.1474839E9f (2147483904) + yield return 0x4F0000004F000000ul; // 2.14748365E9f (2147483648) + yield return 0x4EFFFFFF4EFFFFFFul; // 2.14748352E9f (2147483520) + + // uint + yield return 0x4F8000014F800001ul; // 4.2949678E9f (4294967808) + yield return 0x4F8000004F800000ul; // 4.2949673E9f (4294967296) + yield return 0x4F7FFFFF4F7FFFFFul; // 4.29496704E9f (4294967040) + + yield return 0xFF7FFFFFFF7FFFFFul; // -Max Normal (float.MinValue) + yield return 0x8080000080800000ul; // -Min Normal + yield return 0x807FFFFF807FFFFFul; // -Max Subnormal + yield return 0x8000000180000001ul; // -Min Subnormal (-float.Epsilon) + yield return 0x7F7FFFFF7F7FFFFFul; // +Max Normal (float.MaxValue) + yield return 0x0080000000800000ul; // +Min Normal + yield return 0x007FFFFF007FFFFFul; // +Max Subnormal + yield return 0x0000000100000001ul; // +Min Subnormal (float.Epsilon) + + if (!NoZeros) + { + yield return 0x8000000080000000ul; // -Zero + yield return 0x0000000000000000ul; // +Zero + } + + if (!NoInfs) + { + yield return 0xFF800000FF800000ul; // -Infinity + yield return 0x7F8000007F800000ul; // +Infinity + } + + if (!NoNaNs) + { + yield return 0xFFC00000FFC00000ul; // -QNaN (all zeros payload) (float.NaN) + yield return 0xFFBFFFFFFFBFFFFFul; // -SNaN (all ones payload) + yield return 0x7FC000007FC00000ul; // +QNaN (all zeros payload) (-float.NaN) (DefaultNaN) + yield return 0x7FBFFFFF7FBFFFFFul; // +SNaN (all ones payload) + } + + for (int cnt = 1; cnt <= RndCnt; cnt++) + { + ulong rnd1 = (uint)BitConverter.SingleToInt32Bits( + (float)((int)TestContext.CurrentContext.Random.NextUInt())); + ulong rnd2 = (uint)BitConverter.SingleToInt32Bits( + (float)((uint)TestContext.CurrentContext.Random.NextUInt())); + + ulong rnd3 = GenNormalS(); + ulong rnd4 = GenSubnormalS(); + + yield return (rnd1 << 32) | rnd1; + yield return (rnd2 << 32) | rnd2; + + yield return (rnd3 << 32) | rnd3; + yield return (rnd4 << 32) | rnd4; + } + } + + private static IEnumerable _1D_F_X_() + { + // long + yield return 0xC3E0000000000001ul; // -9.2233720368547780E18d (-9223372036854778000) + yield return 0xC3E0000000000000ul; // -9.2233720368547760E18d (-9223372036854776000) + yield return 0xC3DFFFFFFFFFFFFFul; // -9.2233720368547750E18d (-9223372036854775000) + yield return 0x43E0000000000001ul; // 9.2233720368547780E18d (9223372036854778000) + yield return 0x43E0000000000000ul; // 9.2233720368547760E18d (9223372036854776000) + yield return 0x43DFFFFFFFFFFFFFul; // 9.2233720368547750E18d (9223372036854775000) + + // ulong + yield return 0x43F0000000000001ul; // 1.8446744073709556e19d (18446744073709556000) + yield return 0x43F0000000000000ul; // 1.8446744073709552E19d (18446744073709552000) + yield return 0x43EFFFFFFFFFFFFFul; // 1.8446744073709550e19d (18446744073709550000) + + yield return 0xFFEFFFFFFFFFFFFFul; // -Max Normal (double.MinValue) + yield return 0x8010000000000000ul; // -Min Normal + yield return 0x800FFFFFFFFFFFFFul; // -Max Subnormal + yield return 0x8000000000000001ul; // -Min Subnormal (-double.Epsilon) + yield return 0x7FEFFFFFFFFFFFFFul; // +Max Normal (double.MaxValue) + yield return 0x0010000000000000ul; // +Min Normal + yield return 0x000FFFFFFFFFFFFFul; // +Max Subnormal + yield return 0x0000000000000001ul; // +Min Subnormal (double.Epsilon) + + if (!NoZeros) + { + yield return 0x8000000000000000ul; // -Zero + yield return 0x0000000000000000ul; // +Zero + } + + if (!NoInfs) + { + yield return 0xFFF0000000000000ul; // -Infinity + yield return 0x7FF0000000000000ul; // +Infinity + } + + if (!NoNaNs) + { + yield return 0xFFF8000000000000ul; // -QNaN (all zeros payload) (double.NaN) + yield return 0xFFF7FFFFFFFFFFFFul; // -SNaN (all ones payload) + yield return 0x7FF8000000000000ul; // +QNaN (all zeros payload) (-double.NaN) (DefaultNaN) + yield return 0x7FF7FFFFFFFFFFFFul; // +SNaN (all ones payload) + } + + for (int cnt = 1; cnt <= RndCnt; cnt++) + { + ulong rnd1 = (ulong)BitConverter.DoubleToInt64Bits( + (double)((long)TestContext.CurrentContext.Random.NextULong())); + ulong rnd2 = (ulong)BitConverter.DoubleToInt64Bits( + (double)((ulong)TestContext.CurrentContext.Random.NextULong())); + + ulong rnd3 = GenNormalD(); + ulong rnd4 = GenSubnormalD(); + + yield return rnd1; + yield return rnd2; + + yield return rnd3; + yield return rnd4; + } + } +#endregion + +#region "ValueSource (Opcodes)" + private static uint[] _F_Cvt_Z_SU_V_Fixed_2S_4S_() + { + return new uint[] + { + 0x0F20FC00u, // FCVTZS V0.2S, V0.2S, #32 + 0x2F20FC00u // FCVTZU V0.2S, V0.2S, #32 + }; + } + + private static uint[] _F_Cvt_Z_SU_V_Fixed_2D_() + { + return new uint[] + { + 0x4F40FC00u, // FCVTZS V0.2D, V0.2D, #64 + 0x6F40FC00u // FCVTZU V0.2D, V0.2D, #64 + }; + } + + private static uint[] _SU_Cvt_F_V_Fixed_2S_4S_() + { + return new uint[] + { + 0x0F20E400u, // SCVTF V0.2S, V0.2S, #32 + 0x2F20E400u // UCVTF V0.2S, V0.2S, #32 + }; + } + + private static uint[] _SU_Cvt_F_V_Fixed_2D_() + { + return new uint[] + { + 0x4F40E400u, // SCVTF V0.2D, V0.2D, #64 + 0x6F40E400u // UCVTF V0.2D, V0.2D, #64 + }; + } + + private static uint[] _Shl_Sli_S_D_() + { + return new uint[] + { + 0x5F405400u, // SHL D0, D0, #0 + 0x7F405400u // SLI D0, D0, #0 + }; + } + + private static uint[] _Shl_Sli_V_8B_16B_() + { + return new uint[] + { + 0x0F085400u, // SHL V0.8B, V0.8B, #0 + 0x2F085400u // SLI V0.8B, V0.8B, #0 + }; + } + + private static uint[] _Shl_Sli_V_4H_8H_() + { + return new uint[] + { + 0x0F105400u, // SHL V0.4H, V0.4H, #0 + 0x2F105400u // SLI V0.4H, V0.4H, #0 + }; + } + + private static uint[] _Shl_Sli_V_2S_4S_() + { + return new uint[] + { + 0x0F205400u, // SHL V0.2S, V0.2S, #0 + 0x2F205400u // SLI V0.2S, V0.2S, #0 + }; + } + + private static uint[] _Shl_Sli_V_2D_() + { + return new uint[] + { + 0x4F405400u, // SHL V0.2D, V0.2D, #0 + 0x6F405400u // SLI V0.2D, V0.2D, #0 + }; + } + + private static uint[] _SU_Shll_V_8B8H_16B8H_() + { + return new uint[] + { + 0x0F08A400u, // SSHLL V0.8H, V0.8B, #0 + 0x2F08A400u // USHLL V0.8H, V0.8B, #0 + }; + } + + private static uint[] _SU_Shll_V_4H4S_8H4S_() + { + return new uint[] + { + 0x0F10A400u, // SSHLL V0.4S, V0.4H, #0 + 0x2F10A400u // USHLL V0.4S, V0.4H, #0 + }; + } + + private static uint[] _SU_Shll_V_2S2D_4S2D_() + { + return new uint[] + { + 0x0F20A400u, // SSHLL V0.2D, V0.2S, #0 + 0x2F20A400u // USHLL V0.2D, V0.2S, #0 + }; + } + + private static uint[] _ShrImm_Sri_S_D_() + { + return new uint[] + { + 0x7F404400u, // SRI D0, D0, #64 + 0x5F402400u, // SRSHR D0, D0, #64 + 0x5F403400u, // SRSRA D0, D0, #64 + 0x5F400400u, // SSHR D0, D0, #64 + 0x5F401400u, // SSRA D0, D0, #64 + 0x7F402400u, // URSHR D0, D0, #64 + 0x7F403400u, // URSRA D0, D0, #64 + 0x7F400400u, // USHR D0, D0, #64 + 0x7F401400u // USRA D0, D0, #64 + }; + } + + private static uint[] _ShrImm_Sri_V_8B_16B_() + { + return new uint[] + { + 0x2F084400u, // SRI V0.8B, V0.8B, #8 + 0x0F082400u, // SRSHR V0.8B, V0.8B, #8 + 0x0F083400u, // SRSRA V0.8B, V0.8B, #8 + 0x0F080400u, // SSHR V0.8B, V0.8B, #8 + 0x0F081400u, // SSRA V0.8B, V0.8B, #8 + 0x2F082400u, // URSHR V0.8B, V0.8B, #8 + 0x2F083400u, // URSRA V0.8B, V0.8B, #8 + 0x2F080400u, // USHR V0.8B, V0.8B, #8 + 0x2F081400u // USRA V0.8B, V0.8B, #8 + }; + } + + private static uint[] _ShrImm_Sri_V_4H_8H_() + { + return new uint[] + { + 0x2F104400u, // SRI V0.4H, V0.4H, #16 + 0x0F102400u, // SRSHR V0.4H, V0.4H, #16 + 0x0F103400u, // SRSRA V0.4H, V0.4H, #16 + 0x0F100400u, // SSHR V0.4H, V0.4H, #16 + 0x0F101400u, // SSRA V0.4H, V0.4H, #16 + 0x2F102400u, // URSHR V0.4H, V0.4H, #16 + 0x2F103400u, // URSRA V0.4H, V0.4H, #16 + 0x2F100400u, // USHR V0.4H, V0.4H, #16 + 0x2F101400u // USRA V0.4H, V0.4H, #16 + }; + } + + private static uint[] _ShrImm_Sri_V_2S_4S_() + { + return new uint[] + { + 0x2F204400u, // SRI V0.2S, V0.2S, #32 + 0x0F202400u, // SRSHR V0.2S, V0.2S, #32 + 0x0F203400u, // SRSRA V0.2S, V0.2S, #32 + 0x0F200400u, // SSHR V0.2S, V0.2S, #32 + 0x0F201400u, // SSRA V0.2S, V0.2S, #32 + 0x2F202400u, // URSHR V0.2S, V0.2S, #32 + 0x2F203400u, // URSRA V0.2S, V0.2S, #32 + 0x2F200400u, // USHR V0.2S, V0.2S, #32 + 0x2F201400u // USRA V0.2S, V0.2S, #32 + }; + } + + private static uint[] _ShrImm_Sri_V_2D_() + { + return new uint[] + { + 0x6F404400u, // SRI V0.2D, V0.2D, #64 + 0x4F402400u, // SRSHR V0.2D, V0.2D, #64 + 0x4F403400u, // SRSRA V0.2D, V0.2D, #64 + 0x4F400400u, // SSHR V0.2D, V0.2D, #64 + 0x4F401400u, // SSRA V0.2D, V0.2D, #64 + 0x6F402400u, // URSHR V0.2D, V0.2D, #64 + 0x6F403400u, // URSRA V0.2D, V0.2D, #64 + 0x6F400400u, // USHR V0.2D, V0.2D, #64 + 0x6F401400u // USRA V0.2D, V0.2D, #64 + }; + } + + private static uint[] _ShrImmNarrow_V_8H8B_8H16B_() + { + return new uint[] + { + 0x0F088C00u, // RSHRN V0.8B, V0.8H, #8 + 0x0F088400u // SHRN V0.8B, V0.8H, #8 + }; + } + + private static uint[] _ShrImmNarrow_V_4S4H_4S8H_() + { + return new uint[] + { + 0x0F108C00u, // RSHRN V0.4H, V0.4S, #16 + 0x0F108400u // SHRN V0.4H, V0.4S, #16 + }; + } + + private static uint[] _ShrImmNarrow_V_2D2S_2D4S_() + { + return new uint[] + { + 0x0F208C00u, // RSHRN V0.2S, V0.2D, #32 + 0x0F208400u // SHRN V0.2S, V0.2D, #32 + }; + } + + private static uint[] _ShrImmSaturatingNarrow_S_HB_() + { + return new uint[] + { + 0x5F089C00u, // SQRSHRN B0, H0, #8 + 0x7F089C00u, // UQRSHRN B0, H0, #8 + 0x7F088C00u, // SQRSHRUN B0, H0, #8 + 0x5F089400u, // SQSHRN B0, H0, #8 + 0x7F089400u, // UQSHRN B0, H0, #8 + 0x7F088400u // SQSHRUN B0, H0, #8 + }; + } + + private static uint[] _ShrImmSaturatingNarrow_S_SH_() + { + return new uint[] + { + 0x5F109C00u, // SQRSHRN H0, S0, #16 + 0x7F109C00u, // UQRSHRN H0, S0, #16 + 0x7F108C00u, // SQRSHRUN H0, S0, #16 + 0x5F109400u, // SQSHRN H0, S0, #16 + 0x7F109400u, // UQSHRN H0, S0, #16 + 0x7F108400u // SQSHRUN H0, S0, #16 + }; + } + + private static uint[] _ShrImmSaturatingNarrow_S_DS_() + { + return new uint[] + { + 0x5F209C00u, // SQRSHRN S0, D0, #32 + 0x7F209C00u, // UQRSHRN S0, D0, #32 + 0x7F208C00u, // SQRSHRUN S0, D0, #32 + 0x5F209400u, // SQSHRN S0, D0, #32 + 0x7F209400u, // UQSHRN S0, D0, #32 + 0x7F208400u // SQSHRUN S0, D0, #32 + }; + } + + private static uint[] _ShrImmSaturatingNarrow_V_8H8B_8H16B_() + { + return new uint[] + { + 0x0F089C00u, // SQRSHRN V0.8B, V0.8H, #8 + 0x2F089C00u, // UQRSHRN V0.8B, V0.8H, #8 + 0x2F088C00u, // SQRSHRUN V0.8B, V0.8H, #8 + 0x0F089400u, // SQSHRN V0.8B, V0.8H, #8 + 0x2F089400u, // UQSHRN V0.8B, V0.8H, #8 + 0x2F088400u // SQSHRUN V0.8B, V0.8H, #8 + }; + } + + private static uint[] _ShrImmSaturatingNarrow_V_4S4H_4S8H_() + { + return new uint[] + { + 0x0F109C00u, // SQRSHRN V0.4H, V0.4S, #16 + 0x2F109C00u, // UQRSHRN V0.4H, V0.4S, #16 + 0x2F108C00u, // SQRSHRUN V0.4H, V0.4S, #16 + 0x0F109400u, // SQSHRN V0.4H, V0.4S, #16 + 0x2F109400u, // UQSHRN V0.4H, V0.4S, #16 + 0x2F108400u // SQSHRUN V0.4H, V0.4S, #16 + }; + } + + private static uint[] _ShrImmSaturatingNarrow_V_2D2S_2D4S_() + { + return new uint[] + { + 0x0F209C00u, // SQRSHRN V0.2S, V0.2D, #32 + 0x2F209C00u, // UQRSHRN V0.2S, V0.2D, #32 + 0x2F208C00u, // SQRSHRUN V0.2S, V0.2D, #32 + 0x0F209400u, // SQSHRN V0.2S, V0.2D, #32 + 0x2F209400u, // UQSHRN V0.2S, V0.2D, #32 + 0x2F208400u // SQSHRUN V0.2S, V0.2D, #32 + }; + } +#endregion + + private const int RndCnt = 2; + private const int RndCntFBits = 2; + private const int RndCntShift = 2; + + private static readonly bool NoZeros = false; + private static readonly bool NoInfs = false; + private static readonly bool NoNaNs = false; + + [Test, Pairwise] [Explicit] + public void F_Cvt_Z_SU_V_Fixed_2S_4S([ValueSource("_F_Cvt_Z_SU_V_Fixed_2S_4S_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_2S_F_W_")] ulong z, + [ValueSource("_2S_F_W_")] ulong a, + [Values(1u, 32u)] [Random(2u, 31u, RndCntFBits)] uint fBits, + [Values(0b0u, 0b1u)] uint q) // <2S, 4S> + { + uint immHb = (64 - fBits) & 0x7F; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (immHb << 16); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] [Explicit] + public void F_Cvt_Z_SU_V_Fixed_2D([ValueSource("_F_Cvt_Z_SU_V_Fixed_2D_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_1D_F_X_")] ulong z, + [ValueSource("_1D_F_X_")] ulong a, + [Values(1u, 64u)] [Random(2u, 63u, RndCntFBits)] uint fBits) + { + uint immHb = (128 - fBits) & 0x7F; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (immHb << 16); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] [Explicit] + public void SU_Cvt_F_V_Fixed_2S_4S([ValueSource("_SU_Cvt_F_V_Fixed_2S_4S_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_2S_")] [Random(RndCnt)] ulong a, + [Values(1u, 32u)] [Random(2u, 31u, RndCntFBits)] uint fBits, + [Values(0b0u, 0b1u)] uint q) // <2S, 4S> + { + uint immHb = (64 - fBits) & 0x7F; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (immHb << 16); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] [Explicit] + public void SU_Cvt_F_V_Fixed_2D([ValueSource("_SU_Cvt_F_V_Fixed_2D_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_1D_")] [Random(RndCnt)] ulong a, + [Values(1u, 64u)] [Random(2u, 63u, RndCntFBits)] uint fBits) + { + uint immHb = (128 - fBits) & 0x7F; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (immHb << 16); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Shl_Sli_S_D([ValueSource("_Shl_Sli_S_D_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_1D_")] [Random(RndCnt)] ulong a, + [Values(0u, 63u)] [Random(1u, 62u, RndCntShift)] uint shift) + { + uint immHb = (64 + shift) & 0x7F; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (immHb << 16); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Shl_Sli_V_8B_16B([ValueSource("_Shl_Sli_V_8B_16B_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_8B_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B_")] [Random(RndCnt)] ulong a, + [Values(0u, 7u)] [Random(1u, 6u, RndCntShift)] uint shift, + [Values(0b0u, 0b1u)] uint q) // <8B, 16B> + { + uint immHb = (8 + shift) & 0x7F; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (immHb << 16); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Shl_Sli_V_4H_8H([ValueSource("_Shl_Sli_V_4H_8H_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_4H_")] [Random(RndCnt)] ulong z, + [ValueSource("_4H_")] [Random(RndCnt)] ulong a, + [Values(0u, 15u)] [Random(1u, 14u, RndCntShift)] uint shift, + [Values(0b0u, 0b1u)] uint q) // <4H, 8H> + { + uint immHb = (16 + shift) & 0x7F; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (immHb << 16); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Shl_Sli_V_2S_4S([ValueSource("_Shl_Sli_V_2S_4S_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_2S_")] [Random(RndCnt)] ulong a, + [Values(0u, 31u)] [Random(1u, 30u, RndCntShift)] uint shift, + [Values(0b0u, 0b1u)] uint q) // <2S, 4S> + { + uint immHb = (32 + shift) & 0x7F; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (immHb << 16); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Shl_Sli_V_2D([ValueSource("_Shl_Sli_V_2D_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_1D_")] [Random(RndCnt)] ulong a, + [Values(0u, 63u)] [Random(1u, 62u, RndCntShift)] uint shift) + { + uint immHb = (64 + shift) & 0x7F; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (immHb << 16); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void SU_Shll_V_8B8H_16B8H([ValueSource("_SU_Shll_V_8B8H_16B8H_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_8B_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B_")] [Random(RndCnt)] ulong a, + [Values(0u, 7u)] [Random(1u, 6u, RndCntShift)] uint shift, + [Values(0b0u, 0b1u)] uint q) // <8B8H, 16B8H> + { + uint immHb = (8 + shift) & 0x7F; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (immHb << 16); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(q == 0u ? a : 0ul, q == 1u ? a : 0ul); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void SU_Shll_V_4H4S_8H4S([ValueSource("_SU_Shll_V_4H4S_8H4S_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_4H_")] [Random(RndCnt)] ulong z, + [ValueSource("_4H_")] [Random(RndCnt)] ulong a, + [Values(0u, 15u)] [Random(1u, 14u, RndCntShift)] uint shift, + [Values(0b0u, 0b1u)] uint q) // <4H4S, 8H4S> + { + uint immHb = (16 + shift) & 0x7F; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (immHb << 16); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(q == 0u ? a : 0ul, q == 1u ? a : 0ul); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void SU_Shll_V_2S2D_4S2D([ValueSource("_SU_Shll_V_2S2D_4S2D_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_2S_")] [Random(RndCnt)] ulong a, + [Values(0u, 31u)] [Random(1u, 30u, RndCntShift)] uint shift, + [Values(0b0u, 0b1u)] uint q) // <2S2D, 4S2D> + { + uint immHb = (32 + shift) & 0x7F; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (immHb << 16); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(q == 0u ? a : 0ul, q == 1u ? a : 0ul); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void ShrImm_Sri_S_D([ValueSource("_ShrImm_Sri_S_D_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_1D_")] [Random(RndCnt)] ulong a, + [Values(1u, 64u)] [Random(2u, 63u, RndCntShift)] uint shift) + { + uint immHb = (128 - shift) & 0x7F; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (immHb << 16); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void ShrImm_Sri_V_8B_16B([ValueSource("_ShrImm_Sri_V_8B_16B_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_8B_")] [Random(RndCnt)] ulong z, + [ValueSource("_8B_")] [Random(RndCnt)] ulong a, + [Values(1u, 8u)] [Random(2u, 7u, RndCntShift)] uint shift, + [Values(0b0u, 0b1u)] uint q) // <8B, 16B> + { + uint immHb = (16 - shift) & 0x7F; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (immHb << 16); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void ShrImm_Sri_V_4H_8H([ValueSource("_ShrImm_Sri_V_4H_8H_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_4H_")] [Random(RndCnt)] ulong z, + [ValueSource("_4H_")] [Random(RndCnt)] ulong a, + [Values(1u, 16u)] [Random(2u, 15u, RndCntShift)] uint shift, + [Values(0b0u, 0b1u)] uint q) // <4H, 8H> + { + uint immHb = (32 - shift) & 0x7F; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (immHb << 16); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void ShrImm_Sri_V_2S_4S([ValueSource("_ShrImm_Sri_V_2S_4S_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_2S_")] [Random(RndCnt)] ulong a, + [Values(1u, 32u)] [Random(2u, 31u, RndCntShift)] uint shift, + [Values(0b0u, 0b1u)] uint q) // <2S, 4S> + { + uint immHb = (64 - shift) & 0x7F; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (immHb << 16); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void ShrImm_Sri_V_2D([ValueSource("_ShrImm_Sri_V_2D_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_1D_")] [Random(RndCnt)] ulong a, + [Values(1u, 64u)] [Random(2u, 63u, RndCntShift)] uint shift) + { + uint immHb = (128 - shift) & 0x7F; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (immHb << 16); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void ShrImmNarrow_V_8H8B_8H16B([ValueSource("_ShrImmNarrow_V_8H8B_8H16B_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_4H_")] [Random(RndCnt)] ulong z, + [ValueSource("_4H_")] [Random(RndCnt)] ulong a, + [Values(1u, 8u)] [Random(2u, 7u, RndCntShift)] uint shift, + [Values(0b0u, 0b1u)] uint q) // <8H8B, 8H16B> + { + uint immHb = (16 - shift) & 0x7F; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (immHb << 16); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void ShrImmNarrow_V_4S4H_4S8H([ValueSource("_ShrImmNarrow_V_4S4H_4S8H_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_2S_")] [Random(RndCnt)] ulong a, + [Values(1u, 16u)] [Random(2u, 15u, RndCntShift)] uint shift, + [Values(0b0u, 0b1u)] uint q) // <4S4H, 4S8H> + { + uint immHb = (32 - shift) & 0x7F; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (immHb << 16); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void ShrImmNarrow_V_2D2S_2D4S([ValueSource("_ShrImmNarrow_V_2D2S_2D4S_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_1D_")] [Random(RndCnt)] ulong a, + [Values(1u, 32u)] [Random(2u, 31u, RndCntShift)] uint shift, + [Values(0b0u, 0b1u)] uint q) // <2D2S, 2D4S> + { + uint immHb = (64 - shift) & 0x7F; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (immHb << 16); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void ShrImmSaturatingNarrow_S_HB([ValueSource("_ShrImmSaturatingNarrow_S_HB_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_1H_")] [Random(RndCnt)] ulong z, + [ValueSource("_1H_")] [Random(RndCnt)] ulong a, + [Values(1u, 8u)] [Random(2u, 7u, RndCntShift)] uint shift) + { + uint immHb = (16 - shift) & 0x7F; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (immHb << 16); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise] + public void ShrImmSaturatingNarrow_S_SH([ValueSource("_ShrImmSaturatingNarrow_S_SH_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_1S_")] [Random(RndCnt)] ulong z, + [ValueSource("_1S_")] [Random(RndCnt)] ulong a, + [Values(1u, 16u)] [Random(2u, 15u, RndCntShift)] uint shift) + { + uint immHb = (32 - shift) & 0x7F; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (immHb << 16); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise] + public void ShrImmSaturatingNarrow_S_DS([ValueSource("_ShrImmSaturatingNarrow_S_DS_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_1D_")] [Random(RndCnt)] ulong a, + [Values(1u, 32u)] [Random(2u, 31u, RndCntShift)] uint shift) + { + uint immHb = (64 - shift) & 0x7F; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (immHb << 16); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise] + public void ShrImmSaturatingNarrow_V_8H8B_8H16B([ValueSource("_ShrImmSaturatingNarrow_V_8H8B_8H16B_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_4H_")] [Random(RndCnt)] ulong z, + [ValueSource("_4H_")] [Random(RndCnt)] ulong a, + [Values(1u, 8u)] [Random(2u, 7u, RndCntShift)] uint shift, + [Values(0b0u, 0b1u)] uint q) // <8H8B, 8H16B> + { + uint immHb = (16 - shift) & 0x7F; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (immHb << 16); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise] + public void ShrImmSaturatingNarrow_V_4S4H_4S8H([ValueSource("_ShrImmSaturatingNarrow_V_4S4H_4S8H_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_2S_")] [Random(RndCnt)] ulong z, + [ValueSource("_2S_")] [Random(RndCnt)] ulong a, + [Values(1u, 16u)] [Random(2u, 15u, RndCntShift)] uint shift, + [Values(0b0u, 0b1u)] uint q) // <4S4H, 4S8H> + { + uint immHb = (32 - shift) & 0x7F; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (immHb << 16); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise] + public void ShrImmSaturatingNarrow_V_2D2S_2D4S([ValueSource("_ShrImmSaturatingNarrow_V_2D2S_2D4S_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource("_1D_")] [Random(RndCnt)] ulong z, + [ValueSource("_1D_")] [Random(RndCnt)] ulong a, + [Values(1u, 32u)] [Random(2u, 31u, RndCntShift)] uint shift, + [Values(0b0u, 0b1u)] uint q) // <2D2S, 2D4S> + { + uint immHb = (64 - shift) & 0x7F; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (immHb << 16); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } +#endif + } +} diff --git a/Ryujinx.Tests/Cpu/CpuTestSimdTbl.cs b/Ryujinx.Tests/Cpu/CpuTestSimdTbl.cs new file mode 100644 index 0000000000..0daeab618b --- /dev/null +++ b/Ryujinx.Tests/Cpu/CpuTestSimdTbl.cs @@ -0,0 +1,321 @@ +#define SimdTbl + +using ARMeilleure.State; + +using NUnit.Framework; + +using System.Collections.Generic; + +namespace Ryujinx.Tests.Cpu +{ + [Category("SimdTbl")] + public sealed class CpuTestSimdTbl : CpuTest + { +#if SimdTbl + +#region "Helper methods" + private static ulong GenIdxsForTbls(int regs) + { + const byte idxInRngMin = (byte)0; + byte idxInRngMax = (byte)((16 * regs) - 1); + byte idxOutRngMin = (byte) (16 * regs); + const byte idxOutRngMax = (byte)255; + + ulong idxs = 0ul; + + for (int cnt = 1; cnt <= 8; cnt++) + { + ulong idxInRng = (ulong)TestContext.CurrentContext.Random.NextByte(idxInRngMin, idxInRngMax); + ulong idxOutRng = (ulong)TestContext.CurrentContext.Random.NextByte(idxOutRngMin, idxOutRngMax); + + ulong idx = TestContext.CurrentContext.Random.NextBool() ? idxInRng : idxOutRng; + + idxs = (idxs << 8) | idx; + } + + return idxs; + } +#endregion + +#region "ValueSource (Types)" + private static ulong[] _8B_() + { + return new ulong[] { 0x0000000000000000ul, 0x7F7F7F7F7F7F7F7Ful, + 0x8080808080808080ul, 0xFFFFFFFFFFFFFFFFul }; + } + + private static IEnumerable _GenIdxsForTbl1_() + { + yield return 0x0000000000000000ul; + yield return 0x7F7F7F7F7F7F7F7Ful; + yield return 0x8080808080808080ul; + yield return 0xFFFFFFFFFFFFFFFFul; + + for (int cnt = 1; cnt <= RndCntIdxs; cnt++) + { + yield return GenIdxsForTbls(regs: 1); + } + } + + private static IEnumerable _GenIdxsForTbl2_() + { + yield return 0x0000000000000000ul; + yield return 0x7F7F7F7F7F7F7F7Ful; + yield return 0x8080808080808080ul; + yield return 0xFFFFFFFFFFFFFFFFul; + + for (int cnt = 1; cnt <= RndCntIdxs; cnt++) + { + yield return GenIdxsForTbls(regs: 2); + } + } + + private static IEnumerable _GenIdxsForTbl3_() + { + yield return 0x0000000000000000ul; + yield return 0x7F7F7F7F7F7F7F7Ful; + yield return 0x8080808080808080ul; + yield return 0xFFFFFFFFFFFFFFFFul; + + for (int cnt = 1; cnt <= RndCntIdxs; cnt++) + { + yield return GenIdxsForTbls(regs: 3); + } + } + + private static IEnumerable _GenIdxsForTbl4_() + { + yield return 0x0000000000000000ul; + yield return 0x7F7F7F7F7F7F7F7Ful; + yield return 0x8080808080808080ul; + yield return 0xFFFFFFFFFFFFFFFFul; + + for (int cnt = 1; cnt <= RndCntIdxs; cnt++) + { + yield return GenIdxsForTbls(regs: 4); + } + } +#endregion + +#region "ValueSource (Opcodes)" + private static uint[] _SingleRegisterTable_V_8B_16B_() + { + return new uint[] + { + 0x0E000000u, // TBL V0.8B, { V0.16B }, V0.8B + 0x0E001000u // TBX V0.8B, { V0.16B }, V0.8B + }; + } + + private static uint[] _TwoRegisterTable_V_8B_16B_() + { + return new uint[] + { + 0x0E002000u, // TBL V0.8B, { V0.16B, V1.16B }, V0.8B + 0x0E003000u // TBX V0.8B, { V0.16B, V1.16B }, V0.8B + }; + } + + private static uint[] _ThreeRegisterTable_V_8B_16B_() + { + return new uint[] + { + 0x0E004000u, // TBL V0.8B, { V0.16B, V1.16B, V2.16B }, V0.8B + 0x0E005000u // TBX V0.8B, { V0.16B, V1.16B, V2.16B }, V0.8B + }; + } + + private static uint[] _FourRegisterTable_V_8B_16B_() + { + return new uint[] + { + 0x0E006000u, // TBL V0.8B, { V0.16B, V1.16B, V2.16B, V3.16B }, V0.8B + 0x0E006000u // TBX V0.8B, { V0.16B, V1.16B, V2.16B, V3.16B }, V0.8B + }; + } +#endregion + + private const int RndCntDest = 2; + private const int RndCntTbls = 2; + private const int RndCntIdxs = 2; + + [Test, Pairwise] + public void SingleRegisterTable_V_8B_16B([ValueSource("_SingleRegisterTable_V_8B_16B_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u)] uint rn, + [Values(2u)] uint rm, + [ValueSource("_8B_")] [Random(RndCntDest)] ulong z, + [ValueSource("_8B_")] [Random(RndCntTbls)] ulong table0, + [ValueSource("_GenIdxsForTbl1_")] ulong indexes, + [Values(0b0u, 0b1u)] uint q) // <8B, 16B> + { + opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(table0, table0); + V128 v2 = MakeVectorE0E1(indexes, q == 1u ? indexes : 0ul); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void TwoRegisterTable_V_8B_16B([ValueSource("_TwoRegisterTable_V_8B_16B_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u)] uint rn, + [Values(3u)] uint rm, + [ValueSource("_8B_")] [Random(RndCntDest)] ulong z, + [ValueSource("_8B_")] [Random(RndCntTbls)] ulong table0, + [ValueSource("_8B_")] [Random(RndCntTbls)] ulong table1, + [ValueSource("_GenIdxsForTbl2_")] ulong indexes, + [Values(0b0u, 0b1u)] uint q) // <8B, 16B> + { + opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(table0, table0); + V128 v2 = MakeVectorE0E1(table1, table1); + V128 v3 = MakeVectorE0E1(indexes, q == 1u ? indexes : 0ul); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, v3: v3); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Mod_TwoRegisterTable_V_8B_16B([ValueSource("_TwoRegisterTable_V_8B_16B_")] uint opcodes, + [Values(30u, 1u)] uint rd, + [Values(31u)] uint rn, + [Values(1u, 30u)] uint rm, + [ValueSource("_8B_")] [Random(RndCntDest)] ulong z, + [ValueSource("_8B_")] [Random(RndCntTbls)] ulong table0, + [ValueSource("_8B_")] [Random(RndCntTbls)] ulong table1, + [ValueSource("_GenIdxsForTbl2_")] ulong indexes, + [Values(0b0u, 0b1u)] uint q) // <8B, 16B> + { + opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((q & 1) << 30); + + V128 v30 = MakeVectorE0E1(z, z); + V128 v31 = MakeVectorE0E1(table0, table0); + V128 v0 = MakeVectorE0E1(table1, table1); + V128 v1 = MakeVectorE0E1(indexes, indexes); + + SingleOpcode(opcodes, v0: v0, v1: v1, v30: v30, v31: v31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void ThreeRegisterTable_V_8B_16B([ValueSource("_ThreeRegisterTable_V_8B_16B_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u)] uint rn, + [Values(4u)] uint rm, + [ValueSource("_8B_")] [Random(RndCntDest)] ulong z, + [ValueSource("_8B_")] [Random(RndCntTbls)] ulong table0, + [ValueSource("_8B_")] [Random(RndCntTbls)] ulong table1, + [ValueSource("_8B_")] [Random(RndCntTbls)] ulong table2, + [ValueSource("_GenIdxsForTbl3_")] ulong indexes, + [Values(0b0u, 0b1u)] uint q) // <8B, 16B> + { + opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(table0, table0); + V128 v2 = MakeVectorE0E1(table1, table1); + V128 v3 = MakeVectorE0E1(table2, table2); + V128 v4 = MakeVectorE0E1(indexes, q == 1u ? indexes : 0ul); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, v3: v3, v4: v4); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Mod_ThreeRegisterTable_V_8B_16B([ValueSource("_ThreeRegisterTable_V_8B_16B_")] uint opcodes, + [Values(30u, 2u)] uint rd, + [Values(31u)] uint rn, + [Values(2u, 30u)] uint rm, + [ValueSource("_8B_")] [Random(RndCntDest)] ulong z, + [ValueSource("_8B_")] [Random(RndCntTbls)] ulong table0, + [ValueSource("_8B_")] [Random(RndCntTbls)] ulong table1, + [ValueSource("_8B_")] [Random(RndCntTbls)] ulong table2, + [ValueSource("_GenIdxsForTbl3_")] ulong indexes, + [Values(0b0u, 0b1u)] uint q) // <8B, 16B> + { + opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((q & 1) << 30); + + V128 v30 = MakeVectorE0E1(z, z); + V128 v31 = MakeVectorE0E1(table0, table0); + V128 v0 = MakeVectorE0E1(table1, table1); + V128 v1 = MakeVectorE0E1(table2, table2); + V128 v2 = MakeVectorE0E1(indexes, indexes); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, v30: v30, v31: v31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void FourRegisterTable_V_8B_16B([ValueSource("_FourRegisterTable_V_8B_16B_")] uint opcodes, + [Values(0u)] uint rd, + [Values(1u)] uint rn, + [Values(5u)] uint rm, + [ValueSource("_8B_")] [Random(RndCntDest)] ulong z, + [ValueSource("_8B_")] [Random(RndCntTbls)] ulong table0, + [ValueSource("_8B_")] [Random(RndCntTbls)] ulong table1, + [ValueSource("_8B_")] [Random(RndCntTbls)] ulong table2, + [ValueSource("_8B_")] [Random(RndCntTbls)] ulong table3, + [ValueSource("_GenIdxsForTbl4_")] ulong indexes, + [Values(0b0u, 0b1u)] uint q) // <8B, 16B> + { + opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(table0, table0); + V128 v2 = MakeVectorE0E1(table1, table1); + V128 v3 = MakeVectorE0E1(table2, table2); + V128 v4 = MakeVectorE0E1(table3, table3); + V128 v5 = MakeVectorE0E1(indexes, q == 1u ? indexes : 0ul); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, v3: v3, v4: v4, v5: v5); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Mod_FourRegisterTable_V_8B_16B([ValueSource("_FourRegisterTable_V_8B_16B_")] uint opcodes, + [Values(30u, 3u)] uint rd, + [Values(31u)] uint rn, + [Values(3u, 30u)] uint rm, + [ValueSource("_8B_")] [Random(RndCntDest)] ulong z, + [ValueSource("_8B_")] [Random(RndCntTbls)] ulong table0, + [ValueSource("_8B_")] [Random(RndCntTbls)] ulong table1, + [ValueSource("_8B_")] [Random(RndCntTbls)] ulong table2, + [ValueSource("_8B_")] [Random(RndCntTbls)] ulong table3, + [ValueSource("_GenIdxsForTbl4_")] ulong indexes, + [Values(0b0u, 0b1u)] uint q) // <8B, 16B> + { + opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((q & 1) << 30); + + V128 v30 = MakeVectorE0E1(z, z); + V128 v31 = MakeVectorE0E1(table0, table0); + V128 v0 = MakeVectorE0E1(table1, table1); + V128 v1 = MakeVectorE0E1(table2, table2); + V128 v2 = MakeVectorE0E1(table3, table3); + V128 v3 = MakeVectorE0E1(indexes, indexes); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, v3: v3, v30: v30, v31: v31); + + CompareAgainstUnicorn(); + } +#endif + } +} diff --git a/Ryujinx.Tests/Cpu/CpuTestSystem.cs b/Ryujinx.Tests/Cpu/CpuTestSystem.cs new file mode 100644 index 0000000000..02b1f1bd36 --- /dev/null +++ b/Ryujinx.Tests/Cpu/CpuTestSystem.cs @@ -0,0 +1,73 @@ +#define System + +using ARMeilleure.State; + +using NUnit.Framework; + +using System.Collections.Generic; + +namespace Ryujinx.Tests.Cpu +{ + [Category("System")] + public sealed class CpuTestSystem : CpuTest + { +#if System + +#region "ValueSource (Types)" + private static IEnumerable _GenNzcv_() + { + yield return 0x0000000000000000ul; + yield return 0x7FFFFFFFFFFFFFFFul; + yield return 0x8000000000000000ul; + yield return 0xFFFFFFFFFFFFFFFFul; + + bool v = TestContext.CurrentContext.Random.NextBool(); + bool c = TestContext.CurrentContext.Random.NextBool(); + bool z = TestContext.CurrentContext.Random.NextBool(); + bool n = TestContext.CurrentContext.Random.NextBool(); + + ulong rnd = 0UL; + + rnd |= (v ? 1UL : 0UL) << (int)PState.VFlag; + rnd |= (c ? 1UL : 0UL) << (int)PState.CFlag; + rnd |= (z ? 1UL : 0UL) << (int)PState.ZFlag; + rnd |= (n ? 1UL : 0UL) << (int)PState.NFlag; + + yield return rnd; + } +#endregion + +#region "ValueSource (Opcodes)" + private static uint[] _MrsMsr_Nzcv_() + { + return new uint[] + { + 0xD53B4200u, // MRS X0, NZCV + 0xD51B4200u // MSR NZCV, X0 + }; + } +#endregion + + private const int RndCnt = 2; + + [Test, Pairwise] + public void MrsMsr_Nzcv([ValueSource("_MrsMsr_Nzcv_")] uint opcodes, + [Values(0u, 1u, 31u)] uint rt, + [ValueSource("_GenNzcv_")] [Random(RndCnt)] ulong xt) + { + opcodes |= (rt & 31) << 0; + + bool v = TestContext.CurrentContext.Random.NextBool(); + bool c = TestContext.CurrentContext.Random.NextBool(); + bool z = TestContext.CurrentContext.Random.NextBool(); + bool n = TestContext.CurrentContext.Random.NextBool(); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcodes, x0: xt, x1: xt, x31: x31, overflow: v, carry: c, zero: z, negative: n); + + CompareAgainstUnicorn(); + } +#endif + } +} diff --git a/Ryujinx.Tests/Cpu/Tester/Instructions.cs b/Ryujinx.Tests/Cpu/Tester/Instructions.cs deleted file mode 100644 index 1590019a70..0000000000 --- a/Ryujinx.Tests/Cpu/Tester/Instructions.cs +++ /dev/null @@ -1,4837 +0,0 @@ -// https://github.com/LDj3SNuD/ARM_v8-A_AArch64_Instructions_Tester/blob/master/Tester/Instructions.cs - -// https://developer.arm.com/products/architecture/a-profile/exploration-tools -// ..\A64_v83A_ISA_xml_00bet6.1\ISA_v83A_A64_xml_00bet6.1_OPT\xhtml\ - -using System.Numerics; - -namespace Ryujinx.Tests.Cpu.Tester -{ - using Types; - - using static AArch64; - using static Shared; - - // index.html - internal static class Base - { -#region "Alu" - // cls_int.html - public static void Cls(bool sf, Bits Rn, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - - int datasize = (sf ? 64 : 32); - - /* Operation */ - Bits operand1 = X(datasize, n); - - BigInteger result = (BigInteger)CountLeadingSignBits(operand1); - - X(d, result.SubBigInteger(datasize - 1, 0)); - } - - // clz_int.html - public static void Clz(bool sf, Bits Rn, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - - int datasize = (sf ? 64 : 32); - - /* Operation */ - Bits operand1 = X(datasize, n); - - BigInteger result = (BigInteger)CountLeadingZeroBits(operand1); - - X(d, result.SubBigInteger(datasize - 1, 0)); - } - - // rbit_int.html - public static void Rbit(bool sf, Bits Rn, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - - int datasize = (sf ? 64 : 32); - - /* Operation */ - Bits result = new Bits(datasize); - Bits operand = X(datasize, n); - - for (int i = 0; i <= datasize - 1; i++) - { - result[datasize - 1 - i] = operand[i]; - } - - X(d, result); - } - - // rev16_int.html - public static void Rev16(bool sf, Bits Rn, Bits Rd) - { - /* Bits opc = "01"; */ - - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - - int datasize = (sf ? 64 : 32); - - int container_size = 16; - - /* Operation */ - Bits result = new Bits(datasize); - Bits operand = X(datasize, n); - - int containers = datasize / container_size; - int elements_per_container = container_size / 8; - int index = 0; - int rev_index; - - for (int c = 0; c <= containers - 1; c++) - { - rev_index = index + ((elements_per_container - 1) * 8); - - for (int e = 0; e <= elements_per_container - 1; e++) - { - result[rev_index + 7, rev_index] = operand[index + 7, index]; - - index = index + 8; - rev_index = rev_index - 8; - } - } - - X(d, result); - } - - // rev32_int.html - // (rev.html) - public static void Rev32(bool sf, Bits Rn, Bits Rd) - { - /* Bits opc = "10"; */ - - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - - int datasize = (sf ? 64 : 32); - - int container_size = 32; - - /* Operation */ - Bits result = new Bits(datasize); - Bits operand = X(datasize, n); - - int containers = datasize / container_size; - int elements_per_container = container_size / 8; - int index = 0; - int rev_index; - - for (int c = 0; c <= containers - 1; c++) - { - rev_index = index + ((elements_per_container - 1) * 8); - - for (int e = 0; e <= elements_per_container - 1; e++) - { - result[rev_index + 7, rev_index] = operand[index + 7, index]; - - index = index + 8; - rev_index = rev_index - 8; - } - } - - X(d, result); - } - - // rev64_rev.html - // (rev.html) - public static void Rev64(Bits Rn, Bits Rd) - { - /* Bits opc = "11"; */ - - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - - int container_size = 64; - - /* Operation */ - Bits result = new Bits(64); - Bits operand = X(64, n); - - int containers = 64 / container_size; - int elements_per_container = container_size / 8; - int index = 0; - int rev_index; - - for (int c = 0; c <= containers - 1; c++) - { - rev_index = index + ((elements_per_container - 1) * 8); - - for (int e = 0; e <= elements_per_container - 1; e++) - { - result[rev_index + 7, rev_index] = operand[index + 7, index]; - - index = index + 8; - rev_index = rev_index - 8; - } - } - - X(d, result); - } -#endregion - -#region "AluImm" - // add_addsub_imm.html - public static void Add_Imm(bool sf, Bits shift, Bits imm12, Bits Rn, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - - int datasize = (sf ? 64 : 32); - - Bits imm; - - switch (shift) - { - default: - case Bits bits when bits == "00": - imm = ZeroExtend(imm12, datasize); - break; - case Bits bits when bits == "01": - imm = ZeroExtend(Bits.Concat(imm12, Zeros(12)), datasize); - break; - /* when '1x' ReservedValue(); */ - } - - /* Operation */ - Bits result; - Bits operand1 = (n == 31 ? SP(datasize) : X(datasize, n)); - - (result, _) = AddWithCarry(datasize, operand1, imm, false); - - if (d == 31) - { - SP(result); - } - else - { - X(d, result); - } - } - - // adds_addsub_imm.html - public static void Adds_Imm(bool sf, Bits shift, Bits imm12, Bits Rn, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - - int datasize = (sf ? 64 : 32); - - Bits imm; - - switch (shift) - { - default: - case Bits bits when bits == "00": - imm = ZeroExtend(imm12, datasize); - break; - case Bits bits when bits == "01": - imm = ZeroExtend(Bits.Concat(imm12, Zeros(12)), datasize); - break; - /* when '1x' ReservedValue(); */ - } - - /* Operation */ - Bits result; - Bits operand1 = (n == 31 ? SP(datasize) : X(datasize, n)); - Bits nzcv; - - (result, nzcv) = AddWithCarry(datasize, operand1, imm, false); - - PSTATE.NZCV(nzcv); - - X(d, result); - } - - // and_log_imm.html - public static void And_Imm(bool sf, bool N, Bits immr, Bits imms, Bits Rn, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - - int datasize = (sf ? 64 : 32); - - Bits imm; - - /* if sf == '0' && N != '0' then ReservedValue(); */ - - (imm, _) = DecodeBitMasks(datasize, N, imms, immr, true); - - /* Operation */ - Bits operand1 = X(datasize, n); - - Bits result = AND(operand1, imm); - - if (d == 31) - { - SP(result); - } - else - { - X(d, result); - } - } - - // ands_log_imm.html - public static void Ands_Imm(bool sf, bool N, Bits immr, Bits imms, Bits Rn, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - - int datasize = (sf ? 64 : 32); - - Bits imm; - - /* if sf == '0' && N != '0' then ReservedValue(); */ - - (imm, _) = DecodeBitMasks(datasize, N, imms, immr, true); - - /* Operation */ - Bits operand1 = X(datasize, n); - - Bits result = AND(operand1, imm); - - PSTATE.NZCV(result[datasize - 1], IsZeroBit(result), false, false); - - X(d, result); - } - - // eor_log_imm.html - public static void Eor_Imm(bool sf, bool N, Bits immr, Bits imms, Bits Rn, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - - int datasize = (sf ? 64 : 32); - - Bits imm; - - /* if sf == '0' && N != '0' then ReservedValue(); */ - - (imm, _) = DecodeBitMasks(datasize, N, imms, immr, true); - - /* Operation */ - Bits operand1 = X(datasize, n); - - Bits result = EOR(operand1, imm); - - if (d == 31) - { - SP(result); - } - else - { - X(d, result); - } - } - - // orr_log_imm.html - public static void Orr_Imm(bool sf, bool N, Bits immr, Bits imms, Bits Rn, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - - int datasize = (sf ? 64 : 32); - - Bits imm; - - /* if sf == '0' && N != '0' then ReservedValue(); */ - - (imm, _) = DecodeBitMasks(datasize, N, imms, immr, true); - - /* Operation */ - Bits operand1 = X(datasize, n); - - Bits result = OR(operand1, imm); - - if (d == 31) - { - SP(result); - } - else - { - X(d, result); - } - } - - // sub_addsub_imm.html - public static void Sub_Imm(bool sf, Bits shift, Bits imm12, Bits Rn, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - - int datasize = (sf ? 64 : 32); - - Bits imm; - - switch (shift) - { - default: - case Bits bits when bits == "00": - imm = ZeroExtend(imm12, datasize); - break; - case Bits bits when bits == "01": - imm = ZeroExtend(Bits.Concat(imm12, Zeros(12)), datasize); - break; - /* when '1x' ReservedValue(); */ - } - - /* Operation */ - Bits result; - Bits operand1 = (n == 31 ? SP(datasize) : X(datasize, n)); - Bits operand2 = NOT(imm); - - (result, _) = AddWithCarry(datasize, operand1, operand2, true); - - if (d == 31) - { - SP(result); - } - else - { - X(d, result); - } - } - - // subs_addsub_imm.html - public static void Subs_Imm(bool sf, Bits shift, Bits imm12, Bits Rn, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - - int datasize = (sf ? 64 : 32); - - Bits imm; - - switch (shift) - { - default: - case Bits bits when bits == "00": - imm = ZeroExtend(imm12, datasize); - break; - case Bits bits when bits == "01": - imm = ZeroExtend(Bits.Concat(imm12, Zeros(12)), datasize); - break; - /* when '1x' ReservedValue(); */ - } - - /* Operation */ - Bits result; - Bits operand1 = (n == 31 ? SP(datasize) : X(datasize, n)); - Bits operand2 = NOT(imm); - Bits nzcv; - - (result, nzcv) = AddWithCarry(datasize, operand1, operand2, true); - - PSTATE.NZCV(nzcv); - - X(d, result); - } -#endregion - -#region "AluRs" - // adc.html - public static void Adc(bool sf, Bits Rm, Bits Rn, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - int datasize = (sf ? 64 : 32); - - /* Operation */ - Bits result; - Bits operand1 = X(datasize, n); - Bits operand2 = X(datasize, m); - - (result, _) = AddWithCarry(datasize, operand1, operand2, PSTATE.C); - - X(d, result); - } - - // adcs.html - public static void Adcs(bool sf, Bits Rm, Bits Rn, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - int datasize = (sf ? 64 : 32); - - /* Operation */ - Bits result; - Bits operand1 = X(datasize, n); - Bits operand2 = X(datasize, m); - Bits nzcv; - - (result, nzcv) = AddWithCarry(datasize, operand1, operand2, PSTATE.C); - - PSTATE.NZCV(nzcv); - - X(d, result); - } - - // add_addsub_shift.html - public static void Add_Rs(bool sf, Bits shift, Bits Rm, Bits imm6, Bits Rn, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - int datasize = (sf ? 64 : 32); - - /* if shift == '11' then ReservedValue(); */ - /* if sf == '0' && imm6<5> == '1' then ReservedValue(); */ - - ShiftType shift_type = DecodeShift(shift); - int shift_amount = (int)UInt(imm6); - - /* Operation */ - Bits result; - Bits operand1 = X(datasize, n); - Bits operand2 = ShiftReg(datasize, m, shift_type, shift_amount); - - (result, _) = AddWithCarry(datasize, operand1, operand2, false); - - X(d, result); - } - - // adds_addsub_shift.html - public static void Adds_Rs(bool sf, Bits shift, Bits Rm, Bits imm6, Bits Rn, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - int datasize = (sf ? 64 : 32); - - /* if shift == '11' then ReservedValue(); */ - /* if sf == '0' && imm6<5> == '1' then ReservedValue(); */ - - ShiftType shift_type = DecodeShift(shift); - int shift_amount = (int)UInt(imm6); - - /* Operation */ - Bits result; - Bits operand1 = X(datasize, n); - Bits operand2 = ShiftReg(datasize, m, shift_type, shift_amount); - Bits nzcv; - - (result, nzcv) = AddWithCarry(datasize, operand1, operand2, false); - - PSTATE.NZCV(nzcv); - - X(d, result); - } - - // and_log_shift.html - public static void And_Rs(bool sf, Bits shift, Bits Rm, Bits imm6, Bits Rn, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - int datasize = (sf ? 64 : 32); - - /* if sf == '0' && imm6<5> == '1' then ReservedValue(); */ - - ShiftType shift_type = DecodeShift(shift); - int shift_amount = (int)UInt(imm6); - - /* Operation */ - Bits operand1 = X(datasize, n); - Bits operand2 = ShiftReg(datasize, m, shift_type, shift_amount); - - Bits result = AND(operand1, operand2); - - X(d, result); - } - - // ands_log_shift.html - public static void Ands_Rs(bool sf, Bits shift, Bits Rm, Bits imm6, Bits Rn, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - int datasize = (sf ? 64 : 32); - - /* if sf == '0' && imm6<5> == '1' then ReservedValue(); */ - - ShiftType shift_type = DecodeShift(shift); - int shift_amount = (int)UInt(imm6); - - /* Operation */ - Bits operand1 = X(datasize, n); - Bits operand2 = ShiftReg(datasize, m, shift_type, shift_amount); - - Bits result = AND(operand1, operand2); - - PSTATE.NZCV(result[datasize - 1], IsZeroBit(result), false, false); - - X(d, result); - } - - // asrv.html - public static void Asrv(bool sf, Bits Rm, Bits Rn, Bits Rd) - { - /*readonly */Bits op2 = "10"; - - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - int datasize = (sf ? 64 : 32); - - ShiftType shift_type = DecodeShift(op2); - - /* Operation */ - Bits operand2 = X(datasize, m); - - Bits result = ShiftReg(datasize, n, shift_type, (int)(UInt(operand2) % datasize)); // BigInteger.Modulus Operator (BigInteger, BigInteger) - - X(d, result); - } - - // bic_log_shift.html - public static void Bic(bool sf, Bits shift, Bits Rm, Bits imm6, Bits Rn, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - int datasize = (sf ? 64 : 32); - - /* if sf == '0' && imm6<5> == '1' then ReservedValue(); */ - - ShiftType shift_type = DecodeShift(shift); - int shift_amount = (int)UInt(imm6); - - /* Operation */ - Bits operand1 = X(datasize, n); - Bits operand2 = ShiftReg(datasize, m, shift_type, shift_amount); - - operand2 = NOT(operand2); - - Bits result = AND(operand1, operand2); - - X(d, result); - } - - // bics.html - public static void Bics(bool sf, Bits shift, Bits Rm, Bits imm6, Bits Rn, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - int datasize = (sf ? 64 : 32); - - /* if sf == '0' && imm6<5> == '1' then ReservedValue(); */ - - ShiftType shift_type = DecodeShift(shift); - int shift_amount = (int)UInt(imm6); - - /* Operation */ - Bits operand1 = X(datasize, n); - Bits operand2 = ShiftReg(datasize, m, shift_type, shift_amount); - - operand2 = NOT(operand2); - - Bits result = AND(operand1, operand2); - - PSTATE.NZCV(result[datasize - 1], IsZeroBit(result), false, false); - - X(d, result); - } - - // crc32.html - public static void Crc32(bool sf, Bits Rm, Bits sz, Bits Rn, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - /* if sf == '1' && sz != '11' then UnallocatedEncoding(); */ - /* if sf == '0' && sz == '11' then UnallocatedEncoding(); */ - - int size = 8 << (int)UInt(sz); - - /* Operation */ - /* if !HaveCRCExt() then UnallocatedEncoding(); */ - - Bits acc = X(32, n); // accumulator - Bits val = X(size, m); // input value - Bits poly = new Bits(0x04C11DB7u); - - Bits tempacc = Bits.Concat(BitReverse(acc), Zeros(size)); - Bits tempval = Bits.Concat(BitReverse(val), Zeros(32)); - - // Poly32Mod2 on a bitstring does a polynomial Modulus over {0,1} operation - X(d, BitReverse(Poly32Mod2(EOR(tempacc, tempval), poly))); - } - - // crc32c.html - public static void Crc32c(bool sf, Bits Rm, Bits sz, Bits Rn, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - /* if sf == '1' && sz != '11' then UnallocatedEncoding(); */ - /* if sf == '0' && sz == '11' then UnallocatedEncoding(); */ - - int size = 8 << (int)UInt(sz); - - /* Operation */ - /* if !HaveCRCExt() then UnallocatedEncoding(); */ - - Bits acc = X(32, n); // accumulator - Bits val = X(size, m); // input value - Bits poly = new Bits(0x1EDC6F41u); - - Bits tempacc = Bits.Concat(BitReverse(acc), Zeros(size)); - Bits tempval = Bits.Concat(BitReverse(val), Zeros(32)); - - // Poly32Mod2 on a bitstring does a polynomial Modulus over {0,1} operation - X(d, BitReverse(Poly32Mod2(EOR(tempacc, tempval), poly))); - } - - // eon.html - public static void Eon(bool sf, Bits shift, Bits Rm, Bits imm6, Bits Rn, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - int datasize = (sf ? 64 : 32); - - /* if sf == '0' && imm6<5> == '1' then ReservedValue(); */ - - ShiftType shift_type = DecodeShift(shift); - int shift_amount = (int)UInt(imm6); - - /* Operation */ - Bits operand1 = X(datasize, n); - Bits operand2 = ShiftReg(datasize, m, shift_type, shift_amount); - - operand2 = NOT(operand2); - - Bits result = EOR(operand1, operand2); - - X(d, result); - } - - // eor_log_shift.html - public static void Eor_Rs(bool sf, Bits shift, Bits Rm, Bits imm6, Bits Rn, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - int datasize = (sf ? 64 : 32); - - /* if sf == '0' && imm6<5> == '1' then ReservedValue(); */ - - ShiftType shift_type = DecodeShift(shift); - int shift_amount = (int)UInt(imm6); - - /* Operation */ - Bits operand1 = X(datasize, n); - Bits operand2 = ShiftReg(datasize, m, shift_type, shift_amount); - - Bits result = EOR(operand1, operand2); - - X(d, result); - } - - // extr.html - public static void Extr(bool sf, bool N, Bits Rm, Bits imms, Bits Rn, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - int datasize = (sf ? 64 : 32); - - /* if N != sf then UnallocatedEncoding(); */ - /* if sf == '0' && imms<5> == '1' then ReservedValue(); */ - - int lsb = (int)UInt(imms); - - /* Operation */ - Bits operand1 = X(datasize, n); - Bits operand2 = X(datasize, m); - Bits concat = Bits.Concat(operand1, operand2); - - Bits result = concat[lsb + datasize - 1, lsb]; - - X(d, result); - } - - // lslv.html - public static void Lslv(bool sf, Bits Rm, Bits Rn, Bits Rd) - { - /*readonly */Bits op2 = "00"; - - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - int datasize = (sf ? 64 : 32); - - ShiftType shift_type = DecodeShift(op2); - - /* Operation */ - Bits operand2 = X(datasize, m); - - Bits result = ShiftReg(datasize, n, shift_type, (int)(UInt(operand2) % datasize)); // BigInteger.Modulus Operator (BigInteger, BigInteger) - - X(d, result); - } - - // lsrv.html - public static void Lsrv(bool sf, Bits Rm, Bits Rn, Bits Rd) - { - /*readonly */Bits op2 = "01"; - - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - int datasize = (sf ? 64 : 32); - - ShiftType shift_type = DecodeShift(op2); - - /* Operation */ - Bits operand2 = X(datasize, m); - - Bits result = ShiftReg(datasize, n, shift_type, (int)(UInt(operand2) % datasize)); // BigInteger.Modulus Operator (BigInteger, BigInteger) - - X(d, result); - } - - // orn_log_shift.html - public static void Orn(bool sf, Bits shift, Bits Rm, Bits imm6, Bits Rn, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - int datasize = (sf ? 64 : 32); - - /* if sf == '0' && imm6<5> == '1' then ReservedValue(); */ - - ShiftType shift_type = DecodeShift(shift); - int shift_amount = (int)UInt(imm6); - - /* Operation */ - Bits operand1 = X(datasize, n); - Bits operand2 = ShiftReg(datasize, m, shift_type, shift_amount); - - operand2 = NOT(operand2); - - Bits result = OR(operand1, operand2); - - X(d, result); - } - - // orr_log_shift.html - public static void Orr_Rs(bool sf, Bits shift, Bits Rm, Bits imm6, Bits Rn, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - int datasize = (sf ? 64 : 32); - - /* if sf == '0' && imm6<5> == '1' then ReservedValue(); */ - - ShiftType shift_type = DecodeShift(shift); - int shift_amount = (int)UInt(imm6); - - /* Operation */ - Bits operand1 = X(datasize, n); - Bits operand2 = ShiftReg(datasize, m, shift_type, shift_amount); - - Bits result = OR(operand1, operand2); - - X(d, result); - } - - // rorv.html - public static void Rorv(bool sf, Bits Rm, Bits Rn, Bits Rd) - { - /*readonly */Bits op2 = "11"; - - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - int datasize = (sf ? 64 : 32); - - ShiftType shift_type = DecodeShift(op2); - - /* Operation */ - Bits operand2 = X(datasize, m); - - Bits result = ShiftReg(datasize, n, shift_type, (int)(UInt(operand2) % datasize)); // BigInteger.Modulus Operator (BigInteger, BigInteger) - - X(d, result); - } - - // sbc.html - public static void Sbc(bool sf, Bits Rm, Bits Rn, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - int datasize = (sf ? 64 : 32); - - /* Operation */ - Bits result; - Bits operand1 = X(datasize, n); - Bits operand2 = X(datasize, m); - - operand2 = NOT(operand2); - - (result, _) = AddWithCarry(datasize, operand1, operand2, PSTATE.C); - - X(d, result); - } - - // sbcs.html - public static void Sbcs(bool sf, Bits Rm, Bits Rn, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - int datasize = (sf ? 64 : 32); - - /* Operation */ - Bits result; - Bits operand1 = X(datasize, n); - Bits operand2 = X(datasize, m); - Bits nzcv; - - operand2 = NOT(operand2); - - (result, nzcv) = AddWithCarry(datasize, operand1, operand2, PSTATE.C); - - PSTATE.NZCV(nzcv); - - X(d, result); - } - - // sdiv.html - public static void Sdiv(bool sf, Bits Rm, Bits Rn, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - int datasize = (sf ? 64 : 32); - - /* Operation */ - BigInteger result; - Bits operand1 = X(datasize, n); - Bits operand2 = X(datasize, m); - - if (IsZero(operand2)) - { - result = (BigInteger)0m; - } - else - { - result = RoundTowardsZero(Real(Int(operand1, false)) / Real(Int(operand2, false))); - } - - X(d, result.SubBigInteger(datasize - 1, 0)); - } - - // sub_addsub_shift.html - public static void Sub_Rs(bool sf, Bits shift, Bits Rm, Bits imm6, Bits Rn, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - int datasize = (sf ? 64 : 32); - - /* if shift == '11' then ReservedValue(); */ - /* if sf == '0' && imm6<5> == '1' then ReservedValue(); */ - - ShiftType shift_type = DecodeShift(shift); - int shift_amount = (int)UInt(imm6); - - /* Operation */ - Bits result; - Bits operand1 = X(datasize, n); - Bits operand2 = ShiftReg(datasize, m, shift_type, shift_amount); - - operand2 = NOT(operand2); - - (result, _) = AddWithCarry(datasize, operand1, operand2, true); - - X(d, result); - } - - // subs_addsub_shift.html - public static void Subs_Rs(bool sf, Bits shift, Bits Rm, Bits imm6, Bits Rn, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - int datasize = (sf ? 64 : 32); - - /* if shift == '11' then ReservedValue(); */ - /* if sf == '0' && imm6<5> == '1' then ReservedValue(); */ - - ShiftType shift_type = DecodeShift(shift); - int shift_amount = (int)UInt(imm6); - - /* Operation */ - Bits result; - Bits operand1 = X(datasize, n); - Bits operand2 = ShiftReg(datasize, m, shift_type, shift_amount); - Bits nzcv; - - operand2 = NOT(operand2); - - (result, nzcv) = AddWithCarry(datasize, operand1, operand2, true); - - PSTATE.NZCV(nzcv); - - X(d, result); - } - - // udiv.html - public static void Udiv(bool sf, Bits Rm, Bits Rn, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - int datasize = (sf ? 64 : 32); - - /* Operation */ - BigInteger result; - Bits operand1 = X(datasize, n); - Bits operand2 = X(datasize, m); - - if (IsZero(operand2)) - { - result = (BigInteger)0m; - } - else - { - result = RoundTowardsZero(Real(Int(operand1, true)) / Real(Int(operand2, true))); - } - - X(d, result.SubBigInteger(datasize - 1, 0)); - } -#endregion - -#region "AluRx" - // add_addsub_ext.html - public static void Add_Rx(bool sf, Bits Rm, Bits option, Bits imm3, Bits Rn, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - int datasize = (sf ? 64 : 32); - - ExtendType extend_type = DecodeRegExtend(option); - int shift = (int)UInt(imm3); - - /* if shift > 4 then ReservedValue(); */ - - /* Operation */ - Bits result; - Bits operand1 = (n == 31 ? SP(datasize) : X(datasize, n)); - Bits operand2 = ExtendReg(datasize, m, extend_type, shift); - - (result, _) = AddWithCarry(datasize, operand1, operand2, false); - - if (d == 31) - { - SP(result); - } - else - { - X(d, result); - } - } - - // adds_addsub_ext.html - public static void Adds_Rx(bool sf, Bits Rm, Bits option, Bits imm3, Bits Rn, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - int datasize = (sf ? 64 : 32); - - ExtendType extend_type = DecodeRegExtend(option); - int shift = (int)UInt(imm3); - - /* if shift > 4 then ReservedValue(); */ - - /* Operation */ - Bits result; - Bits operand1 = (n == 31 ? SP(datasize) : X(datasize, n)); - Bits operand2 = ExtendReg(datasize, m, extend_type, shift); - Bits nzcv; - - (result, nzcv) = AddWithCarry(datasize, operand1, operand2, false); - - PSTATE.NZCV(nzcv); - - X(d, result); - } - - // sub_addsub_ext.html - public static void Sub_Rx(bool sf, Bits Rm, Bits option, Bits imm3, Bits Rn, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - int datasize = (sf ? 64 : 32); - - ExtendType extend_type = DecodeRegExtend(option); - int shift = (int)UInt(imm3); - - /* if shift > 4 then ReservedValue(); */ - - /* Operation */ - Bits result; - Bits operand1 = (n == 31 ? SP(datasize) : X(datasize, n)); - Bits operand2 = ExtendReg(datasize, m, extend_type, shift); - - operand2 = NOT(operand2); - - (result, _) = AddWithCarry(datasize, operand1, operand2, true); - - if (d == 31) - { - SP(result); - } - else - { - X(d, result); - } - } - - // subs_addsub_ext.html - public static void Subs_Rx(bool sf, Bits Rm, Bits option, Bits imm3, Bits Rn, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - int datasize = (sf ? 64 : 32); - - ExtendType extend_type = DecodeRegExtend(option); - int shift = (int)UInt(imm3); - - /* if shift > 4 then ReservedValue(); */ - - /* Operation */ - Bits result; - Bits operand1 = (n == 31 ? SP(datasize) : X(datasize, n)); - Bits operand2 = ExtendReg(datasize, m, extend_type, shift); - Bits nzcv; - - operand2 = NOT(operand2); - - (result, nzcv) = AddWithCarry(datasize, operand1, operand2, true); - - PSTATE.NZCV(nzcv); - - X(d, result); - } -#endregion - -#region "Bfm" - // bfm.html - public static void Bfm(bool sf, bool N, Bits immr, Bits imms, Bits Rn, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - - int datasize = (sf ? 64 : 32); - - int R; - Bits wmask; - Bits tmask; - - /* if sf == '1' && N != '1' then ReservedValue(); */ - /* if sf == '0' && (N != '0' || immr<5> != '0' || imms<5> != '0') then ReservedValue(); */ - - R = (int)UInt(immr); - (wmask, tmask) = DecodeBitMasks(datasize, N, imms, immr, false); - - /* Operation */ - Bits dst = X(datasize, d); - Bits src = X(datasize, n); - - // perform bitfield move on low bits - Bits bot = OR(AND(dst, NOT(wmask)), AND(ROR(src, R), wmask)); - - // combine extension bits and result bits - X(d, OR(AND(dst, NOT(tmask)), AND(bot, tmask))); - } - - // sbfm.html - public static void Sbfm(bool sf, bool N, Bits immr, Bits imms, Bits Rn, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - - int datasize = (sf ? 64 : 32); - - int R; - int S; - Bits wmask; - Bits tmask; - - /* if sf == '1' && N != '1' then ReservedValue(); */ - /* if sf == '0' && (N != '0' || immr<5> != '0' || imms<5> != '0') then ReservedValue(); */ - - R = (int)UInt(immr); - S = (int)UInt(imms); - (wmask, tmask) = DecodeBitMasks(datasize, N, imms, immr, false); - - /* Operation */ - Bits src = X(datasize, n); - - // perform bitfield move on low bits - Bits bot = AND(ROR(src, R), wmask); - - // determine extension bits (sign, zero or dest register) - Bits top = Replicate(datasize, src[S]); - - // combine extension bits and result bits - X(d, OR(AND(top, NOT(tmask)), AND(bot, tmask))); - } - - // ubfm.html - public static void Ubfm(bool sf, bool N, Bits immr, Bits imms, Bits Rn, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - - int datasize = (sf ? 64 : 32); - - int R; - Bits wmask; - Bits tmask; - - /* if sf == '1' && N != '1' then ReservedValue(); */ - /* if sf == '0' && (N != '0' || immr<5> != '0' || imms<5> != '0') then ReservedValue(); */ - - R = (int)UInt(immr); - (wmask, tmask) = DecodeBitMasks(datasize, N, imms, immr, false); - - /* Operation */ - Bits src = X(datasize, n); - - // perform bitfield move on low bits - Bits bot = AND(ROR(src, R), wmask); - - // combine extension bits and result bits - X(d, AND(bot, tmask)); - } -#endregion - -#region "CcmpImm" - // ccmn_imm.html - public static void Ccmn_Imm(bool sf, Bits imm5, Bits cond, Bits Rn, Bits nzcv) - { - /* Decode */ - int n = (int)UInt(Rn); - - int datasize = (sf ? 64 : 32); - - Bits flags = nzcv; - Bits imm = ZeroExtend(imm5, datasize); - - /* Operation */ - Bits operand1 = X(datasize, n); - - if (ConditionHolds(cond)) - { - (_, flags) = AddWithCarry(datasize, operand1, imm, false); - } - - PSTATE.NZCV(flags); - } - - // ccmp_imm.html - public static void Ccmp_Imm(bool sf, Bits imm5, Bits cond, Bits Rn, Bits nzcv) - { - /* Decode */ - int n = (int)UInt(Rn); - - int datasize = (sf ? 64 : 32); - - Bits flags = nzcv; - Bits imm = ZeroExtend(imm5, datasize); - - /* Operation */ - Bits operand1 = X(datasize, n); - Bits operand2; - - if (ConditionHolds(cond)) - { - operand2 = NOT(imm); - (_, flags) = AddWithCarry(datasize, operand1, operand2, true); - } - - PSTATE.NZCV(flags); - } -#endregion - -#region "CcmpReg" - // ccmn_reg.html - public static void Ccmn_Reg(bool sf, Bits Rm, Bits cond, Bits Rn, Bits nzcv) - { - /* Decode */ - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - int datasize = (sf ? 64 : 32); - - Bits flags = nzcv; - - /* Operation */ - Bits operand1 = X(datasize, n); - Bits operand2 = X(datasize, m); - - if (ConditionHolds(cond)) - { - (_, flags) = AddWithCarry(datasize, operand1, operand2, false); - } - - PSTATE.NZCV(flags); - } - - // ccmp_reg.html - public static void Ccmp_Reg(bool sf, Bits Rm, Bits cond, Bits Rn, Bits nzcv) - { - /* Decode */ - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - int datasize = (sf ? 64 : 32); - - Bits flags = nzcv; - - /* Operation */ - Bits operand1 = X(datasize, n); - Bits operand2 = X(datasize, m); - - if (ConditionHolds(cond)) - { - operand2 = NOT(operand2); - (_, flags) = AddWithCarry(datasize, operand1, operand2, true); - } - - PSTATE.NZCV(flags); - } -#endregion - -#region "Csel" - // csel.html - public static void Csel(bool sf, Bits Rm, Bits cond, Bits Rn, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - int datasize = (sf ? 64 : 32); - - /* Operation */ - Bits result; - Bits operand1 = X(datasize, n); - Bits operand2 = X(datasize, m); - - if (ConditionHolds(cond)) - { - result = operand1; - } - else - { - result = operand2; - } - - X(d, result); - } - - // csinc.html - public static void Csinc(bool sf, Bits Rm, Bits cond, Bits Rn, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - int datasize = (sf ? 64 : 32); - - /* Operation */ - Bits result; - Bits operand1 = X(datasize, n); - Bits operand2 = X(datasize, m); - - if (ConditionHolds(cond)) - { - result = operand1; - } - else - { - result = operand2 + 1; - } - - X(d, result); - } - - // csinv.html - public static void Csinv(bool sf, Bits Rm, Bits cond, Bits Rn, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - int datasize = (sf ? 64 : 32); - - /* Operation */ - Bits result; - Bits operand1 = X(datasize, n); - Bits operand2 = X(datasize, m); - - if (ConditionHolds(cond)) - { - result = operand1; - } - else - { - result = NOT(operand2); - } - - X(d, result); - } - - // csneg.html - public static void Csneg(bool sf, Bits Rm, Bits cond, Bits Rn, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - int datasize = (sf ? 64 : 32); - - /* Operation */ - Bits result; - Bits operand1 = X(datasize, n); - Bits operand2 = X(datasize, m); - - if (ConditionHolds(cond)) - { - result = operand1; - } - else - { - result = NOT(operand2); - result = result + 1; - } - - X(d, result); - } -#endregion - -#region "Mov" - // movk.html - public static void Movk(bool sf, Bits hw, Bits imm16, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - - int datasize = (sf ? 64 : 32); - - /* if sf == '0' && hw<1> == '1' then UnallocatedEncoding(); */ - - int pos = (int)UInt(Bits.Concat(hw, "0000")); - - /* Operation */ - Bits result = X(datasize, d); - - result[pos + 15, pos] = imm16; - - X(d, result); - } - - // movn.html - public static void Movn(bool sf, Bits hw, Bits imm16, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - - int datasize = (sf ? 64 : 32); - - /* if sf == '0' && hw<1> == '1' then UnallocatedEncoding(); */ - - int pos = (int)UInt(Bits.Concat(hw, "0000")); - - /* Operation */ - Bits result = Zeros(datasize); - - result[pos + 15, pos] = imm16; - result = NOT(result); - - X(d, result); - } - - // movz.html - public static void Movz(bool sf, Bits hw, Bits imm16, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - - int datasize = (sf ? 64 : 32); - - /* if sf == '0' && hw<1> == '1' then UnallocatedEncoding(); */ - - int pos = (int)UInt(Bits.Concat(hw, "0000")); - - /* Operation */ - Bits result = Zeros(datasize); - - result[pos + 15, pos] = imm16; - - X(d, result); - } -#endregion - -#region "Mul" - // madd.html - public static void Madd(bool sf, Bits Rm, Bits Ra, Bits Rn, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - int a = (int)UInt(Ra); - - int datasize = (sf ? 64 : 32); - - /* Operation */ - Bits operand1 = X(datasize, n); - Bits operand2 = X(datasize, m); - Bits operand3 = X(datasize, a); - - BigInteger result = UInt(operand3) + (UInt(operand1) * UInt(operand2)); - - X(d, result.SubBigInteger(datasize - 1, 0)); - } - - // msub.html - public static void Msub(bool sf, Bits Rm, Bits Ra, Bits Rn, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - int a = (int)UInt(Ra); - - int datasize = (sf ? 64 : 32); - - /* Operation */ - Bits operand1 = X(datasize, n); - Bits operand2 = X(datasize, m); - Bits operand3 = X(datasize, a); - - BigInteger result = UInt(operand3) - (UInt(operand1) * UInt(operand2)); - - X(d, result.SubBigInteger(datasize - 1, 0)); - } - - // smaddl.html - public static void Smaddl(Bits Rm, Bits Ra, Bits Rn, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - int a = (int)UInt(Ra); - - /* Operation */ - Bits operand1 = X(32, n); - Bits operand2 = X(32, m); - Bits operand3 = X(64, a); - - BigInteger result = Int(operand3, false) + (Int(operand1, false) * Int(operand2, false)); - - X(d, result.SubBigInteger(63, 0)); - } - - // umaddl.html - public static void Umaddl(Bits Rm, Bits Ra, Bits Rn, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - int a = (int)UInt(Ra); - - /* Operation */ - Bits operand1 = X(32, n); - Bits operand2 = X(32, m); - Bits operand3 = X(64, a); - - BigInteger result = Int(operand3, true) + (Int(operand1, true) * Int(operand2, true)); - - X(d, result.SubBigInteger(63, 0)); - } - - // smsubl.html - public static void Smsubl(Bits Rm, Bits Ra, Bits Rn, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - int a = (int)UInt(Ra); - - /* Operation */ - Bits operand1 = X(32, n); - Bits operand2 = X(32, m); - Bits operand3 = X(64, a); - - BigInteger result = Int(operand3, false) - (Int(operand1, false) * Int(operand2, false)); - - X(d, result.SubBigInteger(63, 0)); - } - - // umsubl.html - public static void Umsubl(Bits Rm, Bits Ra, Bits Rn, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - int a = (int)UInt(Ra); - - /* Operation */ - Bits operand1 = X(32, n); - Bits operand2 = X(32, m); - Bits operand3 = X(64, a); - - BigInteger result = Int(operand3, true) - (Int(operand1, true) * Int(operand2, true)); - - X(d, result.SubBigInteger(63, 0)); - } - - // smulh.html - public static void Smulh(Bits Rm, Bits Rn, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - /* Operation */ - Bits operand1 = X(64, n); - Bits operand2 = X(64, m); - - BigInteger result = Int(operand1, false) * Int(operand2, false); - - X(d, result.SubBigInteger(127, 64)); - } - - // umulh.html - public static void Umulh(Bits Rm, Bits Rn, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - /* Operation */ - Bits operand1 = X(64, n); - Bits operand2 = X(64, m); - - BigInteger result = Int(operand1, true) * Int(operand2, true); - - X(d, result.SubBigInteger(127, 64)); - } -#endregion - } - - // fpsimdindex.html - internal static class SimdFp - { -#region "Simd" - // abs_advsimd.html#ABS_asisdmisc_R - public static void Abs_S(Bits size, Bits Rn, Bits Rd) - { - const bool U = false; - - /* Decode Scalar */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - - /* if size != '11' then ReservedValue(); */ - - int esize = 8 << (int)UInt(size); - int datasize = esize; - int elements = 1; - - bool neg = (U == true); - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits result = new Bits(datasize); - Bits operand = V(datasize, n); - - BigInteger element; - - for (int e = 0; e <= elements - 1; e++) - { - element = SInt(Elem(operand, e, esize)); - - if (neg) - { - element = -element; - } - else - { - element = Abs(element); - } - - Elem(result, e, esize, element.SubBigInteger(esize - 1, 0)); - } - - V(d, result); - } - - // abs_advsimd.html#ABS_asimdmisc_R - public static void Abs_V(bool Q, Bits size, Bits Rn, Bits Rd) - { - const bool U = false; - - /* Decode Vector */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - - /* if size:Q == '110' then ReservedValue(); */ - - int esize = 8 << (int)UInt(size); - int datasize = (Q ? 128 : 64); - int elements = datasize / esize; - - bool neg = (U == true); - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits result = new Bits(datasize); - Bits operand = V(datasize, n); - - BigInteger element; - - for (int e = 0; e <= elements - 1; e++) - { - element = SInt(Elem(operand, e, esize)); - - if (neg) - { - element = -element; - } - else - { - element = Abs(element); - } - - Elem(result, e, esize, element.SubBigInteger(esize - 1, 0)); - } - - V(d, result); - } - - // addp_advsimd_pair.html - public static void Addp_S(Bits size, Bits Rn, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - - /* if size != '11' then ReservedValue(); */ - - int esize = 8 << (int)UInt(size); - int datasize = esize * 2; - // int elements = 2; - - ReduceOp op = ReduceOp.ReduceOp_ADD; - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits operand = V(datasize, n); - - V(d, Reduce(op, operand, esize)); - } - - // addv_advsimd.html - public static void Addv_V(bool Q, Bits size, Bits Rn, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - - /* if size:Q == '100' then ReservedValue(); */ - /* if size == '11' then ReservedValue(); */ - - int esize = 8 << (int)UInt(size); - int datasize = (Q ? 128 : 64); - // int elements = datasize / esize; - - ReduceOp op = ReduceOp.ReduceOp_ADD; - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits operand = V(datasize, n); - - V(d, Reduce(op, operand, esize)); - } - - // cls_advsimd.html - public static void Cls_V(bool Q, Bits size, Bits Rn, Bits Rd) - { - const bool U = false; - - /* Decode Vector */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - - /* if size == '11' then ReservedValue(); */ - - int esize = 8 << (int)UInt(size); - int datasize = (Q ? 128 : 64); - int elements = datasize / esize; - - CountOp countop = (U ? CountOp.CountOp_CLZ : CountOp.CountOp_CLS); - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits result = new Bits(datasize); - Bits operand = V(datasize, n); - - BigInteger count; - - for (int e = 0; e <= elements - 1; e++) - { - if (countop == CountOp.CountOp_CLS) - { - count = (BigInteger)CountLeadingSignBits(Elem(operand, e, esize)); - } - else - { - count = (BigInteger)CountLeadingZeroBits(Elem(operand, e, esize)); - } - - Elem(result, e, esize, count.SubBigInteger(esize - 1, 0)); - } - - V(d, result); - } - - // clz_advsimd.html - public static void Clz_V(bool Q, Bits size, Bits Rn, Bits Rd) - { - const bool U = true; - - /* Decode Vector */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - - /* if size == '11' then ReservedValue(); */ - - int esize = 8 << (int)UInt(size); - int datasize = (Q ? 128 : 64); - int elements = datasize / esize; - - CountOp countop = (U ? CountOp.CountOp_CLZ : CountOp.CountOp_CLS); - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits result = new Bits(datasize); - Bits operand = V(datasize, n); - - BigInteger count; - - for (int e = 0; e <= elements - 1; e++) - { - if (countop == CountOp.CountOp_CLS) - { - count = (BigInteger)CountLeadingSignBits(Elem(operand, e, esize)); - } - else - { - count = (BigInteger)CountLeadingZeroBits(Elem(operand, e, esize)); - } - - Elem(result, e, esize, count.SubBigInteger(esize - 1, 0)); - } - - V(d, result); - } - - // cmeq_advsimd_zero.html#CMEQ_asisdmisc_Z - public static void Cmeq_Zero_S(Bits size, Bits Rn, Bits Rd) - { - const bool U = false; - const bool op = true; - - /* Decode Scalar */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - - /* if size != '11' then ReservedValue(); */ - - int esize = 8 << (int)UInt(size); - int datasize = esize; - int elements = 1; - - CompareOp comparison; - - switch (Bits.Concat(op, U)) - { - case Bits bits when bits == "00": - comparison = CompareOp.CompareOp_GT; - break; - case Bits bits when bits == "01": - comparison = CompareOp.CompareOp_GE; - break; - default: - case Bits bits when bits == "10": - comparison = CompareOp.CompareOp_EQ; - break; - case Bits bits when bits == "11": - comparison = CompareOp.CompareOp_LE; - break; - } - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits result = new Bits(datasize); - Bits operand = V(datasize, n); - BigInteger element; - - bool test_passed; - - for (int e = 0; e <= elements - 1; e++) - { - element = SInt(Elem(operand, e, esize)); - - switch (comparison) - { - case CompareOp.CompareOp_GT: - test_passed = (element > (BigInteger)0); - break; - case CompareOp.CompareOp_GE: - test_passed = (element >= (BigInteger)0); - break; - default: - case CompareOp.CompareOp_EQ: - test_passed = (element == (BigInteger)0); - break; - case CompareOp.CompareOp_LE: - test_passed = (element <= (BigInteger)0); - break; - case CompareOp.CompareOp_LT: - test_passed = (element < (BigInteger)0); - break; - } - - Elem(result, e, esize, test_passed ? Ones(esize) : Zeros(esize)); - } - - V(d, result); - } - - // cmeq_advsimd_zero.html#CMEQ_asimdmisc_Z - public static void Cmeq_Zero_V(bool Q, Bits size, Bits Rn, Bits Rd) - { - const bool U = false; - const bool op = true; - - /* Decode Vector */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - - /* if size:Q == '110' then ReservedValue(); */ - - int esize = 8 << (int)UInt(size); - int datasize = (Q ? 128 : 64); - int elements = datasize / esize; - - CompareOp comparison; - - switch (Bits.Concat(op, U)) - { - case Bits bits when bits == "00": - comparison = CompareOp.CompareOp_GT; - break; - case Bits bits when bits == "01": - comparison = CompareOp.CompareOp_GE; - break; - default: - case Bits bits when bits == "10": - comparison = CompareOp.CompareOp_EQ; - break; - case Bits bits when bits == "11": - comparison = CompareOp.CompareOp_LE; - break; - } - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits result = new Bits(datasize); - Bits operand = V(datasize, n); - BigInteger element; - - bool test_passed; - - for (int e = 0; e <= elements - 1; e++) - { - element = SInt(Elem(operand, e, esize)); - - switch (comparison) - { - case CompareOp.CompareOp_GT: - test_passed = (element > (BigInteger)0); - break; - case CompareOp.CompareOp_GE: - test_passed = (element >= (BigInteger)0); - break; - default: - case CompareOp.CompareOp_EQ: - test_passed = (element == (BigInteger)0); - break; - case CompareOp.CompareOp_LE: - test_passed = (element <= (BigInteger)0); - break; - case CompareOp.CompareOp_LT: - test_passed = (element < (BigInteger)0); - break; - } - - Elem(result, e, esize, test_passed ? Ones(esize) : Zeros(esize)); - } - - V(d, result); - } - - // cmge_advsimd_zero.html#CMGE_asisdmisc_Z - public static void Cmge_Zero_S(Bits size, Bits Rn, Bits Rd) - { - const bool U = true; - const bool op = false; - - /* Decode Scalar */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - - /* if size != '11' then ReservedValue(); */ - - int esize = 8 << (int)UInt(size); - int datasize = esize; - int elements = 1; - - CompareOp comparison; - - switch (Bits.Concat(op, U)) - { - case Bits bits when bits == "00": - comparison = CompareOp.CompareOp_GT; - break; - default: - case Bits bits when bits == "01": - comparison = CompareOp.CompareOp_GE; - break; - case Bits bits when bits == "10": - comparison = CompareOp.CompareOp_EQ; - break; - case Bits bits when bits == "11": - comparison = CompareOp.CompareOp_LE; - break; - } - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits result = new Bits(datasize); - Bits operand = V(datasize, n); - BigInteger element; - - bool test_passed; - - for (int e = 0; e <= elements - 1; e++) - { - element = SInt(Elem(operand, e, esize)); - - switch (comparison) - { - case CompareOp.CompareOp_GT: - test_passed = (element > (BigInteger)0); - break; - default: - case CompareOp.CompareOp_GE: - test_passed = (element >= (BigInteger)0); - break; - case CompareOp.CompareOp_EQ: - test_passed = (element == (BigInteger)0); - break; - case CompareOp.CompareOp_LE: - test_passed = (element <= (BigInteger)0); - break; - case CompareOp.CompareOp_LT: - test_passed = (element < (BigInteger)0); - break; - } - - Elem(result, e, esize, test_passed ? Ones(esize) : Zeros(esize)); - } - - V(d, result); - } - - // cmge_advsimd_zero.html#CMGE_asimdmisc_Z - public static void Cmge_Zero_V(bool Q, Bits size, Bits Rn, Bits Rd) - { - const bool U = true; - const bool op = false; - - /* Decode Vector */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - - /* if size:Q == '110' then ReservedValue(); */ - - int esize = 8 << (int)UInt(size); - int datasize = (Q ? 128 : 64); - int elements = datasize / esize; - - CompareOp comparison; - - switch (Bits.Concat(op, U)) - { - case Bits bits when bits == "00": - comparison = CompareOp.CompareOp_GT; - break; - default: - case Bits bits when bits == "01": - comparison = CompareOp.CompareOp_GE; - break; - case Bits bits when bits == "10": - comparison = CompareOp.CompareOp_EQ; - break; - case Bits bits when bits == "11": - comparison = CompareOp.CompareOp_LE; - break; - } - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits result = new Bits(datasize); - Bits operand = V(datasize, n); - BigInteger element; - - bool test_passed; - - for (int e = 0; e <= elements - 1; e++) - { - element = SInt(Elem(operand, e, esize)); - - switch (comparison) - { - case CompareOp.CompareOp_GT: - test_passed = (element > (BigInteger)0); - break; - default: - case CompareOp.CompareOp_GE: - test_passed = (element >= (BigInteger)0); - break; - case CompareOp.CompareOp_EQ: - test_passed = (element == (BigInteger)0); - break; - case CompareOp.CompareOp_LE: - test_passed = (element <= (BigInteger)0); - break; - case CompareOp.CompareOp_LT: - test_passed = (element < (BigInteger)0); - break; - } - - Elem(result, e, esize, test_passed ? Ones(esize) : Zeros(esize)); - } - - V(d, result); - } - - // cmgt_advsimd_zero.html#CMGT_asisdmisc_Z - public static void Cmgt_Zero_S(Bits size, Bits Rn, Bits Rd) - { - const bool U = false; - const bool op = false; - - /* Decode Scalar */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - - /* if size != '11' then ReservedValue(); */ - - int esize = 8 << (int)UInt(size); - int datasize = esize; - int elements = 1; - - CompareOp comparison; - - switch (Bits.Concat(op, U)) - { - default: - case Bits bits when bits == "00": - comparison = CompareOp.CompareOp_GT; - break; - case Bits bits when bits == "01": - comparison = CompareOp.CompareOp_GE; - break; - case Bits bits when bits == "10": - comparison = CompareOp.CompareOp_EQ; - break; - case Bits bits when bits == "11": - comparison = CompareOp.CompareOp_LE; - break; - } - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits result = new Bits(datasize); - Bits operand = V(datasize, n); - BigInteger element; - - bool test_passed; - - for (int e = 0; e <= elements - 1; e++) - { - element = SInt(Elem(operand, e, esize)); - - switch (comparison) - { - default: - case CompareOp.CompareOp_GT: - test_passed = (element > (BigInteger)0); - break; - case CompareOp.CompareOp_GE: - test_passed = (element >= (BigInteger)0); - break; - case CompareOp.CompareOp_EQ: - test_passed = (element == (BigInteger)0); - break; - case CompareOp.CompareOp_LE: - test_passed = (element <= (BigInteger)0); - break; - case CompareOp.CompareOp_LT: - test_passed = (element < (BigInteger)0); - break; - } - - Elem(result, e, esize, test_passed ? Ones(esize) : Zeros(esize)); - } - - V(d, result); - } - - // cmgt_advsimd_zero.html#CMGT_asimdmisc_Z - public static void Cmgt_Zero_V(bool Q, Bits size, Bits Rn, Bits Rd) - { - const bool U = false; - const bool op = false; - - /* Decode Vector */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - - /* if size:Q == '110' then ReservedValue(); */ - - int esize = 8 << (int)UInt(size); - int datasize = (Q ? 128 : 64); - int elements = datasize / esize; - - CompareOp comparison; - - switch (Bits.Concat(op, U)) - { - default: - case Bits bits when bits == "00": - comparison = CompareOp.CompareOp_GT; - break; - case Bits bits when bits == "01": - comparison = CompareOp.CompareOp_GE; - break; - case Bits bits when bits == "10": - comparison = CompareOp.CompareOp_EQ; - break; - case Bits bits when bits == "11": - comparison = CompareOp.CompareOp_LE; - break; - } - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits result = new Bits(datasize); - Bits operand = V(datasize, n); - BigInteger element; - - bool test_passed; - - for (int e = 0; e <= elements - 1; e++) - { - element = SInt(Elem(operand, e, esize)); - - switch (comparison) - { - default: - case CompareOp.CompareOp_GT: - test_passed = (element > (BigInteger)0); - break; - case CompareOp.CompareOp_GE: - test_passed = (element >= (BigInteger)0); - break; - case CompareOp.CompareOp_EQ: - test_passed = (element == (BigInteger)0); - break; - case CompareOp.CompareOp_LE: - test_passed = (element <= (BigInteger)0); - break; - case CompareOp.CompareOp_LT: - test_passed = (element < (BigInteger)0); - break; - } - - Elem(result, e, esize, test_passed ? Ones(esize) : Zeros(esize)); - } - - V(d, result); - } - - // cmle_advsimd.html#CMLE_asisdmisc_Z - public static void Cmle_S(Bits size, Bits Rn, Bits Rd) - { - const bool U = true; - const bool op = true; - - /* Decode Scalar */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - - /* if size != '11' then ReservedValue(); */ - - int esize = 8 << (int)UInt(size); - int datasize = esize; - int elements = 1; - - CompareOp comparison; - - switch (Bits.Concat(op, U)) - { - case Bits bits when bits == "00": - comparison = CompareOp.CompareOp_GT; - break; - case Bits bits when bits == "01": - comparison = CompareOp.CompareOp_GE; - break; - case Bits bits when bits == "10": - comparison = CompareOp.CompareOp_EQ; - break; - default: - case Bits bits when bits == "11": - comparison = CompareOp.CompareOp_LE; - break; - } - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits result = new Bits(datasize); - Bits operand = V(datasize, n); - BigInteger element; - - bool test_passed; - - for (int e = 0; e <= elements - 1; e++) - { - element = SInt(Elem(operand, e, esize)); - - switch (comparison) - { - case CompareOp.CompareOp_GT: - test_passed = (element > (BigInteger)0); - break; - case CompareOp.CompareOp_GE: - test_passed = (element >= (BigInteger)0); - break; - case CompareOp.CompareOp_EQ: - test_passed = (element == (BigInteger)0); - break; - default: - case CompareOp.CompareOp_LE: - test_passed = (element <= (BigInteger)0); - break; - case CompareOp.CompareOp_LT: - test_passed = (element < (BigInteger)0); - break; - } - - Elem(result, e, esize, test_passed ? Ones(esize) : Zeros(esize)); - } - - V(d, result); - } - - // cmle_advsimd.html#CMLE_asimdmisc_Z - public static void Cmle_V(bool Q, Bits size, Bits Rn, Bits Rd) - { - const bool U = true; - const bool op = true; - - /* Decode Vector */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - - /* if size:Q == '110' then ReservedValue(); */ - - int esize = 8 << (int)UInt(size); - int datasize = (Q ? 128 : 64); - int elements = datasize / esize; - - CompareOp comparison; - - switch (Bits.Concat(op, U)) - { - case Bits bits when bits == "00": - comparison = CompareOp.CompareOp_GT; - break; - case Bits bits when bits == "01": - comparison = CompareOp.CompareOp_GE; - break; - case Bits bits when bits == "10": - comparison = CompareOp.CompareOp_EQ; - break; - default: - case Bits bits when bits == "11": - comparison = CompareOp.CompareOp_LE; - break; - } - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits result = new Bits(datasize); - Bits operand = V(datasize, n); - BigInteger element; - - bool test_passed; - - for (int e = 0; e <= elements - 1; e++) - { - element = SInt(Elem(operand, e, esize)); - - switch (comparison) - { - case CompareOp.CompareOp_GT: - test_passed = (element > (BigInteger)0); - break; - case CompareOp.CompareOp_GE: - test_passed = (element >= (BigInteger)0); - break; - case CompareOp.CompareOp_EQ: - test_passed = (element == (BigInteger)0); - break; - default: - case CompareOp.CompareOp_LE: - test_passed = (element <= (BigInteger)0); - break; - case CompareOp.CompareOp_LT: - test_passed = (element < (BigInteger)0); - break; - } - - Elem(result, e, esize, test_passed ? Ones(esize) : Zeros(esize)); - } - - V(d, result); - } - - // cmlt_advsimd.html#CMLT_asisdmisc_Z - public static void Cmlt_S(Bits size, Bits Rn, Bits Rd) - { - /* Decode Scalar */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - - /* if size != '11' then ReservedValue(); */ - - int esize = 8 << (int)UInt(size); - int datasize = esize; - int elements = 1; - - CompareOp comparison = CompareOp.CompareOp_LT; - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits result = new Bits(datasize); - Bits operand = V(datasize, n); - BigInteger element; - - bool test_passed; - - for (int e = 0; e <= elements - 1; e++) - { - element = SInt(Elem(operand, e, esize)); - - switch (comparison) - { - case CompareOp.CompareOp_GT: - test_passed = (element > (BigInteger)0); - break; - case CompareOp.CompareOp_GE: - test_passed = (element >= (BigInteger)0); - break; - case CompareOp.CompareOp_EQ: - test_passed = (element == (BigInteger)0); - break; - case CompareOp.CompareOp_LE: - test_passed = (element <= (BigInteger)0); - break; - default: - case CompareOp.CompareOp_LT: - test_passed = (element < (BigInteger)0); - break; - } - - Elem(result, e, esize, test_passed ? Ones(esize) : Zeros(esize)); - } - - V(d, result); - } - - // cmlt_advsimd.html#CMLT_asimdmisc_Z - public static void Cmlt_V(bool Q, Bits size, Bits Rn, Bits Rd) - { - /* Decode Vector */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - - /* if size:Q == '110' then ReservedValue(); */ - - int esize = 8 << (int)UInt(size); - int datasize = (Q ? 128 : 64); - int elements = datasize / esize; - - CompareOp comparison = CompareOp.CompareOp_LT; - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits result = new Bits(datasize); - Bits operand = V(datasize, n); - BigInteger element; - - bool test_passed; - - for (int e = 0; e <= elements - 1; e++) - { - element = SInt(Elem(operand, e, esize)); - - switch (comparison) - { - case CompareOp.CompareOp_GT: - test_passed = (element > (BigInteger)0); - break; - case CompareOp.CompareOp_GE: - test_passed = (element >= (BigInteger)0); - break; - case CompareOp.CompareOp_EQ: - test_passed = (element == (BigInteger)0); - break; - case CompareOp.CompareOp_LE: - test_passed = (element <= (BigInteger)0); - break; - default: - case CompareOp.CompareOp_LT: - test_passed = (element < (BigInteger)0); - break; - } - - Elem(result, e, esize, test_passed ? Ones(esize) : Zeros(esize)); - } - - V(d, result); - } - - // cnt_advsimd.html - public static void Cnt_V(bool Q, Bits size, Bits Rn, Bits Rd) - { - /* Decode Vector */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - - /* if size != '00' then ReservedValue(); */ - - int esize = 8; - int datasize = (Q ? 128 : 64); - int elements = datasize / 8; - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits result = new Bits(datasize); - Bits operand = V(datasize, n); - - BigInteger count; - - for (int e = 0; e <= elements - 1; e++) - { - count = (BigInteger)BitCount(Elem(operand, e, esize)); - - Elem(result, e, esize, count.SubBigInteger(esize - 1, 0)); - } - - V(d, result); - } - - // neg_advsimd.html#NEG_asisdmisc_R - public static void Neg_S(Bits size, Bits Rn, Bits Rd) - { - const bool U = true; - - /* Decode Scalar */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - - /* if size != '11' then ReservedValue(); */ - - int esize = 8 << (int)UInt(size); - int datasize = esize; - int elements = 1; - - bool neg = (U == true); - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits result = new Bits(datasize); - Bits operand = V(datasize, n); - - BigInteger element; - - for (int e = 0; e <= elements - 1; e++) - { - element = SInt(Elem(operand, e, esize)); - - if (neg) - { - element = -element; - } - else - { - element = Abs(element); - } - - Elem(result, e, esize, element.SubBigInteger(esize - 1, 0)); - } - - V(d, result); - } - - // neg_advsimd.html#NEG_asimdmisc_R - public static void Neg_V(bool Q, Bits size, Bits Rn, Bits Rd) - { - const bool U = true; - - /* Decode Vector */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - - /* if size:Q == '110' then ReservedValue(); */ - - int esize = 8 << (int)UInt(size); - int datasize = (Q ? 128 : 64); - int elements = datasize / esize; - - bool neg = (U == true); - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits result = new Bits(datasize); - Bits operand = V(datasize, n); - - BigInteger element; - - for (int e = 0; e <= elements - 1; e++) - { - element = SInt(Elem(operand, e, esize)); - - if (neg) - { - element = -element; - } - else - { - element = Abs(element); - } - - Elem(result, e, esize, element.SubBigInteger(esize - 1, 0)); - } - - V(d, result); - } - - // not_advsimd.html - public static void Not_V(bool Q, Bits Rn, Bits Rd) - { - /* Decode Vector */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - - int esize = 8; - int datasize = (Q ? 128 : 64); - int elements = datasize / 8; - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits result = new Bits(datasize); - Bits operand = V(datasize, n); - Bits element; - - for (int e = 0; e <= elements - 1; e++) - { - element = Elem(operand, e, esize); - - Elem(result, e, esize, NOT(element)); - } - - V(d, result); - } - - // rbit_advsimd.html - public static void Rbit_V(bool Q, Bits Rn, Bits Rd) - { - /* Decode Vector */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - - int esize = 8; - int datasize = (Q ? 128 : 64); - int elements = datasize / 8; - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits result = new Bits(datasize); - Bits operand = V(datasize, n); - Bits element; - Bits rev = new Bits(esize); - - for (int e = 0; e <= elements - 1; e++) - { - element = Elem(operand, e, esize); - - for (int i = 0; i <= esize - 1; i++) - { - rev[esize - 1 - i] = element[i]; - } - - Elem(result, e, esize, rev); - } - - V(d, result); - } - - // rev16_advsimd.html - public static void Rev16_V(bool Q, Bits size, Bits Rn, Bits Rd) - { - const bool U = false; - const bool o0 = true; - - /* Decode Vector */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - - // size=esize: B(0), H(1), S(1), D(S) - int esize = 8 << (int)UInt(size); - int datasize = (Q ? 128 : 64); - - // op=REVx: 64(0), 32(1), 16(2) - Bits op = Bits.Concat(o0, U); - - // => op+size: - // 64+B = 0, 64+H = 1, 64+S = 2, 64+D = X - // 32+B = 1, 32+H = 2, 32+S = X, 32+D = X - // 16+B = 2, 16+H = X, 16+S = X, 16+D = X - // 8+B = X, 8+H = X, 8+S = X, 8+D = X - // => 3-(op+size) (index bits in group) - // 64/B = 3, 64+H = 2, 64+S = 1, 64+D = X - // 32+B = 2, 32+H = 1, 32+S = X, 32+D = X - // 16+B = 1, 16+H = X, 16+S = X, 16+D = X - // 8+B = X, 8+H = X, 8+S = X, 8+D = X - - // index bits within group: 1, 2, 3 - /* if UInt(op) + UInt(size) >= 3 then UnallocatedEncoding(); */ - - int container_size; - - switch (op) - { - default: - case Bits bits when bits == "10": - container_size = 16; - break; - case Bits bits when bits == "01": - container_size = 32; - break; - case Bits bits when bits == "00": - container_size = 64; - break; - } - - int containers = datasize / container_size; - int elements_per_container = container_size / esize; - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits result = new Bits(datasize); - Bits operand = V(datasize, n); - - int element = 0; - int rev_element; - - for (int c = 0; c <= containers - 1; c++) - { - rev_element = element + elements_per_container - 1; - - for (int e = 0; e <= elements_per_container - 1; e++) - { - Elem(result, rev_element, esize, Elem(operand, element, esize)); - - element = element + 1; - rev_element = rev_element - 1; - } - } - - V(d, result); - } - - // rev32_advsimd.html - public static void Rev32_V(bool Q, Bits size, Bits Rn, Bits Rd) - { - const bool U = true; - const bool o0 = false; - - /* Decode Vector */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - - // size=esize: B(0), H(1), S(1), D(S) - int esize = 8 << (int)UInt(size); - int datasize = (Q ? 128 : 64); - - // op=REVx: 64(0), 32(1), 16(2) - Bits op = Bits.Concat(o0, U); - - // => op+size: - // 64+B = 0, 64+H = 1, 64+S = 2, 64+D = X - // 32+B = 1, 32+H = 2, 32+S = X, 32+D = X - // 16+B = 2, 16+H = X, 16+S = X, 16+D = X - // 8+B = X, 8+H = X, 8+S = X, 8+D = X - // => 3-(op+size) (index bits in group) - // 64/B = 3, 64+H = 2, 64+S = 1, 64+D = X - // 32+B = 2, 32+H = 1, 32+S = X, 32+D = X - // 16+B = 1, 16+H = X, 16+S = X, 16+D = X - // 8+B = X, 8+H = X, 8+S = X, 8+D = X - - // index bits within group: 1, 2, 3 - /* if UInt(op) + UInt(size) >= 3 then UnallocatedEncoding(); */ - - int container_size; - - switch (op) - { - case Bits bits when bits == "10": - container_size = 16; - break; - default: - case Bits bits when bits == "01": - container_size = 32; - break; - case Bits bits when bits == "00": - container_size = 64; - break; - } - - int containers = datasize / container_size; - int elements_per_container = container_size / esize; - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits result = new Bits(datasize); - Bits operand = V(datasize, n); - - int element = 0; - int rev_element; - - for (int c = 0; c <= containers - 1; c++) - { - rev_element = element + elements_per_container - 1; - - for (int e = 0; e <= elements_per_container - 1; e++) - { - Elem(result, rev_element, esize, Elem(operand, element, esize)); - - element = element + 1; - rev_element = rev_element - 1; - } - } - - V(d, result); - } - - // rev64_advsimd.html - public static void Rev64_V(bool Q, Bits size, Bits Rn, Bits Rd) - { - const bool U = false; - const bool o0 = false; - - /* Decode Vector */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - - // size=esize: B(0), H(1), S(1), D(S) - int esize = 8 << (int)UInt(size); - int datasize = (Q ? 128 : 64); - - // op=REVx: 64(0), 32(1), 16(2) - Bits op = Bits.Concat(o0, U); - - // => op+size: - // 64+B = 0, 64+H = 1, 64+S = 2, 64+D = X - // 32+B = 1, 32+H = 2, 32+S = X, 32+D = X - // 16+B = 2, 16+H = X, 16+S = X, 16+D = X - // 8+B = X, 8+H = X, 8+S = X, 8+D = X - // => 3-(op+size) (index bits in group) - // 64/B = 3, 64+H = 2, 64+S = 1, 64+D = X - // 32+B = 2, 32+H = 1, 32+S = X, 32+D = X - // 16+B = 1, 16+H = X, 16+S = X, 16+D = X - // 8+B = X, 8+H = X, 8+S = X, 8+D = X - - // index bits within group: 1, 2, 3 - /* if UInt(op) + UInt(size) >= 3 then UnallocatedEncoding(); */ - - int container_size; - - switch (op) - { - case Bits bits when bits == "10": - container_size = 16; - break; - case Bits bits when bits == "01": - container_size = 32; - break; - default: - case Bits bits when bits == "00": - container_size = 64; - break; - } - - int containers = datasize / container_size; - int elements_per_container = container_size / esize; - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits result = new Bits(datasize); - Bits operand = V(datasize, n); - - int element = 0; - int rev_element; - - for (int c = 0; c <= containers - 1; c++) - { - rev_element = element + elements_per_container - 1; - - for (int e = 0; e <= elements_per_container - 1; e++) - { - Elem(result, rev_element, esize, Elem(operand, element, esize)); - - element = element + 1; - rev_element = rev_element - 1; - } - } - - V(d, result); - } - - // sqxtn_advsimd.html#SQXTN_asisdmisc_N - public static void Sqxtn_S(Bits size, Bits Rn, Bits Rd) - { - const bool U = false; - - /* Decode Scalar */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - - /* if size == '11' then ReservedValue(); */ - - int esize = 8 << (int)UInt(size); - int datasize = esize; - int part = 0; - int elements = 1; - - bool unsigned = (U == true); - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits result = new Bits(datasize); - Bits operand = V(2 * datasize, n); - Bits element; - bool sat; - - for (int e = 0; e <= elements - 1; e++) - { - element = Elem(operand, e, 2 * esize); - - (Bits _result, bool _sat) = SatQ(Int(element, unsigned), esize, unsigned); - Elem(result, e, esize, _result); - sat = _sat; - - if (sat) - { - /* FPSR.QC = '1'; */ - FPSR[27] = true; // TODO: Add named fields. - } - } - - Vpart(d, part, result); - } - - // sqxtn_advsimd.html#SQXTN_asimdmisc_N - public static void Sqxtn_V(bool Q, Bits size, Bits Rn, Bits Rd) - { - const bool U = false; - - /* Decode Vector */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - - /* if size == '11' then ReservedValue(); */ - - int esize = 8 << (int)UInt(size); - int datasize = 64; - int part = (int)UInt(Q); - int elements = datasize / esize; - - bool unsigned = (U == true); - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits result = new Bits(datasize); - Bits operand = V(2 * datasize, n); - Bits element; - bool sat; - - for (int e = 0; e <= elements - 1; e++) - { - element = Elem(operand, e, 2 * esize); - - (Bits _result, bool _sat) = SatQ(Int(element, unsigned), esize, unsigned); - Elem(result, e, esize, _result); - sat = _sat; - - if (sat) - { - /* FPSR.QC = '1'; */ - FPSR[27] = true; // TODO: Add named fields. - } - } - - Vpart(d, part, result); - } - - // sqxtun_advsimd.html#SQXTUN_asisdmisc_N - public static void Sqxtun_S(Bits size, Bits Rn, Bits Rd) - { - /* Decode Scalar */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - - /* if size == '11' then ReservedValue(); */ - - int esize = 8 << (int)UInt(size); - int datasize = esize; - int part = 0; - int elements = 1; - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits result = new Bits(datasize); - Bits operand = V(2 * datasize, n); - Bits element; - bool sat; - - for (int e = 0; e <= elements - 1; e++) - { - element = Elem(operand, e, 2 * esize); - - (Bits _result, bool _sat) = UnsignedSatQ(SInt(element), esize); - Elem(result, e, esize, _result); - sat = _sat; - - if (sat) - { - /* FPSR.QC = '1'; */ - FPSR[27] = true; // TODO: Add named fields. - } - } - - Vpart(d, part, result); - } - - // sqxtun_advsimd.html#SQXTUN_asimdmisc_N - public static void Sqxtun_V(bool Q, Bits size, Bits Rn, Bits Rd) - { - /* Decode Vector */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - - /* if size == '11' then ReservedValue(); */ - - int esize = 8 << (int)UInt(size); - int datasize = 64; - int part = (int)UInt(Q); - int elements = datasize / esize; - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits result = new Bits(datasize); - Bits operand = V(2 * datasize, n); - Bits element; - bool sat; - - for (int e = 0; e <= elements - 1; e++) - { - element = Elem(operand, e, 2 * esize); - - (Bits _result, bool _sat) = UnsignedSatQ(SInt(element), esize); - Elem(result, e, esize, _result); - sat = _sat; - - if (sat) - { - /* FPSR.QC = '1'; */ - FPSR[27] = true; // TODO: Add named fields. - } - } - - Vpart(d, part, result); - } - - // uqxtn_advsimd.html#UQXTN_asisdmisc_N - public static void Uqxtn_S(Bits size, Bits Rn, Bits Rd) - { - const bool U = true; - - /* Decode Scalar */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - - /* if size == '11' then ReservedValue(); */ - - int esize = 8 << (int)UInt(size); - int datasize = esize; - int part = 0; - int elements = 1; - - bool unsigned = (U == true); - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits result = new Bits(datasize); - Bits operand = V(2 * datasize, n); - Bits element; - bool sat; - - for (int e = 0; e <= elements - 1; e++) - { - element = Elem(operand, e, 2 * esize); - - (Bits _result, bool _sat) = SatQ(Int(element, unsigned), esize, unsigned); - Elem(result, e, esize, _result); - sat = _sat; - - if (sat) - { - /* FPSR.QC = '1'; */ - FPSR[27] = true; // TODO: Add named fields. - } - } - - Vpart(d, part, result); - } - - // uqxtn_advsimd.html#UQXTN_asimdmisc_N - public static void Uqxtn_V(bool Q, Bits size, Bits Rn, Bits Rd) - { - const bool U = true; - - /* Decode Vector */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - - /* if size == '11' then ReservedValue(); */ - - int esize = 8 << (int)UInt(size); - int datasize = 64; - int part = (int)UInt(Q); - int elements = datasize / esize; - - bool unsigned = (U == true); - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits result = new Bits(datasize); - Bits operand = V(2 * datasize, n); - Bits element; - bool sat; - - for (int e = 0; e <= elements - 1; e++) - { - element = Elem(operand, e, 2 * esize); - - (Bits _result, bool _sat) = SatQ(Int(element, unsigned), esize, unsigned); - Elem(result, e, esize, _result); - sat = _sat; - - if (sat) - { - /* FPSR.QC = '1'; */ - FPSR[27] = true; // TODO: Add named fields. - } - } - - Vpart(d, part, result); - } -#endregion - -#region "SimdReg" - // add_advsimd.html#ADD_asisdsame_only - public static void Add_S(Bits size, Bits Rm, Bits Rn, Bits Rd) - { - const bool U = false; - - /* Decode Scalar */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - /* if size != '11' then ReservedValue(); */ - - int esize = 8 << (int)UInt(size); - int datasize = esize; - int elements = 1; - - bool sub_op = (U == true); - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits result = new Bits(datasize); - Bits operand1 = V(datasize, n); - Bits operand2 = V(datasize, m); - Bits element1; - Bits element2; - - for (int e = 0; e <= elements - 1; e++) - { - element1 = Elem(operand1, e, esize); - element2 = Elem(operand2, e, esize); - - if (sub_op) - { - Elem(result, e, esize, element1 - element2); - } - else - { - Elem(result, e, esize, element1 + element2); - } - } - - V(d, result); - } - - // add_advsimd.html#ADD_asimdsame_only - public static void Add_V(bool Q, Bits size, Bits Rm, Bits Rn, Bits Rd) - { - const bool U = false; - - /* Decode Vector */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - /* if size:Q == '110' then ReservedValue(); */ - - int esize = 8 << (int)UInt(size); - int datasize = (Q ? 128 : 64); - int elements = datasize / esize; - - bool sub_op = (U == true); - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits result = new Bits(datasize); - Bits operand1 = V(datasize, n); - Bits operand2 = V(datasize, m); - Bits element1; - Bits element2; - - for (int e = 0; e <= elements - 1; e++) - { - element1 = Elem(operand1, e, esize); - element2 = Elem(operand2, e, esize); - - if (sub_op) - { - Elem(result, e, esize, element1 - element2); - } - else - { - Elem(result, e, esize, element1 + element2); - } - } - - V(d, result); - } - - // addhn_advsimd.html - public static void Addhn_V(bool Q, Bits size, Bits Rm, Bits Rn, Bits Rd) - { - const bool U = false; - const bool o1 = false; - - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - /* if size == '11' then ReservedValue(); */ - - int esize = 8 << (int)UInt(size); - int datasize = 64; - int part = (int)UInt(Q); - int elements = datasize / esize; - - bool sub_op = (o1 == true); - bool round = (U == true); - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits result = new Bits(datasize); - Bits operand1 = V(2 * datasize, n); - Bits operand2 = V(2 * datasize, m); - BigInteger round_const = (round ? (BigInteger)1 << (esize - 1) : 0); - Bits sum; - Bits element1; - Bits element2; - - for (int e = 0; e <= elements - 1; e++) - { - element1 = Elem(operand1, e, 2 * esize); - element2 = Elem(operand2, e, 2 * esize); - - if (sub_op) - { - sum = element1 - element2; - } - else - { - sum = element1 + element2; - } - - sum = sum + round_const; - - Elem(result, e, esize, sum[2 * esize - 1, esize]); - } - - Vpart(d, part, result); - } - - // addp_advsimd_vec.html - public static void Addp_V(bool Q, Bits size, Bits Rm, Bits Rn, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - /* if size:Q == '110' then ReservedValue(); */ - - int esize = 8 << (int)UInt(size); - int datasize = (Q ? 128 : 64); - int elements = datasize / esize; - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits result = new Bits(datasize); - Bits operand1 = V(datasize, n); - Bits operand2 = V(datasize, m); - Bits concat = Bits.Concat(operand2, operand1); - Bits element1; - Bits element2; - - for (int e = 0; e <= elements - 1; e++) - { - element1 = Elem(concat, 2 * e, esize); - element2 = Elem(concat, (2 * e) + 1, esize); - - Elem(result, e, esize, element1 + element2); - } - - V(d, result); - } - - // and_advsimd.html - public static void And_V(bool Q, Bits Rm, Bits Rn, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - int datasize = (Q ? 128 : 64); - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits operand1 = V(datasize, n); - Bits operand2 = V(datasize, m); - - Bits result = AND(operand1, operand2); - - V(d, result); - } - - // bic_advsimd_reg.html - public static void Bic_V(bool Q, Bits Rm, Bits Rn, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - int datasize = (Q ? 128 : 64); - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits operand1 = V(datasize, n); - Bits operand2 = V(datasize, m); - - operand2 = NOT(operand2); - - Bits result = AND(operand1, operand2); - - V(d, result); - } - - // bif_advsimd.html - public static void Bif_V(bool Q, Bits Rm, Bits Rn, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - int datasize = (Q ? 128 : 64); - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits operand1; - Bits operand3; - Bits operand4 = V(datasize, n); - - operand1 = V(datasize, d); - operand3 = NOT(V(datasize, m)); - - V(d, EOR(operand1, AND(EOR(operand1, operand4), operand3))); - } - - // bit_advsimd.html - public static void Bit_V(bool Q, Bits Rm, Bits Rn, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - int datasize = (Q ? 128 : 64); - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits operand1; - Bits operand3; - Bits operand4 = V(datasize, n); - - operand1 = V(datasize, d); - operand3 = V(datasize, m); - - V(d, EOR(operand1, AND(EOR(operand1, operand4), operand3))); - } - - // bsl_advsimd.html - public static void Bsl_V(bool Q, Bits Rm, Bits Rn, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - int datasize = (Q ? 128 : 64); - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits operand1; - Bits operand3; - Bits operand4 = V(datasize, n); - - operand1 = V(datasize, m); - operand3 = V(datasize, d); - - V(d, EOR(operand1, AND(EOR(operand1, operand4), operand3))); - } - - // cmeq_advsimd_reg.html#CMEQ_asisdsame_only - public static void Cmeq_Reg_S(Bits size, Bits Rm, Bits Rn, Bits Rd) - { - const bool U = true; - - /* Decode Scalar */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - /* if size != '11' then ReservedValue(); */ - - int esize = 8 << (int)UInt(size); - int datasize = esize; - int elements = 1; - - bool and_test = (U == false); - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits result = new Bits(datasize); - Bits operand1 = V(datasize, n); - Bits operand2 = V(datasize, m); - Bits element1; - Bits element2; - - bool test_passed; - - for (int e = 0; e <= elements - 1; e++) - { - element1 = Elem(operand1, e, esize); - element2 = Elem(operand2, e, esize); - - if (and_test) - { - test_passed = !IsZero(AND(element1, element2)); - } - else - { - test_passed = (element1 == element2); - } - - Elem(result, e, esize, test_passed ? Ones(esize) : Zeros(esize)); - } - - V(d, result); - } - - // cmeq_advsimd_reg.html#CMEQ_asimdsame_only - public static void Cmeq_Reg_V(bool Q, Bits size, Bits Rm, Bits Rn, Bits Rd) - { - const bool U = true; - - /* Decode Vector */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - /* if size:Q == '110' then ReservedValue(); */ - - int esize = 8 << (int)UInt(size); - int datasize = (Q ? 128 : 64); - int elements = datasize / esize; - - bool and_test = (U == false); - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits result = new Bits(datasize); - Bits operand1 = V(datasize, n); - Bits operand2 = V(datasize, m); - Bits element1; - Bits element2; - - bool test_passed; - - for (int e = 0; e <= elements - 1; e++) - { - element1 = Elem(operand1, e, esize); - element2 = Elem(operand2, e, esize); - - if (and_test) - { - test_passed = !IsZero(AND(element1, element2)); - } - else - { - test_passed = (element1 == element2); - } - - Elem(result, e, esize, test_passed ? Ones(esize) : Zeros(esize)); - } - - V(d, result); - } - - // cmge_advsimd_reg.html#CMGE_asisdsame_only - public static void Cmge_Reg_S(Bits size, Bits Rm, Bits Rn, Bits Rd) - { - const bool U = false; - const bool eq = true; - - /* Decode Scalar */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - /* if size != '11' then ReservedValue(); */ - - int esize = 8 << (int)UInt(size); - int datasize = esize; - int elements = 1; - - bool unsigned = (U == true); - bool cmp_eq = (eq == true); - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits result = new Bits(datasize); - Bits operand1 = V(datasize, n); - Bits operand2 = V(datasize, m); - BigInteger element1; - BigInteger element2; - - bool test_passed; - - for (int e = 0; e <= elements - 1; e++) - { - element1 = Int(Elem(operand1, e, esize), unsigned); - element2 = Int(Elem(operand2, e, esize), unsigned); - - test_passed = (cmp_eq ? element1 >= element2 : element1 > element2); - - Elem(result, e, esize, test_passed ? Ones(esize) : Zeros(esize)); - } - - V(d, result); - } - - // cmge_advsimd_reg.html#CMGE_asimdsame_only - public static void Cmge_Reg_V(bool Q, Bits size, Bits Rm, Bits Rn, Bits Rd) - { - const bool U = false; - const bool eq = true; - - /* Decode Vector */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - /* if size:Q == '110' then ReservedValue(); */ - - int esize = 8 << (int)UInt(size); - int datasize = (Q ? 128 : 64); - int elements = datasize / esize; - - bool unsigned = (U == true); - bool cmp_eq = (eq == true); - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits result = new Bits(datasize); - Bits operand1 = V(datasize, n); - Bits operand2 = V(datasize, m); - BigInteger element1; - BigInteger element2; - - bool test_passed; - - for (int e = 0; e <= elements - 1; e++) - { - element1 = Int(Elem(operand1, e, esize), unsigned); - element2 = Int(Elem(operand2, e, esize), unsigned); - - test_passed = (cmp_eq ? element1 >= element2 : element1 > element2); - - Elem(result, e, esize, test_passed ? Ones(esize) : Zeros(esize)); - } - - V(d, result); - } - - // cmgt_advsimd_reg.html#CMGT_asisdsame_only - public static void Cmgt_Reg_S(Bits size, Bits Rm, Bits Rn, Bits Rd) - { - const bool U = false; - const bool eq = false; - - /* Decode Scalar */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - /* if size != '11' then ReservedValue(); */ - - int esize = 8 << (int)UInt(size); - int datasize = esize; - int elements = 1; - - bool unsigned = (U == true); - bool cmp_eq = (eq == true); - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits result = new Bits(datasize); - Bits operand1 = V(datasize, n); - Bits operand2 = V(datasize, m); - BigInteger element1; - BigInteger element2; - - bool test_passed; - - for (int e = 0; e <= elements - 1; e++) - { - element1 = Int(Elem(operand1, e, esize), unsigned); - element2 = Int(Elem(operand2, e, esize), unsigned); - - test_passed = (cmp_eq ? element1 >= element2 : element1 > element2); - - Elem(result, e, esize, test_passed ? Ones(esize) : Zeros(esize)); - } - - V(d, result); - } - - // cmgt_advsimd_reg.html#CMGT_asimdsame_only - public static void Cmgt_Reg_V(bool Q, Bits size, Bits Rm, Bits Rn, Bits Rd) - { - const bool U = false; - const bool eq = false; - - /* Decode Vector */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - /* if size:Q == '110' then ReservedValue(); */ - - int esize = 8 << (int)UInt(size); - int datasize = (Q ? 128 : 64); - int elements = datasize / esize; - - bool unsigned = (U == true); - bool cmp_eq = (eq == true); - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits result = new Bits(datasize); - Bits operand1 = V(datasize, n); - Bits operand2 = V(datasize, m); - BigInteger element1; - BigInteger element2; - - bool test_passed; - - for (int e = 0; e <= elements - 1; e++) - { - element1 = Int(Elem(operand1, e, esize), unsigned); - element2 = Int(Elem(operand2, e, esize), unsigned); - - test_passed = (cmp_eq ? element1 >= element2 : element1 > element2); - - Elem(result, e, esize, test_passed ? Ones(esize) : Zeros(esize)); - } - - V(d, result); - } - - // cmhi_advsimd.html#CMHI_asisdsame_only - public static void Cmhi_S(Bits size, Bits Rm, Bits Rn, Bits Rd) - { - const bool U = true; - const bool eq = false; - - /* Decode Scalar */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - /* if size != '11' then ReservedValue(); */ - - int esize = 8 << (int)UInt(size); - int datasize = esize; - int elements = 1; - - bool unsigned = (U == true); - bool cmp_eq = (eq == true); - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits result = new Bits(datasize); - Bits operand1 = V(datasize, n); - Bits operand2 = V(datasize, m); - BigInteger element1; - BigInteger element2; - - bool test_passed; - - for (int e = 0; e <= elements - 1; e++) - { - element1 = Int(Elem(operand1, e, esize), unsigned); - element2 = Int(Elem(operand2, e, esize), unsigned); - - test_passed = (cmp_eq ? element1 >= element2 : element1 > element2); - - Elem(result, e, esize, test_passed ? Ones(esize) : Zeros(esize)); - } - - V(d, result); - } - - // cmhi_advsimd.html#CMHI_asimdsame_only - public static void Cmhi_V(bool Q, Bits size, Bits Rm, Bits Rn, Bits Rd) - { - const bool U = true; - const bool eq = false; - - /* Decode Vector */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - /* if size:Q == '110' then ReservedValue(); */ - - int esize = 8 << (int)UInt(size); - int datasize = (Q ? 128 : 64); - int elements = datasize / esize; - - bool unsigned = (U == true); - bool cmp_eq = (eq == true); - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits result = new Bits(datasize); - Bits operand1 = V(datasize, n); - Bits operand2 = V(datasize, m); - BigInteger element1; - BigInteger element2; - - bool test_passed; - - for (int e = 0; e <= elements - 1; e++) - { - element1 = Int(Elem(operand1, e, esize), unsigned); - element2 = Int(Elem(operand2, e, esize), unsigned); - - test_passed = (cmp_eq ? element1 >= element2 : element1 > element2); - - Elem(result, e, esize, test_passed ? Ones(esize) : Zeros(esize)); - } - - V(d, result); - } - - // cmhs_advsimd.html#CMHS_asisdsame_only - public static void Cmhs_S(Bits size, Bits Rm, Bits Rn, Bits Rd) - { - const bool U = true; - const bool eq = true; - - /* Decode Scalar */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - /* if size != '11' then ReservedValue(); */ - - int esize = 8 << (int)UInt(size); - int datasize = esize; - int elements = 1; - - bool unsigned = (U == true); - bool cmp_eq = (eq == true); - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits result = new Bits(datasize); - Bits operand1 = V(datasize, n); - Bits operand2 = V(datasize, m); - BigInteger element1; - BigInteger element2; - - bool test_passed; - - for (int e = 0; e <= elements - 1; e++) - { - element1 = Int(Elem(operand1, e, esize), unsigned); - element2 = Int(Elem(operand2, e, esize), unsigned); - - test_passed = (cmp_eq ? element1 >= element2 : element1 > element2); - - Elem(result, e, esize, test_passed ? Ones(esize) : Zeros(esize)); - } - - V(d, result); - } - - // cmhs_advsimd.html#CMHS_asimdsame_only - public static void Cmhs_V(bool Q, Bits size, Bits Rm, Bits Rn, Bits Rd) - { - const bool U = true; - const bool eq = true; - - /* Decode Vector */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - /* if size:Q == '110' then ReservedValue(); */ - - int esize = 8 << (int)UInt(size); - int datasize = (Q ? 128 : 64); - int elements = datasize / esize; - - bool unsigned = (U == true); - bool cmp_eq = (eq == true); - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits result = new Bits(datasize); - Bits operand1 = V(datasize, n); - Bits operand2 = V(datasize, m); - BigInteger element1; - BigInteger element2; - - bool test_passed; - - for (int e = 0; e <= elements - 1; e++) - { - element1 = Int(Elem(operand1, e, esize), unsigned); - element2 = Int(Elem(operand2, e, esize), unsigned); - - test_passed = (cmp_eq ? element1 >= element2 : element1 > element2); - - Elem(result, e, esize, test_passed ? Ones(esize) : Zeros(esize)); - } - - V(d, result); - } - - // cmtst_advsimd.html#CMTST_asisdsame_only - public static void Cmtst_S(Bits size, Bits Rm, Bits Rn, Bits Rd) - { - const bool U = false; - - /* Decode Scalar */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - /* if size != '11' then ReservedValue(); */ - - int esize = 8 << (int)UInt(size); - int datasize = esize; - int elements = 1; - - bool and_test = (U == false); - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits result = new Bits(datasize); - Bits operand1 = V(datasize, n); - Bits operand2 = V(datasize, m); - Bits element1; - Bits element2; - - bool test_passed; - - for (int e = 0; e <= elements - 1; e++) - { - element1 = Elem(operand1, e, esize); - element2 = Elem(operand2, e, esize); - - if (and_test) - { - test_passed = !IsZero(AND(element1, element2)); - } - else - { - test_passed = (element1 == element2); - } - - Elem(result, e, esize, test_passed ? Ones(esize) : Zeros(esize)); - } - - V(d, result); - } - - // cmtst_advsimd.html#CMTST_asimdsame_only - public static void Cmtst_V(bool Q, Bits size, Bits Rm, Bits Rn, Bits Rd) - { - const bool U = false; - - /* Decode Vector */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - /* if size:Q == '110' then ReservedValue(); */ - - int esize = 8 << (int)UInt(size); - int datasize = (Q ? 128 : 64); - int elements = datasize / esize; - - bool and_test = (U == false); - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits result = new Bits(datasize); - Bits operand1 = V(datasize, n); - Bits operand2 = V(datasize, m); - Bits element1; - Bits element2; - - bool test_passed; - - for (int e = 0; e <= elements - 1; e++) - { - element1 = Elem(operand1, e, esize); - element2 = Elem(operand2, e, esize); - - if (and_test) - { - test_passed = !IsZero(AND(element1, element2)); - } - else - { - test_passed = (element1 == element2); - } - - Elem(result, e, esize, test_passed ? Ones(esize) : Zeros(esize)); - } - - V(d, result); - } - - // eor_advsimd.html - public static void Eor_V(bool Q, Bits Rm, Bits Rn, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - int datasize = (Q ? 128 : 64); - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits operand1 = V(datasize, m); - Bits operand2 = Zeros(datasize); - Bits operand3 = Ones(datasize); - Bits operand4 = V(datasize, n); - - Bits result = EOR(operand1, AND(EOR(operand2, operand4), operand3)); - - V(d, result); - } - - // orn_advsimd.html - public static void Orn_V(bool Q, Bits Rm, Bits Rn, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - int datasize = (Q ? 128 : 64); - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits operand1 = V(datasize, n); - Bits operand2 = V(datasize, m); - - operand2 = NOT(operand2); - - Bits result = OR(operand1, operand2); - - V(d, result); - } - - // orr_advsimd_reg.html - public static void Orr_V(bool Q, Bits Rm, Bits Rn, Bits Rd) - { - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - int datasize = (Q ? 128 : 64); - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits operand1 = V(datasize, n); - Bits operand2 = V(datasize, m); - - Bits result = OR(operand1, operand2); - - V(d, result); - } - - // raddhn_advsimd.html - public static void Raddhn_V(bool Q, Bits size, Bits Rm, Bits Rn, Bits Rd) - { - const bool U = true; - const bool o1 = false; - - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - /* if size == '11' then ReservedValue(); */ - - int esize = 8 << (int)UInt(size); - int datasize = 64; - int part = (int)UInt(Q); - int elements = datasize / esize; - - bool sub_op = (o1 == true); - bool round = (U == true); - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits result = new Bits(datasize); - Bits operand1 = V(2 * datasize, n); - Bits operand2 = V(2 * datasize, m); - BigInteger round_const = (round ? (BigInteger)1 << (esize - 1) : 0); - Bits sum; - Bits element1; - Bits element2; - - for (int e = 0; e <= elements - 1; e++) - { - element1 = Elem(operand1, e, 2 * esize); - element2 = Elem(operand2, e, 2 * esize); - - if (sub_op) - { - sum = element1 - element2; - } - else - { - sum = element1 + element2; - } - - sum = sum + round_const; - - Elem(result, e, esize, sum[2 * esize - 1, esize]); - } - - Vpart(d, part, result); - } - - // rsubhn_advsimd.html - public static void Rsubhn_V(bool Q, Bits size, Bits Rm, Bits Rn, Bits Rd) - { - const bool U = true; - const bool o1 = true; - - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - /* if size == '11' then ReservedValue(); */ - - int esize = 8 << (int)UInt(size); - int datasize = 64; - int part = (int)UInt(Q); - int elements = datasize / esize; - - bool sub_op = (o1 == true); - bool round = (U == true); - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits result = new Bits(datasize); - Bits operand1 = V(2 * datasize, n); - Bits operand2 = V(2 * datasize, m); - BigInteger round_const = (round ? (BigInteger)1 << (esize - 1) : 0); - Bits sum; - Bits element1; - Bits element2; - - for (int e = 0; e <= elements - 1; e++) - { - element1 = Elem(operand1, e, 2 * esize); - element2 = Elem(operand2, e, 2 * esize); - - if (sub_op) - { - sum = element1 - element2; - } - else - { - sum = element1 + element2; - } - - sum = sum + round_const; - - Elem(result, e, esize, sum[2 * esize - 1, esize]); - } - - Vpart(d, part, result); - } - - // saba_advsimd.html - public static void Saba_V(bool Q, Bits size, Bits Rm, Bits Rn, Bits Rd) - { - const bool U = false; - const bool ac = true; - - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - /* if size == '11' then ReservedValue(); */ - - int esize = 8 << (int)UInt(size); - int datasize = (Q ? 128 : 64); - int elements = datasize / esize; - - bool unsigned = (U == true); - bool accumulate = (ac == true); - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits operand1 = V(datasize, n); - Bits operand2 = V(datasize, m); - BigInteger element1; - BigInteger element2; - Bits absdiff; - - Bits result = (accumulate ? V(datasize, d) : Zeros(datasize)); - - for (int e = 0; e <= elements - 1; e++) - { - element1 = Int(Elem(operand1, e, esize), unsigned); - element2 = Int(Elem(operand2, e, esize), unsigned); - - absdiff = Abs(element1 - element2).SubBigInteger(esize - 1, 0); - - Elem(result, e, esize, Elem(result, e, esize) + absdiff); - } - - V(d, result); - } - - // sabal_advsimd.html - public static void Sabal_V(bool Q, Bits size, Bits Rm, Bits Rn, Bits Rd) - { - const bool U = false; - const bool op = false; - - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - /* if size == '11' then ReservedValue(); */ - - int esize = 8 << (int)UInt(size); - int datasize = 64; - int part = (int)UInt(Q); - int elements = datasize / esize; - - bool unsigned = (U == true); - bool accumulate = (op == false); - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits operand1 = Vpart(datasize, n, part); - Bits operand2 = Vpart(datasize, m, part); - BigInteger element1; - BigInteger element2; - Bits absdiff; - - Bits result = (accumulate ? V(2 * datasize, d) : Zeros(2 * datasize)); - - for (int e = 0; e <= elements - 1; e++) - { - element1 = Int(Elem(operand1, e, esize), unsigned); - element2 = Int(Elem(operand2, e, esize), unsigned); - - absdiff = Abs(element1 - element2).SubBigInteger(2 * esize - 1, 0); - - Elem(result, e, 2 * esize, Elem(result, e, 2 * esize) + absdiff); - } - - V(d, result); - } - - // sabd_advsimd.html - public static void Sabd_V(bool Q, Bits size, Bits Rm, Bits Rn, Bits Rd) - { - const bool U = false; - const bool ac = false; - - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - /* if size == '11' then ReservedValue(); */ - - int esize = 8 << (int)UInt(size); - int datasize = (Q ? 128 : 64); - int elements = datasize / esize; - - bool unsigned = (U == true); - bool accumulate = (ac == true); - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits operand1 = V(datasize, n); - Bits operand2 = V(datasize, m); - BigInteger element1; - BigInteger element2; - Bits absdiff; - - Bits result = (accumulate ? V(datasize, d) : Zeros(datasize)); - - for (int e = 0; e <= elements - 1; e++) - { - element1 = Int(Elem(operand1, e, esize), unsigned); - element2 = Int(Elem(operand2, e, esize), unsigned); - - absdiff = Abs(element1 - element2).SubBigInteger(esize - 1, 0); - - Elem(result, e, esize, Elem(result, e, esize) + absdiff); - } - - V(d, result); - } - - // sabdl_advsimd.html - public static void Sabdl_V(bool Q, Bits size, Bits Rm, Bits Rn, Bits Rd) - { - const bool U = false; - const bool op = true; - - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - /* if size == '11' then ReservedValue(); */ - - int esize = 8 << (int)UInt(size); - int datasize = 64; - int part = (int)UInt(Q); - int elements = datasize / esize; - - bool unsigned = (U == true); - bool accumulate = (op == false); - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits operand1 = Vpart(datasize, n, part); - Bits operand2 = Vpart(datasize, m, part); - BigInteger element1; - BigInteger element2; - Bits absdiff; - - Bits result = (accumulate ? V(2 * datasize, d) : Zeros(2 * datasize)); - - for (int e = 0; e <= elements - 1; e++) - { - element1 = Int(Elem(operand1, e, esize), unsigned); - element2 = Int(Elem(operand2, e, esize), unsigned); - - absdiff = Abs(element1 - element2).SubBigInteger(2 * esize - 1, 0); - - Elem(result, e, 2 * esize, Elem(result, e, 2 * esize) + absdiff); - } - - V(d, result); - } - - // sub_advsimd.html#SUB_asisdsame_only - public static void Sub_S(Bits size, Bits Rm, Bits Rn, Bits Rd) - { - const bool U = true; - - /* Decode Scalar */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - /* if size != '11' then ReservedValue(); */ - - int esize = 8 << (int)UInt(size); - int datasize = esize; - int elements = 1; - - bool sub_op = (U == true); - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits result = new Bits(datasize); - Bits operand1 = V(datasize, n); - Bits operand2 = V(datasize, m); - Bits element1; - Bits element2; - - for (int e = 0; e <= elements - 1; e++) - { - element1 = Elem(operand1, e, esize); - element2 = Elem(operand2, e, esize); - - if (sub_op) - { - Elem(result, e, esize, element1 - element2); - } - else - { - Elem(result, e, esize, element1 + element2); - } - } - - V(d, result); - } - - // sub_advsimd.html#SUB_asimdsame_only - public static void Sub_V(bool Q, Bits size, Bits Rm, Bits Rn, Bits Rd) - { - const bool U = true; - - /* Decode Vector */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - /* if size:Q == '110' then ReservedValue(); */ - - int esize = 8 << (int)UInt(size); - int datasize = (Q ? 128 : 64); - int elements = datasize / esize; - - bool sub_op = (U == true); - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits result = new Bits(datasize); - Bits operand1 = V(datasize, n); - Bits operand2 = V(datasize, m); - Bits element1; - Bits element2; - - for (int e = 0; e <= elements - 1; e++) - { - element1 = Elem(operand1, e, esize); - element2 = Elem(operand2, e, esize); - - if (sub_op) - { - Elem(result, e, esize, element1 - element2); - } - else - { - Elem(result, e, esize, element1 + element2); - } - } - - V(d, result); - } - - // subhn_advsimd.html - public static void Subhn_V(bool Q, Bits size, Bits Rm, Bits Rn, Bits Rd) - { - const bool U = false; - const bool o1 = true; - - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - /* if size == '11' then ReservedValue(); */ - - int esize = 8 << (int)UInt(size); - int datasize = 64; - int part = (int)UInt(Q); - int elements = datasize / esize; - - bool sub_op = (o1 == true); - bool round = (U == true); - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits result = new Bits(datasize); - Bits operand1 = V(2 * datasize, n); - Bits operand2 = V(2 * datasize, m); - BigInteger round_const = (round ? (BigInteger)1 << (esize - 1) : 0); - Bits sum; - Bits element1; - Bits element2; - - for (int e = 0; e <= elements - 1; e++) - { - element1 = Elem(operand1, e, 2 * esize); - element2 = Elem(operand2, e, 2 * esize); - - if (sub_op) - { - sum = element1 - element2; - } - else - { - sum = element1 + element2; - } - - sum = sum + round_const; - - Elem(result, e, esize, sum[2 * esize - 1, esize]); - } - - Vpart(d, part, result); - } - - // uaba_advsimd.html - public static void Uaba_V(bool Q, Bits size, Bits Rm, Bits Rn, Bits Rd) - { - const bool U = true; - const bool ac = true; - - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - /* if size == '11' then ReservedValue(); */ - - int esize = 8 << (int)UInt(size); - int datasize = (Q ? 128 : 64); - int elements = datasize / esize; - - bool unsigned = (U == true); - bool accumulate = (ac == true); - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits operand1 = V(datasize, n); - Bits operand2 = V(datasize, m); - BigInteger element1; - BigInteger element2; - Bits absdiff; - - Bits result = (accumulate ? V(datasize, d) : Zeros(datasize)); - - for (int e = 0; e <= elements - 1; e++) - { - element1 = Int(Elem(operand1, e, esize), unsigned); - element2 = Int(Elem(operand2, e, esize), unsigned); - - absdiff = Abs(element1 - element2).SubBigInteger(esize - 1, 0); - - Elem(result, e, esize, Elem(result, e, esize) + absdiff); - } - - V(d, result); - } - - // uabal_advsimd.html - public static void Uabal_V(bool Q, Bits size, Bits Rm, Bits Rn, Bits Rd) - { - const bool U = true; - const bool op = false; - - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - /* if size == '11' then ReservedValue(); */ - - int esize = 8 << (int)UInt(size); - int datasize = 64; - int part = (int)UInt(Q); - int elements = datasize / esize; - - bool unsigned = (U == true); - bool accumulate = (op == false); - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits operand1 = Vpart(datasize, n, part); - Bits operand2 = Vpart(datasize, m, part); - BigInteger element1; - BigInteger element2; - Bits absdiff; - - Bits result = (accumulate ? V(2 * datasize, d) : Zeros(2 * datasize)); - - for (int e = 0; e <= elements - 1; e++) - { - element1 = Int(Elem(operand1, e, esize), unsigned); - element2 = Int(Elem(operand2, e, esize), unsigned); - - absdiff = Abs(element1 - element2).SubBigInteger(2 * esize - 1, 0); - - Elem(result, e, 2 * esize, Elem(result, e, 2 * esize) + absdiff); - } - - V(d, result); - } - - // uabd_advsimd.html - public static void Uabd_V(bool Q, Bits size, Bits Rm, Bits Rn, Bits Rd) - { - const bool U = true; - const bool ac = false; - - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - /* if size == '11' then ReservedValue(); */ - - int esize = 8 << (int)UInt(size); - int datasize = (Q ? 128 : 64); - int elements = datasize / esize; - - bool unsigned = (U == true); - bool accumulate = (ac == true); - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits operand1 = V(datasize, n); - Bits operand2 = V(datasize, m); - BigInteger element1; - BigInteger element2; - Bits absdiff; - - Bits result = (accumulate ? V(datasize, d) : Zeros(datasize)); - - for (int e = 0; e <= elements - 1; e++) - { - element1 = Int(Elem(operand1, e, esize), unsigned); - element2 = Int(Elem(operand2, e, esize), unsigned); - - absdiff = Abs(element1 - element2).SubBigInteger(esize - 1, 0); - - Elem(result, e, esize, Elem(result, e, esize) + absdiff); - } - - V(d, result); - } - - // uabdl_advsimd.html - public static void Uabdl_V(bool Q, Bits size, Bits Rm, Bits Rn, Bits Rd) - { - const bool U = true; - const bool op = true; - - /* Decode */ - int d = (int)UInt(Rd); - int n = (int)UInt(Rn); - int m = (int)UInt(Rm); - - /* if size == '11' then ReservedValue(); */ - - int esize = 8 << (int)UInt(size); - int datasize = 64; - int part = (int)UInt(Q); - int elements = datasize / esize; - - bool unsigned = (U == true); - bool accumulate = (op == false); - - /* Operation */ - /* CheckFPAdvSIMDEnabled64(); */ - - Bits operand1 = Vpart(datasize, n, part); - Bits operand2 = Vpart(datasize, m, part); - BigInteger element1; - BigInteger element2; - Bits absdiff; - - Bits result = (accumulate ? V(2 * datasize, d) : Zeros(2 * datasize)); - - for (int e = 0; e <= elements - 1; e++) - { - element1 = Int(Elem(operand1, e, esize), unsigned); - element2 = Int(Elem(operand2, e, esize), unsigned); - - absdiff = Abs(element1 - element2).SubBigInteger(2 * esize - 1, 0); - - Elem(result, e, 2 * esize, Elem(result, e, 2 * esize) + absdiff); - } - - V(d, result); - } -#endregion - } -} diff --git a/Ryujinx.Tests/Cpu/Tester/Pseudocode.cs b/Ryujinx.Tests/Cpu/Tester/Pseudocode.cs deleted file mode 100644 index 3a877fb1a2..0000000000 --- a/Ryujinx.Tests/Cpu/Tester/Pseudocode.cs +++ /dev/null @@ -1,1211 +0,0 @@ -// https://github.com/LDj3SNuD/ARM_v8-A_AArch64_Instructions_Tester/blob/master/Tester/Pseudocode.cs - -// https://developer.arm.com/products/architecture/a-profile/exploration-tools -// ..\A64_v83A_ISA_xml_00bet6.1\ISA_v83A_A64_xml_00bet6.1_OPT\xhtml\ - -// https://alastairreid.github.io/asl-lexical-syntax/ - -// | ------------------------|----------------------------------- | -// | ASL | C# | -// | ------------------------|----------------------------------- | -// | bit, bits(1); boolean | bool | -// | bits | Bits | -// | integer | BigInteger, int | -// | real | decimal | -// | ------------------------|----------------------------------- | -// | '0'; FALSE | false | -// | '1'; TRUE | true | -// | '010' | "010" | -// | bitsX IN {bitsY, bitsZ} | (bitsX == bitsY || bitsX == bitsZ) | -// | DIV | / | -// | MOD | % | -// | ------------------------|----------------------------------- | - -using System; -using System.Numerics; - -namespace Ryujinx.Tests.Cpu.Tester -{ - using Types; - - using static Shared; - - internal static class AArch64 - { -#region "exceptions/exceptions/" - /* shared_pseudocode.html#AArch64.ResetControlRegisters.1 */ - public static void ResetControlRegisters(bool cold_reset) - { - PSTATE.N = cold_reset; - PSTATE.Z = cold_reset; - PSTATE.C = cold_reset; - PSTATE.V = cold_reset; - } - - /* */ - public static void TakeReset(bool cold_reset) - { - /* assert !HighestELUsingAArch32(); */ - - // Enter the highest implemented Exception level in AArch64 state - if (HaveEL(EL3)) - { - PSTATE.EL = EL3; - } - else if (HaveEL(EL2)) - { - PSTATE.EL = EL2; - } - else - { - PSTATE.EL = EL1; - } - - // Reset the system registers and other system components - AArch64.ResetControlRegisters(cold_reset); - - // Reset all other PSTATE fields - PSTATE.SP = true; // Select stack pointer - - // All registers, bits and fields not reset by the above pseudocode or by the BranchTo() call - // below are UNKNOWN bitstrings after reset. In particular, the return information registers - // ELR_ELx and SPSR_ELx have UNKNOWN values, so that it - // is impossible to return from a reset in an architecturally defined way. - AArch64.ResetGeneralRegisters(); - AArch64.ResetSIMDFPRegisters(); - AArch64.ResetSpecialRegisters(); - } -#endregion - -#region "functions/registers/" - /* shared_pseudocode.html#AArch64.ResetGeneralRegisters.0 */ - public static void ResetGeneralRegisters() - { - for (int i = 0; i <= 30; i++) - { - /* X[i] = bits(64) UNKNOWN; */ - _R[i].SetAll(false); - } - } - - /* shared_pseudocode.html#AArch64.ResetSIMDFPRegisters.0 */ - public static void ResetSIMDFPRegisters() - { - for (int i = 0; i <= 31; i++) - { - /* V[i] = bits(128) UNKNOWN; */ - _V[i].SetAll(false); - } - } - - /* shared_pseudocode.html#AArch64.ResetSpecialRegisters.0 */ - public static void ResetSpecialRegisters() - { - // AArch64 special registers - /* SP_EL0 = bits(64) UNKNOWN; */ - SP_EL0.SetAll(false); - /* SP_EL1 = bits(64) UNKNOWN; */ - SP_EL1.SetAll(false); - - FPSR.SetAll(false); // TODO: Add named fields. - } - - // shared_pseudocode.html#impl-aarch64.SP.write.0 - public static void SP(Bits value) - { - /* int width = value.Count; */ - - /* assert width IN {32,64}; */ - - if (!PSTATE.SP) - { - SP_EL0 = ZeroExtend(64, value); - } - else - { - switch (PSTATE.EL) - { - case Bits bits when bits == EL0: - SP_EL0 = ZeroExtend(64, value); - break; - default: - case Bits bits when bits == EL1: - SP_EL1 = ZeroExtend(64, value); - break;/* - case Bits bits when bits == EL2: - SP_EL2 = ZeroExtend(64, value); - break; - case Bits bits when bits == EL3: - SP_EL3 = ZeroExtend(64, value); - break;*/ - } - } - } - - // shared_pseudocode.html#impl-aarch64.SP.read.0 - public static Bits SP(int width) - { - /* assert width IN {8,16,32,64}; */ - - if (!PSTATE.SP) - { - return SP_EL0[width - 1, 0]; - } - else - { - switch (PSTATE.EL) - { - case Bits bits when bits == EL0: - return SP_EL0[width - 1, 0]; - default: - case Bits bits when bits == EL1: - return SP_EL1[width - 1, 0];/* - case Bits bits when bits == EL2: - return SP_EL2[width - 1, 0]; - case Bits bits when bits == EL3: - return SP_EL3[width - 1, 0];*/ - } - } - } - - // shared_pseudocode.html#impl-aarch64.V.write.1 - public static void V(int n, Bits value) - { - /* int width = value.Count; */ - - /* assert n >= 0 && n <= 31; */ - /* assert width IN {8,16,32,64,128}; */ - - _V[n] = ZeroExtend(128, value); - } - - /* shared_pseudocode.html#impl-aarch64.V.read.1 */ - public static Bits V(int width, int n) - { - /* assert n >= 0 && n <= 31; */ - /* assert width IN {8,16,32,64,128}; */ - - return _V[n][width - 1, 0]; - } - - /* shared_pseudocode.html#impl-aarch64.Vpart.read.2 */ - public static Bits Vpart(int width, int n, int part) - { - /* assert n >= 0 && n <= 31; */ - /* assert part IN {0, 1}; */ - - if (part == 0) - { - /* assert width IN {8,16,32,64}; */ - return _V[n][width - 1, 0]; - } - else - { - /* assert width == 64; */ - return _V[n][(width * 2) - 1, width]; - } - } - - // shared_pseudocode.html#impl-aarch64.Vpart.write.2 - public static void Vpart(int n, int part, Bits value) - { - int width = value.Count; - - /* assert n >= 0 && n <= 31; */ - /* assert part IN {0, 1}; */ - - if (part == 0) - { - /* assert width IN {8,16,32,64}; */ - _V[n] = ZeroExtend(128, value); - } - else - { - /* assert width == 64; */ - _V[n][(width * 2) - 1, width] = value[width - 1, 0]; - } - } - - // shared_pseudocode.html#impl-aarch64.X.write.1 - public static void X(int n, Bits value) - { - /* int width = value.Count; */ - - /* assert n >= 0 && n <= 31; */ - /* assert width IN {32,64}; */ - - if (n != 31) - { - _R[n] = ZeroExtend(64, value); - } - } - - /* shared_pseudocode.html#impl-aarch64.X.read.1 */ - public static Bits X(int width, int n) - { - /* assert n >= 0 && n <= 31; */ - /* assert width IN {8,16,32,64}; */ - - if (n != 31) - { - return _R[n][width - 1, 0]; - } - else - { - return Zeros(width); - } - } -#endregion - -#region "instrs/countop/" - // shared_pseudocode.html#CountOp - public enum CountOp {CountOp_CLZ, CountOp_CLS, CountOp_CNT}; -#endregion - -#region "instrs/extendreg/" - /* shared_pseudocode.html#impl-aarch64.DecodeRegExtend.1 */ - public static ExtendType DecodeRegExtend(Bits op) - { - switch (op) - { - default: - case Bits bits when bits == "000": - return ExtendType.ExtendType_UXTB; - case Bits bits when bits == "001": - return ExtendType.ExtendType_UXTH; - case Bits bits when bits == "010": - return ExtendType.ExtendType_UXTW; - case Bits bits when bits == "011": - return ExtendType.ExtendType_UXTX; - case Bits bits when bits == "100": - return ExtendType.ExtendType_SXTB; - case Bits bits when bits == "101": - return ExtendType.ExtendType_SXTH; - case Bits bits when bits == "110": - return ExtendType.ExtendType_SXTW; - case Bits bits when bits == "111": - return ExtendType.ExtendType_SXTX; - } - } - - /* shared_pseudocode.html#impl-aarch64.ExtendReg.3 */ - public static Bits ExtendReg(int N, int reg, ExtendType type, int shift) - { - /* assert shift >= 0 && shift <= 4; */ - - Bits val = X(N, reg); - bool unsigned; - int len; - - switch (type) - { - default: - case ExtendType.ExtendType_SXTB: - unsigned = false; len = 8; - break; - case ExtendType.ExtendType_SXTH: - unsigned = false; len = 16; - break; - case ExtendType.ExtendType_SXTW: - unsigned = false; len = 32; - break; - case ExtendType.ExtendType_SXTX: - unsigned = false; len = 64; - break; - case ExtendType.ExtendType_UXTB: - unsigned = true; len = 8; - break; - case ExtendType.ExtendType_UXTH: - unsigned = true; len = 16; - break; - case ExtendType.ExtendType_UXTW: - unsigned = true; len = 32; - break; - case ExtendType.ExtendType_UXTX: - unsigned = true; len = 64; - break; - } - - // Note the extended width of the intermediate value and - // that sign extension occurs from bit , not - // from bit . This is equivalent to the instruction - // [SU]BFIZ Rtmp, Rreg, #shift, #len - // It may also be seen as a sign/zero extend followed by a shift: - // LSL(Extend(val, N, unsigned), shift); - - len = Min(len, N - shift); - return Extend(Bits.Concat(val[len - 1, 0], Zeros(shift)), N, unsigned); - } - - // shared_pseudocode.html#ExtendType - public enum ExtendType {ExtendType_SXTB, ExtendType_SXTH, ExtendType_SXTW, ExtendType_SXTX, - ExtendType_UXTB, ExtendType_UXTH, ExtendType_UXTW, ExtendType_UXTX}; -#endregion - -#region "instrs/integer/bitmasks/" - /* shared_pseudocode.html#impl-aarch64.DecodeBitMasks.4 */ - public static (Bits, Bits) DecodeBitMasks(int M, bool immN, Bits imms, Bits immr, bool immediate) - { - Bits tmask, wmask; - Bits tmask_and, wmask_and; - Bits tmask_or, wmask_or; - Bits levels; - - // Compute log2 of element size - // 2^len must be in range [2, M] - int len = HighestSetBit(Bits.Concat(immN, NOT(imms))); - /* if len < 1 then ReservedValue(); */ - /* assert M >= (1 << len); */ - - // Determine S, R and S - R parameters - levels = ZeroExtend(Ones(len), 6); - - // For logical immediates an all-ones value of S is reserved - // since it would generate a useless all-ones result (many times) - /* if immediate && (imms AND levels) == levels then ReservedValue(); */ - - BigInteger S = UInt(AND(imms, levels)); - BigInteger R = UInt(AND(immr, levels)); - BigInteger diff = S - R; // 6-bit subtract with borrow - - // Compute "top mask" - tmask_and = OR(diff.SubBigInteger(5, 0), NOT(levels)); - tmask_or = AND(diff.SubBigInteger(5, 0), levels); - - tmask = Ones(64); - tmask = OR(AND(tmask, Replicate(Bits.Concat(Replicate(tmask_and[0], 1), Ones( 1)), 32)), Replicate(Bits.Concat(Zeros( 1), Replicate(tmask_or[0], 1)), 32)); - tmask = OR(AND(tmask, Replicate(Bits.Concat(Replicate(tmask_and[1], 2), Ones( 2)), 16)), Replicate(Bits.Concat(Zeros( 2), Replicate(tmask_or[1], 2)), 16)); - tmask = OR(AND(tmask, Replicate(Bits.Concat(Replicate(tmask_and[2], 4), Ones( 4)), 8)), Replicate(Bits.Concat(Zeros( 4), Replicate(tmask_or[2], 4)), 8)); - tmask = OR(AND(tmask, Replicate(Bits.Concat(Replicate(tmask_and[3], 8), Ones( 8)), 4)), Replicate(Bits.Concat(Zeros( 8), Replicate(tmask_or[3], 8)), 4)); - tmask = OR(AND(tmask, Replicate(Bits.Concat(Replicate(tmask_and[4], 16), Ones(16)), 2)), Replicate(Bits.Concat(Zeros(16), Replicate(tmask_or[4], 16)), 2)); - tmask = OR(AND(tmask, Replicate(Bits.Concat(Replicate(tmask_and[5], 32), Ones(32)), 1)), Replicate(Bits.Concat(Zeros(32), Replicate(tmask_or[5], 32)), 1)); - - // Compute "wraparound mask" - wmask_and = OR(immr, NOT(levels)); - wmask_or = AND(immr, levels); - - wmask = Zeros(64); - wmask = OR(AND(wmask, Replicate(Bits.Concat(Ones( 1), Replicate(wmask_and[0], 1)), 32)), Replicate(Bits.Concat(Replicate(wmask_or[0], 1), Zeros( 1)), 32)); - wmask = OR(AND(wmask, Replicate(Bits.Concat(Ones( 2), Replicate(wmask_and[1], 2)), 16)), Replicate(Bits.Concat(Replicate(wmask_or[1], 2), Zeros( 2)), 16)); - wmask = OR(AND(wmask, Replicate(Bits.Concat(Ones( 4), Replicate(wmask_and[2], 4)), 8)), Replicate(Bits.Concat(Replicate(wmask_or[2], 4), Zeros( 4)), 8)); - wmask = OR(AND(wmask, Replicate(Bits.Concat(Ones( 8), Replicate(wmask_and[3], 8)), 4)), Replicate(Bits.Concat(Replicate(wmask_or[3], 8), Zeros( 8)), 4)); - wmask = OR(AND(wmask, Replicate(Bits.Concat(Ones(16), Replicate(wmask_and[4], 16)), 2)), Replicate(Bits.Concat(Replicate(wmask_or[4], 16), Zeros(16)), 2)); - wmask = OR(AND(wmask, Replicate(Bits.Concat(Ones(32), Replicate(wmask_and[5], 32)), 1)), Replicate(Bits.Concat(Replicate(wmask_or[5], 32), Zeros(32)), 1)); - - if (diff.SubBigInteger(6)) // borrow from S - R - { - wmask = AND(wmask, tmask); - } - else - { - wmask = OR(wmask, tmask); - } - - return (wmask[M - 1, 0], tmask[M - 1, 0]); - } -#endregion - -#region "instrs/integer/shiftreg/" - /* shared_pseudocode.html#impl-aarch64.DecodeShift.1 */ - public static ShiftType DecodeShift(Bits op) - { - switch (op) - { - default: - case Bits bits when bits == "00": - return ShiftType.ShiftType_LSL; - case Bits bits when bits == "01": - return ShiftType.ShiftType_LSR; - case Bits bits when bits == "10": - return ShiftType.ShiftType_ASR; - case Bits bits when bits == "11": - return ShiftType.ShiftType_ROR; - } - } - - /* shared_pseudocode.html#impl-aarch64.ShiftReg.3 */ - public static Bits ShiftReg(int N, int reg, ShiftType type, int amount) - { - Bits result = X(N, reg); - - switch (type) - { - default: - case ShiftType.ShiftType_LSL: - result = LSL(result, amount); - break; - case ShiftType.ShiftType_LSR: - result = LSR(result, amount); - break; - case ShiftType.ShiftType_ASR: - result = ASR(result, amount); - break; - case ShiftType.ShiftType_ROR: - result = ROR(result, amount); - break; - } - - return result; - } - - // shared_pseudocode.html#ShiftType - public enum ShiftType {ShiftType_LSL, ShiftType_LSR, ShiftType_ASR, ShiftType_ROR}; -#endregion - -#region "instrs/vector/arithmetic/unary/cmp/compareop/" - // shared_pseudocode.html#CompareOp - public enum CompareOp {CompareOp_GT, CompareOp_GE, CompareOp_EQ, CompareOp_LE, CompareOp_LT}; -#endregion - -#region "instrs/vector/reduce/reduceop/" - public static Bits Reduce(ReduceOp op, Bits input, int esize) - { - int N = input.Count; - - int half; - Bits hi; - Bits lo; - Bits result = new Bits(esize); - - if (N == esize) - { - return new Bits(input); - } - - half = N / 2; - hi = Reduce(op, input[N - 1, half], esize); - lo = Reduce(op, input[half - 1, 0], esize); - - switch (op) - { - case ReduceOp.ReduceOp_FMINNUM: - /* result = FPMinNum(lo, hi, FPCR); */ - break; - case ReduceOp.ReduceOp_FMAXNUM: - /* result = FPMaxNum(lo, hi, FPCR); */ - break; - case ReduceOp.ReduceOp_FMIN: - /* result = FPMin(lo, hi, FPCR); */ - break; - case ReduceOp.ReduceOp_FMAX: - /* result = FPMax(lo, hi, FPCR); */ - break; - case ReduceOp.ReduceOp_FADD: - /* result = FPAdd(lo, hi, FPCR); */ - break; - default: - case ReduceOp.ReduceOp_ADD: - result = lo + hi; - break; - } - - return result; - } - - // shared_pseudocode.html#ReduceOp - public enum ReduceOp {ReduceOp_FMINNUM, ReduceOp_FMAXNUM, - ReduceOp_FMIN, ReduceOp_FMAX, - ReduceOp_FADD, ReduceOp_ADD}; -#endregion - } - - internal static class Shared - { - static Shared() - { - _R = new Bits[31]; - for (int i = 0; i <= 30; i++) - { - _R[i] = new Bits(64, false); - } - - _V = new Bits[32]; - for (int i = 0; i <= 31; i++) - { - _V[i] = new Bits(128, false); - } - - SP_EL0 = new Bits(64, false); - SP_EL1 = new Bits(64, false); - - FPSR = new Bits(32, false); // TODO: Add named fields. - - PSTATE.N = false; - PSTATE.Z = false; - PSTATE.C = false; - PSTATE.V = false; - PSTATE.EL = EL1; - PSTATE.SP = true; - } - -#region "functions/common/" - /* */ - public static Bits AND(Bits x, Bits y) - { - return x.And(y); - } - - // shared_pseudocode.html#impl-shared.ASR.2 - public static Bits ASR(Bits x, int shift) - { - int N = x.Count; - - /* assert shift >= 0; */ - - Bits result; - - if (shift == 0) - { - result = new Bits(x); - } - else - { - (result, _) = ASR_C(x, shift); - } - - return result; - } - - // shared_pseudocode.html#impl-shared.ASR_C.2 - public static (Bits, bool) ASR_C(Bits x, int shift) - { - int N = x.Count; - - /* assert shift > 0; */ - - Bits extended_x = SignExtend(x, shift + N); - Bits result = extended_x[shift + N - 1, shift]; - bool carry_out = extended_x[shift - 1]; - - return (result, carry_out); - } - - // shared_pseudocode.html#impl-shared.Abs.1 - public static BigInteger Abs(BigInteger x) - { - return (x >= 0 ? x : -x); - } - - // shared_pseudocode.html#impl-shared.BitCount.1 - public static int BitCount(Bits x) - { - int N = x.Count; - - int result = 0; - - for (int i = 0; i <= N - 1; i++) - { - if (x[i]) - { - result = result + 1; - } - } - - return result; - } - - // shared_pseudocode.html#impl-shared.CountLeadingSignBits.1 - public static int CountLeadingSignBits(Bits x) - { - int N = x.Count; - - return CountLeadingZeroBits(EOR(x[N - 1, 1], x[N - 2, 0])); - } - - // shared_pseudocode.html#impl-shared.CountLeadingZeroBits.1 - public static int CountLeadingZeroBits(Bits x) - { - int N = x.Count; - - return (N - 1 - HighestSetBit(x)); - } - - // shared_pseudocode.html#impl-shared.Elem.read.3 - public static Bits Elem(/*in */Bits vector, int e, int size) - { - /* int N = vector.Count; */ - - /* assert e >= 0 && (e+1)*size <= N; */ - - return vector[e * size + size - 1, e * size]; - } - - // shared_pseudocode.html#impl-shared.Elem.write.3 - public static void Elem(/*out */Bits vector, int e, int size, Bits value) - { - /* int N = vector.Count; */ - - /* assert e >= 0 && (e+1)*size <= N; */ - - vector[(e + 1) * size - 1, e * size] = value; - } - - /* */ - public static Bits EOR(Bits x, Bits y) - { - return x.Xor(y); - } - - // shared_pseudocode.html#impl-shared.Extend.3 - public static Bits Extend(Bits x, int N, bool unsigned) - { - if (unsigned) - { - return ZeroExtend(x, N); - } - else - { - return SignExtend(x, N); - } - } - - /* shared_pseudocode.html#impl-shared.Extend.2 */ - public static Bits Extend(int N, Bits x, bool unsigned) - { - return Extend(x, N, unsigned); - } - - // shared_pseudocode.html#impl-shared.HighestSetBit.1 - public static int HighestSetBit(Bits x) - { - int N = x.Count; - - for (int i = N - 1; i >= 0; i--) - { - if (x[i]) - { - return i; - } - } - - return -1; - } - - // shared_pseudocode.html#impl-shared.Int.2 - public static BigInteger Int(Bits x, bool unsigned) - { - return (unsigned ? UInt(x) : SInt(x)); - } - - // shared_pseudocode.html#impl-shared.IsOnes.1 - public static bool IsOnes(Bits x) - { - int N = x.Count; - - return (x == Ones(N)); - } - - // shared_pseudocode.html#impl-shared.IsZero.1 - public static bool IsZero(Bits x) - { - int N = x.Count; - - return (x == Zeros(N)); - } - - // shared_pseudocode.html#impl-shared.IsZeroBit.1 - public static bool IsZeroBit(Bits x) - { - return IsZero(x); - } - - // shared_pseudocode.html#impl-shared.LSL.2 - public static Bits LSL(Bits x, int shift) - { - int N = x.Count; - - /* assert shift >= 0; */ - - Bits result; - - if (shift == 0) - { - result = new Bits(x); - } - else - { - (result, _) = LSL_C(x, shift); - } - - return result; - } - - // shared_pseudocode.html#impl-shared.LSL_C.2 - public static (Bits, bool) LSL_C(Bits x, int shift) - { - int N = x.Count; - - /* assert shift > 0; */ - - Bits extended_x = Bits.Concat(x, Zeros(shift)); - Bits result = extended_x[N - 1, 0]; - bool carry_out = extended_x[N]; - - return (result, carry_out); - } - - // shared_pseudocode.html#impl-shared.LSR.2 - public static Bits LSR(Bits x, int shift) - { - int N = x.Count; - - /* assert shift >= 0; */ - - Bits result; - - if (shift == 0) - { - result = new Bits(x); - } - else - { - (result, _) = LSR_C(x, shift); - } - - return result; - } - - // shared_pseudocode.html#impl-shared.LSR_C.2 - public static (Bits, bool) LSR_C(Bits x, int shift) - { - int N = x.Count; - - /* assert shift > 0; */ - - Bits extended_x = ZeroExtend(x, shift + N); - Bits result = extended_x[shift + N - 1, shift]; - bool carry_out = extended_x[shift - 1]; - - return (result, carry_out); - } - - // shared_pseudocode.html#impl-shared.Min.2 - public static int Min(int a, int b) - { - if (a <= b) - { - return a; - } - else - { - return b; - } - } - - /* shared_pseudocode.html#impl-shared.NOT.1 */ - public static Bits NOT(Bits x) - { - return x.Not(); - } - - // shared_pseudocode.html#impl-shared.Ones.1 - /* shared_pseudocode.html#impl-shared.Ones.0 */ - public static Bits Ones(int N) - { - return Replicate(true, N); - } - - /* */ - public static Bits OR(Bits x, Bits y) - { - return x.Or(y); - } - - /* */ - public static decimal Real(BigInteger value) - { - return (decimal)value; - } - - // shared_pseudocode.html#impl-shared.ROR.2 - public static Bits ROR(Bits x, int shift) - { - /* assert shift >= 0; */ - - Bits result; - - if (shift == 0) - { - result = new Bits(x); - } - else - { - (result, _) = ROR_C(x, shift); - } - - return result; - } - - // shared_pseudocode.html#impl-shared.ROR_C.2 - public static (Bits, bool) ROR_C(Bits x, int shift) - { - int N = x.Count; - - /* assert shift != 0; */ - - int m = shift % N; - Bits result = OR(LSR(x, m), LSL(x, N - m)); - bool carry_out = result[N - 1]; - - return (result, carry_out); - } - - /* shared_pseudocode.html#impl-shared.Replicate.1 */ - public static Bits Replicate(int N, Bits x) - { - int M = x.Count; - - /* assert N MOD M == 0; */ - - return Replicate(x, N / M); - } - - /* shared_pseudocode.html#impl-shared.Replicate.2 */ - public static Bits Replicate(Bits x, int N) - { - int M = x.Count; - - bool[] dst = new bool[M * N]; - - for (int i = 0; i < N; i++) - { - x.CopyTo(dst, i * M); - } - - return new Bits(dst); - } - - /* shared_pseudocode.html#impl-shared.RoundDown.1 */ - public static BigInteger RoundDown(decimal x) - { - return (BigInteger)Decimal.Floor(x); - } - - // shared_pseudocode.html#impl-shared.RoundTowardsZero.1 - public static BigInteger RoundTowardsZero(decimal x) - { - if (x == 0.0m) - { - return (BigInteger)0m; - } - else if (x >= 0.0m) - { - return RoundDown(x); - } - else - { - return RoundUp(x); - } - } - - /* shared_pseudocode.html#impl-shared.RoundUp.1 */ - public static BigInteger RoundUp(decimal x) - { - return (BigInteger)Decimal.Ceiling(x); - } - - // shared_pseudocode.html#impl-shared.SInt.1 - public static BigInteger SInt(Bits x) - { - int N = x.Count; - - BigInteger result = 0; - - for (int i = 0; i <= N - 1; i++) - { - if (x[i]) - { - result = result + BigInteger.Pow(2, i); - } - } - - if (x[N - 1]) - { - result = result - BigInteger.Pow(2, N); - } - - return result; - } - - // shared_pseudocode.html#impl-shared.SignExtend.2 - public static Bits SignExtend(Bits x, int N) - { - int M = x.Count; - - /* assert N >= M; */ - - return Bits.Concat(Replicate(x[M - 1], N - M), x); - } - - /* shared_pseudocode.html#impl-shared.SignExtend.1 */ - public static Bits SignExtend(int N, Bits x) - { - return SignExtend(x, N); - } - - // shared_pseudocode.html#impl-shared.UInt.1 - public static BigInteger UInt(Bits x) - { - int N = x.Count; - - BigInteger result = 0; - - for (int i = 0; i <= N - 1; i++) - { - if (x[i]) - { - result = result + BigInteger.Pow(2, i); - } - } - - return result; - } - - // shared_pseudocode.html#impl-shared.ZeroExtend.2 - public static Bits ZeroExtend(Bits x, int N) - { - int M = x.Count; - - /* assert N >= M; */ - - return Bits.Concat(Zeros(N - M), x); - } - - /* shared_pseudocode.html#impl-shared.ZeroExtend.1 */ - public static Bits ZeroExtend(int N, Bits x) - { - return ZeroExtend(x, N); - } - - // shared_pseudocode.html#impl-shared.Zeros.1 - /* shared_pseudocode.html#impl-shared.Zeros.0 */ - public static Bits Zeros(int N) - { - return Replicate(false, N); - } -#endregion - -#region "functions/crc/" - // shared_pseudocode.html#impl-shared.BitReverse.1 - public static Bits BitReverse(Bits data) - { - int N = data.Count; - - Bits result = new Bits(N); - - for (int i = 0; i <= N - 1; i++) - { - result[N - i - 1] = data[i]; - } - - return result; - } - - // shared_pseudocode.html#impl-shared.Poly32Mod2.2 - public static Bits Poly32Mod2(Bits _data, Bits poly) - { - int N = _data.Count; - - /* assert N > 32; */ - - Bits data = new Bits(_data); - - for (int i = N - 1; i >= 32; i--) - { - if (data[i]) - { - data[i - 1, 0] = EOR(data[i - 1, 0], Bits.Concat(poly, Zeros(i - 32))); - } - } - - return data[31, 0]; - } -#endregion - -#region "functions/integer/" - /* shared_pseudocode.html#impl-shared.AddWithCarry.3 */ - public static (Bits, Bits) AddWithCarry(int N, Bits x, Bits y, bool carry_in) - { - BigInteger unsigned_sum = UInt(x) + UInt(y) + UInt(carry_in); - BigInteger signed_sum = SInt(x) + SInt(y) + UInt(carry_in); - - Bits result = unsigned_sum.SubBigInteger(N - 1, 0); // same value as signed_sum - - bool n = result[N - 1]; - bool z = IsZero(result); - bool c = !(UInt(result) == unsigned_sum); - bool v = !(SInt(result) == signed_sum); - - return (result, Bits.Concat(n, z, c, v)); - } -#endregion - -#region "functions/registers/" - public static readonly Bits[] _R; - - public static readonly Bits[] _V; - - public static Bits SP_EL0; - public static Bits SP_EL1; - - public static Bits FPSR; // TODO: Add named fields. -#endregion - -#region "functions/system/" - // shared_pseudocode.html#impl-shared.ConditionHolds.1 - public static bool ConditionHolds(Bits cond) - { - bool result; - - // Evaluate base condition. - switch (cond[3, 1]) - { - case Bits bits when bits == "000": - result = (PSTATE.Z == true); // EQ or NE - break; - case Bits bits when bits == "001": - result = (PSTATE.C == true); // CS or CC - break; - case Bits bits when bits == "010": - result = (PSTATE.N == true); // MI or PL - break; - case Bits bits when bits == "011": - result = (PSTATE.V == true); // VS or VC - break; - case Bits bits when bits == "100": - result = (PSTATE.C == true && PSTATE.Z == false); // HI or LS - break; - case Bits bits when bits == "101": - result = (PSTATE.N == PSTATE.V); // GE or LT - break; - case Bits bits when bits == "110": - result = (PSTATE.N == PSTATE.V && PSTATE.Z == false); // GT or LE - break; - default: - case Bits bits when bits == "111": - result = true; // AL - break; - } - - // Condition flag values in the set '111x' indicate always true - // Otherwise, invert condition if necessary. - if (cond[0] == true && cond != "1111") - { - result = !result; - } - - return result; - } - - // shared_pseudocode.html#EL3 - public static readonly Bits EL3 = "11"; - // shared_pseudocode.html#EL2 - public static readonly Bits EL2 = "10"; - // shared_pseudocode.html#EL1 - public static readonly Bits EL1 = "01"; - // shared_pseudocode.html#EL0 - public static readonly Bits EL0 = "00"; - - /* shared_pseudocode.html#impl-shared.HaveEL.1 */ - public static bool HaveEL(Bits el) - { - if (el == EL1 || el == EL0) - { - return true; // EL1 and EL0 must exist - } - - /* return boolean IMPLEMENTATION_DEFINED; */ - return false; - } - - public static ProcState PSTATE; - - /* shared_pseudocode.html#ProcState */ - internal struct ProcState - { - public void NZCV(Bits nzcv) // ASL: ".<,,,>". - { - N = nzcv[3]; - Z = nzcv[2]; - C = nzcv[1]; - V = nzcv[0]; - } - - public void NZCV(bool n, bool z, bool c, bool v) // ASL: ".<,,,>". - { - N = n; - Z = z; - C = c; - V = v; - } - - public bool N; // Negative condition flag - public bool Z; // Zero condition flag - public bool C; // Carry condition flag - public bool V; // oVerflow condition flag - public Bits EL; // Exception Level - public bool SP; // Stack pointer select: 0=SP0, 1=SPx [AArch64 only] - } -#endregion - -#region "functions/vector/" - // shared_pseudocode.html#impl-shared.SatQ.3 - public static (Bits, bool) SatQ(BigInteger i, int N, bool unsigned) - { - (Bits result, bool sat) = (unsigned ? UnsignedSatQ(i, N) : SignedSatQ(i, N)); - - return (result, sat); - } - - // shared_pseudocode.html#impl-shared.SignedSatQ.2 - public static (Bits, bool) SignedSatQ(BigInteger i, int N) - { - BigInteger result; - bool saturated; - - if (i > BigInteger.Pow(2, N - 1) - 1) - { - result = BigInteger.Pow(2, N - 1) - 1; - saturated = true; - } - else if (i < -(BigInteger.Pow(2, N - 1))) - { - result = -(BigInteger.Pow(2, N - 1)); - saturated = true; - } - else - { - result = i; - saturated = false; - } - - return (result.SubBigInteger(N - 1, 0), saturated); - } - - // shared_pseudocode.html#impl-shared.UnsignedSatQ.2 - public static (Bits, bool) UnsignedSatQ(BigInteger i, int N) - { - BigInteger result; - bool saturated; - - if (i > BigInteger.Pow(2, N) - 1) - { - result = BigInteger.Pow(2, N) - 1; - saturated = true; - } - else if (i < 0) - { - result = 0; - saturated = true; - } - else - { - result = i; - saturated = false; - } - - return (result.SubBigInteger(N - 1, 0), saturated); - } -#endregion - } -} diff --git a/Ryujinx.Tests/Cpu/Tester/Types/Bits.cs b/Ryujinx.Tests/Cpu/Tester/Types/Bits.cs deleted file mode 100644 index 30d6326406..0000000000 --- a/Ryujinx.Tests/Cpu/Tester/Types/Bits.cs +++ /dev/null @@ -1,313 +0,0 @@ -// https://github.com/LDj3SNuD/ARM_v8-A_AArch64_Instructions_Tester/blob/master/Tester/Types/Bits.cs - -// https://github.com/dotnet/corefx/blob/master/src/System.Collections/src/System/Collections/BitArray.cs - -using System; -using System.Collections; -using System.Numerics; - -namespace Ryujinx.Tests.Cpu.Tester.Types -{ - internal sealed class Bits : ICollection, IEnumerable, IEquatable - { - private BitArray bits; - - public Bits(bool[] values) => bits = new BitArray(values); - public Bits(byte[] bytes) => bits = new BitArray(bytes); - public Bits(Bits bits) => this.bits = new BitArray(bits.bits); - public Bits(int length) => bits = new BitArray(length); - public Bits(int length, bool defaultValue) => bits = new BitArray(length, defaultValue); - private Bits(BitArray bitArray) => bits = new BitArray(bitArray); - public Bits(ulong value) => bits = new BitArray(BitConverter.GetBytes(value)); - public Bits(uint value) => bits = new BitArray(BitConverter.GetBytes(value)); - public Bits(ushort value) => bits = new BitArray(BitConverter.GetBytes(value)); - public Bits(byte value) => bits = new BitArray(new byte[1] {value}); - - private BitArray ToBitArray() => new BitArray(bits); - public ulong ToUInt64() - { - byte[] dst = new byte[8]; - - bits.CopyTo(dst, 0); - - return BitConverter.ToUInt64(dst, 0); - } - public uint ToUInt32() - { - byte[] dst = new byte[4]; - - bits.CopyTo(dst, 0); - - return BitConverter.ToUInt32(dst, 0); - } - public ushort ToUInt16() - { - byte[] dst = new byte[2]; - - bits.CopyTo(dst, 0); - - return BitConverter.ToUInt16(dst, 0); - } - public byte ToByte() - { - byte[] dst = new byte[1]; - - bits.CopyTo(dst, 0); - - return dst[0]; - } - - public bool this[int index] // ASL: "<>". - { - get - { - return bits.Get(index); - } - set - { - bits.Set(index, value); - } - } - public Bits this[int highIndex, int lowIndex] // ASL: "<:>". - { - get - { - if (highIndex < lowIndex) - { - throw new IndexOutOfRangeException(); - } - - bool[] dst = new bool[highIndex - lowIndex + 1]; - - for (int i = lowIndex, n = 0; i <= highIndex; i++, n++) - { - dst[n] = bits.Get(i); - } - - return new Bits(dst); - } - set - { - if (highIndex < lowIndex) - { - throw new IndexOutOfRangeException(); - } - - for (int i = lowIndex, n = 0; i <= highIndex; i++, n++) - { - bits.Set(i, value.Get(n)); - } - } - } - - public bool IsReadOnly { get => false; } // Mutable. - public int Count { get => bits.Count; } - public bool IsSynchronized { get => bits.IsSynchronized; } - public object SyncRoot { get => bits.SyncRoot; } - public Bits And(Bits value) => new Bits(new BitArray(this.bits).And(value.bits)); // Immutable. - public void CopyTo(Array array, int index) => bits.CopyTo(array, index); - public bool Get(int index) => bits.Get(index); - public IEnumerator GetEnumerator() => bits.GetEnumerator(); - //public Bits LeftShift(int count) => new Bits(new BitArray(bits).LeftShift(count)); // Immutable. - public Bits Not() => new Bits(new BitArray(bits).Not()); // Immutable. - public Bits Or(Bits value) => new Bits(new BitArray(this.bits).Or(value.bits)); // Immutable. - //public Bits RightShift(int count) => new Bits(new BitArray(bits).RightShift(count)); // Immutable. - public void Set(int index, bool value) => bits.Set(index, value); - public void SetAll(bool value) => bits.SetAll(value); - public Bits Xor(Bits value) => new Bits(new BitArray(this.bits).Xor(value.bits)); // Immutable. - - public static Bits Concat(Bits highBits, Bits lowBits) // ASL: ":". - { - if (((object)lowBits == null) || ((object)highBits == null)) - { - throw new ArgumentNullException(); - } - - bool[] dst = new bool[lowBits.Count + highBits.Count]; - - lowBits.CopyTo(dst, 0); - highBits.CopyTo(dst, lowBits.Count); - - return new Bits(dst); - } - public static Bits Concat(bool bit3, bool bit2, bool bit1, bool bit0) // ASL: ":::". - { - return new Bits(new bool[] {bit0, bit1, bit2, bit3}); - } - - public static implicit operator Bits(bool value) => new Bits(1, value); - public static implicit operator Bits(string value) - { - if (String.IsNullOrEmpty(value)) - { - throw new InvalidCastException(); - } - - bool[] dst = new bool[value.Length]; - - for (int i = value.Length - 1, n = 0; i >= 0; i--, n++) - { - if (value[i] == '1') - { - dst[n] = true; - } - else if (value[i] == '0') - { - dst[n] = false; - } - else - { - throw new InvalidCastException(); - } - } - - return new Bits(dst); - } - public static explicit operator bool(Bits bit) - { - if (((object)bit == null) || (bit.Count != 1)) - { - throw new InvalidCastException(); - } - - return bit.Get(0); - } - - public static Bits operator +(Bits left, BigInteger right) // ASL: "+". - { - if (((object)left == null) || ((object)right == null)) - { - throw new ArgumentNullException(); - } - - BigInteger dst; - - switch (left.Count) - { - case 8: dst = left.ToByte() + right; break; - case 16: dst = left.ToUInt16() + right; break; - case 32: dst = left.ToUInt32() + right; break; - case 64: dst = left.ToUInt64() + right; break; - - default: throw new ArgumentOutOfRangeException(); - } - - return dst.SubBigInteger(left.Count - 1, 0); - } - public static Bits operator +(Bits left, Bits right) // ASL: "+". - { - if (((object)left == null) || ((object)right == null)) - { - throw new ArgumentNullException(); - } - - if (left.Count != right.Count) - { - throw new ArgumentException(); - } - - BigInteger dst; - - switch (left.Count) - { - case 8: dst = left.ToByte() + (BigInteger)right.ToByte(); break; - case 16: dst = left.ToUInt16() + (BigInteger)right.ToUInt16(); break; - case 32: dst = left.ToUInt32() + (BigInteger)right.ToUInt32(); break; - case 64: dst = left.ToUInt64() + (BigInteger)right.ToUInt64(); break; - - default: throw new ArgumentOutOfRangeException(); - } - - return dst.SubBigInteger(left.Count - 1, 0); - } - public static Bits operator -(Bits left, Bits right) // ASL: "-". - { - if (((object)left == null) || ((object)right == null)) - { - throw new ArgumentNullException(); - } - - if (left.Count != right.Count) - { - throw new ArgumentException(); - } - - BigInteger dst; - - switch (left.Count) - { - case 8: dst = left.ToByte() - (BigInteger)right.ToByte(); break; - case 16: dst = left.ToUInt16() - (BigInteger)right.ToUInt16(); break; - case 32: dst = left.ToUInt32() - (BigInteger)right.ToUInt32(); break; - case 64: dst = left.ToUInt64() - (BigInteger)right.ToUInt64(); break; - - default: throw new ArgumentOutOfRangeException(); - } - - return dst.SubBigInteger(left.Count - 1, 0); - } - public static bool operator ==(Bits left, Bits right) // ASL: "==". - { - if (((object)left == null) || ((object)right == null)) - { - throw new ArgumentNullException(); - } - - if (left.Count != right.Count) - { - return false; - } - - for (int i = 0; i <= left.Count - 1; i++) - { - if (left.Get(i) != right.Get(i)) - { - return false; - } - } - - return true; - } - public static bool operator !=(Bits left, Bits right) // ASL: "!=". - { - return !(left == right); - } - - public bool Equals(Bits right) // ASL: "==". - { - if ((object)right == null) - { - throw new ArgumentNullException(); - } - - Bits left = this; - - if (left.Count != right.Count) - { - return false; - } - - for (int i = 0; i <= left.Count - 1; i++) - { - if (left.Get(i) != right.Get(i)) - { - return false; - } - } - - return true; - } - public override bool Equals(object obj) - { - if (obj == null) - { - throw new ArgumentNullException(); - } - - Bits right = obj as Bits; - - return Equals(right); - } - public override int GetHashCode() => bits.GetHashCode(); - } -} diff --git a/Ryujinx.Tests/Cpu/Tester/Types/Integer.cs b/Ryujinx.Tests/Cpu/Tester/Types/Integer.cs deleted file mode 100644 index c72f3e2525..0000000000 --- a/Ryujinx.Tests/Cpu/Tester/Types/Integer.cs +++ /dev/null @@ -1,42 +0,0 @@ -// https://github.com/LDj3SNuD/ARM_v8-A_AArch64_Instructions_Tester/blob/master/Tester/Types/Integer.cs - -using System; -using System.Numerics; - -namespace Ryujinx.Tests.Cpu.Tester.Types -{ - internal static class Integer - { - public static Bits SubBigInteger(this BigInteger x, int highIndex, int lowIndex) // ASL: "<:>". - { - if (highIndex < lowIndex) - { - throw new IndexOutOfRangeException(); - } - - Bits src = new Bits(x.ToByteArray()); - bool[] dst = new bool[highIndex - lowIndex + 1]; - - for (int i = lowIndex, n = 0; i <= highIndex; i++, n++) - { - if (i <= src.Count - 1) - { - dst[n] = src[i]; - } - else - { - dst[n] = (x.Sign != -1 ? false : true); // Zero / Sign Extension. - } - } - - return new Bits(dst); - } - - public static bool SubBigInteger(this BigInteger x, int index) // ASL: "<>". - { - Bits dst = x.SubBigInteger(index, index); - - return (bool)dst; - } - } -} diff --git a/Ryujinx.Tests/Ryujinx.Tests.csproj b/Ryujinx.Tests/Ryujinx.Tests.csproj index b4ba379a1a..83ec2e9647 100644 --- a/Ryujinx.Tests/Ryujinx.Tests.csproj +++ b/Ryujinx.Tests/Ryujinx.Tests.csproj @@ -1,20 +1,46 @@ - + + - netcoreapp2.1 - win10-x64;osx-x64;linux-x64 + netcoreapp3.0 + win-x64;osx-x64;linux-x64 Exe false + + windows + osx + linux + Debug;Release;Profile Debug;Profile Release + false + + + TRACE;USE_PROFILING + true + + + + TRACE;USE_PROFILING + false + + - - - - + + + + - + + + + + + + + + diff --git a/Ryujinx.sln b/Ryujinx.sln index cd04dabc20..4ad74077cf 100644 --- a/Ryujinx.sln +++ b/Ryujinx.sln @@ -1,62 +1,179 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26730.8 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29613.14 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx", "Ryujinx\Ryujinx.csproj", "{074045D4-3ED2-4711-9169-E385F2BFB5A0}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Tests", "Ryujinx.Tests\Ryujinx.Tests.csproj", "{EBB55AEA-C7D7-4DEB-BF96-FA1789E225E9}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Tests.Unicorn", "Ryujinx.Tests.Unicorn\Ryujinx.Tests.Unicorn.csproj", "{D8F72938-78EF-4E8C-BAFE-531C9C3C8F15}" +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.HLE", "Ryujinx.HLE\Ryujinx.HLE.csproj", "{CB92CFF9-1D62-4D4F-9E88-8130EF61E351}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChocolArm64", "ChocolArm64\ChocolArm64.csproj", "{2345A1A7-8DEF-419B-9AFB-4DFD41D20D05}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics", "Ryujinx.Graphics\Ryujinx.Graphics.csproj", "{EAAE36AF-7781-4578-A7E0-F0EFD2025569}" + ProjectSection(ProjectDependencies) = postProject + {4E69B67F-8CA7-42CF-A9E1-CCB0915DFB34} = {4E69B67F-8CA7-42CF-A9E1-CCB0915DFB34} + EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Audio", "Ryujinx.Audio\Ryujinx.Audio.csproj", "{5C1D818E-682A-46A5-9D54-30006E26C270}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.ShaderTools", "Ryujinx.ShaderTools\Ryujinx.ShaderTools.csproj", "{3AB294D0-2230-468F-9EB3-BDFCAEAE99A5}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.ShaderTools", "Ryujinx.ShaderTools\Ryujinx.ShaderTools.csproj", "{3AB294D0-2230-468F-9EB3-BDFCAEAE99A5}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Luea", "Ryujinx.LLE\Luea.csproj", "{8E7D36DD-9626-47E2-8EF5-8F2F66751C9C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Luea", "Ryujinx.LLE\Luea.csproj", "{8E7D36DD-9626-47E2-8EF5-8F2F66751C9C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Common", "Ryujinx.Common\Ryujinx.Common.csproj", "{5FD4E4F6-8928-4B3C-BE07-28A675C17226}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Profiler", "Ryujinx.Profiler\Ryujinx.Profiler.csproj", "{4E69B67F-8CA7-42CF-A9E1-CCB0915DFB34}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ARMeilleure", "ARMeilleure\ARMeilleure.csproj", "{ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Gpu", "Ryujinx.Graphics.Gpu\Ryujinx.Graphics.Gpu.csproj", "{ADA7EA87-0D63-4D97-9433-922A2124401F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.GAL", "Ryujinx.Graphics.GAL\Ryujinx.Graphics.GAL.csproj", "{A602AE97-91A5-4608-8DF1-EBF4ED7A0B9E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.OpenGL", "Ryujinx.Graphics.OpenGL\Ryujinx.Graphics.OpenGL.csproj", "{9558FB96-075D-4219-8FFF-401979DC0B69}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Texture", "Ryujinx.Graphics.Texture\Ryujinx.Graphics.Texture.csproj", "{E1B1AD28-289D-47B7-A106-326972240207}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Shader", "Ryujinx.Graphics.Shader\Ryujinx.Graphics.Shader.csproj", "{03B955CD-AD84-4B93-AAA7-BF17923BBAA5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.Graphics.Nvdec", "Ryujinx.Graphics.Nvdec\Ryujinx.Graphics.Nvdec.csproj", "{85A0FA56-DC01-4A42-8808-70DAC76BD66D}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Profile Debug|Any CPU = Profile Debug|Any CPU + Profile Release|Any CPU = Profile Release|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {074045D4-3ED2-4711-9169-E385F2BFB5A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {074045D4-3ED2-4711-9169-E385F2BFB5A0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {074045D4-3ED2-4711-9169-E385F2BFB5A0}.Profile Debug|Any CPU.ActiveCfg = Profile Debug|Any CPU + {074045D4-3ED2-4711-9169-E385F2BFB5A0}.Profile Debug|Any CPU.Build.0 = Profile Debug|Any CPU + {074045D4-3ED2-4711-9169-E385F2BFB5A0}.Profile Release|Any CPU.ActiveCfg = Profile Release|Any CPU + {074045D4-3ED2-4711-9169-E385F2BFB5A0}.Profile Release|Any CPU.Build.0 = Profile Release|Any CPU {074045D4-3ED2-4711-9169-E385F2BFB5A0}.Release|Any CPU.ActiveCfg = Release|Any CPU {074045D4-3ED2-4711-9169-E385F2BFB5A0}.Release|Any CPU.Build.0 = Release|Any CPU {EBB55AEA-C7D7-4DEB-BF96-FA1789E225E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {EBB55AEA-C7D7-4DEB-BF96-FA1789E225E9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EBB55AEA-C7D7-4DEB-BF96-FA1789E225E9}.Profile Debug|Any CPU.ActiveCfg = Profile Debug|Any CPU + {EBB55AEA-C7D7-4DEB-BF96-FA1789E225E9}.Profile Debug|Any CPU.Build.0 = Profile Debug|Any CPU + {EBB55AEA-C7D7-4DEB-BF96-FA1789E225E9}.Profile Release|Any CPU.ActiveCfg = Profile Release|Any CPU + {EBB55AEA-C7D7-4DEB-BF96-FA1789E225E9}.Profile Release|Any CPU.Build.0 = Profile Release|Any CPU {EBB55AEA-C7D7-4DEB-BF96-FA1789E225E9}.Release|Any CPU.ActiveCfg = Release|Any CPU {EBB55AEA-C7D7-4DEB-BF96-FA1789E225E9}.Release|Any CPU.Build.0 = Release|Any CPU + {D8F72938-78EF-4E8C-BAFE-531C9C3C8F15}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D8F72938-78EF-4E8C-BAFE-531C9C3C8F15}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D8F72938-78EF-4E8C-BAFE-531C9C3C8F15}.Profile Debug|Any CPU.ActiveCfg = Profile Debug|Any CPU + {D8F72938-78EF-4E8C-BAFE-531C9C3C8F15}.Profile Debug|Any CPU.Build.0 = Profile Debug|Any CPU + {D8F72938-78EF-4E8C-BAFE-531C9C3C8F15}.Profile Release|Any CPU.ActiveCfg = Profile Release|Any CPU + {D8F72938-78EF-4E8C-BAFE-531C9C3C8F15}.Profile Release|Any CPU.Build.0 = Profile Release|Any CPU + {D8F72938-78EF-4E8C-BAFE-531C9C3C8F15}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D8F72938-78EF-4E8C-BAFE-531C9C3C8F15}.Release|Any CPU.Build.0 = Release|Any CPU {CB92CFF9-1D62-4D4F-9E88-8130EF61E351}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {CB92CFF9-1D62-4D4F-9E88-8130EF61E351}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CB92CFF9-1D62-4D4F-9E88-8130EF61E351}.Profile Debug|Any CPU.ActiveCfg = Profile Debug|Any CPU + {CB92CFF9-1D62-4D4F-9E88-8130EF61E351}.Profile Debug|Any CPU.Build.0 = Profile Debug|Any CPU + {CB92CFF9-1D62-4D4F-9E88-8130EF61E351}.Profile Release|Any CPU.ActiveCfg = Profile Release|Any CPU + {CB92CFF9-1D62-4D4F-9E88-8130EF61E351}.Profile Release|Any CPU.Build.0 = Profile Release|Any CPU {CB92CFF9-1D62-4D4F-9E88-8130EF61E351}.Release|Any CPU.ActiveCfg = Release|Any CPU {CB92CFF9-1D62-4D4F-9E88-8130EF61E351}.Release|Any CPU.Build.0 = Release|Any CPU - {2345A1A7-8DEF-419B-9AFB-4DFD41D20D05}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2345A1A7-8DEF-419B-9AFB-4DFD41D20D05}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2345A1A7-8DEF-419B-9AFB-4DFD41D20D05}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2345A1A7-8DEF-419B-9AFB-4DFD41D20D05}.Release|Any CPU.Build.0 = Release|Any CPU - {EAAE36AF-7781-4578-A7E0-F0EFD2025569}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EAAE36AF-7781-4578-A7E0-F0EFD2025569}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EAAE36AF-7781-4578-A7E0-F0EFD2025569}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EAAE36AF-7781-4578-A7E0-F0EFD2025569}.Release|Any CPU.Build.0 = Release|Any CPU {5C1D818E-682A-46A5-9D54-30006E26C270}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5C1D818E-682A-46A5-9D54-30006E26C270}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5C1D818E-682A-46A5-9D54-30006E26C270}.Profile Debug|Any CPU.ActiveCfg = Profile Debug|Any CPU + {5C1D818E-682A-46A5-9D54-30006E26C270}.Profile Debug|Any CPU.Build.0 = Profile Debug|Any CPU + {5C1D818E-682A-46A5-9D54-30006E26C270}.Profile Release|Any CPU.ActiveCfg = Profile Release|Any CPU + {5C1D818E-682A-46A5-9D54-30006E26C270}.Profile Release|Any CPU.Build.0 = Profile Release|Any CPU {5C1D818E-682A-46A5-9D54-30006E26C270}.Release|Any CPU.ActiveCfg = Release|Any CPU {5C1D818E-682A-46A5-9D54-30006E26C270}.Release|Any CPU.Build.0 = Release|Any CPU {3AB294D0-2230-468F-9EB3-BDFCAEAE99A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3AB294D0-2230-468F-9EB3-BDFCAEAE99A5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3AB294D0-2230-468F-9EB3-BDFCAEAE99A5}.Profile Debug|Any CPU.ActiveCfg = Profile Debug|Any CPU + {3AB294D0-2230-468F-9EB3-BDFCAEAE99A5}.Profile Debug|Any CPU.Build.0 = Profile Debug|Any CPU + {3AB294D0-2230-468F-9EB3-BDFCAEAE99A5}.Profile Release|Any CPU.ActiveCfg = Profile Release|Any CPU + {3AB294D0-2230-468F-9EB3-BDFCAEAE99A5}.Profile Release|Any CPU.Build.0 = Profile Release|Any CPU {3AB294D0-2230-468F-9EB3-BDFCAEAE99A5}.Release|Any CPU.ActiveCfg = Release|Any CPU {3AB294D0-2230-468F-9EB3-BDFCAEAE99A5}.Release|Any CPU.Build.0 = Release|Any CPU {8E7D36DD-9626-47E2-8EF5-8F2F66751C9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8E7D36DD-9626-47E2-8EF5-8F2F66751C9C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8E7D36DD-9626-47E2-8EF5-8F2F66751C9C}.Profile Debug|Any CPU.ActiveCfg = Profile Debug|Any CPU + {8E7D36DD-9626-47E2-8EF5-8F2F66751C9C}.Profile Debug|Any CPU.Build.0 = Profile Debug|Any CPU + {8E7D36DD-9626-47E2-8EF5-8F2F66751C9C}.Profile Release|Any CPU.ActiveCfg = Profile Release|Any CPU + {8E7D36DD-9626-47E2-8EF5-8F2F66751C9C}.Profile Release|Any CPU.Build.0 = Profile Release|Any CPU {8E7D36DD-9626-47E2-8EF5-8F2F66751C9C}.Release|Any CPU.ActiveCfg = Release|Any CPU {8E7D36DD-9626-47E2-8EF5-8F2F66751C9C}.Release|Any CPU.Build.0 = Release|Any CPU + {5FD4E4F6-8928-4B3C-BE07-28A675C17226}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5FD4E4F6-8928-4B3C-BE07-28A675C17226}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5FD4E4F6-8928-4B3C-BE07-28A675C17226}.Profile Debug|Any CPU.ActiveCfg = Profile Debug|Any CPU + {5FD4E4F6-8928-4B3C-BE07-28A675C17226}.Profile Debug|Any CPU.Build.0 = Profile Debug|Any CPU + {5FD4E4F6-8928-4B3C-BE07-28A675C17226}.Profile Release|Any CPU.ActiveCfg = Profile Release|Any CPU + {5FD4E4F6-8928-4B3C-BE07-28A675C17226}.Profile Release|Any CPU.Build.0 = Profile Release|Any CPU + {5FD4E4F6-8928-4B3C-BE07-28A675C17226}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5FD4E4F6-8928-4B3C-BE07-28A675C17226}.Release|Any CPU.Build.0 = Release|Any CPU + {4E69B67F-8CA7-42CF-A9E1-CCB0915DFB34}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4E69B67F-8CA7-42CF-A9E1-CCB0915DFB34}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4E69B67F-8CA7-42CF-A9E1-CCB0915DFB34}.Profile Debug|Any CPU.ActiveCfg = Profile Debug|Any CPU + {4E69B67F-8CA7-42CF-A9E1-CCB0915DFB34}.Profile Debug|Any CPU.Build.0 = Profile Debug|Any CPU + {4E69B67F-8CA7-42CF-A9E1-CCB0915DFB34}.Profile Release|Any CPU.ActiveCfg = Profile Release|Any CPU + {4E69B67F-8CA7-42CF-A9E1-CCB0915DFB34}.Profile Release|Any CPU.Build.0 = Profile Release|Any CPU + {4E69B67F-8CA7-42CF-A9E1-CCB0915DFB34}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4E69B67F-8CA7-42CF-A9E1-CCB0915DFB34}.Release|Any CPU.Build.0 = Release|Any CPU + {ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}.Profile Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}.Profile Debug|Any CPU.Build.0 = Debug|Any CPU + {ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}.Profile Release|Any CPU.ActiveCfg = Release|Any CPU + {ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}.Profile Release|Any CPU.Build.0 = Release|Any CPU + {ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}.Release|Any CPU.Build.0 = Release|Any CPU + {ADA7EA87-0D63-4D97-9433-922A2124401F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ADA7EA87-0D63-4D97-9433-922A2124401F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ADA7EA87-0D63-4D97-9433-922A2124401F}.Profile Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ADA7EA87-0D63-4D97-9433-922A2124401F}.Profile Debug|Any CPU.Build.0 = Debug|Any CPU + {ADA7EA87-0D63-4D97-9433-922A2124401F}.Profile Release|Any CPU.ActiveCfg = Debug|Any CPU + {ADA7EA87-0D63-4D97-9433-922A2124401F}.Profile Release|Any CPU.Build.0 = Debug|Any CPU + {ADA7EA87-0D63-4D97-9433-922A2124401F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ADA7EA87-0D63-4D97-9433-922A2124401F}.Release|Any CPU.Build.0 = Release|Any CPU + {A602AE97-91A5-4608-8DF1-EBF4ED7A0B9E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A602AE97-91A5-4608-8DF1-EBF4ED7A0B9E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A602AE97-91A5-4608-8DF1-EBF4ED7A0B9E}.Profile Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A602AE97-91A5-4608-8DF1-EBF4ED7A0B9E}.Profile Debug|Any CPU.Build.0 = Debug|Any CPU + {A602AE97-91A5-4608-8DF1-EBF4ED7A0B9E}.Profile Release|Any CPU.ActiveCfg = Debug|Any CPU + {A602AE97-91A5-4608-8DF1-EBF4ED7A0B9E}.Profile Release|Any CPU.Build.0 = Debug|Any CPU + {A602AE97-91A5-4608-8DF1-EBF4ED7A0B9E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A602AE97-91A5-4608-8DF1-EBF4ED7A0B9E}.Release|Any CPU.Build.0 = Release|Any CPU + {9558FB96-075D-4219-8FFF-401979DC0B69}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9558FB96-075D-4219-8FFF-401979DC0B69}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9558FB96-075D-4219-8FFF-401979DC0B69}.Profile Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9558FB96-075D-4219-8FFF-401979DC0B69}.Profile Debug|Any CPU.Build.0 = Debug|Any CPU + {9558FB96-075D-4219-8FFF-401979DC0B69}.Profile Release|Any CPU.ActiveCfg = Debug|Any CPU + {9558FB96-075D-4219-8FFF-401979DC0B69}.Profile Release|Any CPU.Build.0 = Debug|Any CPU + {9558FB96-075D-4219-8FFF-401979DC0B69}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9558FB96-075D-4219-8FFF-401979DC0B69}.Release|Any CPU.Build.0 = Release|Any CPU + {E1B1AD28-289D-47B7-A106-326972240207}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E1B1AD28-289D-47B7-A106-326972240207}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E1B1AD28-289D-47B7-A106-326972240207}.Profile Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E1B1AD28-289D-47B7-A106-326972240207}.Profile Debug|Any CPU.Build.0 = Debug|Any CPU + {E1B1AD28-289D-47B7-A106-326972240207}.Profile Release|Any CPU.ActiveCfg = Debug|Any CPU + {E1B1AD28-289D-47B7-A106-326972240207}.Profile Release|Any CPU.Build.0 = Debug|Any CPU + {E1B1AD28-289D-47B7-A106-326972240207}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E1B1AD28-289D-47B7-A106-326972240207}.Release|Any CPU.Build.0 = Release|Any CPU + {03B955CD-AD84-4B93-AAA7-BF17923BBAA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {03B955CD-AD84-4B93-AAA7-BF17923BBAA5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {03B955CD-AD84-4B93-AAA7-BF17923BBAA5}.Profile Debug|Any CPU.ActiveCfg = Debug|Any CPU + {03B955CD-AD84-4B93-AAA7-BF17923BBAA5}.Profile Debug|Any CPU.Build.0 = Debug|Any CPU + {03B955CD-AD84-4B93-AAA7-BF17923BBAA5}.Profile Release|Any CPU.ActiveCfg = Debug|Any CPU + {03B955CD-AD84-4B93-AAA7-BF17923BBAA5}.Profile Release|Any CPU.Build.0 = Debug|Any CPU + {03B955CD-AD84-4B93-AAA7-BF17923BBAA5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {03B955CD-AD84-4B93-AAA7-BF17923BBAA5}.Release|Any CPU.Build.0 = Release|Any CPU + {85A0FA56-DC01-4A42-8808-70DAC76BD66D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {85A0FA56-DC01-4A42-8808-70DAC76BD66D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {85A0FA56-DC01-4A42-8808-70DAC76BD66D}.Profile Debug|Any CPU.ActiveCfg = Debug|Any CPU + {85A0FA56-DC01-4A42-8808-70DAC76BD66D}.Profile Debug|Any CPU.Build.0 = Debug|Any CPU + {85A0FA56-DC01-4A42-8808-70DAC76BD66D}.Profile Release|Any CPU.ActiveCfg = Release|Any CPU + {85A0FA56-DC01-4A42-8808-70DAC76BD66D}.Profile Release|Any CPU.Build.0 = Release|Any CPU + {85A0FA56-DC01-4A42-8808-70DAC76BD66D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {85A0FA56-DC01-4A42-8808-70DAC76BD66D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Ryujinx.sln.DotSettings b/Ryujinx.sln.DotSettings new file mode 100644 index 0000000000..ed35825493 --- /dev/null +++ b/Ryujinx.sln.DotSettings @@ -0,0 +1,20 @@ + + WARNING + WARNING + UseExplicitType + UseExplicitType + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="I" Suffix="" Style="AaBb" /></Policy> + True + True + True + True + True + True + True + True + True + True + True + True + True + \ No newline at end of file diff --git a/Ryujinx/Config.cs b/Ryujinx/Config.cs deleted file mode 100644 index 940753ba53..0000000000 --- a/Ryujinx/Config.cs +++ /dev/null @@ -1,155 +0,0 @@ -using Ryujinx.UI.Input; -using Ryujinx.HLE.Logging; -using System; -using System.Globalization; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; - -namespace Ryujinx -{ - public static class Config - { - public static JoyConKeyboard JoyConKeyboard { get; private set; } - public static JoyConController JoyConController { get; private set; } - - public static float GamePadDeadzone { get; private set; } - public static bool GamePadEnable { get; private set; } - public static int GamePadIndex { get; private set; } - public static float GamePadTriggerThreshold { get; private set; } - - public static void Read(Logger Log) - { - string IniFolder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); - - string IniPath = Path.Combine(IniFolder, "Ryujinx.conf"); - - IniParser Parser = new IniParser(IniPath); - - AOptimizations.DisableMemoryChecks = !Convert.ToBoolean(Parser.Value("Enable_Memory_Checks")); - - Log.SetEnable(LogLevel.Debug, Convert.ToBoolean(Parser.Value("Logging_Enable_Debug"))); - Log.SetEnable(LogLevel.Stub, Convert.ToBoolean(Parser.Value("Logging_Enable_Stub"))); - Log.SetEnable(LogLevel.Info, Convert.ToBoolean(Parser.Value("Logging_Enable_Info"))); - Log.SetEnable(LogLevel.Warning, Convert.ToBoolean(Parser.Value("Logging_Enable_Warn"))); - Log.SetEnable(LogLevel.Error, Convert.ToBoolean(Parser.Value("Logging_Enable_Error"))); - - GamePadEnable = Convert.ToBoolean(Parser.Value("GamePad_Enable")); - GamePadIndex = Convert.ToInt32 (Parser.Value("GamePad_Index")); - GamePadDeadzone = (float)Convert.ToDouble (Parser.Value("GamePad_Deadzone"), CultureInfo.InvariantCulture); - GamePadTriggerThreshold = (float)Convert.ToDouble (Parser.Value("GamePad_Trigger_Threshold"), CultureInfo.InvariantCulture); - - string[] FilteredLogClasses = Parser.Value("Logging_Filtered_Classes").Split(',', StringSplitOptions.RemoveEmptyEntries); - - //When the classes are specified on the list, we only - //enable the classes that are on the list. - //So, first disable everything, then enable - //the classes that the user added to the list. - if (FilteredLogClasses.Length > 0) - { - foreach (LogClass Class in Enum.GetValues(typeof(LogClass))) - { - Log.SetEnable(Class, false); - } - } - - foreach (string LogClass in FilteredLogClasses) - { - if (!string.IsNullOrEmpty(LogClass.Trim())) - { - foreach (LogClass Class in Enum.GetValues(typeof(LogClass))) - { - if (Class.ToString().ToLower().Contains(LogClass.Trim().ToLower())) - { - Log.SetEnable(Class, true); - } - } - } - } - - JoyConKeyboard = new JoyConKeyboard - { - Left = new JoyConKeyboardLeft - { - StickUp = Convert.ToInt16(Parser.Value("Controls_Left_JoyConKeyboard_Stick_Up")), - StickDown = Convert.ToInt16(Parser.Value("Controls_Left_JoyConKeyboard_Stick_Down")), - StickLeft = Convert.ToInt16(Parser.Value("Controls_Left_JoyConKeyboard_Stick_Left")), - StickRight = Convert.ToInt16(Parser.Value("Controls_Left_JoyConKeyboard_Stick_Right")), - StickButton = Convert.ToInt16(Parser.Value("Controls_Left_JoyConKeyboard_Stick_Button")), - DPadUp = Convert.ToInt16(Parser.Value("Controls_Left_JoyConKeyboard_DPad_Up")), - DPadDown = Convert.ToInt16(Parser.Value("Controls_Left_JoyConKeyboard_DPad_Down")), - DPadLeft = Convert.ToInt16(Parser.Value("Controls_Left_JoyConKeyboard_DPad_Left")), - DPadRight = Convert.ToInt16(Parser.Value("Controls_Left_JoyConKeyboard_DPad_Right")), - ButtonMinus = Convert.ToInt16(Parser.Value("Controls_Left_JoyConKeyboard_Button_Minus")), - ButtonL = Convert.ToInt16(Parser.Value("Controls_Left_JoyConKeyboard_Button_L")), - ButtonZL = Convert.ToInt16(Parser.Value("Controls_Left_JoyConKeyboard_Button_ZL")) - }, - - Right = new JoyConKeyboardRight - { - StickUp = Convert.ToInt16(Parser.Value("Controls_Right_JoyConKeyboard_Stick_Up")), - StickDown = Convert.ToInt16(Parser.Value("Controls_Right_JoyConKeyboard_Stick_Down")), - StickLeft = Convert.ToInt16(Parser.Value("Controls_Right_JoyConKeyboard_Stick_Left")), - StickRight = Convert.ToInt16(Parser.Value("Controls_Right_JoyConKeyboard_Stick_Right")), - StickButton = Convert.ToInt16(Parser.Value("Controls_Right_JoyConKeyboard_Stick_Button")), - ButtonA = Convert.ToInt16(Parser.Value("Controls_Right_JoyConKeyboard_Button_A")), - ButtonB = Convert.ToInt16(Parser.Value("Controls_Right_JoyConKeyboard_Button_B")), - ButtonX = Convert.ToInt16(Parser.Value("Controls_Right_JoyConKeyboard_Button_X")), - ButtonY = Convert.ToInt16(Parser.Value("Controls_Right_JoyConKeyboard_Button_Y")), - ButtonPlus = Convert.ToInt16(Parser.Value("Controls_Right_JoyConKeyboard_Button_Plus")), - ButtonR = Convert.ToInt16(Parser.Value("Controls_Right_JoyConKeyboard_Button_R")), - ButtonZR = Convert.ToInt16(Parser.Value("Controls_Right_JoyConKeyboard_Button_ZR")) - } - }; - - JoyConController = new JoyConController - { - Left = new JoyConControllerLeft - { - Stick = Parser.Value("Controls_Left_JoyConController_Stick"), - StickButton = Parser.Value("Controls_Left_JoyConController_Stick_Button"), - DPadUp = Parser.Value("Controls_Left_JoyConController_DPad_Up"), - DPadDown = Parser.Value("Controls_Left_JoyConController_DPad_Down"), - DPadLeft = Parser.Value("Controls_Left_JoyConController_DPad_Left"), - DPadRight = Parser.Value("Controls_Left_JoyConController_DPad_Right"), - ButtonMinus = Parser.Value("Controls_Left_JoyConController_Button_Minus"), - ButtonL = Parser.Value("Controls_Left_JoyConController_Button_L"), - ButtonZL = Parser.Value("Controls_Left_JoyConController_Button_ZL") - }, - - Right = new JoyConControllerRight - { - Stick = Parser.Value("Controls_Right_JoyConController_Stick"), - StickButton = Parser.Value("Controls_Right_JoyConController_Stick_Button"), - ButtonA = Parser.Value("Controls_Right_JoyConController_Button_A"), - ButtonB = Parser.Value("Controls_Right_JoyConController_Button_B"), - ButtonX = Parser.Value("Controls_Right_JoyConController_Button_X"), - ButtonY = Parser.Value("Controls_Right_JoyConController_Button_Y"), - ButtonPlus = Parser.Value("Controls_Right_JoyConController_Button_Plus"), - ButtonR = Parser.Value("Controls_Right_JoyConController_Button_R"), - ButtonZR = Parser.Value("Controls_Right_JoyConController_Button_ZR") - } - }; - } - } - - //https://stackoverflow.com/a/37772571 - public class IniParser - { - private readonly Dictionary Values; - - public IniParser(string Path) - { - Values = File.ReadLines(Path) - .Where(Line => !string.IsNullOrWhiteSpace(Line) && !Line.StartsWith('#')) - .Select(Line => Line.Split('=', 2)) - .ToDictionary(Parts => Parts[0].Trim(), Parts => Parts.Length > 1 ? Parts[1].Trim() : null); - } - - public string Value(string Name) - { - return Values.TryGetValue(Name, out string Value) ? Value : null; - } - } -} diff --git a/Ryujinx/Config.json b/Ryujinx/Config.json new file mode 100644 index 0000000000..e35a67acd7 --- /dev/null +++ b/Ryujinx/Config.json @@ -0,0 +1,99 @@ +{ + "version": 1, + "graphics_shaders_dump_path": "", + "logging_enable_debug": false, + "logging_enable_stub": true, + "logging_enable_info": true, + "logging_enable_warn": true, + "logging_enable_error": true, + "logging_enable_guest": true, + "logging_enable_fs_access_log": false, + "logging_filtered_classes": [], + "enable_file_log": true, + "system_language": "AmericanEnglish", + "docked_mode": false, + "enable_discord_integration": true, + "enable_vsync": true, + "enable_multicore_scheduling": true, + "enable_fs_integrity_checks": true, + "fs_global_access_log_mode": 0, + "ignore_missing_services": false, + "controller_type": "Handheld", + "gui_columns": { + "fav_column": true, + "icon_column": true, + "app_column": true, + "dev_column": true, + "version_column": true, + "time_played_column": true, + "last_played_column": true, + "file_ext_column": true, + "file_size_column": true, + "path_column": true + }, + "game_dirs": [], + "enable_custom_theme": false, + "custom_theme_path": "", + "enable_keyboard": false, + "keyboard_controls": { + "left_joycon": { + "stick_up": "W", + "stick_down": "S", + "stick_left": "A", + "stick_right": "D", + "stick_button": "F", + "dpad_up": "Up", + "dpad_down": "Down", + "dpad_left": "Left", + "dpad_right": "Right", + "button_minus": "Minus", + "button_l": "E", + "button_zl": "Q" + }, + "right_joycon": { + "stick_up": "I", + "stick_down": "K", + "stick_left": "J", + "stick_right": "L", + "stick_button": "H", + "button_a": "Z", + "button_b": "X", + "button_x": "C", + "button_y": "V", + "button_plus": "Plus", + "button_r": "U", + "button_zr": "O" + }, + "hotkeys": { + "toggle_vsync": "Tab" + } + }, + "joystick_controls": { + "enabled": true, + "index": 0, + "deadzone": 0.05, + "trigger_threshold": 0.5, + "left_joycon": { + "stick": "Axis0", + "stick_button": "Button8", + "button_minus": "Button6", + "button_l": "Button4", + "button_zl": "Axis2", + "dpad_up": "Hat0Up", + "dpad_down": "Hat0Down", + "dpad_left": "Hat0Left", + "dpad_right": "Hat0Right" + }, + "right_joycon": { + "stick": "Axis3", + "stick_button": "Button9", + "button_a": "Button1", + "button_b": "Button0", + "button_x": "Button3", + "button_y": "Button2", + "button_plus": "Button7", + "button_r": "Button5", + "button_zr": "Axis5" + } + } +} \ No newline at end of file diff --git a/Ryujinx/Configuration/DiscordIntegrationModule.cs b/Ryujinx/Configuration/DiscordIntegrationModule.cs new file mode 100644 index 0000000000..15540a1c82 --- /dev/null +++ b/Ryujinx/Configuration/DiscordIntegrationModule.cs @@ -0,0 +1,92 @@ +using DiscordRPC; +using Ryujinx.Common; +using System; +using System.IO; +using System.Linq; + +namespace Ryujinx.Configuration +{ + static class DiscordIntegrationModule + { + private static DiscordRpcClient DiscordClient; + + private static string LargeDescription = "Ryujinx is a Nintendo Switch emulator."; + + public static RichPresence DiscordPresence { get; private set; } + + public static void Initialize() + { + DiscordPresence = new RichPresence + { + Assets = new Assets + { + LargeImageKey = "ryujinx", + LargeImageText = LargeDescription + }, + Details = "Main Menu", + State = "Idling", + Timestamps = new Timestamps(DateTime.UtcNow) + }; + + ConfigurationState.Instance.EnableDiscordIntegration.Event += Update; + } + + private static void Update(object sender, ReactiveEventArgs e) + { + if (e.OldValue != e.NewValue) + { + // If the integration was active, disable it and unload everything + if (e.OldValue) + { + DiscordClient?.Dispose(); + + DiscordClient = null; + } + + // If we need to activate it and the client isn't active, initialize it + if (e.NewValue && DiscordClient == null) + { + DiscordClient = new DiscordRpcClient("568815339807309834"); + + DiscordClient.Initialize(); + DiscordClient.SetPresence(DiscordPresence); + } + } + } + + public static void SwitchToPlayingState(string titleId, string titleName) + { + if (File.ReadAllLines(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "RPsupported.dat")).Contains(titleId)) + { + DiscordPresence.Assets.LargeImageKey = titleId; + } + + string state = titleId; + + if (state == null) + { + state = "Ryujinx"; + } + else + { + state = state.ToUpper(); + } + + string details = "Idling"; + + if (titleName != null) + { + details = $"Playing {titleName}"; + } + + DiscordPresence.Details = details; + DiscordPresence.State = state; + DiscordPresence.Assets.LargeImageText = titleName; + DiscordPresence.Assets.SmallImageKey = "ryujinx"; + DiscordPresence.Assets.SmallImageText = LargeDescription; + DiscordPresence.Timestamps = new Timestamps(DateTime.UtcNow); + + DiscordClient?.SetPresence(DiscordPresence); + } + } +} diff --git a/Ryujinx/Program.cs b/Ryujinx/Program.cs new file mode 100644 index 0000000000..fbf1196d0c --- /dev/null +++ b/Ryujinx/Program.cs @@ -0,0 +1,81 @@ +using Gtk; +using Ryujinx.Common.Logging; +using Ryujinx.Configuration; +using Ryujinx.Profiler; +using Ryujinx.Ui; +using System; +using System.IO; + +namespace Ryujinx +{ + class Program + { + static void Main(string[] args) + { + Console.Title = "Ryujinx Console"; + + string systemPath = Environment.GetEnvironmentVariable("Path", EnvironmentVariableTarget.Machine); + Environment.SetEnvironmentVariable("Path", $"{Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "bin")};{systemPath}"); + + GLib.ExceptionManager.UnhandledException += Glib_UnhandledException; + + // Initialize the configuration + ConfigurationState.Initialize(); + + // Initialize the logger system + LoggerModule.Initialize(); + + // Initialize Discord integration + DiscordIntegrationModule.Initialize(); + + string configurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json"); + + // Now load the configuration as the other subsystems are now registered + if (File.Exists(configurationPath)) + { + ConfigurationFileFormat configurationFileFormat = ConfigurationFileFormat.Load(configurationPath); + ConfigurationState.Instance.Load(configurationFileFormat); + } + else + { + // No configuration, we load the default values and save it on disk + ConfigurationState.Instance.LoadDefault(); + ConfigurationState.Instance.ToFileFormat().SaveConfig(configurationPath); + } + + + Profile.Initialize(); + + Application.Init(); + + string appDataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Ryujinx", "system", "prod.keys"); + string userProfilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".switch", "prod.keys"); + if (!File.Exists(appDataPath) && !File.Exists(userProfilePath) && !Migration.IsMigrationNeeded()) + { + GtkDialog.CreateErrorDialog("Key file was not found. Please refer to `KEYS.md` for more info"); + } + + MainWindow mainWindow = new MainWindow(); + mainWindow.Show(); + + if (args.Length == 1) + { + mainWindow.LoadApplication(args[0]); + } + + Application.Run(); + } + + private static void Glib_UnhandledException(GLib.UnhandledExceptionArgs e) + { + Exception exception = e.ExceptionObject as Exception; + + Logger.PrintError(LogClass.Application, $"Unhandled exception caught: {exception}"); + + if (e.IsTerminating) + { + Logger.Shutdown(); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx/RPsupported.dat b/Ryujinx/RPsupported.dat new file mode 100644 index 0000000000..c58195bab9 --- /dev/null +++ b/Ryujinx/RPsupported.dat @@ -0,0 +1,49 @@ +01000d200ac0c000 +01000d700be88000 +01000dc007e90000 +01000e2003fa0000 +0100225000fee000 +010028d0045ce000 +01002b30028f6000 +01002fc00c6d0000 +010034e005c9c000 +01004f8006a78000 +010051f00ac5e000 +010056e00853a000 +0100574009f9e000 +0100628004bce000 +0100633007d48000 +010065500b218000 +010068f00aa78000 +01006a800016e000 +010072800cbe8000 +01007300020fa000 +01007330027ee000 +0100749009844000 +01007a4008486000 +01007ef00011e000 +010080b00ad66000 +01008db008c2c000 +010094e00b52e000 +01009aa000faa000 +01009b90006dc000 +01009cc00c97c000 +0100a4200a284000 +0100a5c00d162000 +0100abf008968000 +0100ae000aebc000 +0100b3f000be2000 +0100bc2004ff4000 +0100cf3007578000 +0100d5d00c6be000 +0100d6b00cd88000 +0100d870045b6000 +0100e0c00adac000 +0100e46006708000 +0100e7200b272000 +0100e9f00b882000 +0100eab00605c000 +0100efd00a4fa000 +0100f6a00a684000 +0100f9f00c696000 +051337133769a000 \ No newline at end of file diff --git a/Ryujinx/Ryujinx.conf b/Ryujinx/Ryujinx.conf deleted file mode 100644 index 59f7f859e7..0000000000 --- a/Ryujinx/Ryujinx.conf +++ /dev/null @@ -1,82 +0,0 @@ -#Enable cpu memory checks (slow) -Enable_Memory_Checks = false - -#Enable print debug logs -Logging_Enable_Debug = false - -#Enable print stubbed calls logs -Logging_Enable_Stub = true - -#Enable print informations logs -Logging_Enable_Info = true - -#Enable print warning logs -Logging_Enable_Warn = true - -#Enable print error logs -Logging_Enable_Error = true - -#Filtered log classes, seperated by ", ", eg. `Logging_Filtered_Classes = Loader, ServiceFS` -Logging_Filtered_Classes = - -#Controller Device Index -GamePad_Index = 0 - -#Controller Analog Stick Deadzone -GamePad_Deadzone = 0.05 - -#The value of how pressed down each trigger has to be in order to register a button press -GamePad_Trigger_Threshold = 0.5 - -#Whether or not to enable Controller support -GamePad_Enable = true - -#https://github.com/opentk/opentk/blob/develop/src/OpenTK/Input/Key.cs -Controls_Left_JoyConKeyboard_Stick_Up = 105 -Controls_Left_JoyConKeyboard_Stick_Down = 101 -Controls_Left_JoyConKeyboard_Stick_Left = 83 -Controls_Left_JoyConKeyboard_Stick_Right = 86 -Controls_Left_JoyConKeyboard_Stick_Button = 88 -Controls_Left_JoyConKeyboard_DPad_Up = 45 -Controls_Left_JoyConKeyboard_DPad_Down = 46 -Controls_Left_JoyConKeyboard_DPad_Left = 47 -Controls_Left_JoyConKeyboard_DPad_Right = 48 -Controls_Left_JoyConKeyboard_Button_Minus = 120 -Controls_Left_JoyConKeyboard_Button_L = 87 -Controls_Left_JoyConKeyboard_Button_ZL = 99 - -Controls_Right_JoyConKeyboard_Stick_Up = 91 -Controls_Right_JoyConKeyboard_Stick_Down = 93 -Controls_Right_JoyConKeyboard_Stick_Left = 92 -Controls_Right_JoyConKeyboard_Stick_Right = 94 -Controls_Right_JoyConKeyboard_Stick_Button = 90 -Controls_Right_JoyConKeyboard_Button_A = 108 -Controls_Right_JoyConKeyboard_Button_B = 106 -Controls_Right_JoyConKeyboard_Button_X = 85 -Controls_Right_JoyConKeyboard_Button_Y = 104 -Controls_Right_JoyConKeyboard_Button_Plus = 121 -Controls_Right_JoyConKeyboard_Button_R = 103 -Controls_Right_JoyConKeyboard_Button_ZR = 97 - -#Controller Controls - -Controls_Left_JoyConController_Stick_Button = LStick -Controls_Left_JoyConController_DPad_Up = DPadUp -Controls_Left_JoyConController_DPad_Down = DPadDown -Controls_Left_JoyConController_DPad_Left = DPadLeft -Controls_Left_JoyConController_DPad_Right = DPadRight -Controls_Left_JoyConController_Button_Minus = Back -Controls_Left_JoyConController_Button_L = LShoulder -Controls_Left_JoyConController_Button_ZL = LTrigger - -Controls_Right_JoyConController_Stick_Button = RStick -Controls_Right_JoyConController_Button_A = B -Controls_Right_JoyConController_Button_B = A -Controls_Right_JoyConController_Button_X = Y -Controls_Right_JoyConController_Button_Y = X -Controls_Right_JoyConController_Button_Plus = Start -Controls_Right_JoyConController_Button_R = RShoulder -Controls_Right_JoyConController_Button_ZR = RTrigger - -Controls_Left_JoyConController_Stick = LJoystick -Controls_Right_JoyConController_Stick = RJoystick \ No newline at end of file diff --git a/Ryujinx/Ryujinx.csproj b/Ryujinx/Ryujinx.csproj index 443e7bbad8..4977086345 100644 --- a/Ryujinx/Ryujinx.csproj +++ b/Ryujinx/Ryujinx.csproj @@ -1,23 +1,98 @@  + + netcoreapp3.0 + win-x64;osx-x64;linux-x64 Exe - netcoreapp2.1 true - win10-x64;osx-x64;linux-x64 + Debug;Release;Profile Debug;Profile Release + + + TRACE;USE_PROFILING + true + + + + TRACE;USE_PROFILING + false + + + + + false + + + + MACOS_BUILD + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - + + + + + - + + PreserveNewest + + PreserveNewest - \ No newline at end of file + + diff --git a/Ryujinx/Ui/AboutWindow.cs b/Ryujinx/Ui/AboutWindow.cs new file mode 100644 index 0000000000..122dcaae1f --- /dev/null +++ b/Ryujinx/Ui/AboutWindow.cs @@ -0,0 +1,97 @@ +using Gtk; +using System; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; +using Utf8Json; +using Utf8Json.Resolvers; + +using GUI = Gtk.Builder.ObjectAttribute; + +namespace Ryujinx.Ui +{ + public class AboutWindow : Window + { +#pragma warning disable CS0649 +#pragma warning disable IDE0044 + [GUI] Window _aboutWin; + [GUI] Label _versionText; + [GUI] Image _ryujinxLogo; + [GUI] Image _patreonLogo; + [GUI] Image _gitHubLogo; + [GUI] Image _discordLogo; + [GUI] Image _twitterLogo; +#pragma warning restore CS0649 +#pragma warning restore IDE0044 + + public AboutWindow() : this(new Builder("Ryujinx.Ui.AboutWindow.glade")) { } + + private AboutWindow(Builder builder) : base(builder.GetObject("_aboutWin").Handle) + { + builder.Autoconnect(this); + + _aboutWin.Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png"); + _ryujinxLogo.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png" , 100, 100); + _patreonLogo.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.PatreonLogo.png", 30 , 30 ); + _gitHubLogo.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.GitHubLogo.png" , 30 , 30 ); + _discordLogo.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.DiscordLogo.png", 30 , 30 ); + _twitterLogo.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.TwitterLogo.png", 30 , 30 ); + + // todo: Get version string + _versionText.Text = "Unknown Version"; + } + + private static void OpenUrl(string url) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + Process.Start(new ProcessStartInfo("cmd", $"/c start {url}")); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + Process.Start("xdg-open", url); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + Process.Start("open", url); + } + } + + //Events + private void RyujinxButton_Pressed(object sender, ButtonPressEventArgs args) + { + OpenUrl("https://ryujinx.org"); + } + + private void PatreonButton_Pressed(object sender, ButtonPressEventArgs args) + { + OpenUrl("https://www.patreon.com/ryujinx"); + } + + private void GitHubButton_Pressed(object sender, ButtonPressEventArgs args) + { + OpenUrl("https://github.com/Ryujinx/Ryujinx"); + } + + private void DiscordButton_Pressed(object sender, ButtonPressEventArgs args) + { + OpenUrl("https://discordapp.com/invite/N2FmfVc"); + } + + private void TwitterButton_Pressed(object sender, ButtonPressEventArgs args) + { + OpenUrl("https://twitter.com/RyujinxEmu"); + } + + private void ContributorsButton_Pressed(object sender, ButtonPressEventArgs args) + { + OpenUrl("https://github.com/Ryujinx/Ryujinx/graphs/contributors?type=a"); + } + + private void CloseToggle_Activated(object sender, EventArgs args) + { + Dispose(); + } + } +} diff --git a/Ryujinx/Ui/AboutWindow.glade b/Ryujinx/Ui/AboutWindow.glade new file mode 100644 index 0000000000..8a27f372e9 --- /dev/null +++ b/Ryujinx/Ui/AboutWindow.glade @@ -0,0 +1,574 @@ + + + + + + False + False + True + center + 800 + 350 + dialog + + + + + + False + vertical + + + False + + + False + False + 0 + + + + + True + False + + + True + False + 10 + 15 + 10 + 15 + vertical + + + True + False + start + vertical + + + True + False + + + + True + True + 0 + + + + + True + False + center + vertical + + + True + False + Ryujinx + center + + + + + + False + True + 0 + + + + + True + False + (REE-YOU-JI-NX) + center + + + False + True + 1 + + + + + True + False + + + + True + False + Click to open the Ryujinx website in your default browser + www.ryujinx.org + center + + + + + + + + False + True + 5 + 2 + + + + + True + True + 1 + + + + + False + True + 0 + + + + + True + False + Version x.x.x (Commit Number) + center + + + False + True + 2 + 1 + + + + + True + False + MIT License + center + + + False + True + 5 + 2 + + + + + True + False + Ryujinx is not affiliated with Nintendo, +or any of its partners, in any way + center + + + + + + False + True + 5 + 3 + + + + + False + False + 0 + + + + + True + False + 25 + + + True + False + Click to open the Ryujinx Patreon page in your default browser + + + + True + False + vertical + + + + True + True + 0 + + + + + True + False + Patreon + + + False + True + 1 + + + + + + + True + True + 0 + + + + + True + False + Click to open the Ryujinx GitHub page in your default browser + + + + True + False + vertical + + + + True + True + 0 + + + + + True + False + GitHub + + + False + True + 1 + + + + + + + True + True + 1 + + + + + True + False + Click to open an invite to the Ryujinx Discord server in your default browser + + + + True + False + vertical + + + + True + True + 0 + + + + + True + False + Discord + + + False + True + 1 + + + + + + + True + True + 2 + + + + + True + False + Click to open the Ryujinx Twitter page in your default browser + + + + True + False + vertical + + + + True + True + 0 + + + + + True + False + Twitter + + + False + True + 1 + + + + + + + True + True + 3 + + + + + False + False + end + 2 + + + + + True + False + 0 + + + + + True + False + 10 + 10 + + + False + True + 1 + + + + + True + False + 15 + 10 + 40 + 15 + vertical + + + True + False + start + About + + + + + + False + True + 0 + + + + + True + False + start + 10 + Ryujinx is an emulator for the Nintendo Switch. +Please support us on Patreon. +Get all the latest news on our Twitter or Discord. +Developers interested in contributing can find out more on our Discord. + + + False + True + 5 + 1 + + + + + True + False + start + Created By: + + + + + + False + True + 5 + 2 + + + + + True + False + 10 + vertical + + + True + True + in + + + True + False + + + True + False + top + + + True + False + start + gdkchan +LDj3SNuD +Ac_K +Thog + 0 + + + True + True + 0 + + + + + True + False + start + »jD« +emmaus +Thealexbarney +Andy A (BaronKiko) + 0 + + + True + True + 1 + + + + + + + + + True + True + 0 + + + + + True + False + start + + + + True + False + end + 5 + All Contributors... + + + + + + + + False + False + 2 + + + + + True + True + 3 + + + + + True + True + 2 + + + + + True + True + 1 + + + + + + diff --git a/Ryujinx/Ui/ApplicationAddedEventArgs.cs b/Ryujinx/Ui/ApplicationAddedEventArgs.cs new file mode 100644 index 0000000000..85a2f5a182 --- /dev/null +++ b/Ryujinx/Ui/ApplicationAddedEventArgs.cs @@ -0,0 +1,11 @@ +using System; + +namespace Ryujinx.Ui +{ + public class ApplicationAddedEventArgs : EventArgs + { + public ApplicationData AppData { get; set; } + public int NumAppsFound { get; set; } + public int NumAppsLoaded { get; set; } + } +} diff --git a/Ryujinx/Ui/ApplicationData.cs b/Ryujinx/Ui/ApplicationData.cs new file mode 100644 index 0000000000..defc5e9837 --- /dev/null +++ b/Ryujinx/Ui/ApplicationData.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Ui +{ + public struct ApplicationData + { + public bool Favorite { get; set; } + public byte[] Icon { get; set; } + public string TitleName { get; set; } + public string TitleId { get; set; } + public string Developer { get; set; } + public string Version { get; set; } + public string TimePlayed { get; set; } + public string LastPlayed { get; set; } + public string FileExtension { get; set; } + public string FileSize { get; set; } + public string Path { get; set; } + public string SaveDataPath { get; set; } + } +} diff --git a/Ryujinx/Ui/ApplicationLibrary.cs b/Ryujinx/Ui/ApplicationLibrary.cs new file mode 100644 index 0000000000..2ab6107776 --- /dev/null +++ b/Ryujinx/Ui/ApplicationLibrary.cs @@ -0,0 +1,501 @@ +using JsonPrettyPrinterPlus; +using LibHac; +using LibHac.Fs; +using LibHac.Fs.Shim; +using LibHac.FsSystem; +using LibHac.FsSystem.NcaUtils; +using LibHac.Ncm; +using LibHac.Spl; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.Loaders.Npdm; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using Utf8Json; +using Utf8Json.Resolvers; + +using RightsId = LibHac.Fs.RightsId; +using TitleLanguage = Ryujinx.HLE.HOS.SystemState.TitleLanguage; + +namespace Ryujinx.Ui +{ + public class ApplicationLibrary + { + public static event EventHandler ApplicationAdded; + + private static readonly byte[] _nspIcon = GetResourceBytes("Ryujinx.Ui.assets.NSPIcon.png"); + private static readonly byte[] _xciIcon = GetResourceBytes("Ryujinx.Ui.assets.XCIIcon.png"); + private static readonly byte[] _ncaIcon = GetResourceBytes("Ryujinx.Ui.assets.NCAIcon.png"); + private static readonly byte[] _nroIcon = GetResourceBytes("Ryujinx.Ui.assets.NROIcon.png"); + private static readonly byte[] _nsoIcon = GetResourceBytes("Ryujinx.Ui.assets.NSOIcon.png"); + + private static Keyset _keySet; + private static TitleLanguage _desiredTitleLanguage; + + public static void LoadApplications(List appDirs, Keyset keySet, TitleLanguage desiredTitleLanguage, FileSystemClient fsClient = null, VirtualFileSystem vfs = null) + { + int numApplicationsFound = 0; + int numApplicationsLoaded = 0; + + _keySet = keySet; + _desiredTitleLanguage = desiredTitleLanguage; + + // Builds the applications list with paths to found applications + List applications = new List(); + foreach (string appDir in appDirs) + { + if (Directory.Exists(appDir) == false) + { + Logger.PrintWarning(LogClass.Application, $"The \"game_dirs\" section in \"Config.json\" contains an invalid directory: \"{appDir}\""); + + continue; + } + + foreach (string app in Directory.GetFiles(appDir, "*.*", SearchOption.AllDirectories)) + { + if ((Path.GetExtension(app) == ".xci") || + (Path.GetExtension(app) == ".nro") || + (Path.GetExtension(app) == ".nso") || + (Path.GetFileName(app) == "hbl.nsp")) + { + applications.Add(app); + numApplicationsFound++; + } + else if ((Path.GetExtension(app) == ".nsp") || (Path.GetExtension(app) == ".pfs0")) + { + try + { + bool hasMainNca = false; + + PartitionFileSystem nsp = new PartitionFileSystem(new FileStream(app, FileMode.Open, FileAccess.Read).AsStorage()); + foreach (DirectoryEntryEx fileEntry in nsp.EnumerateEntries("/", "*.nca")) + { + nsp.OpenFile(out IFile ncaFile, fileEntry.FullPath, OpenMode.Read).ThrowIfFailure(); + + Nca nca = new Nca(_keySet, ncaFile.AsStorage()); + int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program); + + if (nca.Header.ContentType == NcaContentType.Program && !nca.Header.GetFsHeader(dataIndex).IsPatchSection()) + { + hasMainNca = true; + } + } + + if (!hasMainNca) + { + continue; + } + } + catch (InvalidDataException) + { + Logger.PrintWarning(LogClass.Application, $"{app}: The header key is incorrect or missing and therefore the NCA header content type check has failed."); + } + + applications.Add(app); + numApplicationsFound++; + } + else if (Path.GetExtension(app) == ".nca") + { + try + { + Nca nca = new Nca(_keySet, new FileStream(app, FileMode.Open, FileAccess.Read).AsStorage()); + int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program); + + if (nca.Header.ContentType != NcaContentType.Program || nca.Header.GetFsHeader(dataIndex).IsPatchSection()) + { + continue; + } + } + catch (InvalidDataException) + { + Logger.PrintWarning(LogClass.Application, $"{app}: The header key is incorrect or missing and therefore the NCA header content type check has failed."); + } + + applications.Add(app); + numApplicationsFound++; + } + } + } + + // Loops through applications list, creating a struct and then firing an event containing the struct for each application + foreach (string applicationPath in applications) + { + double fileSize = new FileInfo(applicationPath).Length * 0.000000000931; + string titleName = "Unknown"; + string titleId = "0000000000000000"; + string developer = "Unknown"; + string version = "0"; + string saveDataPath = null; + byte[] applicationIcon = null; + + using (FileStream file = new FileStream(applicationPath, FileMode.Open, FileAccess.Read)) + { + if ((Path.GetExtension(applicationPath) == ".nsp") || + (Path.GetExtension(applicationPath) == ".pfs0") || + (Path.GetExtension(applicationPath) == ".xci")) + { + try + { + PartitionFileSystem pfs; + + if (Path.GetExtension(applicationPath) == ".xci") + { + Xci xci = new Xci(_keySet, file.AsStorage()); + + pfs = xci.OpenPartition(XciPartitionType.Secure); + } + else + { + pfs = new PartitionFileSystem(file.AsStorage()); + } + + // Store the ControlFS in variable called controlFs + IFileSystem controlFs = GetControlFs(pfs); + + // If this is null then this is probably not a normal NSP, it's probably an ExeFS as an NSP + if (controlFs == null) + { + applicationIcon = _nspIcon; + + Result result = pfs.OpenFile(out IFile npdmFile, "/main.npdm", OpenMode.Read); + + if (result != ResultFs.PathNotFound) + { + Npdm npdm = new Npdm(npdmFile.AsStream()); + + titleName = npdm.TitleName; + titleId = npdm.Aci0.TitleId.ToString("x16"); + } + } + else + { + // Creates NACP class from the NACP file + controlFs.OpenFile(out IFile controlNacpFile, "/control.nacp", OpenMode.Read).ThrowIfFailure(); + + Nacp controlData = new Nacp(controlNacpFile.AsStream()); + + // Get the title name, title ID, developer name and version number from the NACP + version = controlData.DisplayVersion; + + titleName = controlData.Descriptions[(int)_desiredTitleLanguage].Title; + + if (string.IsNullOrWhiteSpace(titleName)) + { + titleName = controlData.Descriptions.ToList().Find(x => !string.IsNullOrWhiteSpace(x.Title)).Title; + } + + titleId = controlData.PresenceGroupId.ToString("x16"); + + if (string.IsNullOrWhiteSpace(titleId)) + { + titleId = controlData.SaveDataOwnerId.ToString("x16"); + } + + if (string.IsNullOrWhiteSpace(titleId)) + { + titleId = (controlData.AddOnContentBaseId - 0x1000).ToString("x16"); + } + + developer = controlData.Descriptions[(int)_desiredTitleLanguage].Developer; + + if (string.IsNullOrWhiteSpace(developer)) + { + developer = controlData.Descriptions.ToList().Find(x => !string.IsNullOrWhiteSpace(x.Developer)).Developer; + } + + // Read the icon from the ControlFS and store it as a byte array + try + { + controlFs.OpenFile(out IFile icon, $"/icon_{_desiredTitleLanguage}.dat", OpenMode.Read).ThrowIfFailure(); + + using (MemoryStream stream = new MemoryStream()) + { + icon.AsStream().CopyTo(stream); + applicationIcon = stream.ToArray(); + } + } + catch (HorizonResultException) + { + foreach (DirectoryEntryEx entry in controlFs.EnumerateEntries("/", "*")) + { + if (entry.Name == "control.nacp") + { + continue; + } + + controlFs.OpenFile(out IFile icon, entry.FullPath, OpenMode.Read).ThrowIfFailure(); + + using (MemoryStream stream = new MemoryStream()) + { + icon.AsStream().CopyTo(stream); + applicationIcon = stream.ToArray(); + } + + if (applicationIcon != null) + { + break; + } + } + + if (applicationIcon == null) + { + applicationIcon = Path.GetExtension(applicationPath) == ".xci" ? _xciIcon : _nspIcon; + } + } + } + } + catch (MissingKeyException exception) + { + applicationIcon = Path.GetExtension(applicationPath) == ".xci" ? _xciIcon : _nspIcon; + + Logger.PrintWarning(LogClass.Application, $"Your key set is missing a key with the name: {exception.Name}"); + } + catch (InvalidDataException) + { + applicationIcon = Path.GetExtension(applicationPath) == ".xci" ? _xciIcon : _nspIcon; + + Logger.PrintWarning(LogClass.Application, $"The header key is incorrect or missing and therefore the NCA header content type check has failed. Errored File: {applicationPath}"); + } + } + else if (Path.GetExtension(applicationPath) == ".nro") + { + BinaryReader reader = new BinaryReader(file); + + byte[] Read(long position, int size) + { + file.Seek(position, SeekOrigin.Begin); + + return reader.ReadBytes(size); + } + + file.Seek(24, SeekOrigin.Begin); + int assetOffset = reader.ReadInt32(); + + if (Encoding.ASCII.GetString(Read(assetOffset, 4)) == "ASET") + { + byte[] iconSectionInfo = Read(assetOffset + 8, 0x10); + + long iconOffset = BitConverter.ToInt64(iconSectionInfo, 0); + long iconSize = BitConverter.ToInt64(iconSectionInfo, 8); + + ulong nacpOffset = reader.ReadUInt64(); + ulong nacpSize = reader.ReadUInt64(); + + // Reads and stores game icon as byte array + applicationIcon = Read(assetOffset + iconOffset, (int)iconSize); + + // Creates memory stream out of byte array which is the NACP + using (MemoryStream stream = new MemoryStream(Read(assetOffset + (int)nacpOffset, (int)nacpSize))) + { + // Creates NACP class from the memory stream + Nacp controlData = new Nacp(stream); + + // Get the title name, title ID, developer name and version number from the NACP + version = controlData.DisplayVersion; + + titleName = controlData.Descriptions[(int)_desiredTitleLanguage].Title; + + if (string.IsNullOrWhiteSpace(titleName)) + { + titleName = controlData.Descriptions.ToList().Find(x => !string.IsNullOrWhiteSpace(x.Title)).Title; + } + + titleId = controlData.PresenceGroupId.ToString("x16"); + + if (string.IsNullOrWhiteSpace(titleId)) + { + titleId = controlData.SaveDataOwnerId.ToString("x16"); + } + + if (string.IsNullOrWhiteSpace(titleId)) + { + titleId = (controlData.AddOnContentBaseId - 0x1000).ToString("x16"); + } + + developer = controlData.Descriptions[(int)_desiredTitleLanguage].Developer; + + if (string.IsNullOrWhiteSpace(developer)) + { + developer = controlData.Descriptions.ToList().Find(x => !string.IsNullOrWhiteSpace(x.Developer)).Developer; + } + } + } + else + { + applicationIcon = _nroIcon; + } + } + // If its an NCA or NSO we just set defaults + else if ((Path.GetExtension(applicationPath) == ".nca") || (Path.GetExtension(applicationPath) == ".nso")) + { + applicationIcon = Path.GetExtension(applicationPath) == ".nca" ? _ncaIcon : _nsoIcon; + titleName = Path.GetFileNameWithoutExtension(applicationPath); + } + } + + ApplicationMetadata appMetadata = LoadAndSaveMetaData(titleId); + + if (ulong.TryParse(titleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdNum)) + { + SaveDataFilter filter = new SaveDataFilter(); + filter.SetUserId(new UserId(1, 0)); + filter.SetTitleId(new TitleId(titleIdNum)); + + Result result = fsClient.FindSaveDataWithFilter(out SaveDataInfo saveDataInfo, SaveDataSpaceId.User, ref filter); + + if (result.IsSuccess()) + { + saveDataPath = Path.Combine(vfs.GetNandPath(), $"user/save/{saveDataInfo.SaveDataId:x16}"); + } + } + + ApplicationData data = new ApplicationData() + { + Favorite = appMetadata.Favorite, + Icon = applicationIcon, + TitleName = titleName, + TitleId = titleId, + Developer = developer, + Version = version, + TimePlayed = ConvertSecondsToReadableString(appMetadata.TimePlayed), + LastPlayed = appMetadata.LastPlayed, + FileExtension = Path.GetExtension(applicationPath).ToUpper().Remove(0 ,1), + FileSize = (fileSize < 1) ? (fileSize * 1024).ToString("0.##") + "MB" : fileSize.ToString("0.##") + "GB", + Path = applicationPath, + SaveDataPath = saveDataPath + }; + + numApplicationsLoaded++; + + OnApplicationAdded(new ApplicationAddedEventArgs() + { + AppData = data, + NumAppsFound = numApplicationsFound, + NumAppsLoaded = numApplicationsLoaded + }); + } + } + + protected static void OnApplicationAdded(ApplicationAddedEventArgs e) + { + ApplicationAdded?.Invoke(null, e); + } + + private static byte[] GetResourceBytes(string resourceName) + { + Stream resourceStream = Assembly.GetCallingAssembly().GetManifestResourceStream(resourceName); + byte[] resourceByteArray = new byte[resourceStream.Length]; + + resourceStream.Read(resourceByteArray); + + return resourceByteArray; + } + + private static IFileSystem GetControlFs(PartitionFileSystem pfs) + { + Nca controlNca = null; + + // Add keys to key set if needed + foreach (DirectoryEntryEx ticketEntry in pfs.EnumerateEntries("/", "*.tik")) + { + Result result = pfs.OpenFile(out IFile ticketFile, ticketEntry.FullPath, OpenMode.Read); + + if (result.IsSuccess()) + { + Ticket ticket = new Ticket(ticketFile.AsStream()); + + _keySet.ExternalKeySet.Add(new RightsId(ticket.RightsId), new AccessKey(ticket.GetTitleKey(_keySet))); + } + } + + // Find the Control NCA and store it in variable called controlNca + foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca")) + { + pfs.OpenFile(out IFile ncaFile, fileEntry.FullPath, OpenMode.Read).ThrowIfFailure(); + + Nca nca = new Nca(_keySet, ncaFile.AsStorage()); + + if (nca.Header.ContentType == NcaContentType.Control) + { + controlNca = nca; + } + } + + // Return the ControlFS + return controlNca?.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None); + } + + internal static ApplicationMetadata LoadAndSaveMetaData(string titleId, Action modifyFunction = null) + { + string metadataFolder = Path.Combine(new VirtualFileSystem().GetBasePath(), "games", titleId, "gui"); + string metadataFile = Path.Combine(metadataFolder, "metadata.json"); + + IJsonFormatterResolver resolver = CompositeResolver.Create(new[] { StandardResolver.AllowPrivateSnakeCase }); + + ApplicationMetadata appMetadata; + + if (!File.Exists(metadataFile)) + { + Directory.CreateDirectory(metadataFolder); + + appMetadata = new ApplicationMetadata + { + Favorite = false, + TimePlayed = 0, + LastPlayed = "Never" + }; + + byte[] data = JsonSerializer.Serialize(appMetadata, resolver); + File.WriteAllText(metadataFile, Encoding.UTF8.GetString(data, 0, data.Length).PrettyPrintJson()); + } + + using (Stream stream = File.OpenRead(metadataFile)) + { + appMetadata = JsonSerializer.Deserialize(stream, resolver); + } + + if (modifyFunction != null) + { + modifyFunction(appMetadata); + + byte[] saveData = JsonSerializer.Serialize(appMetadata, resolver); + File.WriteAllText(metadataFile, Encoding.UTF8.GetString(saveData, 0, saveData.Length).PrettyPrintJson()); + } + + return appMetadata; + } + + private static string ConvertSecondsToReadableString(double seconds) + { + const int secondsPerMinute = 60; + const int secondsPerHour = secondsPerMinute * 60; + const int secondsPerDay = secondsPerHour * 24; + + string readableString; + + if (seconds < secondsPerMinute) + { + readableString = $"{seconds}s"; + } + else if (seconds < secondsPerHour) + { + readableString = $"{Math.Round(seconds / secondsPerMinute, 2, MidpointRounding.AwayFromZero)} mins"; + } + else if (seconds < secondsPerDay) + { + readableString = $"{Math.Round(seconds / secondsPerHour, 2, MidpointRounding.AwayFromZero)} hrs"; + } + else + { + readableString = $"{Math.Round(seconds / secondsPerDay, 2, MidpointRounding.AwayFromZero)} days"; + } + + return readableString; + } + } +} diff --git a/Ryujinx/Ui/ApplicationMetadata.cs b/Ryujinx/Ui/ApplicationMetadata.cs new file mode 100644 index 0000000000..cdedf91b80 --- /dev/null +++ b/Ryujinx/Ui/ApplicationMetadata.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Ui +{ + internal class ApplicationMetadata + { + public bool Favorite { get; set; } + public double TimePlayed { get; set; } + public string LastPlayed { get; set; } + } +} diff --git a/Ryujinx/Ui/ConsoleLog.cs b/Ryujinx/Ui/ConsoleLog.cs deleted file mode 100644 index 1a2899946b..0000000000 --- a/Ryujinx/Ui/ConsoleLog.cs +++ /dev/null @@ -1,51 +0,0 @@ -using Ryujinx.HLE.Logging; -using System; -using System.Collections.Generic; -using System.Threading; - -namespace Ryujinx -{ - static class ConsoleLog - { - private static Dictionary LogColors; - - private static object ConsoleLock; - - static ConsoleLog() - { - LogColors = new Dictionary() - { - { LogLevel.Stub, ConsoleColor.DarkGray }, - { LogLevel.Info, ConsoleColor.White }, - { LogLevel.Warning, ConsoleColor.Yellow }, - { LogLevel.Error, ConsoleColor.Red } - }; - - ConsoleLock = new object(); - } - - public static void PrintLog(object sender, LogEventArgs e) - { - string FormattedTime = e.Time.ToString(@"hh\:mm\:ss\.fff"); - - string CurrentThread = Thread.CurrentThread.ManagedThreadId.ToString("d4"); - - string Message = FormattedTime + " | " + CurrentThread + " " + e.Message; - - if (LogColors.TryGetValue(e.Level, out ConsoleColor Color)) - { - lock (ConsoleLock) - { - Console.ForegroundColor = Color; - - Console.WriteLine(Message); - Console.ResetColor(); - } - } - else - { - Console.WriteLine(Message); - } - } - } -} \ No newline at end of file diff --git a/Ryujinx/Ui/GLScreen.cs b/Ryujinx/Ui/GLScreen.cs index 7a4e42e9e2..d32ddb5ca5 100644 --- a/Ryujinx/Ui/GLScreen.cs +++ b/Ryujinx/Ui/GLScreen.cs @@ -1,333 +1,390 @@ using OpenTK; using OpenTK.Graphics; using OpenTK.Input; -using Ryujinx.Graphics.Gal; +using Ryujinx.Configuration; +using Ryujinx.Graphics.OpenGL; using Ryujinx.HLE; using Ryujinx.HLE.Input; +using Ryujinx.Profiler.UI; +using Ryujinx.Ui; using System; +using System.Threading; -namespace Ryujinx +using Stopwatch = System.Diagnostics.Stopwatch; + +namespace Ryujinx.Ui { - public class GLScreen : GameWindow + public class GlScreen : GameWindow { private const int TouchScreenWidth = 1280; private const int TouchScreenHeight = 720; - private const float TouchScreenRatioX = (float)TouchScreenWidth / TouchScreenHeight; - private const float TouchScreenRatioY = (float)TouchScreenHeight / TouchScreenWidth; + private const int TargetFps = 60; - private Switch Ns; + private Switch _device; - private IGalRenderer Renderer; + private Renderer _renderer; - private KeyboardState? Keyboard = null; + private HotkeyButtons _prevHotkeyButtons = 0; - private MouseState? Mouse = null; + private KeyboardState? _keyboard = null; - public GLScreen(Switch Ns, IGalRenderer Renderer) + private MouseState? _mouse = null; + + private Input.NpadController _primaryController; + + private Thread _renderThread; + + private bool _resizeEvent; + + private bool _titleEvent; + + private string _newTitle; + +#if USE_PROFILING + private ProfileWindowManager _profileWindow; +#endif + + public GlScreen(Switch device, Renderer renderer) : base(1280, 720, new GraphicsMode(), "Ryujinx", 0, DisplayDevice.Default, 3, 3, GraphicsContextFlags.ForwardCompatible) { - this.Ns = Ns; - this.Renderer = Renderer; + _device = device; + _renderer = renderer; + + _primaryController = new Input.NpadController(ConfigurationState.Instance.Hid.JoystickControls); Location = new Point( (DisplayDevice.Default.Width / 2) - (Width / 2), (DisplayDevice.Default.Height / 2) - (Height / 2)); + +#if USE_PROFILING + // Start profile window, it will handle itself from there + _profileWindow = new ProfileWindowManager(); +#endif } - protected override void OnLoad(EventArgs e) + private void RenderLoop() { - VSync = VSyncMode.On; + MakeCurrent(); - Renderer.FrameBuffer.SetWindowSize(Width, Height); - } - - private bool IsGamePadButtonPressedFromString(GamePadState GamePad, string Button) - { - if (Button.ToUpper() == "LTRIGGER" || Button.ToUpper() == "RTRIGGER") + _renderer.Initialize(); + + Stopwatch chrono = new Stopwatch(); + + chrono.Start(); + + long ticksPerFrame = Stopwatch.Frequency / TargetFps; + + long ticks = 0; + + while (Exists && !IsExiting) { - return GetGamePadTriggerFromString(GamePad, Button) >= Config.GamePadTriggerThreshold; - } - else - { - return (GetGamePadButtonFromString(GamePad, Button) == ButtonState.Pressed); - } - } - - private ButtonState GetGamePadButtonFromString(GamePadState GamePad, string Button) - { - switch (Button.ToUpper()) - { - case "A": return GamePad.Buttons.A; - case "B": return GamePad.Buttons.B; - case "X": return GamePad.Buttons.X; - case "Y": return GamePad.Buttons.Y; - case "LSTICK": return GamePad.Buttons.LeftStick; - case "RSTICK": return GamePad.Buttons.RightStick; - case "LSHOULDER": return GamePad.Buttons.LeftShoulder; - case "RSHOULDER": return GamePad.Buttons.RightShoulder; - case "DPADUP": return GamePad.DPad.Up; - case "DPADDOWN": return GamePad.DPad.Down; - case "DPADLEFT": return GamePad.DPad.Left; - case "DPADRIGHT": return GamePad.DPad.Right; - case "START": return GamePad.Buttons.Start; - case "BACK": return GamePad.Buttons.Back; - default: throw new ArgumentException(); - } - } - - private float GetGamePadTriggerFromString(GamePadState GamePad, string Trigger) - { - switch (Trigger.ToUpper()) - { - case "LTRIGGER": return GamePad.Triggers.Left; - case "RTRIGGER": return GamePad.Triggers.Right; - default: throw new ArgumentException(); - } - } - - private Vector2 GetJoystickAxisFromString(GamePadState GamePad, string Joystick) - { - switch (Joystick.ToUpper()) - { - case "LJOYSTICK": return GamePad.ThumbSticks.Left; - case "RJOYSTICK": return new Vector2(-GamePad.ThumbSticks.Right.Y, -GamePad.ThumbSticks.Right.X); - default: throw new ArgumentException(); - } - } - - protected override void OnUpdateFrame(FrameEventArgs e) - { - HidControllerButtons CurrentButton = 0; - HidJoystickPosition LeftJoystick; - HidJoystickPosition RightJoystick; - - int LeftJoystickDX = 0; - int LeftJoystickDY = 0; - int RightJoystickDX = 0; - int RightJoystickDY = 0; - float AnalogStickDeadzone = Config.GamePadDeadzone; - - //Keyboard Input - if (Keyboard.HasValue) - { - KeyboardState Keyboard = this.Keyboard.Value; - - if (Keyboard[Key.Escape]) this.Exit(); - - //LeftJoystick - if (Keyboard[(Key)Config.JoyConKeyboard.Left.StickUp]) LeftJoystickDY = short.MaxValue; - if (Keyboard[(Key)Config.JoyConKeyboard.Left.StickDown]) LeftJoystickDY = -short.MaxValue; - if (Keyboard[(Key)Config.JoyConKeyboard.Left.StickLeft]) LeftJoystickDX = -short.MaxValue; - if (Keyboard[(Key)Config.JoyConKeyboard.Left.StickRight]) LeftJoystickDX = short.MaxValue; - - //LeftButtons - if (Keyboard[(Key)Config.JoyConKeyboard.Left.StickButton]) CurrentButton |= HidControllerButtons.KEY_LSTICK; - if (Keyboard[(Key)Config.JoyConKeyboard.Left.DPadUp]) CurrentButton |= HidControllerButtons.KEY_DUP; - if (Keyboard[(Key)Config.JoyConKeyboard.Left.DPadDown]) CurrentButton |= HidControllerButtons.KEY_DDOWN; - if (Keyboard[(Key)Config.JoyConKeyboard.Left.DPadLeft]) CurrentButton |= HidControllerButtons.KEY_DLEFT; - if (Keyboard[(Key)Config.JoyConKeyboard.Left.DPadRight]) CurrentButton |= HidControllerButtons.KEY_DRIGHT; - if (Keyboard[(Key)Config.JoyConKeyboard.Left.ButtonMinus]) CurrentButton |= HidControllerButtons.KEY_MINUS; - if (Keyboard[(Key)Config.JoyConKeyboard.Left.ButtonL]) CurrentButton |= HidControllerButtons.KEY_L; - if (Keyboard[(Key)Config.JoyConKeyboard.Left.ButtonZL]) CurrentButton |= HidControllerButtons.KEY_ZL; - - //RightJoystick - if (Keyboard[(Key)Config.JoyConKeyboard.Right.StickUp]) RightJoystickDY = short.MaxValue; - if (Keyboard[(Key)Config.JoyConKeyboard.Right.StickDown]) RightJoystickDY = -short.MaxValue; - if (Keyboard[(Key)Config.JoyConKeyboard.Right.StickLeft]) RightJoystickDX = -short.MaxValue; - if (Keyboard[(Key)Config.JoyConKeyboard.Right.StickRight]) RightJoystickDX = short.MaxValue; - - //RightButtons - if (Keyboard[(Key)Config.JoyConKeyboard.Right.StickButton]) CurrentButton |= HidControllerButtons.KEY_RSTICK; - if (Keyboard[(Key)Config.JoyConKeyboard.Right.ButtonA]) CurrentButton |= HidControllerButtons.KEY_A; - if (Keyboard[(Key)Config.JoyConKeyboard.Right.ButtonB]) CurrentButton |= HidControllerButtons.KEY_B; - if (Keyboard[(Key)Config.JoyConKeyboard.Right.ButtonX]) CurrentButton |= HidControllerButtons.KEY_X; - if (Keyboard[(Key)Config.JoyConKeyboard.Right.ButtonY]) CurrentButton |= HidControllerButtons.KEY_Y; - if (Keyboard[(Key)Config.JoyConKeyboard.Right.ButtonPlus]) CurrentButton |= HidControllerButtons.KEY_PLUS; - if (Keyboard[(Key)Config.JoyConKeyboard.Right.ButtonR]) CurrentButton |= HidControllerButtons.KEY_R; - if (Keyboard[(Key)Config.JoyConKeyboard.Right.ButtonZR]) CurrentButton |= HidControllerButtons.KEY_ZR; - } - - //Controller Input - if (Config.GamePadEnable) - { - GamePadState GamePad = OpenTK.Input.GamePad.GetState(Config.GamePadIndex); - //LeftButtons - if (IsGamePadButtonPressedFromString(GamePad, Config.JoyConController.Left.DPadUp)) CurrentButton |= HidControllerButtons.KEY_DUP; - if (IsGamePadButtonPressedFromString(GamePad, Config.JoyConController.Left.DPadDown)) CurrentButton |= HidControllerButtons.KEY_DDOWN; - if (IsGamePadButtonPressedFromString(GamePad, Config.JoyConController.Left.DPadLeft)) CurrentButton |= HidControllerButtons.KEY_DLEFT; - if (IsGamePadButtonPressedFromString(GamePad, Config.JoyConController.Left.DPadRight)) CurrentButton |= HidControllerButtons.KEY_DRIGHT; - if (IsGamePadButtonPressedFromString(GamePad, Config.JoyConController.Left.StickButton)) CurrentButton |= HidControllerButtons.KEY_LSTICK; - if (IsGamePadButtonPressedFromString(GamePad, Config.JoyConController.Left.ButtonMinus)) CurrentButton |= HidControllerButtons.KEY_MINUS; - if (IsGamePadButtonPressedFromString(GamePad, Config.JoyConController.Left.ButtonL)) CurrentButton |= HidControllerButtons.KEY_L; - if (IsGamePadButtonPressedFromString(GamePad, Config.JoyConController.Left.ButtonZL)) CurrentButton |= HidControllerButtons.KEY_ZL; - - //RightButtons - if (IsGamePadButtonPressedFromString(GamePad, Config.JoyConController.Right.ButtonA)) CurrentButton |= HidControllerButtons.KEY_A; - if (IsGamePadButtonPressedFromString(GamePad, Config.JoyConController.Right.ButtonB)) CurrentButton |= HidControllerButtons.KEY_B; - if (IsGamePadButtonPressedFromString(GamePad, Config.JoyConController.Right.ButtonX)) CurrentButton |= HidControllerButtons.KEY_X; - if (IsGamePadButtonPressedFromString(GamePad, Config.JoyConController.Right.ButtonY)) CurrentButton |= HidControllerButtons.KEY_Y; - if (IsGamePadButtonPressedFromString(GamePad, Config.JoyConController.Right.StickButton)) CurrentButton |= HidControllerButtons.KEY_RSTICK; - if (IsGamePadButtonPressedFromString(GamePad, Config.JoyConController.Right.ButtonPlus)) CurrentButton |= HidControllerButtons.KEY_PLUS; - if (IsGamePadButtonPressedFromString(GamePad, Config.JoyConController.Right.ButtonR)) CurrentButton |= HidControllerButtons.KEY_R; - if (IsGamePadButtonPressedFromString(GamePad, Config.JoyConController.Right.ButtonZR)) CurrentButton |= HidControllerButtons.KEY_ZR; - - //LeftJoystick - if (GetJoystickAxisFromString(GamePad, Config.JoyConController.Left.Stick).X >= AnalogStickDeadzone - || GetJoystickAxisFromString(GamePad, Config.JoyConController.Left.Stick).X <= -AnalogStickDeadzone) - LeftJoystickDX = (int)(GetJoystickAxisFromString(GamePad, Config.JoyConController.Left.Stick).X * short.MaxValue); - - if (GetJoystickAxisFromString(GamePad, Config.JoyConController.Left.Stick).Y >= AnalogStickDeadzone - || GetJoystickAxisFromString(GamePad, Config.JoyConController.Left.Stick).Y <= -AnalogStickDeadzone) - LeftJoystickDY = (int)(GetJoystickAxisFromString(GamePad, Config.JoyConController.Left.Stick).Y * short.MaxValue); - - //RightJoystick - if (GetJoystickAxisFromString(GamePad, Config.JoyConController.Right.Stick).X >= AnalogStickDeadzone - || GetJoystickAxisFromString(GamePad, Config.JoyConController.Right.Stick).X <= -AnalogStickDeadzone) - RightJoystickDX = (int)(GetJoystickAxisFromString(GamePad, Config.JoyConController.Right.Stick).X * short.MaxValue); - - if (GetJoystickAxisFromString(GamePad, Config.JoyConController.Right.Stick).Y >= AnalogStickDeadzone - || GetJoystickAxisFromString(GamePad, Config.JoyConController.Right.Stick).Y <= -AnalogStickDeadzone) - RightJoystickDY = (int)(GetJoystickAxisFromString(GamePad, Config.JoyConController.Right.Stick).Y * short.MaxValue); - } - - LeftJoystick = new HidJoystickPosition - { - DX = LeftJoystickDX, - DY = LeftJoystickDY - }; - - RightJoystick = new HidJoystickPosition - { - DX = RightJoystickDX, - DY = RightJoystickDY - }; - - bool HasTouch = false; - - //Get screen touch position from left mouse click - //OpenTK always captures mouse events, even if out of focus, so check if window is focused. - if (Focused && Mouse?.LeftButton == ButtonState.Pressed) - { - MouseState Mouse = this.Mouse.Value; - - int ScrnWidth = Width; - int ScrnHeight = Height; - - if (Width > Height * TouchScreenRatioX) + if (_device.WaitFifo()) { - ScrnWidth = (int)(Height * TouchScreenRatioX); + _device.ProcessFrame(); + } + + if (_resizeEvent) + { + _resizeEvent = false; + + _renderer.Window.SetSize(Width, Height); + } + + ticks += chrono.ElapsedTicks; + + chrono.Restart(); + + if (ticks >= ticksPerFrame) + { + RenderFrame(); + + // Queue max. 1 vsync + ticks = Math.Min(ticks - ticksPerFrame, ticksPerFrame); + } + } + + _device.DisposeGpu(); + _renderer.Dispose(); + } + + public void MainLoop() + { + VSync = VSyncMode.Off; + + Visible = true; + + Context.MakeCurrent(null); + + // OpenTK doesn't like sleeps in its thread, to avoid this a renderer thread is created + _renderThread = new Thread(RenderLoop) + { + Name = "GUI.RenderThread" + }; + + _renderThread.Start(); + + while (Exists && !IsExiting) + { + ProcessEvents(); + + if (!IsExiting) + { + UpdateFrame(); + + if (_titleEvent) + { + _titleEvent = false; + + Title = _newTitle; + } + } + + // Polling becomes expensive if it's not slept + Thread.Sleep(1); + } + } + + private new void UpdateFrame() + { + HotkeyButtons currentHotkeyButtons = 0; + ControllerButtons currentButton = 0; + JoystickPosition leftJoystick; + JoystickPosition rightJoystick; + HLE.Input.Keyboard? hidKeyboard = null; + + int leftJoystickDx = 0; + int leftJoystickDy = 0; + int rightJoystickDx = 0; + int rightJoystickDy = 0; + + // Keyboard Input + if (_keyboard.HasValue) + { + KeyboardState keyboard = _keyboard.Value; + +#if USE_PROFILING + // Profiler input, lets the profiler get access to the main windows keyboard state + _profileWindow.UpdateKeyInput(keyboard); +#endif + + // Normal Input + currentHotkeyButtons = KeyboardControls.GetHotkeyButtons(ConfigurationState.Instance.Hid.KeyboardControls, keyboard); + currentButton = KeyboardControls.GetButtons(ConfigurationState.Instance.Hid.KeyboardControls, keyboard); + + if (ConfigurationState.Instance.Hid.EnableKeyboard) + { + hidKeyboard = KeyboardControls.GetKeysDown(ConfigurationState.Instance.Hid.KeyboardControls, keyboard); + } + + (leftJoystickDx, leftJoystickDy) = KeyboardControls.GetLeftStick(ConfigurationState.Instance.Hid.KeyboardControls, keyboard); + (rightJoystickDx, rightJoystickDy) = KeyboardControls.GetRightStick(ConfigurationState.Instance.Hid.KeyboardControls, keyboard); + } + + if (!hidKeyboard.HasValue) + { + hidKeyboard = new HLE.Input.Keyboard + { + Modifier = 0, + Keys = new int[0x8] + }; + } + + currentButton |= _primaryController.GetButtons(); + + // Keyboard has priority stick-wise + if (leftJoystickDx == 0 && leftJoystickDy == 0) + { + (leftJoystickDx, leftJoystickDy) = _primaryController.GetLeftStick(); + } + + if (rightJoystickDx == 0 && rightJoystickDy == 0) + { + (rightJoystickDx, rightJoystickDy) = _primaryController.GetRightStick(); + } + + leftJoystick = new JoystickPosition + { + Dx = leftJoystickDx, + Dy = leftJoystickDy + }; + + rightJoystick = new JoystickPosition + { + Dx = rightJoystickDx, + Dy = rightJoystickDy + }; + + currentButton |= _device.Hid.UpdateStickButtons(leftJoystick, rightJoystick); + + bool hasTouch = false; + + // Get screen touch position from left mouse click + // OpenTK always captures mouse events, even if out of focus, so check if window is focused. + if (Focused && _mouse?.LeftButton == ButtonState.Pressed) + { + MouseState mouse = _mouse.Value; + + int scrnWidth = Width; + int scrnHeight = Height; + + if (Width > (Height * TouchScreenWidth) / TouchScreenHeight) + { + scrnWidth = (Height * TouchScreenWidth) / TouchScreenHeight; } else { - ScrnHeight = (int)(Width * TouchScreenRatioY); + scrnHeight = (Width * TouchScreenHeight) / TouchScreenWidth; } - int StartX = (Width - ScrnWidth) >> 1; - int StartY = (Height - ScrnHeight) >> 1; + int startX = (Width - scrnWidth) >> 1; + int startY = (Height - scrnHeight) >> 1; - int EndX = StartX + ScrnWidth; - int EndY = StartY + ScrnHeight; + int endX = startX + scrnWidth; + int endY = startY + scrnHeight; - if (Mouse.X >= StartX && - Mouse.Y >= StartY && - Mouse.X < EndX && - Mouse.Y < EndY) + if (mouse.X >= startX && + mouse.Y >= startY && + mouse.X < endX && + mouse.Y < endY) { - int ScrnMouseX = Mouse.X - StartX; - int ScrnMouseY = Mouse.Y - StartY; + int scrnMouseX = mouse.X - startX; + int scrnMouseY = mouse.Y - startY; - int MX = (int)(((float)ScrnMouseX / ScrnWidth) * TouchScreenWidth); - int MY = (int)(((float)ScrnMouseY / ScrnHeight) * TouchScreenHeight); + int mX = (scrnMouseX * TouchScreenWidth) / scrnWidth; + int mY = (scrnMouseY * TouchScreenHeight) / scrnHeight; - HidTouchPoint CurrentPoint = new HidTouchPoint + TouchPoint currentPoint = new TouchPoint { - X = MX, - Y = MY, + X = mX, + Y = mY, - //Placeholder values till more data is acquired + // Placeholder values till more data is acquired DiameterX = 10, DiameterY = 10, Angle = 90 }; - HasTouch = true; + hasTouch = true; - Ns.Hid.SetTouchPoints(CurrentPoint); + _device.Hid.SetTouchPoints(currentPoint); } } - if (!HasTouch) + if (!hasTouch) { - Ns.Hid.SetTouchPoints(); + _device.Hid.SetTouchPoints(); } - Ns.Hid.SetJoyconButton( - HidControllerId.CONTROLLER_HANDHELD, - HidControllerLayouts.Handheld_Joined, - CurrentButton, - LeftJoystick, - RightJoystick); + if (ConfigurationState.Instance.Hid.EnableKeyboard && hidKeyboard.HasValue) + { + _device.Hid.WriteKeyboard(hidKeyboard.Value); + } - Ns.Hid.SetJoyconButton( - HidControllerId.CONTROLLER_HANDHELD, - HidControllerLayouts.Main, - CurrentButton, - LeftJoystick, - RightJoystick); + BaseController controller = _device.Hid.PrimaryController; - Ns.ProcessFrame(); + controller.SendInput(currentButton, leftJoystick, rightJoystick); - Renderer.RunActions(); + // Toggle vsync + if (currentHotkeyButtons.HasFlag(HotkeyButtons.ToggleVSync) && + !_prevHotkeyButtons.HasFlag(HotkeyButtons.ToggleVSync)) + { + _device.EnableDeviceVsync = !_device.EnableDeviceVsync; + } + + _prevHotkeyButtons = currentHotkeyButtons; } - protected override void OnRenderFrame(FrameEventArgs e) + private new void RenderFrame() { - Renderer.FrameBuffer.Render(); + _device.PresentFrame(SwapBuffers); - Ns.Statistics.RecordSystemFrameTime(); + _device.Statistics.RecordSystemFrameTime(); - double HostFps = Ns.Statistics.GetSystemFrameRate(); - double GameFps = Ns.Statistics.GetGameFrameRate(); + double hostFps = _device.Statistics.GetSystemFrameRate(); + double gameFps = _device.Statistics.GetGameFrameRate(); - Title = $"Ryujinx | Host FPS: {HostFps:0.0} | Game FPS: {GameFps:0.0}"; + string titleNameSection = string.IsNullOrWhiteSpace(_device.System.TitleName) ? string.Empty + : " | " + _device.System.TitleName; - SwapBuffers(); + string titleIdSection = string.IsNullOrWhiteSpace(_device.System.TitleIdText) ? string.Empty + : " | " + _device.System.TitleIdText.ToUpper(); - Ns.Os.SignalVsync(); + _newTitle = $"Ryujinx{titleNameSection}{titleIdSection} | Host FPS: {hostFps:0.0} | Game FPS: {gameFps:0.0} | " + + $"Game Vsync: {(_device.EnableDeviceVsync ? "On" : "Off")}"; + + _titleEvent = true; + + _device.System.SignalVsync(); + + _device.VsyncEvent.Set(); + } + + protected override void OnUnload(EventArgs e) + { +#if USE_PROFILING + _profileWindow.Close(); +#endif + + _renderThread.Join(); + + base.OnUnload(e); } protected override void OnResize(EventArgs e) { - Renderer.FrameBuffer.SetWindowSize(Width, Height); + _resizeEvent = true; } protected override void OnKeyDown(KeyboardKeyEventArgs e) { - Keyboard = e.Keyboard; + bool toggleFullscreen = e.Key == Key.F11 || + (e.Modifiers.HasFlag(KeyModifiers.Alt) && e.Key == Key.Enter); + + if (WindowState == WindowState.Fullscreen) + { + if (e.Key == Key.Escape || toggleFullscreen) + { + WindowState = WindowState.Normal; + } + } + else + { + if (e.Key == Key.Escape) + { + Exit(); + } + + if (toggleFullscreen) + { + WindowState = WindowState.Fullscreen; + } + } + + _keyboard = e.Keyboard; } protected override void OnKeyUp(KeyboardKeyEventArgs e) { - Keyboard = e.Keyboard; + _keyboard = e.Keyboard; } protected override void OnMouseDown(MouseButtonEventArgs e) { - Mouse = e.Mouse; + _mouse = e.Mouse; } protected override void OnMouseUp(MouseButtonEventArgs e) { - Mouse = e.Mouse; + _mouse = e.Mouse; } protected override void OnMouseMove(MouseMoveEventArgs e) { - Mouse = e.Mouse; + _mouse = e.Mouse; } } -} \ No newline at end of file +} diff --git a/Ryujinx/Ui/GameTableContextMenu.cs b/Ryujinx/Ui/GameTableContextMenu.cs new file mode 100644 index 0000000000..e74d182854 --- /dev/null +++ b/Ryujinx/Ui/GameTableContextMenu.cs @@ -0,0 +1,152 @@ +using Gtk; +using LibHac; +using LibHac.Fs; +using LibHac.Fs.Shim; +using LibHac.Ncm; +using Ryujinx.HLE.FileSystem; +using System; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Reflection; + +using GUI = Gtk.Builder.ObjectAttribute; + +namespace Ryujinx.Ui +{ + public class GameTableContextMenu : Menu + { + private static ListStore _gameTableStore; + private static TreeIter _rowIter; + private FileSystemClient _fsClient; + +#pragma warning disable CS0649 +#pragma warning disable IDE0044 + [GUI] MenuItem _openSaveDir; +#pragma warning restore CS0649 +#pragma warning restore IDE0044 + + public GameTableContextMenu(ListStore gameTableStore, TreeIter rowIter, FileSystemClient fsClient) + : this(new Builder("Ryujinx.Ui.GameTableContextMenu.glade"), gameTableStore, rowIter, fsClient) { } + + private GameTableContextMenu(Builder builder, ListStore gameTableStore, TreeIter rowIter, FileSystemClient fsClient) : base(builder.GetObject("_contextMenu").Handle) + { + builder.Autoconnect(this); + + _openSaveDir.Activated += OpenSaveDir_Clicked; + + _gameTableStore = gameTableStore; + _rowIter = rowIter; + _fsClient = fsClient; + } + + //Events + private void OpenSaveDir_Clicked(object sender, EventArgs args) + { + string titleName = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[0]; + string titleId = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[1].ToLower(); + + if (!TryFindSaveData(titleName, titleId, out ulong saveDataId)) + { + return; + } + + string saveDir = GetSaveDataDirectory(saveDataId); + + Process.Start(new ProcessStartInfo() + { + FileName = saveDir, + UseShellExecute = true, + Verb = "open" + }); + } + + private bool TryFindSaveData(string titleName, string titleIdText, out ulong saveDataId) + { + saveDataId = default; + + if (!ulong.TryParse(titleIdText, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleId)) + { + GtkDialog.CreateErrorDialog("UI error: The selected game did not have a valid title ID"); + + return false; + } + + SaveDataFilter filter = new SaveDataFilter(); + filter.SetUserId(new UserId(1, 0)); + filter.SetTitleId(new TitleId(titleId)); + + Result result = _fsClient.FindSaveDataWithFilter(out SaveDataInfo saveDataInfo, SaveDataSpaceId.User, ref filter); + + if (result == ResultFs.TargetNotFound) + { + // Savedata was not found. Ask the user if they want to create it + using MessageDialog messageDialog = new MessageDialog(null, DialogFlags.Modal, MessageType.Question, ButtonsType.YesNo, null) + { + Title = "Ryujinx", + Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png"), + Text = $"There is no savedata for {titleName} [{titleId:x16}]", + SecondaryText = "Would you like to create savedata for this game?", + WindowPosition = WindowPosition.Center + }; + + if (messageDialog.Run() != (int)ResponseType.Yes) + { + return false; + } + + result = _fsClient.CreateSaveData(new TitleId(titleId), new UserId(1, 0), new TitleId(titleId), 0, 0, 0); + + if (result.IsFailure()) + { + GtkDialog.CreateErrorDialog($"There was an error creating the specified savedata: {result.ToStringWithName()}"); + + return false; + } + + // Try to find the savedata again after creating it + result = _fsClient.FindSaveDataWithFilter(out saveDataInfo, SaveDataSpaceId.User, ref filter); + } + + if (result.IsSuccess()) + { + saveDataId = saveDataInfo.SaveDataId; + + return true; + } + + GtkDialog.CreateErrorDialog($"There was an error finding the specified savedata: {result.ToStringWithName()}"); + + return false; + } + + private string GetSaveDataDirectory(ulong saveDataId) + { + string saveRootPath = System.IO.Path.Combine(new VirtualFileSystem().GetNandPath(), $"user/save/{saveDataId:x16}"); + + if (!Directory.Exists(saveRootPath)) + { + // Inconsistent state. Create the directory + Directory.CreateDirectory(saveRootPath); + } + + string committedPath = System.IO.Path.Combine(saveRootPath, "0"); + string workingPath = System.IO.Path.Combine(saveRootPath, "1"); + + // If the committed directory exists, that path will be loaded the next time the savedata is mounted + if (Directory.Exists(committedPath)) + { + return committedPath; + } + + // If the working directory exists and the committed directory doesn't, + // the working directory will be loaded the next time the savedata is mounted + if (!Directory.Exists(workingPath)) + { + Directory.CreateDirectory(workingPath); + } + + return workingPath; + } + } +} diff --git a/Ryujinx/Ui/GameTableContextMenu.glade b/Ryujinx/Ui/GameTableContextMenu.glade new file mode 100644 index 0000000000..2c9e097292 --- /dev/null +++ b/Ryujinx/Ui/GameTableContextMenu.glade @@ -0,0 +1,18 @@ + + + + + + True + False + + + True + False + Open the folder where saves for the application is loaded + Open Save Directory + True + + + + diff --git a/Ryujinx/Ui/GtkDialog.cs b/Ryujinx/Ui/GtkDialog.cs new file mode 100644 index 0000000000..7f6be8dc7a --- /dev/null +++ b/Ryujinx/Ui/GtkDialog.cs @@ -0,0 +1,23 @@ +using Gtk; +using System.Reflection; + +namespace Ryujinx.Ui +{ + internal class GtkDialog + { + internal static void CreateErrorDialog(string errorMessage) + { + MessageDialog errorDialog = new MessageDialog(null, DialogFlags.Modal, MessageType.Error, ButtonsType.Ok, null) + { + Title = "Ryujinx - Error", + Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png"), + Text = "Ryujinx has encountered an error", + SecondaryText = errorMessage, + WindowPosition = WindowPosition.Center + }; + errorDialog.SetSizeRequest(100, 20); + errorDialog.Run(); + errorDialog.Dispose(); + } + } +} diff --git a/Ryujinx/Ui/JoyConController.cs b/Ryujinx/Ui/JoyConController.cs deleted file mode 100644 index e525017d3e..0000000000 --- a/Ryujinx/Ui/JoyConController.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Ryujinx.UI.Input -{ - public struct JoyConControllerLeft - { - public string Stick; - public string StickButton; - public string DPadUp; - public string DPadDown; - public string DPadLeft; - public string DPadRight; - public string ButtonMinus; - public string ButtonL; - public string ButtonZL; - } - - public struct JoyConControllerRight - { - public string Stick; - public string StickButton; - public string ButtonA; - public string ButtonB; - public string ButtonX; - public string ButtonY; - public string ButtonPlus; - public string ButtonR; - public string ButtonZR; - } - - public struct JoyConController - { - public JoyConControllerLeft Left; - public JoyConControllerRight Right; - } -} diff --git a/Ryujinx/Ui/JoyConKeyboard.cs b/Ryujinx/Ui/JoyConKeyboard.cs deleted file mode 100644 index b329d9ecd1..0000000000 --- a/Ryujinx/Ui/JoyConKeyboard.cs +++ /dev/null @@ -1,40 +0,0 @@ -namespace Ryujinx.UI.Input -{ - public struct JoyConKeyboardLeft - { - public int StickUp; - public int StickDown; - public int StickLeft; - public int StickRight; - public int StickButton; - public int DPadUp; - public int DPadDown; - public int DPadLeft; - public int DPadRight; - public int ButtonMinus; - public int ButtonL; - public int ButtonZL; - } - - public struct JoyConKeyboardRight - { - public int StickUp; - public int StickDown; - public int StickLeft; - public int StickRight; - public int StickButton; - public int ButtonA; - public int ButtonB; - public int ButtonX; - public int ButtonY; - public int ButtonPlus; - public int ButtonR; - public int ButtonZR; - } - - public struct JoyConKeyboard - { - public JoyConKeyboardLeft Left; - public JoyConKeyboardRight Right; - } -} diff --git a/Ryujinx/Ui/KeyboardControls.cs b/Ryujinx/Ui/KeyboardControls.cs new file mode 100644 index 0000000000..db9c0cda8c --- /dev/null +++ b/Ryujinx/Ui/KeyboardControls.cs @@ -0,0 +1,244 @@ +using OpenTK.Input; +using Ryujinx.HLE.Input; +using Ryujinx.UI.Input; + +namespace Ryujinx.Ui +{ + public static class KeyboardControls + { + public static ControllerButtons GetButtons(NpadKeyboard npad, KeyboardState keyboard) + { + ControllerButtons buttons = 0; + + if (keyboard[(Key)npad.LeftJoycon.StickButton]) buttons |= ControllerButtons.StickLeft; + if (keyboard[(Key)npad.LeftJoycon.DPadUp]) buttons |= ControllerButtons.DpadUp; + if (keyboard[(Key)npad.LeftJoycon.DPadDown]) buttons |= ControllerButtons.DpadDown; + if (keyboard[(Key)npad.LeftJoycon.DPadLeft]) buttons |= ControllerButtons.DpadLeft; + if (keyboard[(Key)npad.LeftJoycon.DPadRight]) buttons |= ControllerButtons.DPadRight; + if (keyboard[(Key)npad.LeftJoycon.ButtonMinus]) buttons |= ControllerButtons.Minus; + if (keyboard[(Key)npad.LeftJoycon.ButtonL]) buttons |= ControllerButtons.L; + if (keyboard[(Key)npad.LeftJoycon.ButtonZl]) buttons |= ControllerButtons.Zl; + + if (keyboard[(Key)npad.RightJoycon.StickButton]) buttons |= ControllerButtons.StickRight; + if (keyboard[(Key)npad.RightJoycon.ButtonA]) buttons |= ControllerButtons.A; + if (keyboard[(Key)npad.RightJoycon.ButtonB]) buttons |= ControllerButtons.B; + if (keyboard[(Key)npad.RightJoycon.ButtonX]) buttons |= ControllerButtons.X; + if (keyboard[(Key)npad.RightJoycon.ButtonY]) buttons |= ControllerButtons.Y; + if (keyboard[(Key)npad.RightJoycon.ButtonPlus]) buttons |= ControllerButtons.Plus; + if (keyboard[(Key)npad.RightJoycon.ButtonR]) buttons |= ControllerButtons.R; + if (keyboard[(Key)npad.RightJoycon.ButtonZr]) buttons |= ControllerButtons.Zr; + + return buttons; + } + + public static (short, short) GetLeftStick(NpadKeyboard npad, KeyboardState keyboard) + { + short dx = 0; + short dy = 0; + + if (keyboard[(Key)npad.LeftJoycon.StickUp]) dy = short.MaxValue; + if (keyboard[(Key)npad.LeftJoycon.StickDown]) dy = -short.MaxValue; + if (keyboard[(Key)npad.LeftJoycon.StickLeft]) dx = -short.MaxValue; + if (keyboard[(Key)npad.LeftJoycon.StickRight]) dx = short.MaxValue; + + return (dx, dy); + } + + public static (short, short) GetRightStick(NpadKeyboard npad, KeyboardState keyboard) + { + short dx = 0; + short dy = 0; + + if (keyboard[(Key)npad.RightJoycon.StickUp]) dy = short.MaxValue; + if (keyboard[(Key)npad.RightJoycon.StickDown]) dy = -short.MaxValue; + if (keyboard[(Key)npad.RightJoycon.StickLeft]) dx = -short.MaxValue; + if (keyboard[(Key)npad.RightJoycon.StickRight]) dx = short.MaxValue; + + return (dx, dy); + } + + public static HotkeyButtons GetHotkeyButtons(NpadKeyboard npad, KeyboardState keyboard) + { + HotkeyButtons buttons = 0; + + if (keyboard[(Key)npad.Hotkeys.ToggleVsync]) buttons |= HotkeyButtons.ToggleVSync; + + return buttons; + } + + class KeyMappingEntry + { + public Key TargetKey; + public byte Target; + } + + private static readonly KeyMappingEntry[] KeyMapping = new KeyMappingEntry[] + { + new KeyMappingEntry { TargetKey = Key.A, Target = 0x4 }, + new KeyMappingEntry { TargetKey = Key.B, Target = 0x5 }, + new KeyMappingEntry { TargetKey = Key.C, Target = 0x6 }, + new KeyMappingEntry { TargetKey = Key.D, Target = 0x7 }, + new KeyMappingEntry { TargetKey = Key.E, Target = 0x8 }, + new KeyMappingEntry { TargetKey = Key.F, Target = 0x9 }, + new KeyMappingEntry { TargetKey = Key.G, Target = 0xA }, + new KeyMappingEntry { TargetKey = Key.H, Target = 0xB }, + new KeyMappingEntry { TargetKey = Key.I, Target = 0xC }, + new KeyMappingEntry { TargetKey = Key.J, Target = 0xD }, + new KeyMappingEntry { TargetKey = Key.K, Target = 0xE }, + new KeyMappingEntry { TargetKey = Key.L, Target = 0xF }, + new KeyMappingEntry { TargetKey = Key.M, Target = 0x10 }, + new KeyMappingEntry { TargetKey = Key.N, Target = 0x11 }, + new KeyMappingEntry { TargetKey = Key.O, Target = 0x12 }, + new KeyMappingEntry { TargetKey = Key.P, Target = 0x13 }, + new KeyMappingEntry { TargetKey = Key.Q, Target = 0x14 }, + new KeyMappingEntry { TargetKey = Key.R, Target = 0x15 }, + new KeyMappingEntry { TargetKey = Key.S, Target = 0x16 }, + new KeyMappingEntry { TargetKey = Key.T, Target = 0x17 }, + new KeyMappingEntry { TargetKey = Key.U, Target = 0x18 }, + new KeyMappingEntry { TargetKey = Key.V, Target = 0x19 }, + new KeyMappingEntry { TargetKey = Key.W, Target = 0x1A }, + new KeyMappingEntry { TargetKey = Key.X, Target = 0x1B }, + new KeyMappingEntry { TargetKey = Key.Y, Target = 0x1C }, + new KeyMappingEntry { TargetKey = Key.Z, Target = 0x1D }, + + new KeyMappingEntry { TargetKey = Key.Number1, Target = 0x1E }, + new KeyMappingEntry { TargetKey = Key.Number2, Target = 0x1F }, + new KeyMappingEntry { TargetKey = Key.Number3, Target = 0x20 }, + new KeyMappingEntry { TargetKey = Key.Number4, Target = 0x21 }, + new KeyMappingEntry { TargetKey = Key.Number5, Target = 0x22 }, + new KeyMappingEntry { TargetKey = Key.Number6, Target = 0x23 }, + new KeyMappingEntry { TargetKey = Key.Number7, Target = 0x24 }, + new KeyMappingEntry { TargetKey = Key.Number8, Target = 0x25 }, + new KeyMappingEntry { TargetKey = Key.Number9, Target = 0x26 }, + new KeyMappingEntry { TargetKey = Key.Number0, Target = 0x27 }, + + new KeyMappingEntry { TargetKey = Key.Enter, Target = 0x28 }, + new KeyMappingEntry { TargetKey = Key.Escape, Target = 0x29 }, + new KeyMappingEntry { TargetKey = Key.BackSpace, Target = 0x2A }, + new KeyMappingEntry { TargetKey = Key.Tab, Target = 0x2B }, + new KeyMappingEntry { TargetKey = Key.Space, Target = 0x2C }, + new KeyMappingEntry { TargetKey = Key.Minus, Target = 0x2D }, + new KeyMappingEntry { TargetKey = Key.Plus, Target = 0x2E }, + new KeyMappingEntry { TargetKey = Key.BracketLeft, Target = 0x2F }, + new KeyMappingEntry { TargetKey = Key.BracketRight, Target = 0x30 }, + new KeyMappingEntry { TargetKey = Key.BackSlash, Target = 0x31 }, + new KeyMappingEntry { TargetKey = Key.Tilde, Target = 0x32 }, + new KeyMappingEntry { TargetKey = Key.Semicolon, Target = 0x33 }, + new KeyMappingEntry { TargetKey = Key.Quote, Target = 0x34 }, + new KeyMappingEntry { TargetKey = Key.Grave, Target = 0x35 }, + new KeyMappingEntry { TargetKey = Key.Comma, Target = 0x36 }, + new KeyMappingEntry { TargetKey = Key.Period, Target = 0x37 }, + new KeyMappingEntry { TargetKey = Key.Slash, Target = 0x38 }, + new KeyMappingEntry { TargetKey = Key.CapsLock, Target = 0x39 }, + + new KeyMappingEntry { TargetKey = Key.F1, Target = 0x3a }, + new KeyMappingEntry { TargetKey = Key.F2, Target = 0x3b }, + new KeyMappingEntry { TargetKey = Key.F3, Target = 0x3c }, + new KeyMappingEntry { TargetKey = Key.F4, Target = 0x3d }, + new KeyMappingEntry { TargetKey = Key.F5, Target = 0x3e }, + new KeyMappingEntry { TargetKey = Key.F6, Target = 0x3f }, + new KeyMappingEntry { TargetKey = Key.F7, Target = 0x40 }, + new KeyMappingEntry { TargetKey = Key.F8, Target = 0x41 }, + new KeyMappingEntry { TargetKey = Key.F9, Target = 0x42 }, + new KeyMappingEntry { TargetKey = Key.F10, Target = 0x43 }, + new KeyMappingEntry { TargetKey = Key.F11, Target = 0x44 }, + new KeyMappingEntry { TargetKey = Key.F12, Target = 0x45 }, + + new KeyMappingEntry { TargetKey = Key.PrintScreen, Target = 0x46 }, + new KeyMappingEntry { TargetKey = Key.ScrollLock, Target = 0x47 }, + new KeyMappingEntry { TargetKey = Key.Pause, Target = 0x48 }, + new KeyMappingEntry { TargetKey = Key.Insert, Target = 0x49 }, + new KeyMappingEntry { TargetKey = Key.Home, Target = 0x4A }, + new KeyMappingEntry { TargetKey = Key.PageUp, Target = 0x4B }, + new KeyMappingEntry { TargetKey = Key.Delete, Target = 0x4C }, + new KeyMappingEntry { TargetKey = Key.End, Target = 0x4D }, + new KeyMappingEntry { TargetKey = Key.PageDown, Target = 0x4E }, + new KeyMappingEntry { TargetKey = Key.Right, Target = 0x4F }, + new KeyMappingEntry { TargetKey = Key.Left, Target = 0x50 }, + new KeyMappingEntry { TargetKey = Key.Down, Target = 0x51 }, + new KeyMappingEntry { TargetKey = Key.Up, Target = 0x52 }, + + new KeyMappingEntry { TargetKey = Key.NumLock, Target = 0x53 }, + new KeyMappingEntry { TargetKey = Key.KeypadDivide, Target = 0x54 }, + new KeyMappingEntry { TargetKey = Key.KeypadMultiply, Target = 0x55 }, + new KeyMappingEntry { TargetKey = Key.KeypadMinus, Target = 0x56 }, + new KeyMappingEntry { TargetKey = Key.KeypadPlus, Target = 0x57 }, + new KeyMappingEntry { TargetKey = Key.KeypadEnter, Target = 0x58 }, + new KeyMappingEntry { TargetKey = Key.Keypad1, Target = 0x59 }, + new KeyMappingEntry { TargetKey = Key.Keypad2, Target = 0x5A }, + new KeyMappingEntry { TargetKey = Key.Keypad3, Target = 0x5B }, + new KeyMappingEntry { TargetKey = Key.Keypad4, Target = 0x5C }, + new KeyMappingEntry { TargetKey = Key.Keypad5, Target = 0x5D }, + new KeyMappingEntry { TargetKey = Key.Keypad6, Target = 0x5E }, + new KeyMappingEntry { TargetKey = Key.Keypad7, Target = 0x5F }, + new KeyMappingEntry { TargetKey = Key.Keypad8, Target = 0x60 }, + new KeyMappingEntry { TargetKey = Key.Keypad9, Target = 0x61 }, + new KeyMappingEntry { TargetKey = Key.Keypad0, Target = 0x62 }, + new KeyMappingEntry { TargetKey = Key.KeypadPeriod, Target = 0x63 }, + + new KeyMappingEntry { TargetKey = Key.NonUSBackSlash, Target = 0x64 }, + + new KeyMappingEntry { TargetKey = Key.F13, Target = 0x68 }, + new KeyMappingEntry { TargetKey = Key.F14, Target = 0x69 }, + new KeyMappingEntry { TargetKey = Key.F15, Target = 0x6A }, + new KeyMappingEntry { TargetKey = Key.F16, Target = 0x6B }, + new KeyMappingEntry { TargetKey = Key.F17, Target = 0x6C }, + new KeyMappingEntry { TargetKey = Key.F18, Target = 0x6D }, + new KeyMappingEntry { TargetKey = Key.F19, Target = 0x6E }, + new KeyMappingEntry { TargetKey = Key.F20, Target = 0x6F }, + new KeyMappingEntry { TargetKey = Key.F21, Target = 0x70 }, + new KeyMappingEntry { TargetKey = Key.F22, Target = 0x71 }, + new KeyMappingEntry { TargetKey = Key.F23, Target = 0x72 }, + new KeyMappingEntry { TargetKey = Key.F24, Target = 0x73 }, + + new KeyMappingEntry { TargetKey = Key.ControlLeft, Target = 0xE0 }, + new KeyMappingEntry { TargetKey = Key.ShiftLeft, Target = 0xE1 }, + new KeyMappingEntry { TargetKey = Key.AltLeft, Target = 0xE2 }, + new KeyMappingEntry { TargetKey = Key.WinLeft, Target = 0xE3 }, + new KeyMappingEntry { TargetKey = Key.ControlRight, Target = 0xE4 }, + new KeyMappingEntry { TargetKey = Key.ShiftRight, Target = 0xE5 }, + new KeyMappingEntry { TargetKey = Key.AltRight, Target = 0xE6 }, + new KeyMappingEntry { TargetKey = Key.WinRight, Target = 0xE7 }, + }; + + private static readonly KeyMappingEntry[] KeyModifierMapping = new KeyMappingEntry[] + { + new KeyMappingEntry { TargetKey = Key.ControlLeft, Target = 0 }, + new KeyMappingEntry { TargetKey = Key.ShiftLeft, Target = 1 }, + new KeyMappingEntry { TargetKey = Key.AltLeft, Target = 2 }, + new KeyMappingEntry { TargetKey = Key.WinLeft, Target = 3 }, + new KeyMappingEntry { TargetKey = Key.ControlRight, Target = 4 }, + new KeyMappingEntry { TargetKey = Key.ShiftRight, Target = 5 }, + new KeyMappingEntry { TargetKey = Key.AltRight, Target = 6 }, + new KeyMappingEntry { TargetKey = Key.WinRight, Target = 7 }, + new KeyMappingEntry { TargetKey = Key.CapsLock, Target = 8 }, + new KeyMappingEntry { TargetKey = Key.ScrollLock, Target = 9 }, + new KeyMappingEntry { TargetKey = Key.NumLock, Target = 10 }, + }; + + public static HLE.Input.Keyboard GetKeysDown(NpadKeyboard npad, KeyboardState keyboard) + { + HLE.Input.Keyboard hidKeyboard = new HLE.Input.Keyboard + { + Modifier = 0, + Keys = new int[0x8] + }; + + foreach (KeyMappingEntry entry in KeyMapping) + { + int value = keyboard[entry.TargetKey] ? 1 : 0; + + hidKeyboard.Keys[entry.Target / 0x20] |= (value << (entry.Target % 0x20)); + } + + foreach (KeyMappingEntry entry in KeyModifierMapping) + { + int value = keyboard[entry.TargetKey] ? 1 : 0; + + hidKeyboard.Modifier |= value << entry.Target; + } + + return hidKeyboard; + } + } +} diff --git a/Ryujinx/Ui/MainWindow.cs b/Ryujinx/Ui/MainWindow.cs new file mode 100644 index 0000000000..451df2fd95 --- /dev/null +++ b/Ryujinx/Ui/MainWindow.cs @@ -0,0 +1,927 @@ +using Gtk; +using JsonPrettyPrinterPlus; +using Ryujinx.Audio; +using Ryujinx.Common.Logging; +using Ryujinx.Configuration; +using Ryujinx.Graphics.OpenGL; +using Ryujinx.HLE.FileSystem; +using Ryujinx.Profiler; +using System; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Utf8Json; +using Utf8Json.Resolvers; + +using GUI = Gtk.Builder.ObjectAttribute; + +namespace Ryujinx.Ui +{ + public class MainWindow : Window + { + private static HLE.Switch _device; + + private static Renderer _renderer; + + private static IAalOutput _audioOut; + + private static GlScreen _screen; + + private static ListStore _tableStore; + + private static bool _updatingGameTable; + private static bool _gameLoaded; + private static bool _ending; + + private static TreeView _treeView; + +#pragma warning disable CS0649 +#pragma warning disable IDE0044 + [GUI] Window _mainWin; + [GUI] CheckMenuItem _fullScreen; + [GUI] MenuItem _stopEmulation; + [GUI] CheckMenuItem _favToggle; + [GUI] MenuItem _firmwareInstallFile; + [GUI] MenuItem _firmwareInstallDirectory; + [GUI] CheckMenuItem _iconToggle; + [GUI] CheckMenuItem _appToggle; + [GUI] CheckMenuItem _developerToggle; + [GUI] CheckMenuItem _versionToggle; + [GUI] CheckMenuItem _timePlayedToggle; + [GUI] CheckMenuItem _lastPlayedToggle; + [GUI] CheckMenuItem _fileExtToggle; + [GUI] CheckMenuItem _fileSizeToggle; + [GUI] CheckMenuItem _pathToggle; + [GUI] TreeView _gameTable; + [GUI] TreeSelection _gameTableSelection; + [GUI] Label _progressLabel; + [GUI] Label _firmwareVersionLabel; + [GUI] LevelBar _progressBar; +#pragma warning restore CS0649 +#pragma warning restore IDE0044 + + public MainWindow() : this(new Builder("Ryujinx.Ui.MainWindow.glade")) { } + + private MainWindow(Builder builder) : base(builder.GetObject("_mainWin").Handle) + { + builder.Autoconnect(this); + + DeleteEvent += Window_Close; + + ApplicationLibrary.ApplicationAdded += Application_Added; + + _gameTable.ButtonReleaseEvent += Row_Clicked; + + bool continueWithStartup = Migration.PromptIfMigrationNeededForStartup(this, out bool migrationNeeded); + if (!continueWithStartup) + { + End(); + } + + _renderer = new Renderer(); + + _audioOut = InitializeAudioEngine(); + + // TODO: Initialization and dispose of HLE.Switch when starting/stoping emulation. + _device = InitializeSwitchInstance(); + + if (migrationNeeded) + { + bool migrationSuccessful = Migration.DoMigrationForStartup(this, _device); + + if (!migrationSuccessful) + { + End(); + } + } + + _treeView = _gameTable; + + ApplyTheme(); + + _mainWin.Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png"); + _stopEmulation.Sensitive = false; + + if (ConfigurationState.Instance.Ui.GuiColumns.FavColumn) _favToggle.Active = true; + if (ConfigurationState.Instance.Ui.GuiColumns.IconColumn) _iconToggle.Active = true; + if (ConfigurationState.Instance.Ui.GuiColumns.AppColumn) _appToggle.Active = true; + if (ConfigurationState.Instance.Ui.GuiColumns.DevColumn) _developerToggle.Active = true; + if (ConfigurationState.Instance.Ui.GuiColumns.VersionColumn) _versionToggle.Active = true; + if (ConfigurationState.Instance.Ui.GuiColumns.TimePlayedColumn) _timePlayedToggle.Active = true; + if (ConfigurationState.Instance.Ui.GuiColumns.LastPlayedColumn) _lastPlayedToggle.Active = true; + if (ConfigurationState.Instance.Ui.GuiColumns.FileExtColumn) _fileExtToggle.Active = true; + if (ConfigurationState.Instance.Ui.GuiColumns.FileSizeColumn) _fileSizeToggle.Active = true; + if (ConfigurationState.Instance.Ui.GuiColumns.PathColumn) _pathToggle.Active = true; + + _gameTable.Model = _tableStore = new ListStore( + typeof(bool), + typeof(Gdk.Pixbuf), + typeof(string), + typeof(string), + typeof(string), + typeof(string), + typeof(string), + typeof(string), + typeof(string), + typeof(string)); + + _tableStore.SetSortFunc(5, TimePlayedSort); + _tableStore.SetSortFunc(6, LastPlayedSort); + _tableStore.SetSortFunc(8, FileSizeSort); + _tableStore.SetSortColumnId(0, SortType.Descending); + + UpdateColumns(); +#pragma warning disable CS4014 + UpdateGameTable(); +#pragma warning restore CS4014 + + Task.Run(RefreshFirmwareLabel); + } + + internal static void ApplyTheme() + { + if (!ConfigurationState.Instance.Ui.EnableCustomTheme) + { + return; + } + + if (File.Exists(ConfigurationState.Instance.Ui.CustomThemePath) && (System.IO.Path.GetExtension(ConfigurationState.Instance.Ui.CustomThemePath) == ".css")) + { + CssProvider cssProvider = new CssProvider(); + + cssProvider.LoadFromPath(ConfigurationState.Instance.Ui.CustomThemePath); + + StyleContext.AddProviderForScreen(Gdk.Screen.Default, cssProvider, 800); + } + else + { + Logger.PrintWarning(LogClass.Application, $"The \"custom_theme_path\" section in \"Config.json\" contains an invalid path: \"{ConfigurationState.Instance.Ui.CustomThemePath}\"."); + } + } + + private void UpdateColumns() + { + foreach (TreeViewColumn column in _gameTable.Columns) + { + _gameTable.RemoveColumn(column); + } + + CellRendererToggle favToggle = new CellRendererToggle(); + favToggle.Toggled += FavToggle_Toggled; + + if (ConfigurationState.Instance.Ui.GuiColumns.FavColumn) _gameTable.AppendColumn("Fav", favToggle, "active", 0); + if (ConfigurationState.Instance.Ui.GuiColumns.IconColumn) _gameTable.AppendColumn("Icon", new CellRendererPixbuf(), "pixbuf", 1); + if (ConfigurationState.Instance.Ui.GuiColumns.AppColumn) _gameTable.AppendColumn("Application", new CellRendererText(), "text", 2); + if (ConfigurationState.Instance.Ui.GuiColumns.DevColumn) _gameTable.AppendColumn("Developer", new CellRendererText(), "text", 3); + if (ConfigurationState.Instance.Ui.GuiColumns.VersionColumn) _gameTable.AppendColumn("Version", new CellRendererText(), "text", 4); + if (ConfigurationState.Instance.Ui.GuiColumns.TimePlayedColumn) _gameTable.AppendColumn("Time Played", new CellRendererText(), "text", 5); + if (ConfigurationState.Instance.Ui.GuiColumns.LastPlayedColumn) _gameTable.AppendColumn("Last Played", new CellRendererText(), "text", 6); + if (ConfigurationState.Instance.Ui.GuiColumns.FileExtColumn) _gameTable.AppendColumn("File Ext", new CellRendererText(), "text", 7); + if (ConfigurationState.Instance.Ui.GuiColumns.FileSizeColumn) _gameTable.AppendColumn("File Size", new CellRendererText(), "text", 8); + if (ConfigurationState.Instance.Ui.GuiColumns.PathColumn) _gameTable.AppendColumn("Path", new CellRendererText(), "text", 9); + + foreach (TreeViewColumn column in _gameTable.Columns) + { + if (column.Title == "Fav" && ConfigurationState.Instance.Ui.GuiColumns.FavColumn) column.SortColumnId = 0; + else if (column.Title == "Application" && ConfigurationState.Instance.Ui.GuiColumns.AppColumn) column.SortColumnId = 2; + else if (column.Title == "Developer" && ConfigurationState.Instance.Ui.GuiColumns.DevColumn) column.SortColumnId = 3; + else if (column.Title == "Version" && ConfigurationState.Instance.Ui.GuiColumns.VersionColumn) column.SortColumnId = 4; + else if (column.Title == "Time Played" && ConfigurationState.Instance.Ui.GuiColumns.TimePlayedColumn) column.SortColumnId = 5; + else if (column.Title == "Last Played" && ConfigurationState.Instance.Ui.GuiColumns.LastPlayedColumn) column.SortColumnId = 6; + else if (column.Title == "File Ext" && ConfigurationState.Instance.Ui.GuiColumns.FileExtColumn) column.SortColumnId = 7; + else if (column.Title == "File Size" && ConfigurationState.Instance.Ui.GuiColumns.FileSizeColumn) column.SortColumnId = 8; + else if (column.Title == "Path" && ConfigurationState.Instance.Ui.GuiColumns.PathColumn) column.SortColumnId = 9; + } + } + + private HLE.Switch InitializeSwitchInstance() + { + HLE.Switch instance = new HLE.Switch(_renderer, _audioOut); + + instance.Initialize(); + + return instance; + } + + internal static async Task UpdateGameTable() + { + if (_updatingGameTable) + { + return; + } + + _updatingGameTable = true; + + _tableStore.Clear(); + + await Task.Run(() => ApplicationLibrary.LoadApplications(ConfigurationState.Instance.Ui.GameDirs, + _device.System.KeySet, _device.System.State.DesiredTitleLanguage, _device.System.FsClient, + _device.FileSystem)); + + _updatingGameTable = false; + } + + internal void LoadApplication(string path) + { + if (_gameLoaded) + { + GtkDialog.CreateErrorDialog("A game has already been loaded. Please close the emulator and try again"); + } + else + { + Logger.RestartTime(); + + // TODO: Move this somewhere else + reloadable? + Ryujinx.Graphics.Gpu.GraphicsConfig.ShadersDumpPath = ConfigurationState.Instance.Graphics.ShadersDumpPath; + + if (Directory.Exists(path)) + { + string[] romFsFiles = Directory.GetFiles(path, "*.istorage"); + + if (romFsFiles.Length == 0) + { + romFsFiles = Directory.GetFiles(path, "*.romfs"); + } + + if (romFsFiles.Length > 0) + { + Logger.PrintInfo(LogClass.Application, "Loading as cart with RomFS."); + _device.LoadCart(path, romFsFiles[0]); + } + else + { + Logger.PrintInfo(LogClass.Application, "Loading as cart WITHOUT RomFS."); + _device.LoadCart(path); + } + } + else if (File.Exists(path)) + { + switch (System.IO.Path.GetExtension(path).ToLowerInvariant()) + { + case ".xci": + Logger.PrintInfo(LogClass.Application, "Loading as XCI."); + _device.LoadXci(path); + break; + case ".nca": + Logger.PrintInfo(LogClass.Application, "Loading as NCA."); + _device.LoadNca(path); + break; + case ".nsp": + case ".pfs0": + Logger.PrintInfo(LogClass.Application, "Loading as NSP."); + _device.LoadNsp(path); + break; + default: + Logger.PrintInfo(LogClass.Application, "Loading as homebrew."); + try + { + _device.LoadProgram(path); + } + catch (ArgumentOutOfRangeException) + { + Logger.PrintError(LogClass.Application, "The file which you have specified is unsupported by Ryujinx."); + } + break; + } + } + else + { + Logger.PrintWarning(LogClass.Application, "Please specify a valid XCI/NCA/NSP/PFS0/NRO file."); + End(); + } + +#if MACOS_BUILD + CreateGameWindow(); +#else + new Thread(CreateGameWindow).Start(); +#endif + + _gameLoaded = true; + _stopEmulation.Sensitive = true; + + _firmwareInstallFile.Sensitive = false; + _firmwareInstallDirectory.Sensitive = false; + + DiscordIntegrationModule.SwitchToPlayingState(_device.System.TitleIdText, _device.System.TitleName); + + ApplicationLibrary.LoadAndSaveMetaData(_device.System.TitleIdText, appMetadata => + { + appMetadata.LastPlayed = DateTime.UtcNow.ToString(); + }); + } + } + + private static void CreateGameWindow() + { + _device.Hid.InitializePrimaryController(ConfigurationState.Instance.Hid.ControllerType); + + using (_screen = new GlScreen(_device, _renderer)) + { + _screen.MainLoop(); + + End(); + } + } + + private static void End() + { + if (_ending) + { + return; + } + + _ending = true; + + if (_gameLoaded) + { + ApplicationLibrary.LoadAndSaveMetaData(_device.System.TitleIdText, appMetadata => + { + DateTime lastPlayedDateTime = DateTime.Parse(appMetadata.LastPlayed); + double sessionTimePlayed = DateTime.UtcNow.Subtract(lastPlayedDateTime).TotalSeconds; + + appMetadata.TimePlayed += Math.Round(sessionTimePlayed, MidpointRounding.AwayFromZero); + }); + } + + Profile.FinishProfiling(); + _device?.Dispose(); + _audioOut?.Dispose(); + Logger.Shutdown(); + Environment.Exit(0); + } + + /// + /// Picks an audio output renderer supported on this machine + /// + /// An supported by this machine + private static IAalOutput InitializeAudioEngine() + { + if (OpenALAudioOut.IsSupported) + { + return new OpenALAudioOut(); + } + else if (SoundIoAudioOut.IsSupported) + { + return new SoundIoAudioOut(); + } + else + { + return new DummyAudioOut(); + } + } + + //Events + private void Application_Added(object sender, ApplicationAddedEventArgs args) + { + Application.Invoke(delegate + { + _tableStore.AppendValues( + args.AppData.Favorite, + new Gdk.Pixbuf(args.AppData.Icon, 75, 75), + $"{args.AppData.TitleName}\n{args.AppData.TitleId.ToUpper()}", + args.AppData.Developer, + args.AppData.Version, + args.AppData.TimePlayed, + args.AppData.LastPlayed, + args.AppData.FileExtension, + args.AppData.FileSize, + args.AppData.Path); + + _progressLabel.Text = $"{args.NumAppsLoaded}/{args.NumAppsFound} Games Loaded"; + _progressBar.Value = (float)args.NumAppsLoaded / args.NumAppsFound; + }); + } + + private void FavToggle_Toggled(object sender, ToggledArgs args) + { + _tableStore.GetIter(out TreeIter treeIter, new TreePath(args.Path)); + + string titleId = _tableStore.GetValue(treeIter, 2).ToString().Split("\n")[1].ToLower(); + + bool newToggleValue = !(bool)_tableStore.GetValue(treeIter, 0); + + _tableStore.SetValue(treeIter, 0, newToggleValue); + + ApplicationLibrary.LoadAndSaveMetaData(titleId, appMetadata => + { + appMetadata.Favorite = newToggleValue; + }); + } + + private void Row_Activated(object sender, RowActivatedArgs args) + { + _gameTableSelection.GetSelected(out TreeIter treeIter); + string path = (string)_tableStore.GetValue(treeIter, 9); + + LoadApplication(path); + } + + private void Row_Clicked(object sender, ButtonReleaseEventArgs args) + { + if (args.Event.Button != 3) return; + + _gameTableSelection.GetSelected(out TreeIter treeIter); + + if (treeIter.UserData == IntPtr.Zero) return; + + GameTableContextMenu contextMenu = new GameTableContextMenu(_tableStore, treeIter, _device.System.FsClient); + contextMenu.ShowAll(); + contextMenu.PopupAtPointer(null); + } + + private void Load_Application_File(object sender, EventArgs args) + { + FileChooserDialog fileChooser = new FileChooserDialog("Choose the file to open", this, FileChooserAction.Open, "Cancel", ResponseType.Cancel, "Open", ResponseType.Accept); + + fileChooser.Filter = new FileFilter(); + fileChooser.Filter.AddPattern("*.nsp" ); + fileChooser.Filter.AddPattern("*.pfs0"); + fileChooser.Filter.AddPattern("*.xci" ); + fileChooser.Filter.AddPattern("*.nca" ); + fileChooser.Filter.AddPattern("*.nro" ); + fileChooser.Filter.AddPattern("*.nso" ); + + if (fileChooser.Run() == (int)ResponseType.Accept) + { + LoadApplication(fileChooser.Filename); + } + + fileChooser.Dispose(); + } + + private void Load_Application_Folder(object sender, EventArgs args) + { + FileChooserDialog fileChooser = new FileChooserDialog("Choose the folder to open", this, FileChooserAction.SelectFolder, "Cancel", ResponseType.Cancel, "Open", ResponseType.Accept); + + if (fileChooser.Run() == (int)ResponseType.Accept) + { + LoadApplication(fileChooser.Filename); + } + + fileChooser.Dispose(); + } + + private void Open_Ryu_Folder(object sender, EventArgs args) + { + Process.Start(new ProcessStartInfo() + { + FileName = new VirtualFileSystem().GetBasePath(), + UseShellExecute = true, + Verb = "open" + }); + } + + private void Exit_Pressed(object sender, EventArgs args) + { + _screen?.Exit(); + End(); + } + + private void Window_Close(object sender, DeleteEventArgs args) + { + _screen?.Exit(); + End(); + } + + private void StopEmulation_Pressed(object sender, EventArgs args) + { + // TODO: Write logic to kill running game + + _gameLoaded = false; + } + + private void Installer_File_Pressed(object o, EventArgs args) + { + FileChooserDialog fileChooser = new FileChooserDialog("Choose the firmware file to open", + this, + FileChooserAction.Open, + "Cancel", + ResponseType.Cancel, + "Open", + ResponseType.Accept); + + fileChooser.Filter = new FileFilter(); + fileChooser.Filter.AddPattern("*.zip"); + fileChooser.Filter.AddPattern("*.xci"); + + HandleInstallerDialog(fileChooser); + } + + private void Installer_Directory_Pressed(object o, EventArgs args) + { + FileChooserDialog directoryChooser = new FileChooserDialog("Choose the firmware directory to open", + this, + FileChooserAction.SelectFolder, + "Cancel", + ResponseType.Cancel, + "Open", + ResponseType.Accept); + + HandleInstallerDialog(directoryChooser); + } + + private void RefreshFirmwareLabel() + { + var currentFirmware = _device.System.GetCurrentFirmwareVersion(); + + GLib.Idle.Add(new GLib.IdleHandler(() => + { + _firmwareVersionLabel.Text = currentFirmware != null ? currentFirmware.VersionString : "0.0.0"; + + return false; + })); + } + + private void HandleInstallerDialog(FileChooserDialog fileChooser) + { + if (fileChooser.Run() == (int)ResponseType.Accept) + { + MessageDialog dialog = null; + + try + { + string filename = fileChooser.Filename; + + fileChooser.Dispose(); + + var firmwareVersion = _device.System.VerifyFirmwarePackage(filename); + + if (firmwareVersion == null) + { + dialog = new MessageDialog(this, DialogFlags.Modal, MessageType.Info, ButtonsType.Ok, false, ""); + + dialog.Text = "Firmware not found."; + + dialog.SecondaryText = $"A valid system firmware was not found in {filename}."; + + Logger.PrintError(LogClass.Application, $"A valid system firmware was not found in {filename}."); + + dialog.Run(); + dialog.Hide(); + dialog.Dispose(); + + return; + } + + var currentVersion = _device.System.GetCurrentFirmwareVersion(); + + string dialogMessage = $"System version {firmwareVersion.VersionString} will be installed."; + + if (currentVersion != null) + { + dialogMessage += $"This will replace the current system version {currentVersion.VersionString}. "; + } + + dialogMessage += "Do you want to continue?"; + + dialog = new MessageDialog(this, DialogFlags.Modal, MessageType.Question, ButtonsType.YesNo, false, ""); + + dialog.Text = $"Install Firmware {firmwareVersion.VersionString}"; + dialog.SecondaryText = dialogMessage; + + int response = dialog.Run(); + + dialog.Dispose(); + + dialog = new MessageDialog(this, DialogFlags.Modal, MessageType.Info, ButtonsType.None, false, ""); + + dialog.Text = $"Install Firmware {firmwareVersion.VersionString}"; + + dialog.SecondaryText = "Installing firmware..."; + + if (response == (int)ResponseType.Yes) + { + Logger.PrintInfo(LogClass.Application, $"Installing firmware {firmwareVersion.VersionString}"); + + Thread thread = new Thread(() => + { + GLib.Idle.Add(new GLib.IdleHandler(() => + { + dialog.Run(); + return false; + })); + + try + { + _device.System.InstallFirmware(filename); + + GLib.Idle.Add(new GLib.IdleHandler(() => + { + dialog.Dispose(); + + dialog = new MessageDialog(this, DialogFlags.Modal, MessageType.Info, ButtonsType.Ok, false, ""); + + dialog.Text = $"Install Firmware {firmwareVersion.VersionString}"; + + dialog.SecondaryText = $"System version {firmwareVersion.VersionString} successfully installed."; + + Logger.PrintInfo(LogClass.Application, $"System version {firmwareVersion.VersionString} successfully installed."); + + dialog.Run(); + dialog.Dispose(); + + return false; + })); + } + catch (Exception ex) + { + GLib.Idle.Add(new GLib.IdleHandler(() => + { + dialog.Dispose(); + + dialog = new MessageDialog(this, DialogFlags.Modal, MessageType.Info, ButtonsType.Ok, false, ""); + + dialog.Text = $"Install Firmware {firmwareVersion.VersionString} Failed."; + + dialog.SecondaryText = $"An error occured while installing system version {firmwareVersion.VersionString}." + + " Please check logs for more info."; + + Logger.PrintError(LogClass.Application, ex.Message); + + dialog.Run(); + dialog.Dispose(); + + return false; + })); + } + finally + { + RefreshFirmwareLabel(); + } + }); + + thread.Name = "GUI.FirmwareInstallerThread"; + thread.Start(); + } + else + { + dialog.Dispose(); + } + } + catch (Exception ex) + { + if (dialog != null) + { + dialog.Dispose(); + } + + dialog = new MessageDialog(this, DialogFlags.Modal, MessageType.Info, ButtonsType.Ok, false, ""); + + dialog.Text = "Parsing Firmware Failed."; + + dialog.SecondaryText = "An error occured while parsing firmware. Please check the logs for more info."; + + Logger.PrintError(LogClass.Application, ex.Message); + + dialog.Run(); + dialog.Dispose(); + } + } + else + { + fileChooser.Dispose(); + } + } + + private void FullScreen_Toggled(object o, EventArgs args) + { + if (_fullScreen.Active) + { + Fullscreen(); + } + else + { + Unfullscreen(); + } + } + + private void Settings_Pressed(object sender, EventArgs args) + { + SwitchSettings settingsWin = new SwitchSettings(); + settingsWin.Show(); + } + + private void Update_Pressed(object sender, EventArgs args) + { + string ryuUpdater = System.IO.Path.Combine(new VirtualFileSystem().GetBasePath(), "RyuUpdater.exe"); + + try + { + Process.Start(new ProcessStartInfo(ryuUpdater, "/U") { UseShellExecute = true }); + } + catch(System.ComponentModel.Win32Exception) + { + GtkDialog.CreateErrorDialog("Update canceled by user or updater was not found"); + } + } + + private void About_Pressed(object sender, EventArgs args) + { + AboutWindow aboutWin = new AboutWindow(); + aboutWin.Show(); + } + + private void Fav_Toggled(object sender, EventArgs args) + { + ConfigurationState.Instance.Ui.GuiColumns.FavColumn.Value = _favToggle.Active; + + SaveConfig(); + UpdateColumns(); + } + + private void Icon_Toggled(object sender, EventArgs args) + { + ConfigurationState.Instance.Ui.GuiColumns.IconColumn.Value = _iconToggle.Active; + + SaveConfig(); + UpdateColumns(); + } + + private void Title_Toggled(object sender, EventArgs args) + { + ConfigurationState.Instance.Ui.GuiColumns.AppColumn.Value = _appToggle.Active; + + SaveConfig(); + UpdateColumns(); + } + + private void Developer_Toggled(object sender, EventArgs args) + { + ConfigurationState.Instance.Ui.GuiColumns.DevColumn.Value = _developerToggle.Active; + + SaveConfig(); + UpdateColumns(); + } + + private void Version_Toggled(object sender, EventArgs args) + { + ConfigurationState.Instance.Ui.GuiColumns.VersionColumn.Value = _versionToggle.Active; + + SaveConfig(); + UpdateColumns(); + } + + private void TimePlayed_Toggled(object sender, EventArgs args) + { + ConfigurationState.Instance.Ui.GuiColumns.TimePlayedColumn.Value = _timePlayedToggle.Active; + + SaveConfig(); + UpdateColumns(); + } + + private void LastPlayed_Toggled(object sender, EventArgs args) + { + ConfigurationState.Instance.Ui.GuiColumns.LastPlayedColumn.Value = _lastPlayedToggle.Active; + + SaveConfig(); + UpdateColumns(); + } + + private void FileExt_Toggled(object sender, EventArgs args) + { + ConfigurationState.Instance.Ui.GuiColumns.FileExtColumn.Value = _fileExtToggle.Active; + + SaveConfig(); + UpdateColumns(); + } + + private void FileSize_Toggled(object sender, EventArgs args) + { + ConfigurationState.Instance.Ui.GuiColumns.FileSizeColumn.Value = _fileSizeToggle.Active; + + SaveConfig(); + UpdateColumns(); + } + + private void Path_Toggled(object sender, EventArgs args) + { + ConfigurationState.Instance.Ui.GuiColumns.PathColumn.Value = _pathToggle.Active; + + SaveConfig(); + UpdateColumns(); + } + + private void RefreshList_Pressed(object sender, ButtonReleaseEventArgs args) + { +#pragma warning disable CS4014 + UpdateGameTable(); +#pragma warning restore CS4014 + } + + private static int TimePlayedSort(ITreeModel model, TreeIter a, TreeIter b) + { + string aValue = model.GetValue(a, 5).ToString(); + string bValue = model.GetValue(b, 5).ToString(); + + if (aValue.Length > 4 && aValue.Substring(aValue.Length - 4) == "mins") + { + aValue = (float.Parse(aValue.Substring(0, aValue.Length - 5)) * 60).ToString(); + } + else if (aValue.Length > 3 && aValue.Substring(aValue.Length - 3) == "hrs") + { + aValue = (float.Parse(aValue.Substring(0, aValue.Length - 4)) * 3600).ToString(); + } + else if (aValue.Length > 4 && aValue.Substring(aValue.Length - 4) == "days") + { + aValue = (float.Parse(aValue.Substring(0, aValue.Length - 5)) * 86400).ToString(); + } + else + { + aValue = aValue.Substring(0, aValue.Length - 1); + } + + if (bValue.Length > 4 && bValue.Substring(bValue.Length - 4) == "mins") + { + bValue = (float.Parse(bValue.Substring(0, bValue.Length - 5)) * 60).ToString(); + } + else if (bValue.Length > 3 && bValue.Substring(bValue.Length - 3) == "hrs") + { + bValue = (float.Parse(bValue.Substring(0, bValue.Length - 4)) * 3600).ToString(); + } + else if (bValue.Length > 4 && bValue.Substring(bValue.Length - 4) == "days") + { + bValue = (float.Parse(bValue.Substring(0, bValue.Length - 5)) * 86400).ToString(); + } + else + { + bValue = bValue.Substring(0, bValue.Length - 1); + } + + if (float.Parse(aValue) > float.Parse(bValue)) + { + return -1; + } + else if (float.Parse(bValue) > float.Parse(aValue)) + { + return 1; + } + else + { + return 0; + } + } + + private static int LastPlayedSort(ITreeModel model, TreeIter a, TreeIter b) + { + string aValue = model.GetValue(a, 6).ToString(); + string bValue = model.GetValue(b, 6).ToString(); + + if (aValue == "Never") + { + aValue = DateTime.UnixEpoch.ToString(); + } + + if (bValue == "Never") + { + bValue = DateTime.UnixEpoch.ToString(); + } + + return DateTime.Compare(DateTime.Parse(bValue), DateTime.Parse(aValue)); + } + + private static int FileSizeSort(ITreeModel model, TreeIter a, TreeIter b) + { + string aValue = model.GetValue(a, 8).ToString(); + string bValue = model.GetValue(b, 8).ToString(); + + if (aValue.Substring(aValue.Length - 2) == "GB") + { + aValue = (float.Parse(aValue[0..^2]) * 1024).ToString(); + } + else + { + aValue = aValue[0..^2]; + } + + if (bValue.Substring(bValue.Length - 2) == "GB") + { + bValue = (float.Parse(bValue[0..^2]) * 1024).ToString(); + } + else + { + bValue = bValue[0..^2]; + } + + if (float.Parse(aValue) > float.Parse(bValue)) + { + return -1; + } + else if (float.Parse(bValue) > float.Parse(aValue)) + { + return 1; + } + else + { + return 0; + } + } + + public static void SaveConfig() + { + ConfigurationState.Instance.ToFileFormat().SaveConfig(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json")); + } + } +} diff --git a/Ryujinx/Ui/MainWindow.glade b/Ryujinx/Ui/MainWindow.glade new file mode 100644 index 0000000000..8e2eab9391 --- /dev/null +++ b/Ryujinx/Ui/MainWindow.glade @@ -0,0 +1,506 @@ + + + + + + False + Ryujinx + center + 1280 + 750 + + + True + False + vertical + + + True + False + + + True + False + File + True + + + True + False + + + True + False + Open a file chooser to chose a switch compatible file to load + Load Application from File + True + + + + + + True + False + Open a file chooser to chose a switch compatible, unpacked application to load + Load Unpacked Game + True + + + + + + True + False + + + + + True + False + Open Ryujinx filesystem folder + Open Ryujinx Folder + True + + + + + + True + False + + + + + True + False + Exit Ryujinx + Exit + True + + + + + + + + + + True + False + Options + True + + + True + False + + + True + False + Fullscreens the window + Fullscreen + True + + + + + + True + False + Stop emualtion of the current game and return to game selection + Stop Emulation + True + + + + + + True + False + + + + + True + False + Select which GUI columns to enable + Enable GUI Columns + True + + + True + False + + + True + False + Enable or Disable Favorite Games Column in the game list + Enable Favorite Games Column + True + + + + + + True + False + Enable or Disable Icon Column in the game list + Enable Icon Column + True + + + + + + True + False + Enable or Disable Title Name/ID Column in the game list + Enable Title Name/ID Column + True + + + + + + True + False + Enable or Disable Developer Column in the game list + Enable Developer Column + True + + + + + + True + False + Enable or Disable Version Column in the game list + Enable Version Column + True + + + + + + True + False + Enable or Disable Time Played Column in the game list + Enable Time Played Column + True + + + + + + True + False + Enable or Disable Last Played Column in the game list + Enable Last Played Column + True + + + + + + True + False + Enable or Disable file extension column in the game list + Enable File Ext Column + True + + + + + + True + False + Enable or Disable File Size Column in the game list + Enable File Size Column + True + + + + + + True + False + Enable or Disable Path Column in the game list + Enable Path Column + True + + + + + + + + + + True + False + + + + + True + False + Open settings window + Settings + True + + + + + + + + + + True + False + Tools + True + + + True + False + + + True + False + Install Firmware + True + + + True + False + + + True + False + Install a firmware from XCI or ZIP + True + + + + + + True + False + Install a firmware from a directory + True + + + + + + + + + + + + + + True + False + Help + True + + + True + False + + + True + False + Check for updates to Ryujinx (requires Ryujinx Installer) + Check for Updates + True + + + + + + True + False + + + + + True + False + Open about window + About + True + + + + + + + + + + False + True + 0 + + + + + True + False + vertical + + + True + True + in + + + True + True + True + True + + + + + + + + + True + True + 0 + + + + + True + False + + + True + False + 5 + + + + RefreshList + True + False + gtk-refresh + + + + + False + True + 0 + + + + + True + False + 10 + 5 + 2 + 2 + 0/0 Games Loaded + + + False + True + 1 + + + + + 200 + True + False + start + 10 + 5 + + + True + True + 2 + + + + + True + False + + + False + True + 3 + + + + + True + False + 5 + + + True + False + System Version + + + False + True + 0 + + + + + 50 + True + False + 5 + 5 + + + False + True + end + 1 + + + + + False + True + end + 4 + + + + + False + True + 1 + + + + + True + True + 1 + + + + + + + + + diff --git a/Ryujinx/Ui/Migration.cs b/Ryujinx/Ui/Migration.cs new file mode 100644 index 0000000000..9e9b80001d --- /dev/null +++ b/Ryujinx/Ui/Migration.cs @@ -0,0 +1,187 @@ +using Gtk; +using LibHac; +using System; +using System.IO; +using System.Linq; +using System.Reflection; + +using Switch = Ryujinx.HLE.Switch; + +namespace Ryujinx.Ui +{ + internal class Migration + { + private Switch Device { get; } + + public Migration(Switch device) + { + Device = device; + } + + public static bool PromptIfMigrationNeededForStartup(Window parentWindow, out bool isMigrationNeeded) + { + if (!IsMigrationNeeded()) + { + isMigrationNeeded = false; + + return true; + } + + isMigrationNeeded = true; + + int dialogResponse; + + using (MessageDialog dialog = new MessageDialog(parentWindow, DialogFlags.Modal, MessageType.Question, + ButtonsType.YesNo, "What's this?")) + { + dialog.Title = "Data Migration Needed"; + dialog.Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png"); + dialog.Text = + "The folder structure of Ryujinx's RyuFs folder has been updated and renamed to \"Ryujinx\". " + + "Your RyuFs folder must be copied and migrated to the new \"Ryujinx\" structure. Would you like to do the migration now?\n\n" + + "Select \"Yes\" to automatically perform the migration. Your old RyuFs folder will remain as it is.\n\n" + + "Selecting \"No\" will exit Ryujinx without changing anything."; + + dialogResponse = dialog.Run(); + } + + return dialogResponse == (int)ResponseType.Yes; + } + + public static bool DoMigrationForStartup(Window parentWindow, Switch device) + { + try + { + Migration migration = new Migration(device); + int saveCount = migration.Migrate(); + + using MessageDialog dialogSuccess = new MessageDialog(parentWindow, DialogFlags.Modal, MessageType.Info, ButtonsType.Ok, null) + { + Title = "Migration Success", + Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png"), + Text = $"Data migration was successful. {saveCount} saves were migrated.", + }; + + dialogSuccess.Run(); + + // Reload key set after migration to be sure to catch the keys in the system directory. + device.System.LoadKeySet(); + + return true; + } + catch (HorizonResultException ex) + { + GtkDialog.CreateErrorDialog(ex.Message); + + return false; + } + } + + // Returns the number of saves migrated + public int Migrate() + { + string appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); + + string oldBasePath = Path.Combine(appDataPath, "RyuFs"); + string newBasePath = Path.Combine(appDataPath, "Ryujinx"); + + string oldSaveDir = Path.Combine(oldBasePath, "nand/user/save"); + + CopyRyuFs(oldBasePath, newBasePath); + + SaveImporter importer = new SaveImporter(oldSaveDir, Device.System.FsClient); + + return importer.Import(); + } + + private static void CopyRyuFs(string oldPath, string newPath) + { + Directory.CreateDirectory(newPath); + + CopyExcept(oldPath, newPath, "nand", "bis", "sdmc", "sdcard"); + + string oldNandPath = Path.Combine(oldPath, "nand"); + string newNandPath = Path.Combine(newPath, "bis"); + + CopyExcept(oldNandPath, newNandPath, "system", "user"); + + string oldSdPath = Path.Combine(oldPath, "sdmc"); + string newSdPath = Path.Combine(newPath, "sdcard"); + + CopyDirectory(oldSdPath, newSdPath); + + string oldSystemPath = Path.Combine(oldNandPath, "system"); + string newSystemPath = Path.Combine(newNandPath, "system"); + + CopyExcept(oldSystemPath, newSystemPath, "save"); + + string oldUserPath = Path.Combine(oldNandPath, "user"); + string newUserPath = Path.Combine(newNandPath, "user"); + + CopyExcept(oldUserPath, newUserPath, "save"); + } + + private static void CopyExcept(string srcPath, string dstPath, params string[] exclude) + { + exclude = exclude.Select(x => x.ToLowerInvariant()).ToArray(); + + DirectoryInfo srcDir = new DirectoryInfo(srcPath); + + if (!srcDir.Exists) + { + return; + } + + Directory.CreateDirectory(dstPath); + + foreach (DirectoryInfo subDir in srcDir.EnumerateDirectories()) + { + if (exclude.Contains(subDir.Name.ToLowerInvariant())) + { + continue; + } + + CopyDirectory(subDir.FullName, Path.Combine(dstPath, subDir.Name)); + } + + foreach (FileInfo file in srcDir.EnumerateFiles()) + { + file.CopyTo(Path.Combine(dstPath, file.Name)); + } + } + + private static void CopyDirectory(string srcPath, string dstPath) + { + Directory.CreateDirectory(dstPath); + + DirectoryInfo srcDir = new DirectoryInfo(srcPath); + + if (!srcDir.Exists) + { + return; + } + + Directory.CreateDirectory(dstPath); + + foreach (DirectoryInfo subDir in srcDir.EnumerateDirectories()) + { + CopyDirectory(subDir.FullName, Path.Combine(dstPath, subDir.Name)); + } + + foreach (FileInfo file in srcDir.EnumerateFiles()) + { + file.CopyTo(Path.Combine(dstPath, file.Name)); + } + } + + public static bool IsMigrationNeeded() + { + string appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); + + string oldBasePath = Path.Combine(appDataPath, "RyuFs"); + string newBasePath = Path.Combine(appDataPath, "Ryujinx"); + + return Directory.Exists(oldBasePath) && !Directory.Exists(newBasePath); + } + } +} diff --git a/Ryujinx/Ui/NpadController.cs b/Ryujinx/Ui/NpadController.cs new file mode 100644 index 0000000000..67961b4914 --- /dev/null +++ b/Ryujinx/Ui/NpadController.cs @@ -0,0 +1,143 @@ +using OpenTK; +using OpenTK.Input; +using Ryujinx.Common.Configuration.Hid; +using Ryujinx.HLE.Input; +using System; + +using InnerNpadController = Ryujinx.Common.Configuration.Hid.NpadController; + +namespace Ryujinx.Ui.Input +{ + public class NpadController + { + private InnerNpadController _inner; + + // NOTE: This should be initialized AFTER GTK for compat reasons with OpenTK SDL2 backend and GTK on Linux. + // BODY: Usage of Joystick.GetState must be defer to after GTK full initialization. Otherwise, GTK will segfault because SDL2 was already init *sighs* + public NpadController(InnerNpadController inner) + { + _inner = inner; + } + + private bool IsEnabled() + { + return _inner.Enabled && Joystick.GetState(_inner.Index).IsConnected; + } + + public ControllerButtons GetButtons() + { + if (!IsEnabled()) + { + return 0; + } + + JoystickState joystickState = Joystick.GetState(_inner.Index); + + ControllerButtons buttons = 0; + + if (IsActivated(joystickState, _inner.LeftJoycon.DPadUp)) buttons |= ControllerButtons.DpadUp; + if (IsActivated(joystickState, _inner.LeftJoycon.DPadDown)) buttons |= ControllerButtons.DpadDown; + if (IsActivated(joystickState, _inner.LeftJoycon.DPadLeft)) buttons |= ControllerButtons.DpadLeft; + if (IsActivated(joystickState, _inner.LeftJoycon.DPadRight)) buttons |= ControllerButtons.DPadRight; + if (IsActivated(joystickState, _inner.LeftJoycon.StickButton)) buttons |= ControllerButtons.StickLeft; + if (IsActivated(joystickState, _inner.LeftJoycon.ButtonMinus)) buttons |= ControllerButtons.Minus; + if (IsActivated(joystickState, _inner.LeftJoycon.ButtonL)) buttons |= ControllerButtons.L; + if (IsActivated(joystickState, _inner.LeftJoycon.ButtonZl)) buttons |= ControllerButtons.Zl; + + if (IsActivated(joystickState, _inner.RightJoycon.ButtonA)) buttons |= ControllerButtons.A; + if (IsActivated(joystickState, _inner.RightJoycon.ButtonB)) buttons |= ControllerButtons.B; + if (IsActivated(joystickState, _inner.RightJoycon.ButtonX)) buttons |= ControllerButtons.X; + if (IsActivated(joystickState, _inner.RightJoycon.ButtonY)) buttons |= ControllerButtons.Y; + if (IsActivated(joystickState, _inner.RightJoycon.StickButton)) buttons |= ControllerButtons.StickRight; + if (IsActivated(joystickState, _inner.RightJoycon.ButtonPlus)) buttons |= ControllerButtons.Plus; + if (IsActivated(joystickState, _inner.RightJoycon.ButtonR)) buttons |= ControllerButtons.R; + if (IsActivated(joystickState, _inner.RightJoycon.ButtonZr)) buttons |= ControllerButtons.Zr; + + return buttons; + } + + private bool IsActivated(JoystickState joystickState,ControllerInputId controllerInputId) + { + if (controllerInputId <= ControllerInputId.Button20) + { + return joystickState.IsButtonDown((int)controllerInputId); + } + else if (controllerInputId <= ControllerInputId.Axis5) + { + int axis = controllerInputId - ControllerInputId.Axis0; + + return joystickState.GetAxis(axis) > _inner.TriggerThreshold; + } + else if (controllerInputId <= ControllerInputId.Hat2Right) + { + int hat = (controllerInputId - ControllerInputId.Hat0Up) / 4; + + int baseHatId = (int)ControllerInputId.Hat0Up + (hat * 4); + + JoystickHatState hatState = joystickState.GetHat((JoystickHat)hat); + + if (hatState.IsUp && ((int)controllerInputId % baseHatId == 0)) return true; + if (hatState.IsDown && ((int)controllerInputId % baseHatId == 1)) return true; + if (hatState.IsLeft && ((int)controllerInputId % baseHatId == 2)) return true; + if (hatState.IsRight && ((int)controllerInputId % baseHatId == 3)) return true; + } + + return false; + } + + public (short, short) GetLeftStick() + { + if (!IsEnabled()) + { + return (0, 0); + } + + return GetStick(_inner.LeftJoycon.Stick); + } + + public (short, short) GetRightStick() + { + if (!IsEnabled()) + { + return (0, 0); + } + + return GetStick(_inner.RightJoycon.Stick); + } + + private (short, short) GetStick(ControllerInputId stickInputId) + { + if (stickInputId < ControllerInputId.Axis0 || stickInputId > ControllerInputId.Axis5) + { + return (0, 0); + } + + JoystickState jsState = Joystick.GetState(_inner.Index); + + int xAxis = stickInputId - ControllerInputId.Axis0; + + float xValue = jsState.GetAxis(xAxis); + float yValue = 0 - jsState.GetAxis(xAxis + 1); // Invert Y-axis + + return ApplyDeadzone(new Vector2(xValue, yValue)); + } + + private (short, short) ApplyDeadzone(Vector2 axis) + { + return (ClampAxis(MathF.Abs(axis.X) > _inner.Deadzone ? axis.X : 0f), + ClampAxis(MathF.Abs(axis.Y) > _inner.Deadzone ? axis.Y : 0f)); + } + + private static short ClampAxis(float value) + { + if (value <= -short.MaxValue) + { + return -short.MaxValue; + } + else + { + return (short)(value * short.MaxValue); + } + } + } +} diff --git a/Ryujinx/Ui/Program.cs b/Ryujinx/Ui/Program.cs deleted file mode 100644 index b14897695d..0000000000 --- a/Ryujinx/Ui/Program.cs +++ /dev/null @@ -1,76 +0,0 @@ -using Ryujinx.Audio; -using Ryujinx.Audio.OpenAL; -using Ryujinx.Graphics.Gal; -using Ryujinx.Graphics.Gal.OpenGL; -using Ryujinx.HLE; -using System; -using System.IO; - -namespace Ryujinx -{ - class Program - { - static void Main(string[] args) - { - Console.Title = "Ryujinx Console"; - - IGalRenderer Renderer = new OGLRenderer(); - - IAalOutput AudioOut = new OpenALAudioOut(); - - Switch Ns = new Switch(Renderer, AudioOut); - - Config.Read(Ns.Log); - - Ns.Log.Updated += ConsoleLog.PrintLog; - - if (args.Length == 1) - { - if (Directory.Exists(args[0])) - { - string[] RomFsFiles = Directory.GetFiles(args[0], "*.istorage"); - - if (RomFsFiles.Length == 0) - { - RomFsFiles = Directory.GetFiles(args[0], "*.romfs"); - } - - if (RomFsFiles.Length > 0) - { - Console.WriteLine("Loading as cart with RomFS."); - - Ns.LoadCart(args[0], RomFsFiles[0]); - } - else - { - Console.WriteLine("Loading as cart WITHOUT RomFS."); - - Ns.LoadCart(args[0]); - } - } - else if (File.Exists(args[0])) - { - Console.WriteLine("Loading as homebrew."); - - Ns.LoadProgram(args[0]); - } - } - else - { - Console.WriteLine("Please specify the folder with the NSOs/IStorage or a NSO/NRO."); - } - - using (GLScreen Screen = new GLScreen(Ns, Renderer)) - { - Ns.Finish += (Sender, Args) => - { - Screen.Exit(); - }; - - Screen.Run(0.0, 60.0); - } - - Environment.Exit(0); - } - } -} diff --git a/Ryujinx/Ui/SaveImporter.cs b/Ryujinx/Ui/SaveImporter.cs new file mode 100644 index 0000000000..b0a5f64336 --- /dev/null +++ b/Ryujinx/Ui/SaveImporter.cs @@ -0,0 +1,218 @@ +using LibHac; +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Shim; +using LibHac.FsSystem; +using LibHac.FsSystem.Save; +using LibHac.Ncm; +using Ryujinx.HLE.Utilities; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Ui +{ + internal class SaveImporter + { + private FileSystemClient FsClient { get; } + private string ImportPath { get; } + + public SaveImporter(string importPath, FileSystemClient destFsClient) + { + ImportPath = importPath; + FsClient = destFsClient; + } + + // Returns the number of saves imported + public int Import() + { + return ImportSaves(FsClient, ImportPath); + } + + private static int ImportSaves(FileSystemClient fsClient, string rootSaveDir) + { + if (!Directory.Exists(rootSaveDir)) + { + return 0; + } + + SaveFinder finder = new SaveFinder(); + finder.FindSaves(rootSaveDir); + + foreach (SaveToImport save in finder.Saves) + { + Result importResult = ImportSave(fsClient, save); + + if (importResult.IsFailure()) + { + throw new HorizonResultException(importResult, $"Error importing save {save.Path}"); + } + } + + return finder.Saves.Count; + } + + private static Result ImportSave(FileSystemClient fs, SaveToImport save) + { + SaveDataAttribute key = save.Attribute; + + Result result = fs.CreateSaveData(key.TitleId, key.UserId, key.TitleId, 0, 0, 0); + if (result.IsFailure()) return result; + + bool isOldMounted = false; + bool isNewMounted = false; + + try + { + result = fs.Register("OldSave".ToU8Span(), new LocalFileSystem(save.Path)); + if (result.IsFailure()) return result; + + isOldMounted = true; + + result = fs.MountSaveData("NewSave".ToU8Span(), key.TitleId, key.UserId); + if (result.IsFailure()) return result; + + isNewMounted = true; + + result = fs.CopyDirectory("OldSave:/", "NewSave:/"); + if (result.IsFailure()) return result; + + result = fs.Commit("NewSave"); + } + finally + { + if (isOldMounted) + { + fs.Unmount("OldSave"); + } + + if (isNewMounted) + { + fs.Unmount("NewSave"); + } + } + + return result; + } + + private class SaveFinder + { + public List Saves { get; } = new List(); + + public void FindSaves(string rootPath) + { + foreach (string subDir in Directory.EnumerateDirectories(rootPath)) + { + if (TryGetUInt64(subDir, out ulong saveDataId)) + { + SearchSaveId(subDir, saveDataId); + } + } + } + + private void SearchSaveId(string path, ulong saveDataId) + { + foreach (string subDir in Directory.EnumerateDirectories(path)) + { + if (TryGetUserId(subDir, out UserId userId)) + { + SearchUser(subDir, saveDataId, userId); + } + } + } + + private void SearchUser(string path, ulong saveDataId, UserId userId) + { + foreach (string subDir in Directory.EnumerateDirectories(path)) + { + if (TryGetUInt64(subDir, out ulong titleId) && TryGetDataPath(subDir, out string dataPath)) + { + SaveDataAttribute attribute = new SaveDataAttribute + { + Type = SaveDataType.SaveData, + UserId = userId, + TitleId = new TitleId(titleId) + }; + + SaveToImport save = new SaveToImport(dataPath, attribute); + + Saves.Add(save); + } + } + } + + private static bool TryGetDataPath(string path, out string dataPath) + { + string committedPath = Path.Combine(path, "0"); + string workingPath = Path.Combine(path, "1"); + + if (Directory.Exists(committedPath) && Directory.EnumerateFileSystemEntries(committedPath).Any()) + { + dataPath = committedPath; + return true; + } + + if (Directory.Exists(workingPath) && Directory.EnumerateFileSystemEntries(workingPath).Any()) + { + dataPath = workingPath; + return true; + } + + dataPath = default; + return false; + } + + private static bool TryGetUInt64(string path, out ulong converted) + { + string name = Path.GetFileName(path); + + if (name.Length == 16) + { + try + { + converted = Convert.ToUInt64(name, 16); + return true; + } + catch { } + } + + converted = default; + return false; + } + + private static bool TryGetUserId(string path, out UserId userId) + { + string name = Path.GetFileName(path); + + if (name.Length == 32) + { + try + { + UInt128 id = new UInt128(name); + + userId = Unsafe.As(ref id); + return true; + } + catch { } + } + + userId = default; + return false; + } + } + + private class SaveToImport + { + public string Path { get; } + public SaveDataAttribute Attribute { get; } + + public SaveToImport(string path, SaveDataAttribute attribute) + { + Path = path; + Attribute = attribute; + } + } + } +} diff --git a/Ryujinx/Ui/SwitchSettings.cs b/Ryujinx/Ui/SwitchSettings.cs new file mode 100644 index 0000000000..8bd164d81f --- /dev/null +++ b/Ryujinx/Ui/SwitchSettings.cs @@ -0,0 +1,499 @@ +using Gtk; +using Ryujinx.Configuration; +using Ryujinx.Configuration.Hid; +using Ryujinx.Configuration.System; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; + +using GUI = Gtk.Builder.ObjectAttribute; + +namespace Ryujinx.Ui +{ + public class SwitchSettings : Window + { + private static ListStore _gameDirsBoxStore; + + private static bool _listeningForKeypress; + +#pragma warning disable CS0649 +#pragma warning disable IDE0044 + [GUI] Window _settingsWin; + [GUI] CheckButton _errorLogToggle; + [GUI] CheckButton _warningLogToggle; + [GUI] CheckButton _infoLogToggle; + [GUI] CheckButton _stubLogToggle; + [GUI] CheckButton _debugLogToggle; + [GUI] CheckButton _fileLogToggle; + [GUI] CheckButton _guestLogToggle; + [GUI] CheckButton _fsAccessLogToggle; + [GUI] Adjustment _fsLogSpinAdjustment; + [GUI] CheckButton _dockedModeToggle; + [GUI] CheckButton _discordToggle; + [GUI] CheckButton _vSyncToggle; + [GUI] CheckButton _multiSchedToggle; + [GUI] CheckButton _fsicToggle; + [GUI] CheckButton _ignoreToggle; + [GUI] CheckButton _directKeyboardAccess; + [GUI] ComboBoxText _systemLanguageSelect; + [GUI] CheckButton _custThemeToggle; + [GUI] Entry _custThemePath; + [GUI] ToggleButton _browseThemePath; + [GUI] Label _custThemePathLabel; + [GUI] TreeView _gameDirsBox; + [GUI] Entry _addGameDirBox; + [GUI] ToggleButton _addDir; + [GUI] ToggleButton _browseDir; + [GUI] ToggleButton _removeDir; + [GUI] Entry _logPath; + [GUI] Entry _graphicsShadersDumpPath; + [GUI] Image _controller1Image; + + [GUI] ComboBoxText _controller1Type; + [GUI] ToggleButton _lStickUp1; + [GUI] ToggleButton _lStickDown1; + [GUI] ToggleButton _lStickLeft1; + [GUI] ToggleButton _lStickRight1; + [GUI] ToggleButton _lStickButton1; + [GUI] ToggleButton _dpadUp1; + [GUI] ToggleButton _dpadDown1; + [GUI] ToggleButton _dpadLeft1; + [GUI] ToggleButton _dpadRight1; + [GUI] ToggleButton _minus1; + [GUI] ToggleButton _l1; + [GUI] ToggleButton _zL1; + [GUI] ToggleButton _rStickUp1; + [GUI] ToggleButton _rStickDown1; + [GUI] ToggleButton _rStickLeft1; + [GUI] ToggleButton _rStickRight1; + [GUI] ToggleButton _rStickButton1; + [GUI] ToggleButton _a1; + [GUI] ToggleButton _b1; + [GUI] ToggleButton _x1; + [GUI] ToggleButton _y1; + [GUI] ToggleButton _plus1; + [GUI] ToggleButton _r1; + [GUI] ToggleButton _zR1; +#pragma warning restore CS0649 +#pragma warning restore IDE0044 + + public SwitchSettings() : this(new Builder("Ryujinx.Ui.SwitchSettings.glade")) { } + + private SwitchSettings(Builder builder) : base(builder.GetObject("_settingsWin").Handle) + { + builder.Autoconnect(this); + + _settingsWin.Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png"); + _controller1Image.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.JoyCon.png", 500, 500); + + //Bind Events + _lStickUp1.Clicked += (sender, args) => Button_Pressed(sender, args, _lStickUp1); + _lStickDown1.Clicked += (sender, args) => Button_Pressed(sender, args, _lStickDown1); + _lStickLeft1.Clicked += (sender, args) => Button_Pressed(sender, args, _lStickLeft1); + _lStickRight1.Clicked += (sender, args) => Button_Pressed(sender, args, _lStickRight1); + _lStickButton1.Clicked += (sender, args) => Button_Pressed(sender, args, _lStickButton1); + _dpadUp1.Clicked += (sender, args) => Button_Pressed(sender, args, _dpadUp1); + _dpadDown1.Clicked += (sender, args) => Button_Pressed(sender, args, _dpadDown1); + _dpadLeft1.Clicked += (sender, args) => Button_Pressed(sender, args, _dpadLeft1); + _dpadRight1.Clicked += (sender, args) => Button_Pressed(sender, args, _dpadRight1); + _minus1.Clicked += (sender, args) => Button_Pressed(sender, args, _minus1); + _l1.Clicked += (sender, args) => Button_Pressed(sender, args, _l1); + _zL1.Clicked += (sender, args) => Button_Pressed(sender, args, _zL1); + _rStickUp1.Clicked += (sender, args) => Button_Pressed(sender, args, _rStickUp1); + _rStickDown1.Clicked += (sender, args) => Button_Pressed(sender, args, _rStickDown1); + _rStickLeft1.Clicked += (sender, args) => Button_Pressed(sender, args, _rStickLeft1); + _rStickRight1.Clicked += (sender, args) => Button_Pressed(sender, args, _rStickRight1); + _rStickButton1.Clicked += (sender, args) => Button_Pressed(sender, args, _rStickButton1); + _a1.Clicked += (sender, args) => Button_Pressed(sender, args, _a1); + _b1.Clicked += (sender, args) => Button_Pressed(sender, args, _b1); + _x1.Clicked += (sender, args) => Button_Pressed(sender, args, _x1); + _y1.Clicked += (sender, args) => Button_Pressed(sender, args, _y1); + _plus1.Clicked += (sender, args) => Button_Pressed(sender, args, _plus1); + _r1.Clicked += (sender, args) => Button_Pressed(sender, args, _r1); + _zR1.Clicked += (sender, args) => Button_Pressed(sender, args, _zR1); + _controller1Type.Changed += (sender, args) => Controller_Changed(sender, args, _controller1Type.ActiveId, _controller1Image); + + //Setup Currents + if (ConfigurationState.Instance.Logger.EnableFileLog) + { + _fileLogToggle.Click(); + } + + if (ConfigurationState.Instance.Logger.EnableError) + { + _errorLogToggle.Click(); + } + + if (ConfigurationState.Instance.Logger.EnableWarn) + { + _warningLogToggle.Click(); + } + + if (ConfigurationState.Instance.Logger.EnableInfo) + { + _infoLogToggle.Click(); + } + + if (ConfigurationState.Instance.Logger.EnableStub) + { + _stubLogToggle.Click(); + } + + if (ConfigurationState.Instance.Logger.EnableDebug) + { + _debugLogToggle.Click(); + } + + if (ConfigurationState.Instance.Logger.EnableGuest) + { + _guestLogToggle.Click(); + } + + if (ConfigurationState.Instance.Logger.EnableFsAccessLog) + { + _fsAccessLogToggle.Click(); + } + + if (ConfigurationState.Instance.System.EnableDockedMode) + { + _dockedModeToggle.Click(); + } + + if (ConfigurationState.Instance.EnableDiscordIntegration) + { + _discordToggle.Click(); + } + + if (ConfigurationState.Instance.Graphics.EnableVsync) + { + _vSyncToggle.Click(); + } + + if (ConfigurationState.Instance.System.EnableMulticoreScheduling) + { + _multiSchedToggle.Click(); + } + + if (ConfigurationState.Instance.System.EnableFsIntegrityChecks) + { + _fsicToggle.Click(); + } + + if (ConfigurationState.Instance.System.IgnoreMissingServices) + { + _ignoreToggle.Click(); + } + + if (ConfigurationState.Instance.Hid.EnableKeyboard) + { + _directKeyboardAccess.Click(); + } + + if (ConfigurationState.Instance.Ui.EnableCustomTheme) + { + _custThemeToggle.Click(); + } + + _systemLanguageSelect.SetActiveId(ConfigurationState.Instance.System.Language.Value.ToString()); + _controller1Type .SetActiveId(ConfigurationState.Instance.Hid.ControllerType.Value.ToString()); + Controller_Changed(null, null, _controller1Type.ActiveId, _controller1Image); + + _lStickUp1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.LeftJoycon.StickUp.ToString(); + _lStickDown1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.LeftJoycon.StickDown.ToString(); + _lStickLeft1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.LeftJoycon.StickLeft.ToString(); + _lStickRight1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.LeftJoycon.StickRight.ToString(); + _lStickButton1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.LeftJoycon.StickButton.ToString(); + _dpadUp1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.LeftJoycon.DPadUp.ToString(); + _dpadDown1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.LeftJoycon.DPadDown.ToString(); + _dpadLeft1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.LeftJoycon.DPadLeft.ToString(); + _dpadRight1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.LeftJoycon.DPadRight.ToString(); + _minus1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.LeftJoycon.ButtonMinus.ToString(); + _l1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.LeftJoycon.ButtonL.ToString(); + _zL1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.LeftJoycon.ButtonZl.ToString(); + _rStickUp1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.RightJoycon.StickUp.ToString(); + _rStickDown1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.RightJoycon.StickDown.ToString(); + _rStickLeft1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.RightJoycon.StickLeft.ToString(); + _rStickRight1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.RightJoycon.StickRight.ToString(); + _rStickButton1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.RightJoycon.StickButton.ToString(); + _a1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.RightJoycon.ButtonA.ToString(); + _b1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.RightJoycon.ButtonB.ToString(); + _x1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.RightJoycon.ButtonX.ToString(); + _y1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.RightJoycon.ButtonY.ToString(); + _plus1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.RightJoycon.ButtonPlus.ToString(); + _r1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.RightJoycon.ButtonR.ToString(); + _zR1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.RightJoycon.ButtonZr.ToString(); + + _custThemePath.Buffer.Text = ConfigurationState.Instance.Ui.CustomThemePath; + _graphicsShadersDumpPath.Buffer.Text = ConfigurationState.Instance.Graphics.ShadersDumpPath; + _fsLogSpinAdjustment.Value = ConfigurationState.Instance.System.FsGlobalAccessLogMode; + + _gameDirsBox.AppendColumn("", new CellRendererText(), "text", 0); + _gameDirsBoxStore = new ListStore(typeof(string)); + _gameDirsBox.Model = _gameDirsBoxStore; + foreach (string gameDir in ConfigurationState.Instance.Ui.GameDirs.Value) + { + _gameDirsBoxStore.AppendValues(gameDir); + } + + if (_custThemeToggle.Active == false) + { + _custThemePath.Sensitive = false; + _custThemePathLabel.Sensitive = false; + _browseThemePath.Sensitive = false; + } + + _logPath.Buffer.Text = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Ryujinx.log"); + + _listeningForKeypress = false; + } + + //Events + private void Button_Pressed(object sender, EventArgs args, ToggleButton button) + { + if (_listeningForKeypress == false) + { + KeyPressEvent += On_KeyPress; + + _listeningForKeypress = true; + + void On_KeyPress(object o, KeyPressEventArgs keyPressed) + { + string key = keyPressed.Event.Key.ToString(); + string capKey = key.First().ToString().ToUpper() + key.Substring(1); + + if (Enum.IsDefined(typeof(Configuration.Hid.Key), capKey)) + { + button.Label = capKey; + } + else if (GdkToOpenTkInput.ContainsKey(key)) + { + button.Label = GdkToOpenTkInput[key]; + } + else + { + button.Label = "Space"; + } + + button.SetStateFlags(0, true); + + KeyPressEvent -= On_KeyPress; + + _listeningForKeypress = false; + } + } + else + { + button.SetStateFlags(0, true); + } + } + + private void Controller_Changed(object sender, EventArgs args, string controllerType, Image controllerImage) + { + switch (controllerType) + { + case "ProController": + controllerImage.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.ProCon.png", 500, 500); + break; + case "NpadLeft": + controllerImage.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.BlueCon.png", 500, 500); + break; + case "NpadRight": + controllerImage.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.RedCon.png", 500, 500); + break; + default: + controllerImage.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.JoyCon.png", 500, 500); + break; + } + } + + private void AddDir_Pressed(object sender, EventArgs args) + { + if (Directory.Exists(_addGameDirBox.Buffer.Text)) + { + _gameDirsBoxStore.AppendValues(_addGameDirBox.Buffer.Text); + } + + _addDir.SetStateFlags(0, true); + } + + private void BrowseDir_Pressed(object sender, EventArgs args) + { + FileChooserDialog fileChooser = new FileChooserDialog("Choose the game directory to add to the list", this, FileChooserAction.SelectFolder, "Cancel", ResponseType.Cancel, "Add", ResponseType.Accept); + + if (fileChooser.Run() == (int)ResponseType.Accept) + { + _gameDirsBoxStore.AppendValues(fileChooser.Filename); + } + + fileChooser.Dispose(); + + _browseDir.SetStateFlags(0, true); + } + + private void RemoveDir_Pressed(object sender, EventArgs args) + { + TreeSelection selection = _gameDirsBox.Selection; + + selection.GetSelected(out TreeIter treeIter); + _gameDirsBoxStore.Remove(ref treeIter); + + _removeDir.SetStateFlags(0, true); + } + + private void CustThemeToggle_Activated(object sender, EventArgs args) + { + _custThemePath.Sensitive = _custThemeToggle.Active; + _custThemePathLabel.Sensitive = _custThemeToggle.Active; + _browseThemePath.Sensitive = _custThemeToggle.Active; + } + + private void BrowseThemeDir_Pressed(object sender, EventArgs args) + { + FileChooserDialog fileChooser = new FileChooserDialog("Choose the theme to load", this, FileChooserAction.Open, "Cancel", ResponseType.Cancel, "Select", ResponseType.Accept); + + fileChooser.Filter = new FileFilter(); + fileChooser.Filter.AddPattern("*.css"); + + if (fileChooser.Run() == (int)ResponseType.Accept) + { + _custThemePath.Buffer.Text = fileChooser.Filename; + } + + fileChooser.Dispose(); + + _browseThemePath.SetStateFlags(0, true); + } + + private void SaveToggle_Activated(object sender, EventArgs args) + { + List gameDirs = new List(); + + _gameDirsBoxStore.GetIterFirst(out TreeIter treeIter); + for (int i = 0; i < _gameDirsBoxStore.IterNChildren(); i++) + { + _gameDirsBoxStore.GetValue(treeIter, i); + + gameDirs.Add((string)_gameDirsBoxStore.GetValue(treeIter, 0)); + + _gameDirsBoxStore.IterNext(ref treeIter); + } + + ConfigurationState.Instance.Logger.EnableError.Value = _errorLogToggle.Active; + ConfigurationState.Instance.Logger.EnableWarn.Value = _warningLogToggle.Active; + ConfigurationState.Instance.Logger.EnableInfo.Value = _infoLogToggle.Active; + ConfigurationState.Instance.Logger.EnableStub.Value = _stubLogToggle.Active; + ConfigurationState.Instance.Logger.EnableDebug.Value = _debugLogToggle.Active; + ConfigurationState.Instance.Logger.EnableGuest.Value = _guestLogToggle.Active; + ConfigurationState.Instance.Logger.EnableFsAccessLog.Value = _fsAccessLogToggle.Active; + ConfigurationState.Instance.Logger.EnableFileLog.Value = _fileLogToggle.Active; + ConfigurationState.Instance.System.EnableDockedMode.Value = _dockedModeToggle.Active; + ConfigurationState.Instance.EnableDiscordIntegration.Value = _discordToggle.Active; + ConfigurationState.Instance.Graphics.EnableVsync.Value = _vSyncToggle.Active; + ConfigurationState.Instance.System.EnableMulticoreScheduling.Value = _multiSchedToggle.Active; + ConfigurationState.Instance.System.EnableFsIntegrityChecks.Value = _fsicToggle.Active; + ConfigurationState.Instance.System.IgnoreMissingServices.Value = _ignoreToggle.Active; + ConfigurationState.Instance.Hid.EnableKeyboard.Value = _directKeyboardAccess.Active; + ConfigurationState.Instance.Ui.EnableCustomTheme.Value = _custThemeToggle.Active; + + ConfigurationState.Instance.Hid.KeyboardControls.Value.LeftJoycon = new NpadKeyboardLeft() + { + StickUp = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _lStickUp1.Label), + StickDown = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _lStickDown1.Label), + StickLeft = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _lStickLeft1.Label), + StickRight = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _lStickRight1.Label), + StickButton = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _lStickButton1.Label), + DPadUp = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _dpadUp1.Label), + DPadDown = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _dpadDown1.Label), + DPadLeft = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _dpadLeft1.Label), + DPadRight = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _dpadRight1.Label), + ButtonMinus = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _minus1.Label), + ButtonL = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _l1.Label), + ButtonZl = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _zL1.Label), + }; + + ConfigurationState.Instance.Hid.KeyboardControls.Value.RightJoycon = new NpadKeyboardRight() + { + StickUp = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _rStickUp1.Label), + StickDown = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _rStickDown1.Label), + StickLeft = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _rStickLeft1.Label), + StickRight = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _rStickRight1.Label), + StickButton = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _rStickButton1.Label), + ButtonA = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _a1.Label), + ButtonB = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _b1.Label), + ButtonX = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _x1.Label), + ButtonY = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _y1.Label), + ButtonPlus = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _plus1.Label), + ButtonR = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _r1.Label), + ButtonZr = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _zR1.Label), + }; + + ConfigurationState.Instance.System.Language.Value = (Language)Enum.Parse(typeof(Language), _systemLanguageSelect.ActiveId); + ConfigurationState.Instance.Hid.ControllerType.Value = (ControllerType)Enum.Parse(typeof(ControllerType), _controller1Type.ActiveId); + ConfigurationState.Instance.Ui.CustomThemePath.Value = _custThemePath.Buffer.Text; + ConfigurationState.Instance.Graphics.ShadersDumpPath.Value = _graphicsShadersDumpPath.Buffer.Text; + ConfigurationState.Instance.Ui.GameDirs.Value = gameDirs; + ConfigurationState.Instance.System.FsGlobalAccessLogMode.Value = (int)_fsLogSpinAdjustment.Value; + + MainWindow.SaveConfig(); + MainWindow.ApplyTheme(); +#pragma warning disable CS4014 + MainWindow.UpdateGameTable(); +#pragma warning restore CS4014 + Dispose(); + } + + private void CloseToggle_Activated(object sender, EventArgs args) + { + Dispose(); + } + + public readonly Dictionary GdkToOpenTkInput = new Dictionary() + { + { "Key_0", "Number0" }, + { "Key_1", "Number1" }, + { "Key_2", "Number2" }, + { "Key_3", "Number3" }, + { "Key_4", "Number4" }, + { "Key_5", "Number5" }, + { "Key_6", "Number6" }, + { "Key_7", "Number7" }, + { "Key_8", "Number8" }, + { "Key_9", "Number9" }, + { "equal", "Plus" }, + { "uparrow", "Up" }, + { "downarrow", "Down" }, + { "leftarrow", "Left" }, + { "rightarrow", "Right" }, + { "Control_L", "ControlLeft" }, + { "Control_R", "ControlRight" }, + { "Shift_L", "ShiftLeft" }, + { "Shift_R", "ShiftRight" }, + { "Alt_L", "AltLeft" }, + { "Alt_R", "AltRight" }, + { "Page_Up", "PageUp" }, + { "Page_Down", "PageDown" }, + { "KP_Enter", "KeypadEnter" }, + { "KP_Up", "Up" }, + { "KP_Down", "Down" }, + { "KP_Left", "Left" }, + { "KP_Right", "Right" }, + { "KP_Divide", "KeypadDivide" }, + { "KP_Multiply", "KeypadMultiply" }, + { "KP_Subtract", "KeypadSubtract" }, + { "KP_Add", "KeypadAdd" }, + { "KP_Decimal", "KeypadDecimal" }, + { "KP_0", "Keypad0" }, + { "KP_1", "Keypad1" }, + { "KP_2", "Keypad2" }, + { "KP_3", "Keypad3" }, + { "KP_4", "Keypad4" }, + { "KP_5", "Keypad5" }, + { "KP_6", "Keypad6" }, + { "KP_7", "Keypad7" }, + { "KP_8", "Keypad8" }, + { "KP_9", "Keypad9" }, + }; + } +} diff --git a/Ryujinx/Ui/SwitchSettings.glade b/Ryujinx/Ui/SwitchSettings.glade new file mode 100644 index 0000000000..cd00625c4c --- /dev/null +++ b/Ryujinx/Ui/SwitchSettings.glade @@ -0,0 +1,1815 @@ + + + + + + 3 + 1 + 10 + + + False + Ryujinx - Settings + True + center + 910 + 790 + dialog + + + + + + False + vertical + 2 + + + False + 5 + 3 + 3 + end + + + Save + True + True + True + + + + False + True + 0 + + + + + Close + True + True + True + + + + False + True + 5 + 1 + + + + + False + False + 0 + + + + + True + True + in + + + True + False + + + True + True + + + True + False + 5 + 10 + 5 + vertical + + + True + False + 5 + 5 + vertical + + + True + False + start + 5 + General + + + + + + False + True + 0 + + + + + True + False + vertical + + + True + False + + + True + False + Change System Language + end + System Language: + + + False + True + 0 + + + + + True + False + Change System Language + 5 + + American English + British English + Canadian French + Chinese + Dutch + French + German + Italian + Japanese + Korean + Latin American Spanish + Portuguese + Russian + Simplified Chinese + Spanish + Taiwanese + Traditional Chinese + + + + False + True + 1 + + + + + False + True + 0 + + + + + Enable Discord Rich Presence + True + True + False + Enables or disables Discord Rich Presense + start + True + + + False + True + 5 + 1 + + + + + True + True + 1 + + + + + False + True + 5 + 1 + + + + + True + False + 5 + 5 + + + False + True + 5 + 2 + + + + + True + False + 5 + 5 + vertical + + + True + False + start + 5 + Game Directories + + + + + + False + True + 0 + + + + + True + False + 10 + 10 + vertical + + + True + True + 10 + in + + + True + True + False + False + + + + + + + + + True + True + 0 + + + + + True + False + + + True + True + Enter a game directroy to add to the list + + + True + True + 0 + + + + + Add + 80 + True + True + True + Add a game directory to the list + 5 + + + + False + True + 1 + + + + + Browse... + 80 + True + True + True + Browse for a game directory + 5 + + + + False + True + 2 + + + + + Remove + 80 + True + True + True + Remove selected game directory + 5 + + + + False + True + 3 + + + + + False + True + 1 + + + + + True + True + 1 + + + + + True + True + 5 + 4 + + + + + True + False + 5 + 5 + + + False + True + 5 + 5 + + + + + True + False + 5 + 5 + vertical + + + True + False + start + 5 + Themes + + + + + + False + True + 0 + + + + + True + False + 10 + 10 + vertical + + + Use Custom Theme + True + True + False + Enable or disable custom themes in the GUI + start + True + + + + False + True + 5 + 1 + + + + + True + False + + + True + False + Path to custom GUI theme + Custom Theme Path: + + + False + True + 5 + 0 + + + + + True + True + Path to custom GUI theme + center + + + True + True + 1 + + + + + Browse... + 80 + True + True + True + Browse for a custom GUI theme + 5 + + + + False + True + 2 + + + + + False + True + 10 + 2 + + + + + False + True + 1 + + + + + False + True + 5 + 6 + + + + + + + True + False + General + + + False + + + + + True + False + vertical + + + True + False + 5 + 5 + + + Enable Docked Mode + True + True + False + Enable or disable Docked Mode + True + + + False + True + 10 + 0 + + + + + Direct Keyboard Access + True + True + False + Enable or disable "direct keyboard access (HID) support" (Provides games access to your keyboard as a text entry device) + True + + + False + False + 10 + 1 + + + + + False + True + 5 + 0 + + + + + True + False + + + False + True + 1 + + + + + True + False + 10 + 10 + 5 + + + True + False + vertical + + + True + False + + + True + False + The primary controller's type + center + 5 + 5 + Controller Type: + + + False + True + 0 + + + + + True + False + The primary controller's type + 5 + 0 + + Handheld + Pro Controller + Paired Joycons + Left Joycon + Right Joycon + + + + True + True + 1 + + + + + False + True + 10 + 0 + + + + + True + False + 2 + 5 + + + True + False + LStick Up + + + 0 + 0 + + + + + True + False + LStick Down + + + 0 + 1 + + + + + True + False + LStick Left + + + 0 + 2 + + + + + True + False + LStick Right + + + 0 + 3 + + + + + True + False + LStick Button + + + 0 + 4 + + + + + True + False + Dpad Up + + + 0 + 5 + + + + + True + False + Dpad Down + + + 0 + 6 + + + + + True + False + Dpad Left + + + 0 + 7 + + + + + True + False + Dpad Right + + + 0 + 8 + + + + + True + False + - + + + 0 + 9 + + + + + True + False + L + + + 0 + 10 + + + + + True + False + ZL + + + 0 + 11 + + + + + True + False + ZR + + + 2 + 11 + + + + + True + False + R + + + 2 + 10 + + + + + True + False + + + + + 2 + 9 + + + + + True + False + Y + + + 2 + 8 + + + + + True + False + X + + + 2 + 7 + + + + + True + False + B + + + 2 + 6 + + + + + True + False + A + + + 2 + 5 + + + + + True + False + RStick Button + + + 2 + 4 + + + + + True + False + RStick Right + + + 2 + 3 + + + + + True + False + RStick Left + + + 2 + 2 + + + + + True + False + RStick Down + + + 2 + 1 + + + + + True + False + RStick Up + + + 2 + 0 + + + + + + True + True + True + + + 1 + 0 + + + + + + True + True + True + + + 1 + 1 + + + + + + True + True + True + + + 1 + 2 + + + + + + True + True + True + + + 1 + 3 + + + + + + True + True + True + + + 1 + 4 + + + + + + True + True + True + + + 1 + 5 + + + + + + True + True + True + + + 1 + 6 + + + + + + True + True + True + + + 1 + 7 + + + + + + True + True + True + + + 1 + 8 + + + + + + True + True + True + + + 1 + 9 + + + + + + True + True + True + + + 1 + 10 + + + + + + True + True + True + + + 1 + 11 + + + + + + True + True + True + + + 3 + 0 + + + + + + True + True + True + + + 3 + 1 + + + + + + True + True + True + + + 3 + 2 + + + + + + True + True + True + + + 3 + 3 + + + + + + True + True + True + + + 3 + 4 + + + + + + True + True + True + + + 3 + 5 + + + + + + True + True + True + + + 3 + 6 + + + + + + True + True + True + + + 3 + 7 + + + + + + True + True + True + + + 3 + 8 + + + + + + True + True + True + + + 3 + 9 + + + + + + True + True + True + + + 3 + 10 + + + + + + True + True + True + + + 3 + 11 + + + + + False + True + 10 + 1 + + + + + False + True + 0 + + + + + True + False + + + True + True + 1 + + + + + False + True + 2 + + + + + 1 + + + + + True + False + Input + + + 1 + False + + + + + True + False + 5 + 10 + 5 + vertical + + + True + False + start + 5 + 5 + vertical + + + True + False + start + 5 + 5 + Core + + + + + + False + True + 0 + + + + + True + False + 10 + 10 + vertical + + + Enable VSync + True + True + False + Enables or disables Vertical Sync + start + 5 + 5 + True + + + False + True + 0 + + + + + Enable Multicore Scheduling + True + True + False + Enables or disables multi-core scheduling of threads + start + 5 + 5 + True + + + False + True + 1 + + + + + Enable FS Integrity Checks + True + True + False + Enables integrity checks on Game content files + start + 5 + 5 + True + + + False + True + 2 + + + + + True + False + + + True + False + Graphics Shaders Dump Path + Graphics Shaders Dump Path: + + + False + True + 5 + 0 + + + + + True + True + Graphics Shaders Dump Path + center + False + + + True + True + 1 + + + + + False + True + 5 + 4 + + + + + True + True + 1 + + + + + False + True + 5 + 0 + + + + + True + False + 5 + 5 + + + False + True + 5 + 1 + + + + + True + False + 5 + 5 + vertical + + + True + False + start + 5 + Logging + + + + + + False + True + 0 + + + + + True + False + 10 + 10 + vertical + + + Enable Logging to File + True + True + False + Enables or disables logging to a file on disk + start + 5 + 5 + True + + + False + True + 0 + + + + + True + False + 5 + 10 + + + True + False + Location of the log file + Log File Location: + + + False + True + 5 + 0 + + + + + True + True + Location of the log file + center + False + False + + + True + True + 1 + + + + + False + True + 5 + 1 + + + + + Enable Debug Logs + True + True + False + Enables printing debug log messages + start + 5 + 5 + True + + + False + True + 2 + + + + + Enable Stub Logs + True + True + False + Enables printing stub log messages + start + 5 + 5 + True + + + False + True + 3 + + + + + Enable Info Logs + True + True + False + Enables printing info log messages + start + 5 + 5 + True + + + False + True + 4 + + + + + Enable Warning Logs + True + True + False + Enables printing warning log messages + start + 5 + 5 + True + + + False + True + 5 + + + + + Enable Error Logs + True + True + False + Enables printing error log messages + start + 5 + 5 + True + + + False + True + 6 + + + + + Enable Guest Logs + True + True + False + Enables printing guest log messages + start + 5 + 5 + True + + + False + True + 7 + + + + + Enable Fs Access Logs + True + True + False + Enables printing fs access log messages + start + 5 + 5 + True + + + False + True + 8 + + + + + True + False + + + True + False + Enables FS access log output to the console. Possible modes are 0-3 + Fs Global Access Log Mode: + + + False + True + 5 + 0 + + + + + True + True + Enables FS access log output to the console. Possible modes are 0-3 + _fsLogSpinAdjustment + + + True + True + 1 + + + + + False + True + 5 + 9 + + + + + True + True + 1 + + + + + False + True + 5 + 2 + + + + + True + False + 5 + 5 + + + False + True + 5 + 3 + + + + + True + False + 5 + 5 + vertical + + + True + False + + + True + False + start + 5 + Hacks + + + + + + False + True + 0 + + + + + True + False + start + 5 + - These may cause instability + + + False + True + 1 + + + + + False + True + 1 + + + + + True + False + 10 + 10 + vertical + + + Ignore Missing Services + True + True + False + Enable or disable ignoring missing services + start + 5 + 5 + True + + + False + True + 0 + + + + + True + True + 2 + + + + + False + True + 5 + 4 + + + + + 2 + + + + + True + False + end + System + + + 2 + False + + + + + + + + + True + True + 1 + + + + + + diff --git a/Ryujinx/Ui/assets/BlueCon.png b/Ryujinx/Ui/assets/BlueCon.png new file mode 100644 index 0000000000..25691957eb Binary files /dev/null and b/Ryujinx/Ui/assets/BlueCon.png differ diff --git a/Ryujinx/Ui/assets/DiscordLogo.png b/Ryujinx/Ui/assets/DiscordLogo.png new file mode 100644 index 0000000000..f3486b99c9 Binary files /dev/null and b/Ryujinx/Ui/assets/DiscordLogo.png differ diff --git a/Ryujinx/Ui/assets/GitHubLogo.png b/Ryujinx/Ui/assets/GitHubLogo.png new file mode 100644 index 0000000000..2e860709ed Binary files /dev/null and b/Ryujinx/Ui/assets/GitHubLogo.png differ diff --git a/Ryujinx/Ui/assets/Icon.png b/Ryujinx/Ui/assets/Icon.png new file mode 100644 index 0000000000..2fc7b0174c Binary files /dev/null and b/Ryujinx/Ui/assets/Icon.png differ diff --git a/Ryujinx/Ui/assets/JoyCon.png b/Ryujinx/Ui/assets/JoyCon.png new file mode 100644 index 0000000000..ec74586376 Binary files /dev/null and b/Ryujinx/Ui/assets/JoyCon.png differ diff --git a/Ryujinx/Ui/assets/NCAIcon.png b/Ryujinx/Ui/assets/NCAIcon.png new file mode 100644 index 0000000000..6d73c8c70c Binary files /dev/null and b/Ryujinx/Ui/assets/NCAIcon.png differ diff --git a/Ryujinx/Ui/assets/NROIcon.png b/Ryujinx/Ui/assets/NROIcon.png new file mode 100644 index 0000000000..bc6b65bf4f Binary files /dev/null and b/Ryujinx/Ui/assets/NROIcon.png differ diff --git a/Ryujinx/Ui/assets/NSOIcon.png b/Ryujinx/Ui/assets/NSOIcon.png new file mode 100644 index 0000000000..8782b3ea69 Binary files /dev/null and b/Ryujinx/Ui/assets/NSOIcon.png differ diff --git a/Ryujinx/Ui/assets/NSPIcon.png b/Ryujinx/Ui/assets/NSPIcon.png new file mode 100644 index 0000000000..d01dc48229 Binary files /dev/null and b/Ryujinx/Ui/assets/NSPIcon.png differ diff --git a/Ryujinx/Ui/assets/PatreonLogo.png b/Ryujinx/Ui/assets/PatreonLogo.png new file mode 100644 index 0000000000..19c7ffbc6b Binary files /dev/null and b/Ryujinx/Ui/assets/PatreonLogo.png differ diff --git a/Ryujinx/Ui/assets/ProCon.png b/Ryujinx/Ui/assets/ProCon.png new file mode 100644 index 0000000000..8563622616 Binary files /dev/null and b/Ryujinx/Ui/assets/ProCon.png differ diff --git a/Ryujinx/Ui/assets/RedCon.png b/Ryujinx/Ui/assets/RedCon.png new file mode 100644 index 0000000000..6094b2e812 Binary files /dev/null and b/Ryujinx/Ui/assets/RedCon.png differ diff --git a/Ryujinx/Ui/assets/TwitterLogo.png b/Ryujinx/Ui/assets/TwitterLogo.png new file mode 100644 index 0000000000..3d01efc091 Binary files /dev/null and b/Ryujinx/Ui/assets/TwitterLogo.png differ diff --git a/Ryujinx/Ui/assets/XCIIcon.png b/Ryujinx/Ui/assets/XCIIcon.png new file mode 100644 index 0000000000..08f783a80d Binary files /dev/null and b/Ryujinx/Ui/assets/XCIIcon.png differ diff --git a/Ryujinx/_schema.json b/Ryujinx/_schema.json new file mode 100644 index 0000000000..f0321f8470 --- /dev/null +++ b/Ryujinx/_schema.json @@ -0,0 +1,976 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://ryujinx.org/_schema/config.json", + "type": "object", + "title": "Ryujinx Configuration Schema", + "required": [ + "graphics_shaders_dump_path", + "logging_enable_debug", + "logging_enable_stub", + "logging_enable_info", + "logging_enable_warn", + "logging_enable_error", + "logging_enable_guest", + "logging_enable_fs_access_log", + "logging_filtered_classes", + "enable_file_log", + "system_language", + "docked_mode", + "enable_vsync", + "enable_multicore_scheduling", + "enable_fs_integrity_checks", + "fs_global_access_log_mode", + "controller_type", + "enable_keyboard", + "keyboard_controls", + "joystick_controls" + ], + "definitions": { + "key": { + "type": "string", + "enum": [ + "ShiftLeft", + "LShift", + "ShiftRight", + "RShift", + "ControlLeft", + "LControl", + "ControlRight", + "RControl", + "AltLeft", + "LAlt", + "AltRight", + "RAlt", + "WinLeft", + "LWin", + "WinRight", + "RWin", + "Menu", + "F1", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "F10", + "F11", + "F12", + "F13", + "F14", + "F15", + "F16", + "F17", + "F18", + "F19", + "F20", + "F21", + "F22", + "F23", + "F24", + "F25", + "F26", + "F27", + "F28", + "F29", + "F30", + "F31", + "F32", + "F33", + "F34", + "F35", + "Up", + "Down", + "Left", + "Right", + "Enter", + "Escape", + "Space", + "Tab", + "BackSpace", + "Back", + "Insert", + "Delete", + "PageUp", + "PageDown", + "Home", + "End", + "CapsLock", + "ScrollLock", + "PrintScreen", + "Pause", + "NumLock", + "Clear", + "Sleep", + "Keypad0", + "Keypad1", + "Keypad2", + "Keypad3", + "Keypad4", + "Keypad5", + "Keypad6", + "Keypad7", + "Keypad8", + "Keypad9", + "KeypadDivide", + "KeypadMultiply", + "KeypadSubtract", + "KeypadMinus", + "KeypadAdd", + "KeypadPlus", + "KeypadDecimal", + "KeypadPeriod", + "KeypadEnter", + "A", + "B", + "C", + "D", + "E", + "F", + "G", + "H", + "I", + "J", + "K", + "L", + "M", + "N", + "O", + "P", + "Q", + "R", + "S", + "T", + "U", + "V", + "W", + "X", + "Y", + "Z", + "Number0", + "Number1", + "Number2", + "Number3", + "Number4", + "Number5", + "Number6", + "Number7", + "Number8", + "Number9", + "Tilde", + "Grave", + "Minus", + "Plus", + "BracketLeft", + "LBracket", + "BracketRight", + "RBracket", + "Semicolon", + "Quote", + "Comma", + "Period", + "Slash", + "BackSlash", + "NonUSBackSlash", + "LastKey" + ] + }, + "input": { + "type": "string", + "enum": [ + "Button0", + "Button1", + "Button2", + "Button3", + "Button4", + "Button5", + "Button6", + "Button7", + "Button8", + "Button9", + "Button10", + "Button11", + "Button12", + "Button13", + "Button14", + "Button15", + "Button16", + "Button17", + "Button18", + "Button19", + "Button20", + "Axis0", + "Axis1", + "Axis2", + "Axis3", + "Axis4", + "Axis5", + "Hat0Up", + "Hat0Down", + "Hat0Left", + "Hat0Right", + "Hat1Up", + "Hat1Down", + "Hat1Left", + "Hat1Right", + "Hat2Up", + "Hat2Down", + "Hat2Left", + "Hat2Right" + ] + } + }, + "properties": { + "graphics_shaders_dump_path": { + "$id": "#/properties/graphics_shaders_dump_path", + "type": "string", + "title": "Graphics Shaders Dump Path", + "description": "Dumps shaders in this local directory", + "default": "", + "examples": [ + "C:\\ShaderDumps" + ] + }, + "logging_enable_debug": { + "$id": "#/properties/logging_enable_debug", + "type": "boolean", + "title": "Logging Enable Debug", + "description": "Enables printing debug log messages", + "default": false, + "examples": [ + true, + false + ] + }, + "logging_enable_stub": { + "$id": "#/properties/logging_enable_stub", + "type": "boolean", + "title": "Logging Enable Stub", + "description": "Enables printing stub log messages", + "default": true, + "examples": [ + true, + false + ] + }, + "logging_enable_info": { + "$id": "#/properties/logging_enable_info", + "type": "boolean", + "title": "Logging Enable Info", + "description": "Enables printing info log messages", + "default": true, + "examples": [ + true, + false + ] + }, + "logging_enable_warn": { + "$id": "#/properties/logging_enable_warn", + "type": "boolean", + "title": "Logging Enable Warn", + "description": "Enables printing warning log messages", + "default": true, + "examples": [ + true, + false + ] + }, + "logging_enable_error": { + "$id": "#/properties/logging_enable_error", + "type": "boolean", + "title": "Logging Enable Error", + "description": "Enables printing error log messages", + "default": true, + "examples": [ + true, + false + ] + }, + "logging_enable_guest": { + "$id": "#/properties/logging_enable_guest", + "type": "boolean", + "title": "Logging Enable Guest", + "description": "Enables printing guest log messages", + "default": true, + "examples": [ + true, + false + ] + }, + "logging_enable_fs_access": { + "$id": "#/properties/logging_enable_fs_access_log", + "type": "boolean", + "title": "Logging Enable FS Access Log", + "description": "Enables printing FS access log messages", + "default": true, + "examples": [ + true, + false + ] + }, + "logging_filtered_classes": { + "$id": "#/properties/logging_filtered_classes", + "type": "array", + "title": "Logging Filtered Classes", + "description": "Controls which log messages are written to the log targets", + "items": { + "type": "string", + "enum": [ + "Application", + "Audio", + "Cpu", + "Font", + "Emulation", + "Gpu", + "Hid", + "Kernel", + "KernelIpc", + "KernelScheduler", + "KernelSvc", + "Loader", + "Service", + "ServiceAcc", + "ServiceAm", + "ServiceApm", + "ServiceAudio", + "ServiceBsd", + "ServiceCaps", + "ServiceFriend", + "ServiceFs", + "ServiceHid", + "ServiceIrs", + "ServiceLdr", + "ServiceLm", + "ServiceMm", + "ServiceNfp", + "ServiceNifm", + "ServiceNs", + "ServiceNv", + "ServicePctl", + "ServicePl", + "ServicePrepo", + "ServicePsm", + "ServiceSet", + "ServiceSfdnsres", + "ServiceSm", + "ServiceSsl", + "ServiceSss", + "ServiceTime", + "ServiceVi" + ] + } + }, + "enable_file_log": { + "$id": "#/properties/enable_file_log", + "type": "boolean", + "title": "Enable File Log", + "description": "Enables logging to a file on disk", + "default": true, + "examples": [ + true, + false + ] + }, + "system_language": { + "$id": "#/properties/system_language", + "type": "string", + "title": "System Language", + "description": "Change System Language", + "default": "AmericanEnglish", + "enum": [ + "Japanese", + "AmericanEnglish", + "French", + "German", + "Italian", + "Spanish", + "Chinese", + "Korean", + "Dutch", + "Portuguese", + "Russian", + "Taiwanese", + "BritishEnglish", + "CanadianFrench", + "LatinAmericanSpanish", + "SimplifiedChinese", + "TraditionalChinese" + ], + "examples": [ + "AmericanEnglish" + ] + }, + "docked_mode": { + "$id": "#/properties/docked_mode", + "type": "boolean", + "title": "Enable Docked Mode", + "description": "Enables or disables Docked Mode", + "default": false, + "examples": [ + true, + false + ] + }, + "enable_discord_integration": { + "$id": "#/properties/enable_discord_integration", + "type": "boolean", + "title": "Enable Discord Rich Presence", + "description": "Enable or disable Discord Rich Presence", + "default": true, + "examples": [ + true, + false + ] + }, + "enable_vsync": { + "$id": "#/properties/enable_vsync", + "type": "boolean", + "title": "Enable Vertical Sync", + "description": "Enables or disables Vertical Sync", + "default": true, + "examples": [ + true, + false + ] + }, + "enable_multicore_scheduling": { + "$id": "#/properties/enable_multicore_scheduling", + "type": "boolean", + "title": "Enable Multicore Scheduling", + "description": "Enables or disables multi-core scheduling of threads", + "default": true, + "examples": [ + true, + false + ] + }, + "enable_fs_integrity_checks": { + "$id": "#/properties/enable_fs_integrity_checks", + "type": "boolean", + "title": "Enable Filesystem Integrity Checks", + "description": "Enables integrity checks on Game content files. Only applies to ROMs loaded as XCI files", + "default": true, + "examples": [ + true, + false + ] + }, + "fs_global_access_log_mode": { + "$id": "#/properties/fs_global_access_log_mode", + "type": "integer", + "title": "Enable FS access log", + "description": "Enables FS access log output. Possible modes are 0-3. Modes 2 and 3 output to the console", + "default": 0, + "minimum": 0, + "examples": [ + 0, + 1, + 2, + 3 + ] + }, + "ignore_missing_services": { + "$id": "#/properties/ignore_missing_services", + "type": "boolean", + "title": "Ignore Missing Services", + "description": "Enable or disable ignoring missing services, this may cause instability", + "default": false, + "examples": [ + true, + false + ] + }, + "game_dirs": { + "$id": "#/properties/game_dirs", + "type": "array", + "title": "List of Game Directories", + "description": "A list of directories containing games to be used to load games into the games list", + "default": [] + }, + "gui_columns": { + "$id": "#/properties/gui_columns", + "type": "array", + "title": "Used to toggle columns in the GUI", + "description": "Used to toggle columns in the GUI", + "default": { + "fav_column": true, + "icon_column": true, + "app_column": true, + "dev_column": true, + "version_column": true, + "time_played_column": true, + "last_played_column": true, + "file_ext_column": true, + "file_size_column": true, + "path_column": true + } + }, + "enable_custom_theme": { + "$id": "#/properties/enable_custom_theme", + "type": "boolean", + "title": "Enable custom themes in the GUI", + "description": "Enable or disable custom themes in the GUI", + "default": false, + "examples": [ + true, + false + ] + }, + "custom_theme_path": { + "$id": "#/properties/custom_theme_path", + "type": "string", + "title": "Path to custom GUI theme", + "description": "Path to custom GUI theme", + "default": "" + }, + "controller_type": { + "$id": "#/properties/controller_type", + "type": "string", + "title": "Controller Type", + "default": "Handheld", + "enum": [ + "Handheld", + "ProController", + "NpadPair", + "NpadLeft", + "NpadRight" + ], + "examples": [ + "Handheld", + "ProController", + "NpadPair", + "NpadLeft", + "NpadRight" + ] + }, + "enable_keyboard": { + "$id": "#/properties/enable_keyboard", + "type": "boolean", + "title": "(HID) Keyboard Enable", + "description": "Enable or disable direct keyboard access (HID) support (Provides games access to your keyboard as a text entry device).", + "default": true, + "examples": [ + true, + false + ] + }, + "keyboard_controls": { + "$id": "#/properties/keyboard_controls", + "type": "object", + "title": "Keyboard Controls", + "required": [ + "left_joycon", + "right_joycon" + ], + "properties": { + "left_joycon": { + "$id": "#/properties/keyboard_controls/properties/left_joycon", + "type": "object", + "title": "Left JoyCon Controls", + "required": [ + "stick_up", + "stick_down", + "stick_left", + "stick_right", + "stick_button", + "dpad_up", + "dpad_down", + "dpad_left", + "dpad_right", + "button_minus", + "button_l", + "button_zl" + ], + "properties": { + "stick_up": { + "$id": "#/properties/keyboard_controls/properties/left_joycon/properties/stick_up", + "$ref": "#/definitions/key", + "title": "Stick Up", + "default": "w" + }, + "stick_down": { + "$id": "#/properties/keyboard_controls/properties/left_joycon/properties/stick_down", + "$ref": "#/definitions/key", + "title": "Stick Down", + "default": "S" + }, + "stick_left": { + "$id": "#/properties/keyboard_controls/properties/left_joycon/properties/stick_left", + "$ref": "#/definitions/key", + "title": "Stick Left", + "default": "A" + }, + "stick_right": { + "$id": "#/properties/keyboard_controls/properties/left_joycon/properties/stick_right", + "$ref": "#/definitions/key", + "title": "Stick Right", + "default": "D" + }, + "stick_button": { + "$id": "#/properties/keyboard_controls/properties/left_joycon/properties/stick_button", + "$ref": "#/definitions/key", + "title": "Stick Button", + "default": "F" + }, + "dpad_up": { + "$id": "#/properties/keyboard_controls/properties/left_joycon/properties/dpad_up", + "$ref": "#/definitions/key", + "title": "Dpad Up", + "default": "Up" + }, + "dpad_down": { + "$id": "#/properties/keyboard_controls/properties/left_joycon/properties/dpad_down", + "$ref": "#/definitions/key", + "title": "Dpad Down", + "default": "Down" + }, + "dpad_left": { + "$id": "#/properties/keyboard_controls/properties/left_joycon/properties/dpad_left", + "$ref": "#/definitions/key", + "title": "Dpad Left", + "default": "Left" + }, + "dpad_right": { + "$id": "#/properties/keyboard_controls/properties/left_joycon/properties/dpad_right", + "$ref": "#/definitions/key", + "title": "Dpad Right", + "default": "Right" + }, + "button_minus": { + "$id": "#/properties/keyboard_controls/properties/left_joycon/properties/button_minus", + "$ref": "#/definitions/key", + "title": "Button Minus", + "default": "Minus" + }, + "button_l": { + "$id": "#/properties/keyboard_controls/properties/left_joycon/properties/button_l", + "$ref": "#/definitions/key", + "title": "Button L", + "default": "E" + }, + "button_zl": { + "$id": "#/properties/keyboard_controls/properties/left_joycon/properties/button_zl", + "$ref": "#/definitions/key", + "title": "Button ZL", + "default": "Q" + } + } + }, + "right_joycon": { + "$id": "#/properties/keyboard_controls/properties/right_joycon", + "type": "object", + "title": "Right JoyCon Controls", + "required": [ + "stick_up", + "stick_down", + "stick_left", + "stick_right", + "stick_button", + "button_a", + "button_b", + "button_x", + "button_y", + "button_plus", + "button_r", + "button_zr" + ], + "properties": { + "stick_up": { + "$id": "#/properties/keyboard_controls/properties/right_joycon/properties/stick_up", + "$ref": "#/definitions/key", + "title": "Stick Up", + "default": "I" + }, + "stick_down": { + "$id": "#/properties/keyboard_controls/properties/right_joycon/properties/stick_down", + "$ref": "#/definitions/key", + "title": "Stick Down", + "default": "K" + }, + "stick_left": { + "$id": "#/properties/keyboard_controls/properties/right_joycon/properties/stick_left", + "$ref": "#/definitions/key", + "title": "Stick Left", + "default": "J" + }, + "stick_right": { + "$id": "#/properties/keyboard_controls/properties/right_joycon/properties/stick_right", + "$ref": "#/definitions/key", + "title": "Stick Right", + "default": "L" + }, + "stick_button": { + "$id": "#/properties/keyboard_controls/properties/right_joycon/properties/stick_button", + "$ref": "#/definitions/key", + "title": "Stick Button", + "default": "H" + }, + "button_a": { + "$id": "#/properties/keyboard_controls/properties/right_joycon/properties/button_a", + "$ref": "#/definitions/key", + "title": "Button A", + "default": "Z" + }, + "button_b": { + "$id": "#/properties/keyboard_controls/properties/right_joycon/properties/button_b", + "$ref": "#/definitions/key", + "title": "Button B", + "default": "X" + }, + "button_x": { + "$id": "#/properties/keyboard_controls/properties/right_joycon/properties/button_x", + "$ref": "#/definitions/key", + "title": "Button X", + "default": "C" + }, + "button_y": { + "$id": "#/properties/keyboard_controls/properties/right_joycon/properties/button_y", + "$ref": "#/definitions/key", + "title": "Button Y", + "default": "V" + }, + "button_plus": { + "$id": "#/properties/keyboard_controls/properties/right_joycon/properties/button_plus", + "$ref": "#/definitions/key", + "title": "Button Plus", + "default": "Plus" + }, + "button_r": { + "$id": "#/properties/keyboard_controls/properties/right_joycon/properties/button_r", + "$ref": "#/definitions/key", + "title": "Button R", + "default": "U" + }, + "button_zr": { + "$id": "#/properties/keyboard_controls/properties/right_joycon/properties/button_zr", + "$ref": "#/definitions/key", + "title": "Button Zr", + "default": "O" + } + } + }, + "hotkeys": { + "$id": "#/properties/keyboard_controls/properties/hotkeys", + "type": "object", + "title": "Hotkey Controls", + "required": [ + "toggle_vsync" + ], + "properties": { + "toggle_vsync": { + "$id": "#/properties/keyboard_controls/properties/hotkeys/properties/toggle_vsync", + "$ref": "#/definitions/key", + "title": "Toggle VSync", + "default": "Tab" + } + } + } + } + }, + "joystick_controls": { + "$id": "#/properties/joystick_controls", + "type": "object", + "title": "Joystick Controls", + "required": [ + "left_joycon", + "right_joycon" + ], + "properties": { + "enable": { + "$id": "#/properties/joystick_controls/properties/enable", + "type": "boolean", + "title": "Joystick Enable", + "description": "Enables or disables controller support", + "default": true, + "examples": [ + true, + false + ] + }, + "index": { + "$id": "#/properties/joystick_controls/properties/index", + "type": "integer", + "title": "Joystick Index", + "description": "Controller Device Index", + "default": 0, + "minimum": 0, + "examples": [ + 0, + 1, + 2 + ] + }, + "deadzone": { + "$id": "#/properties/joystick_controls/properties/deadzone", + "type": "number", + "title": "Joystick Deadzone", + "description": "Controller Analog Stick Deadzone", + "default": 0.05, + "minimum": -32768.0, + "maximum": 32767.0, + "examples": [ + 0.05 + ] + }, + "trigger_threshold": { + "$id": "#/properties/joystick_controls/properties/trigger_threshold", + "type": "number", + "title": "Controller Trigger Threshold", + "description": "The value of how pressed down each trigger has to be in order to register a button press", + "default": 0.5, + "minimum": 0.0, + "maximum": 1.0, + "examples": [ + 0.5 + ] + }, + "left_joycon": { + "$id": "#/properties/joystick_controls/properties/left_joycon", + "type": "object", + "title": "Left JoyCon Controls", + "required": [ + "stick", + "stick_button", + "dpad_up", + "dpad_down", + "dpad_left", + "dpad_right", + "button_minus", + "button_l", + "button_zl" + ], + "properties": { + "stick": { + "$id": "#/properties/joystick_controls/properties/left_joycon/properties/stick", + "$ref": "#/definitions/input", + "title": "Stick", + "default": "Axis0" + }, + "stick_button": { + "$id": "#/properties/joystick_controls/properties/left_joycon/properties/stick_button", + "$ref": "#/definitions/input", + "title": "Stick Button", + "default": "Button13" + }, + "dpad_up": { + "$id": "#/properties/joystick_controls/properties/left_joycon/properties/dpad_up", + "$ref": "#/definitions/input", + "title": "Dpad Up", + "default": "Hat0Up" + }, + "dpad_down": { + "$id": "#/properties/joystick_controls/properties/left_joycon/properties/dpad_down", + "$ref": "#/definitions/input", + "title": "Dpad Down", + "default": "Hat0Down" + }, + "dpad_left": { + "$id": "#/properties/joystick_controls/properties/left_joycon/properties/dpad_left", + "$ref": "#/definitions/input", + "title": "Dpad Left", + "default": "Hat0Left" + }, + "dpad_right": { + "$id": "#/properties/joystick_controls/properties/left_joycon/properties/dpad_right", + "$ref": "#/definitions/input", + "title": "Dpad Right", + "default": "Hat0Right" + }, + "button_minus": { + "$id": "#/properties/joystick_controls/properties/left_joycon/properties/button_minus", + "$ref": "#/definitions/input", + "title": "Button Minus", + "default": "Button10" + }, + "button_l": { + "$id": "#/properties/joystick_controls/properties/left_joycon/properties/button_l", + "$ref": "#/definitions/input", + "title": "Button L", + "default": "Button6" + }, + "button_zl": { + "$id": "#/properties/joystick_controls/properties/left_joycon/properties/button_zl", + "$ref": "#/definitions/input", + "title": "Button ZL", + "default": "Button8" + } + } + }, + "right_joycon": { + "$id": "#/properties/joystick_controls/properties/right_joycon", + "type": "object", + "title": "Right JoyCon Controls", + "required": [ + "stick", + "stick_button", + "button_a", + "button_b", + "button_x", + "button_y", + "button_plus", + "button_r", + "button_zr" + ], + "properties": { + "stick": { + "$id": "#/properties/joystick_controls/properties/right_joycon/properties/stick", + "$ref": "#/definitions/input", + "title": "Stick", + "default": "Axis2" + }, + "stick_button": { + "$id": "#/properties/joystick_controls/properties/right_joycon/properties/stick_button", + "$ref": "#/definitions/input", + "title": "Stick Button", + "default": "Button14" + }, + "button_a": { + "$id": "#/properties/joystick_controls/properties/right_joycon/properties/button_a", + "$ref": "#/definitions/input", + "title": "Button A", + "default": "Button0" + }, + "button_b": { + "$id": "#/properties/joystick_controls/properties/right_joycon/properties/button_b", + "$ref": "#/definitions/input", + "title": "Button B", + "default": "Button1" + }, + "button_x": { + "$id": "#/properties/joystick_controls/properties/right_joycon/properties/button_x", + "$ref": "#/definitions/input", + "title": "Button X", + "default": "Button3" + }, + "button_y": { + "$id": "#/properties/joystick_controls/properties/right_joycon/properties/button_y", + "$ref": "#/definitions/input", + "title": "Button Y", + "default": "Button4" + }, + "button_plus": { + "$id": "#/properties/joystick_controls/properties/right_joycon/properties/button_plus", + "$ref": "#/definitions/input", + "title": "Button Plus", + "default": "Button11" + }, + "button_r": { + "$id": "#/properties/joystick_controls/properties/right_joycon/properties/button_r", + "$ref": "#/definitions/input", + "title": "Button R", + "default": "Button7" + }, + "button_zr": { + "$id": "#/properties/joystick_controls/properties/right_joycon/properties/button_zr", + "$ref": "#/definitions/input", + "title": "Button ZR", + "default": "Button9" + } + } + } + } + } + } +} diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000000..73f4654d66 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,34 @@ +version: 1.0.{build} +branches: + only: + - master +image: Visual Studio 2019 +environment: + matrix: + - config: Release + config_name: '-' + + - config: Profile Release + config_name: '-profiled-' +build_script: +- ps: >- + dotnet --version + + dotnet publish -c $env:config -r win-x64 + + dotnet publish -c $env:config -r linux-x64 + + dotnet publish -c $env:config -r osx-x64 + + 7z a ryujinx$env:config_name$env:APPVEYOR_BUILD_VERSION-win_x64.zip $env:APPVEYOR_BUILD_FOLDER\Ryujinx\bin\$env:config\netcoreapp3.0\win-x64\publish\ + + 7z a ryujinx$env:config_name$env:APPVEYOR_BUILD_VERSION-linux_x64.tar $env:APPVEYOR_BUILD_FOLDER\Ryujinx\bin\$env:config\netcoreapp3.0\linux-x64\publish\ + + 7z a ryujinx$env:config_name$env:APPVEYOR_BUILD_VERSION-linux_x64.tar.gz ryujinx$env:config_name$env:APPVEYOR_BUILD_VERSION-linux_x64.tar + + 7z a ryujinx$env:config_name$env:APPVEYOR_BUILD_VERSION-osx_x64.zip $env:APPVEYOR_BUILD_FOLDER\Ryujinx\bin\$env:config\netcoreapp3.0\osx-x64\publish\ + +artifacts: +- path: ryujinx%config_name%%APPVEYOR_BUILD_VERSION%-win_x64.zip +- path: ryujinx%config_name%%APPVEYOR_BUILD_VERSION%-linux_x64.tar.gz +- path: ryujinx%config_name%%APPVEYOR_BUILD_VERSION%-osx_x64.zip