Compare commits
5 Commits
Canary-1.3
...
Loong64
| Author | SHA1 | Date | |
|---|---|---|---|
| 597556cc59 | |||
| c1f7a5eabe | |||
| 100a9d3546 | |||
| caf0f58fb0 | |||
| ba99106348 |
10
.claude/settings.local.json
Normal file
10
.claude/settings.local.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(dir:*)",
|
||||
"Bash(wc:*)",
|
||||
"Bash(for f in src/ARMeilleure/CodeGen/X86/*.cs)",
|
||||
"Bash(done)"
|
||||
]
|
||||
}
|
||||
}
|
||||
182
CLAUDE.md
Normal file
182
CLAUDE.md
Normal file
@ -0,0 +1,182 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
Ryujinx is an open-source Nintendo Switch emulator written in C#. This is a community fork (Ryubing/Ryujinx) focused on Quality of Life improvements for existing users. The emulator provides high-accuracy emulation of the Nintendo Switch's ARMv8 CPU and Maxwell GPU.
|
||||
|
||||
## Build Commands
|
||||
|
||||
### Building the Project
|
||||
```bash
|
||||
dotnet build -c Release -o build
|
||||
```
|
||||
|
||||
### Code Style Enforcement
|
||||
Run `dotnet format` before committing to ensure code style compliance:
|
||||
```bash
|
||||
dotnet format
|
||||
```
|
||||
|
||||
### Running Tests
|
||||
```bash
|
||||
# Run all tests
|
||||
dotnet test
|
||||
|
||||
# Run specific test project
|
||||
dotnet test src/Ryujinx.Tests/Ryujinx.Tests.csproj
|
||||
dotnet test src/Ryujinx.Tests.Memory/Ryujinx.Tests.Memory.csproj
|
||||
dotnet test src/Ryujinx.Tests.Unicorn/Ryujinx.Tests.Unicorn.csproj
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
- .NET 9.0 SDK or higher (version 9.0.100 specified in `global.json`)
|
||||
- Minimum 8GB RAM for optimal performance
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
### Three-Layer Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ UI Layer (Avalonia MVVM) │ src/Ryujinx (GUI)
|
||||
│ Headless Mode (SDL2) │ src/Ryujinx.Headless.SDL2
|
||||
└────────────────────┬────────────────┘
|
||||
│
|
||||
┌────────────────────┴────────────────┐
|
||||
│ High-Level Emulation (HLE) │
|
||||
│ - Ryujinx.HLE │ Horizon OS services emulation
|
||||
│ - Ryujinx.Horizon │ System services implementation
|
||||
└────────────────────┬────────────────┘
|
||||
│
|
||||
┌────────────────────┴────────────────┐
|
||||
│ Core Emulation │
|
||||
│ - ARMeilleure (CPU) │ ARM instruction translation
|
||||
│ - Ryujinx.Graphics.Gpu │ Maxwell GPU emulation
|
||||
│ - Ryujinx.Audio │ Audio engine
|
||||
└────────────────────┬────────────────┘
|
||||
│
|
||||
┌────────────────────┴────────────────┐
|
||||
│ Backend Implementations │
|
||||
│ - OpenGL/Vulkan/Metal (Graphics) │
|
||||
│ - OpenAL/SDL2/SoundIO (Audio) │
|
||||
│ - SDL2 (Input) │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Key Components
|
||||
|
||||
**ARMeilleure (src/ARMeilleure/)**
|
||||
- CPU emulator that translates ARMv8 instructions to x86/ARM64/Loong64 machine code
|
||||
- Translation pipeline: ARM decode → Custom IR → Optimizations → Code generation
|
||||
- Supports Profiled Persistent Translation Cache (PPTC) for faster load times
|
||||
- Code generation backends in `CodeGen/` for different architectures
|
||||
|
||||
**Ryujinx.HLE (src/Ryujinx.HLE/)**
|
||||
- High-Level Emulation of Nintendo Switch OS (Horizon OS)
|
||||
- `HOS/Services/` contains system service implementations (am, audio, fs, hid, etc.)
|
||||
- `HOS/Kernel/` handles thread/process management
|
||||
- `Loaders/` support ELF, NSO, NRO executable formats
|
||||
- `Debugger/` provides GDB protocol support
|
||||
|
||||
**Graphics Stack**
|
||||
- `Ryujinx.Graphics.Gpu/` - Main GPU emulation engine (Maxwell architecture)
|
||||
- `Engine/` subdirectory contains graphics command processors (Compute, DMA, GPFifo, Threed, etc.)
|
||||
- `Shader/` handles shader compilation and disk caching
|
||||
- `Ryujinx.Graphics.GAL/` - Graphics Abstraction Layer (backend-agnostic interface)
|
||||
- Backend implementations: OpenGL, Vulkan, Metal (via MoltenVK on macOS)
|
||||
- `Ryujinx.Graphics.Shader/` - Shader translation to GLSL/SPIR-V
|
||||
|
||||
**Memory Management (src/Ryujinx.Memory/)**
|
||||
- Three memory manager modes: checked (slowest), untuned, host (default/fastest)
|
||||
- Balances emulation accuracy vs. performance
|
||||
|
||||
**UI (src/Ryujinx/)**
|
||||
- Built with Avalonia (cross-platform XAML framework)
|
||||
- MVVM pattern using CommunityToolkit.Mvvm
|
||||
- UI structure: `UI/Views/`, `UI/ViewModels/`, `UI/Windows/`, `UI/Controls/`
|
||||
- Locale management with auto-generated code (`Ryujinx.UI.LocaleGenerator`)
|
||||
|
||||
### Code Generation
|
||||
|
||||
Several components use source generators:
|
||||
- `Ryujinx.HLE.Generators` - Generates HLE service stubs
|
||||
- `Ryujinx.Horizon.Generators` - Generates Horizon OS bindings
|
||||
- `Ryujinx.Horizon.Kernel.Generators` - Generates kernel syscalls
|
||||
- `Ryujinx.UI.LocaleGenerator` - Generates locale string bindings
|
||||
- `Spv.Generator` - Generates SPIR-V shader code for Vulkan
|
||||
|
||||
## Coding Guidelines
|
||||
|
||||
Follow the C# coding style defined in `.editorconfig` and `docs/coding-guidelines/coding-style.md`:
|
||||
|
||||
- **Braces**: Allman style (braces on new lines)
|
||||
- **Indentation**: 4 spaces, no tabs
|
||||
- **Fields**: `_camelCase` for private instance, `s_` prefix for static, `t_` for thread-static
|
||||
- **Visibility**: Always specify explicitly (e.g., `private`, `public`)
|
||||
- **var**: Only use when type is explicitly named on right-hand side
|
||||
- **Imports**: At top of file, outside namespace declarations
|
||||
- **Types**: Make internal/private types static or sealed unless derivation needed
|
||||
- **Magic Numbers**: Define as named constants
|
||||
|
||||
Always run `dotnet format` before committing.
|
||||
|
||||
## Development Workflow
|
||||
|
||||
1. Create a branch off `master` (the main branch, not `main`)
|
||||
2. Make changes and ensure `dotnet format` is run
|
||||
3. Build with `dotnet build -c Release` and verify clean build
|
||||
4. Run relevant tests
|
||||
5. Submit PR against `master` branch
|
||||
6. Two team members must review and approve before merge
|
||||
7. CI runs automatically via GitHub Actions (`.github/workflows/`)
|
||||
|
||||
## Important Project Patterns
|
||||
|
||||
**Abstraction Layers**: The project uses abstraction layers extensively to support multiple backends:
|
||||
- Graphics backends (OpenGL/Vulkan/Metal)
|
||||
- Audio backends (OpenAL/SDL2/SoundIO)
|
||||
- Input systems (SDL2)
|
||||
- Memory management modes
|
||||
|
||||
**Translation Caching**: ARMeilleure uses a persistent translation cache that significantly improves load times after the third launch of a game.
|
||||
|
||||
**Disk Shader Caching**: Compiled shaders are cached to disk to avoid recompilation (`Ryujinx.Graphics.Gpu/Shader/DiskCache/`).
|
||||
|
||||
**Platform-Specific Code**: The codebase handles Windows, macOS, and Linux with platform-specific implementations where needed.
|
||||
|
||||
## Common File Locations
|
||||
|
||||
- **System Files**: User folder (accessible via GUI: File → Open Ryujinx Folder)
|
||||
- **Logs**: `[Executable Folder]/Logs` - chronologically named
|
||||
- **Configuration**: `Config.json` in Ryujinx data folder
|
||||
- **Build Output**: `build/` directory after running build command
|
||||
|
||||
## Branches and Releases
|
||||
|
||||
- **master**: Main branch for stable releases (released monthly)
|
||||
- **canary**: Automated builds for every commit on master (may be unstable)
|
||||
- Current working branch is **Loong64** (architecture support feature branch)
|
||||
|
||||
## When Working With Specific Systems
|
||||
|
||||
**Adding/Modifying HLE Services**: Follow the service implementation guidelines at https://gist.github.com/gdkchan/84ba88cd50efbe58d1babfaa7cd7c455
|
||||
|
||||
**Graphics Work**: Understand the GPU command engine structure in `Ryujinx.Graphics.Gpu/Engine/` - each engine (Compute, DMA, Threed, Twod) handles specific GPU operations.
|
||||
|
||||
**CPU Instruction Support**: ARM instruction decoding happens in `ARMeilleure/Decoders/`, implementation in `Instructions/`, and code generation in `CodeGen/`.
|
||||
|
||||
**UI Changes**: Follow MVVM pattern - Views in `UI/Views/`, logic in `UI/ViewModels/`. Use FluentAvaloniaUI controls for consistency.
|
||||
|
||||
## Third-Party Dependencies
|
||||
|
||||
The project uses several key libraries:
|
||||
- **LibHac**: Nintendo Switch filesystem support
|
||||
- **Avalonia**: Cross-platform UI framework
|
||||
- **Silk.NET**: Graphics API bindings
|
||||
- **SDL2**: Audio/Input/Windowing
|
||||
- **SharpZipLib**: Archive handling
|
||||
|
||||
Contributions using code from other projects must follow permissive licenses and be properly attributed in `distribution/legal/THIRDPARTY.md`.
|
||||
132
src/ARMeilleure/CodeGen/Loong64/Assembler.cs
Normal file
132
src/ARMeilleure/CodeGen/Loong64/Assembler.cs
Normal file
@ -0,0 +1,132 @@
|
||||
using ARMeilleure.IntermediateRepresentation;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using static ARMeilleure.IntermediateRepresentation.Operand;
|
||||
|
||||
namespace ARMeilleure.CodeGen.Loong64
|
||||
{
|
||||
class Assembler
|
||||
{
|
||||
private readonly Stream _stream;
|
||||
|
||||
public Assembler(Stream stream)
|
||||
{
|
||||
_stream = stream;
|
||||
}
|
||||
|
||||
public void Add(Operand rd, Operand rj, Operand rk)
|
||||
{
|
||||
if (rk.Kind == OperandKind.Constant)
|
||||
{
|
||||
Addi(rd, rj, rk.AsInt32());
|
||||
}
|
||||
else
|
||||
{
|
||||
AddReg(rd, rj, rk);
|
||||
}
|
||||
}
|
||||
|
||||
public void B(int imm)
|
||||
{
|
||||
WriteUInt32(0x50000000 | EncodeSImm26_2(imm));
|
||||
}
|
||||
|
||||
public void Addi(Operand rd, Operand rj, int imm)
|
||||
{
|
||||
if (rd.Type == OperandType.I64)
|
||||
{
|
||||
WriteUInt32(0x02c00000u | EncodeReg(rd) | EncodeReg(rj) << 5 | EncodeSImm12(imm) << 10);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteUInt32(0x02800000u | EncodeReg(rd) | EncodeReg(rj) << 5 | EncodeSImm12(imm) << 10);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddReg(Operand rd, Operand rj, Operand rk)
|
||||
{
|
||||
if (rd.Type == OperandType.I64)
|
||||
{
|
||||
WriteUInt32(0x00108000u | EncodeReg(rd) | EncodeReg(rj) << 5 | EncodeReg(rk) << 10);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteUInt32(0x00100000u | EncodeReg(rd) | EncodeReg(rj) << 5 | EncodeReg(rk) << 10);
|
||||
}
|
||||
}
|
||||
|
||||
private static uint EncodeSImm12(int value)
|
||||
{
|
||||
uint imm = (uint)value & 0xfff;
|
||||
Debug.Assert(((int)imm << 20) >> 20 == value, $"Failed to encode constant 0x{value:X}.");
|
||||
return imm;
|
||||
}
|
||||
|
||||
private static uint EncodeSImm26_2(int value)
|
||||
{
|
||||
int shifted = value >> 2;
|
||||
|
||||
uint immLo = (uint)shifted & 0xFFFF; // imm[15:0]
|
||||
uint immHi = ((uint)shifted >> 16) & 0x3FF; // imm[25:16]
|
||||
|
||||
uint encoded =
|
||||
(immLo << 10) | // inst[25:10]
|
||||
(immHi << 0); // inst[9:0]
|
||||
|
||||
uint recon =
|
||||
((encoded & 0x3FF) << 16) | // inst[9:0] -> imm[25:16]
|
||||
((encoded >> 10) & 0xFFFF); // inst[25:10] -> imm[15:0]
|
||||
|
||||
int decoded = (int)(recon << 6) >> 4;
|
||||
|
||||
Debug.Assert(decoded == value, $"Failed to encode constant 0x{value:X}.");
|
||||
|
||||
return encoded;
|
||||
}
|
||||
|
||||
private static uint EncodeReg(Operand reg)
|
||||
{
|
||||
if (reg.Kind == OperandKind.Constant && reg.Value == 0)
|
||||
{
|
||||
return (uint)Loong64Register.zero;
|
||||
}
|
||||
|
||||
uint regIndex = (uint)reg.GetRegister().Index;
|
||||
Debug.Assert(reg.Kind == OperandKind.Register);
|
||||
Debug.Assert(regIndex < 32);
|
||||
return regIndex;
|
||||
}
|
||||
|
||||
#pragma warning disable IDE0051 // Remove unused private member
|
||||
private void WriteInt16(short value)
|
||||
{
|
||||
WriteUInt16((ushort)value);
|
||||
}
|
||||
|
||||
private void WriteInt32(int value)
|
||||
{
|
||||
WriteUInt32((uint)value);
|
||||
}
|
||||
|
||||
private void WriteByte(byte value)
|
||||
{
|
||||
_stream.WriteByte(value);
|
||||
}
|
||||
#pragma warning restore IDE0051
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
100
src/ARMeilleure/CodeGen/Loong64/CallingConvention.cs
Normal file
100
src/ARMeilleure/CodeGen/Loong64/CallingConvention.cs
Normal file
@ -0,0 +1,100 @@
|
||||
using System;
|
||||
|
||||
namespace ARMeilleure.CodeGen.Loong64
|
||||
{
|
||||
static class CallingConvention
|
||||
{
|
||||
// 寄存器掩码 (32位整数,每位代表一个寄存器)
|
||||
private const int RegistersMask = unchecked((int)0xffffffff);
|
||||
|
||||
private const int ReservedRegsMask = (1 << (int)Loong64Register.zero) | (1 << (int)Loong64Register.ra) | (1 << (int)Loong64Register.tp) | (1 << (int)Loong64Register.sp) | (1 << (int)Loong64Register.r21) | (1 << (int)Loong64Register.fp);
|
||||
|
||||
public static int GetIntAvailableRegisters()
|
||||
{
|
||||
// 返回可用的通用寄存器掩码
|
||||
return RegistersMask & ~ReservedRegsMask;
|
||||
}
|
||||
|
||||
public static int GetVecAvailableRegisters()
|
||||
{
|
||||
// 返回可用的浮点/向量寄存器掩码
|
||||
return RegistersMask;
|
||||
}
|
||||
|
||||
public static int GetIntCallerSavedRegisters()
|
||||
{
|
||||
// 调用者保存的寄存器 (临时寄存器)
|
||||
return (GetIntCalleeSavedRegisters() ^ RegistersMask) & ~ReservedRegsMask;
|
||||
}
|
||||
|
||||
public static int GetFpCallerSavedRegisters()
|
||||
{
|
||||
return GetFpCalleeSavedRegisters() ^ RegistersMask;
|
||||
}
|
||||
|
||||
public static int GetVecCallerSavedRegisters()
|
||||
{
|
||||
return GetVecCalleeSavedRegisters() ^ RegistersMask;
|
||||
}
|
||||
|
||||
public static int GetIntCalleeSavedRegisters()
|
||||
{
|
||||
// 被调用者保存的寄存器 (需要保存/恢复)
|
||||
return unchecked((int)0xff000000);
|
||||
}
|
||||
|
||||
public static int GetFpCalleeSavedRegisters()
|
||||
{
|
||||
return unchecked((int)0xff000000); // f24 to f31
|
||||
}
|
||||
|
||||
public static int GetVecCalleeSavedRegisters()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static int GetArgumentsOnRegsCount()
|
||||
{
|
||||
return 8;
|
||||
}
|
||||
|
||||
// 参数寄存器 (Loong64: A0-A7 用于参数)
|
||||
public static int GetIntArgumentRegister(int index)
|
||||
{
|
||||
// index 0-7 → A0-A7 寄存器
|
||||
if ((uint)index < (uint)GetArgumentsOnRegsCount())
|
||||
{
|
||||
return index;
|
||||
}
|
||||
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
}
|
||||
|
||||
public static int GetVecArgumentRegister(int index)
|
||||
{
|
||||
// index 0-7 → FA0-FA7 寄存器
|
||||
if ((uint)index < (uint)GetArgumentsOnRegsCount())
|
||||
{
|
||||
return index;
|
||||
}
|
||||
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
}
|
||||
|
||||
// 返回值寄存器
|
||||
public static int GetIntReturnRegister()
|
||||
{
|
||||
return (int)Loong64Register.a0; // 返回值放在 A0
|
||||
}
|
||||
|
||||
public static int GetIntReturnRegisterHigh()
|
||||
{
|
||||
return (int)Loong64Register.a1; // 64位返回值高位
|
||||
}
|
||||
|
||||
public static int GetVecReturnRegister()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
9
src/ARMeilleure/CodeGen/Loong64/CodeGenCommon.cs
Normal file
9
src/ARMeilleure/CodeGen/Loong64/CodeGenCommon.cs
Normal file
@ -0,0 +1,9 @@
|
||||
using ARMeilleure.IntermediateRepresentation;
|
||||
|
||||
namespace ARMeilleure.CodeGen.Loong64
|
||||
{
|
||||
static class CodeGenCommon
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
196
src/ARMeilleure/CodeGen/Loong64/CodeGenContext.cs
Normal file
196
src/ARMeilleure/CodeGen/Loong64/CodeGenContext.cs
Normal file
@ -0,0 +1,196 @@
|
||||
using ARMeilleure.CodeGen.Linking;
|
||||
using ARMeilleure.CodeGen.RegisterAllocators;
|
||||
using ARMeilleure.IntermediateRepresentation;
|
||||
using Microsoft.IO;
|
||||
using Ryujinx.Common.Memory;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace ARMeilleure.CodeGen.Loong64
|
||||
{
|
||||
class CodeGenContext
|
||||
{
|
||||
private const int BranchInstLength = 4;
|
||||
|
||||
private readonly RecyclableMemoryStream _stream;
|
||||
|
||||
public int StreamOffset => (int)_stream.Length;
|
||||
|
||||
public AllocationResult AllocResult { get; }
|
||||
|
||||
public Assembler Assembler { get; }
|
||||
|
||||
public BasicBlock CurrBlock { get; private set; }
|
||||
|
||||
public bool HasCall { get; }
|
||||
|
||||
public int CallArgsRegionSize { get; }
|
||||
|
||||
private readonly Dictionary<BasicBlock, long> _visitedBlocks;
|
||||
private readonly Dictionary<BasicBlock, List<PendingBranch>> _pendingBranches;
|
||||
|
||||
private readonly bool _relocatable;
|
||||
|
||||
private readonly struct PendingBranch
|
||||
{
|
||||
public readonly Comparison ComparisonType;
|
||||
public readonly Operand? Operand1;
|
||||
public readonly Operand? Operand2;
|
||||
public readonly long BranchPosition;
|
||||
|
||||
public PendingBranch(Comparison compType, Operand? op1, Operand? op2, long pos)
|
||||
{
|
||||
ComparisonType = compType;
|
||||
Operand1 = op1;
|
||||
Operand2 = op2;
|
||||
BranchPosition = pos;
|
||||
}
|
||||
}
|
||||
|
||||
public CodeGenContext(AllocationResult allocResult, int maxCallArgs, bool relocatable)
|
||||
{
|
||||
_stream = MemoryStreamManager.Shared.GetStream();
|
||||
|
||||
AllocResult = allocResult;
|
||||
|
||||
Assembler = new Assembler(_stream);
|
||||
|
||||
bool hasCall = maxCallArgs >= 0;
|
||||
|
||||
HasCall = hasCall;
|
||||
|
||||
if (maxCallArgs < 0)
|
||||
{
|
||||
maxCallArgs = 0;
|
||||
}
|
||||
|
||||
CallArgsRegionSize = maxCallArgs * 16;
|
||||
|
||||
_visitedBlocks = new Dictionary<BasicBlock, long>();
|
||||
_pendingBranches = new Dictionary<BasicBlock, List<PendingBranch>>();
|
||||
|
||||
_relocatable = relocatable;
|
||||
}
|
||||
|
||||
public void EnterBlock(BasicBlock block)
|
||||
{
|
||||
CurrBlock = block;
|
||||
|
||||
long target = _stream.Position;
|
||||
|
||||
// 修补所有跳转到此块的待处理分支
|
||||
if (_pendingBranches.TryGetValue(block, out List<PendingBranch> list))
|
||||
{
|
||||
foreach (PendingBranch pending in list)
|
||||
{
|
||||
_stream.Seek(pending.BranchPosition, SeekOrigin.Begin);
|
||||
WriteBranch(pending.ComparisonType, pending.Operand1, pending.Operand2, target);
|
||||
}
|
||||
|
||||
_stream.Seek(target, SeekOrigin.Begin);
|
||||
_pendingBranches.Remove(block);
|
||||
}
|
||||
|
||||
_visitedBlocks.Add(block, target);
|
||||
}
|
||||
|
||||
public void JumpTo(BasicBlock target)
|
||||
{
|
||||
if (_visitedBlocks.TryGetValue(target, out long offset))
|
||||
{
|
||||
// 目标块已生成,直接写入无条件跳转
|
||||
WriteUnconditionalBranch(offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 目标块未生成,记录待修补(使用特殊的无条件标记)
|
||||
if (!_pendingBranches.TryGetValue(target, out List<PendingBranch> list))
|
||||
{
|
||||
list = new List<PendingBranch>();
|
||||
_pendingBranches.Add(target, list);
|
||||
}
|
||||
|
||||
// 使用 Comparison.Equal 和 null 操作数表示无条件跳转
|
||||
list.Add(new PendingBranch(Comparison.Equal, null, null, _stream.Position));
|
||||
|
||||
_stream.Seek(BranchInstLength, SeekOrigin.Current);
|
||||
}
|
||||
}
|
||||
|
||||
public void JumpToIf(Comparison compType, Operand op1, Operand op2, BasicBlock target)
|
||||
{
|
||||
if (_visitedBlocks.TryGetValue(target, out long offset))
|
||||
{
|
||||
// 目标块已生成,直接写入条件分支
|
||||
WriteBranch(compType, op1, op2, offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 目标块未生成,记录待修补
|
||||
if (!_pendingBranches.TryGetValue(target, out List<PendingBranch> list))
|
||||
{
|
||||
list = new List<PendingBranch>();
|
||||
_pendingBranches.Add(target, list);
|
||||
}
|
||||
|
||||
list.Add(new PendingBranch(compType, op1, op2, _stream.Position));
|
||||
|
||||
_stream.Seek(BranchInstLength, SeekOrigin.Current);
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteBranch(Comparison compType, Operand? op1, Operand? op2, long targetPos)
|
||||
{
|
||||
// 如果操作数为 null,表示无条件跳转
|
||||
if (op1 == null && op2 == null)
|
||||
{
|
||||
WriteUnconditionalBranch(targetPos);
|
||||
return;
|
||||
}
|
||||
|
||||
int offset = checked((int)(targetPos - _stream.Position));
|
||||
|
||||
// TODO: 根据 Comparison 类型调用相应的 Assembler 方法
|
||||
// 这里需要实现 Loong64 的条件分支指令
|
||||
switch (compType)
|
||||
{
|
||||
case Comparison.Equal:
|
||||
// Assembler.Beq(op1, op2, offset);
|
||||
throw new NotImplementedException("Beq not implemented yet");
|
||||
|
||||
case Comparison.NotEqual:
|
||||
// Assembler.Bne(op1, op2, offset);
|
||||
throw new NotImplementedException("Bne not implemented yet");
|
||||
|
||||
case Comparison.Less:
|
||||
// Assembler.Blt(op1, op2, offset);
|
||||
throw new NotImplementedException("Blt not implemented yet");
|
||||
|
||||
case Comparison.LessOrEqual:
|
||||
// Assembler.Ble(op1, op2, offset);
|
||||
throw new NotImplementedException("Ble not implemented yet");
|
||||
|
||||
case Comparison.Greater:
|
||||
// Assembler.Bgt(op1, op2, offset);
|
||||
throw new NotImplementedException("Bgt not implemented yet");
|
||||
|
||||
case Comparison.GreaterOrEqual:
|
||||
// Assembler.Bge(op1, op2, offset);
|
||||
throw new NotImplementedException("Bge not implemented yet");
|
||||
|
||||
default:
|
||||
throw new ArgumentException($"Unsupported comparison type: {compType}");
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteUnconditionalBranch(long targetPos)
|
||||
{
|
||||
int offset = checked((int)(targetPos - _stream.Position));
|
||||
|
||||
// TODO: 实现 Loong64 的无条件跳转指令
|
||||
Assembler.B(offset);
|
||||
throw new NotImplementedException("Unconditional branch not implemented yet");
|
||||
}
|
||||
}
|
||||
}
|
||||
153
src/ARMeilleure/CodeGen/Loong64/CodeGenerator.cs
Normal file
153
src/ARMeilleure/CodeGen/Loong64/CodeGenerator.cs
Normal file
@ -0,0 +1,153 @@
|
||||
using ARMeilleure.CodeGen.Linking;
|
||||
using ARMeilleure.CodeGen.Optimizations;
|
||||
using ARMeilleure.CodeGen.RegisterAllocators;
|
||||
using ARMeilleure.CodeGen.Unwinding;
|
||||
using ARMeilleure.Common;
|
||||
using ARMeilleure.Diagnostics;
|
||||
using ARMeilleure.IntermediateRepresentation;
|
||||
using ARMeilleure.Translation;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Numerics;
|
||||
using static ARMeilleure.IntermediateRepresentation.Operand;
|
||||
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
|
||||
|
||||
namespace ARMeilleure.CodeGen.Loong64
|
||||
{
|
||||
static class CodeGenerator
|
||||
{
|
||||
private const int RegistersCount = 32;
|
||||
|
||||
private static readonly Action<CodeGenContext, Operation>[] _instTable;
|
||||
|
||||
static CodeGenerator()
|
||||
{
|
||||
_instTable = new Action<CodeGenContext, Operation>[EnumUtils.GetCount(typeof(Instruction))];
|
||||
|
||||
#pragma warning disable IDE0055 // Disable formatting
|
||||
Add(Instruction.Add, GenerateAdd);
|
||||
#pragma warning restore IDE0055
|
||||
|
||||
static void Add(Instruction inst, Action<CodeGenContext, Operation> func)
|
||||
{
|
||||
_instTable[(int)inst] = func;
|
||||
}
|
||||
}
|
||||
|
||||
public static CompiledFunction Generate(CompilerContext cctx)
|
||||
{
|
||||
ControlFlowGraph cfg = cctx.Cfg;
|
||||
|
||||
Logger.StartPass(PassName.Optimization);
|
||||
|
||||
if (cctx.Options.HasFlag(CompilerOptions.Optimize))
|
||||
{
|
||||
if (cctx.Options.HasFlag(CompilerOptions.SsaForm))
|
||||
{
|
||||
Optimizer.RunPass(cfg); // SSA 优化
|
||||
}
|
||||
BlockPlacement.RunPass(cfg); // 基本块重排序
|
||||
}
|
||||
|
||||
Logger.EndPass(PassName.Optimization, cfg);
|
||||
|
||||
Logger.StartPass(PassName.PreAllocation);
|
||||
|
||||
PreAllocator.RunPass(cctx, out int maxCallArgs);
|
||||
|
||||
Logger.EndPass(PassName.PreAllocation, cfg);
|
||||
|
||||
Logger.StartPass(PassName.RegisterAllocation);
|
||||
|
||||
StackAllocator stackAlloc = new();
|
||||
|
||||
if (cctx.Options.HasFlag(CompilerOptions.SsaForm))
|
||||
{
|
||||
Ssa.Deconstruct(cfg);
|
||||
}
|
||||
|
||||
IRegisterAllocator regAlloc;
|
||||
|
||||
if (cctx.Options.HasFlag(CompilerOptions.Lsra))
|
||||
{
|
||||
regAlloc = new LinearScanAllocator(); // 快速分配
|
||||
}
|
||||
else
|
||||
{
|
||||
regAlloc = new HybridAllocator(); // 高质量分配
|
||||
}
|
||||
|
||||
RegisterMasks regMasks = new(
|
||||
CallingConvention.GetIntAvailableRegisters(),
|
||||
CallingConvention.GetVecAvailableRegisters(),
|
||||
CallingConvention.GetIntCallerSavedRegisters(),
|
||||
CallingConvention.GetVecCallerSavedRegisters(),
|
||||
CallingConvention.GetIntCalleeSavedRegisters(),
|
||||
CallingConvention.GetVecCalleeSavedRegisters(),
|
||||
RegistersCount);
|
||||
|
||||
AllocationResult allocResult = regAlloc.RunPass(cfg, stackAlloc, regMasks);
|
||||
|
||||
Logger.EndPass(PassName.RegisterAllocation, cfg);
|
||||
|
||||
Logger.StartPass(PassName.CodeGeneration);
|
||||
|
||||
bool relocatable = (cctx.Options & CompilerOptions.Relocatable) != 0;
|
||||
|
||||
CodeGenContext context = new(allocResult, maxCallArgs, relocatable);
|
||||
|
||||
UnwindInfo unwindInfo = WritePrologue(context);
|
||||
|
||||
for (BasicBlock block = cfg.Blocks.First; block != null; block = block.ListNext)
|
||||
{
|
||||
context.EnterBlock(block);
|
||||
|
||||
for (Operation node = block.Operations.First; node != default; node = node.ListNext)
|
||||
{
|
||||
GenerateOperation(context, node);
|
||||
}
|
||||
|
||||
if (block.SuccessorsCount == 0)
|
||||
{
|
||||
// The only blocks which can have 0 successors are exit blocks.
|
||||
Operation last = block.Operations.Last;
|
||||
|
||||
Debug.Assert(last.Instruction is Instruction.Tailcall or
|
||||
Instruction.Return);
|
||||
}
|
||||
else
|
||||
{
|
||||
BasicBlock succ = block.GetSuccessor(0);
|
||||
|
||||
if (succ != block.ListNext)
|
||||
{
|
||||
context.JumpTo(succ);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(byte[] code, RelocInfo relocInfo) = context.Assembler.GetCode();
|
||||
|
||||
Logger.EndPass(PassName.CodeGeneration);
|
||||
|
||||
return new CompiledFunction(code, unwindInfo, relocInfo);
|
||||
}
|
||||
|
||||
private static void GenerateAdd(CodeGenContext context, Operation operation)
|
||||
{
|
||||
Operand dest = operation.Destination;
|
||||
Operand src1 = operation.GetSource(0);
|
||||
Operand src2 = operation.GetSource(1);
|
||||
|
||||
if (dest.Type.IsInteger())
|
||||
{
|
||||
context.Assembler.Add(dest, src1, src2);
|
||||
}
|
||||
else
|
||||
{
|
||||
// todo
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
73
src/ARMeilleure/CodeGen/Loong64/Loong64Register.cs
Normal file
73
src/ARMeilleure/CodeGen/Loong64/Loong64Register.cs
Normal file
@ -0,0 +1,73 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace ARMeilleure.CodeGen.Loong64
|
||||
{
|
||||
enum Loong64Register
|
||||
{
|
||||
zero = 0,
|
||||
ra = 1,
|
||||
tp = 2,
|
||||
sp = 3,
|
||||
a0 = 4,
|
||||
a1 = 5,
|
||||
a2 = 6,
|
||||
a3 = 7,
|
||||
a4 = 8,
|
||||
a5 = 9,
|
||||
a6 = 10,
|
||||
a7 = 11,
|
||||
t0 = 12,
|
||||
t1 = 13,
|
||||
t2 = 14,
|
||||
t3 = 15,
|
||||
t4 = 16,
|
||||
t5 = 17,
|
||||
t6 = 18,
|
||||
t7 = 19,
|
||||
t8 = 20,
|
||||
r21 = 21,
|
||||
fp = 22,
|
||||
s0 = 23,
|
||||
s1 = 24,
|
||||
s2 = 25,
|
||||
s3 = 26,
|
||||
s4 = 27,
|
||||
s5 = 28,
|
||||
s6 = 29,
|
||||
s7 = 30,
|
||||
s8 = 31,
|
||||
|
||||
fa0 = 0,
|
||||
fa1 = 1,
|
||||
fa2 = 2,
|
||||
fa3 = 3,
|
||||
fa4 = 4,
|
||||
fa5 = 5,
|
||||
fa6 = 6,
|
||||
fa7 = 7,
|
||||
ft0 = 8,
|
||||
ft1 = 9,
|
||||
ft2 = 10,
|
||||
ft3 = 11,
|
||||
ft4 = 12,
|
||||
ft5 = 13,
|
||||
ft6 = 14,
|
||||
ft7 = 15,
|
||||
ft8 = 16,
|
||||
ft9 = 17,
|
||||
ft10 = 18,
|
||||
ft11 = 19,
|
||||
ft12 = 20,
|
||||
ft13 = 21,
|
||||
ft14 = 22,
|
||||
ft15 = 23,
|
||||
fs0 = 24,
|
||||
fs1 = 25,
|
||||
fs2 = 26,
|
||||
fs3 = 27,
|
||||
fs4 = 28,
|
||||
fs5 = 29,
|
||||
fs6 = 30,
|
||||
fs7 = 31,
|
||||
}
|
||||
}
|
||||
18
src/ARMeilleure/CodeGen/Loong64/PreAllocator.cs
Normal file
18
src/ARMeilleure/CodeGen/Loong64/PreAllocator.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using ARMeilleure.IntermediateRepresentation;
|
||||
using ARMeilleure.Translation;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
|
||||
using static ARMeilleure.IntermediateRepresentation.Operation.Factory;
|
||||
|
||||
namespace ARMeilleure.CodeGen.Loong64
|
||||
{
|
||||
static class PreAllocator
|
||||
{
|
||||
public static void RunPass(CompilerContext cctx, out int maxCallArgs)
|
||||
{
|
||||
maxCallArgs = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
1610
src/ARMeilleure/CodeGen/Loong64/readme.md
Normal file
1610
src/ARMeilleure/CodeGen/Loong64/readme.md
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user