Add GenerateArrays task

This commit is contained in:
TSR Berry 2022-12-15 16:42:01 +01:00
parent 33cd96ba0e
commit a2032ade65
No known key found for this signature in database
GPG key ID: 52353C0A4CCA15E2
4 changed files with 278 additions and 171 deletions

168
.gitignore vendored
View file

@ -1,168 +0,0 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.sln.docstates
.vs
.vscode
# Build results
[Dd]ebug/
[Rr]elease/
x64/
build/
[Bb]in/
[Oo]bj/
# Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets
!packages/*/build/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
*_i.c
*_p.c
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.scc
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opensdf
*.sdf
*.cachefile
# Visual Studio profiler
*.psess
*.vsp
*.vspx
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# Rider is a Visual Studio alternative
.idea/*
# NCrunch
*.ncrunch*
.*crunch*.local.xml
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.Publish.xml
# NuGet Packages Directory
## TODO: If you have NuGet Package Restore enabled, uncomment the next line
packages/
# Windows Azure Build Output
csx
*.build.csdef
# Windows Store app package directory
AppPackages/
# Others
sql/
*.Cache
ClientBin/
[Ss]tyle[Cc]op.*
~$*
*~
*.dbmdl
*.[Pp]ublish.xml
*.pfx
*.publishsettings
packages/*
*.config
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file to a newer
# Visual Studio version. Backup files are not needed, because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
App_Data/*.mdf
App_Data/*.ldf
#LightSwitch generated files
GeneratedArtifacts/
_Pvt_Extensions/
ModelManifest.xml
# =========================
# Windows detritus
# =========================
# Windows image file caches
Thumbs.db
ehthumbs.db
# Folder config file
Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Mac desktop service store files
.DS_Store
# VS Launch Settings
launchSettings.json
# NetCore Publishing Profiles
PublishProfiles/

View file

@ -0,0 +1,243 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.Build.Framework;
using System.Collections.Generic;
using System.IO;
using Ryujinx.CustomTasks.SyntaxWalker;
using Ryujinx.CustomTasks.Helper;
using Task = Microsoft.Build.Utilities.Task;
namespace Ryujinx.CustomTasks
{
public class GenerateArrays : Task
{
private const string InterfaceFileName = "IArray.g.cs";
private const string ArraysFileName = "Arrays.g.cs";
private readonly List<string> _outputFiles = new List<string>();
[Required]
public string ArrayNamespace { get; set; }
[Required]
public string OutputPath { get; set; }
[Required]
public ITaskItem[] InputFiles { get; set; }
[Output]
public string[] OutputFiles { get; set; }
private static int GetAvailableMaxSize(IReadOnlyList<int> availableSizes, ref int index, int missingSize)
{
if (availableSizes.Count == 0 || missingSize == 1)
{
return 1;
}
if (availableSizes.Count < index + 1)
{
return 0;
}
int size = 0;
while (size == 0 || size > missingSize && availableSizes.Count - index > 0)
{
index++;
size = availableSizes[availableSizes.Count - index];
}
return size;
}
private void AddGeneratedSource(string filePath, string content)
{
File.WriteAllText(filePath, content);
_outputFiles.Add(filePath);
}
private ICollection<int> GetArraySizes(string itemPath)
{
Log.LogMessage(MessageImportance.Low, $"Searching for StructArray types in: {itemPath}");
SyntaxTree tree = CSharpSyntaxTree.ParseText(File.ReadAllText(itemPath), path: itemPath);
CompilationUnitSyntax root = tree.GetCompilationUnitRoot();
ArraySizeCollector collector = new ArraySizeCollector();
collector.Visit(root);
foreach (int size in collector.ArraySizes)
{
Log.LogMessage(MessageImportance.Low, $"\tFound array size: {size}");
}
return collector.ArraySizes;
}
private string GenerateInterface()
{
CodeGenerator generator = new CodeGenerator();
generator.EnterScope($"namespace {ArrayNamespace}");
generator.AppendLine("/// <summary>");
generator.AppendLine("/// Array interface.");
generator.AppendLine("/// </summary>");
generator.AppendLine("/// <typeparam name=\"T\">Element type</typeparam>);");
generator.EnterScope("public interface IArray<T> where T : unmanaged");
generator.AppendLine("/// <summary>");
generator.AppendLine("/// Used to index the array.");
generator.AppendLine("/// </summary>");
generator.AppendLine("/// <param name=\"index\">Element index</param>");
generator.AppendLine("/// <returns>Element at the specified index</returns>");
generator.AppendLine("ref T this[int index] { get; }");
generator.AppendLine();
generator.AppendLine("/// <summary>");
generator.AppendLine("/// Number of elements on the array.");
generator.AppendLine("/// </summary>");
generator.AppendLine("int Length { get; }");
generator.LeaveScope();
generator.LeaveScope();
return generator.ToString();
}
private void GenerateArray(CodeGenerator generator, int size, IReadOnlyList<int> availableSizes)
{
generator.EnterScope($"public struct Array{size}<T> : IArray<T> where T : unmanaged");
generator.AppendLine("T _e0;");
if (size > 1)
{
generator.AppendLine("#pragma warning disable CS0169");
}
if (availableSizes.Count == 0)
{
for (int i = 1; i < size; i++)
{
generator.AppendLine($"T _e{i};");
}
}
else
{
int counter = 1;
int currentSize = 1;
int maxSizeIndex = 0;
int maxSize = 0;
while (currentSize < size)
{
if (maxSize == 0 || currentSize + maxSize > size)
{
maxSize = GetAvailableMaxSize(availableSizes, ref maxSizeIndex, size - currentSize);
}
generator.AppendLine(maxSize > 1
? counter == 1
? $"Array{maxSize}<T> _other;"
: $"Array{maxSize}<T> _other{counter};"
: $"T _e{counter};"
);
counter++;
currentSize += maxSize;
}
}
if (size > 1)
{
generator.AppendLine("#pragma warning restore CS0169");
}
generator.AppendLine();
generator.AppendLine($"public int Length => {size};");
generator.AppendLine("public ref T this[int index] => ref AsSpan()[index];");
generator.AppendLine("public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length);");
generator.LeaveScope();
generator.AppendLine();
}
public override bool Execute()
{
string interfaceFilePath = Path.Combine(OutputPath, InterfaceFileName);
string arraysFilePath = Path.Combine(OutputPath, ArraysFileName);
List<int> arraySizes = new List<int>();
File.Delete(interfaceFilePath);
File.Delete(arraysFilePath);
foreach (var item in InputFiles)
{
string fullPath = item.GetMetadata("FullPath");
if (fullPath.EndsWith(".g.cs") || fullPath.Contains("obj\\Debug\\") || fullPath.Contains("obj\\Release\\"))
{
continue;
}
foreach (int size in GetArraySizes(fullPath))
{
if (!arraySizes.Contains(size))
{
arraySizes.Add(size);
}
}
}
if (arraySizes.Count == 0)
{
Log.LogWarning("No StructArray types found. Skipping code generation.");
return true;
}
arraySizes.Sort();
AddGeneratedSource(interfaceFilePath, GenerateInterface());
CodeGenerator generator = new CodeGenerator();
List<int> sizesAvailable = new List<int>();
generator.AppendLine("using System;");
generator.AppendLine("using System.Runtime.InteropServices;");
generator.AppendLine();
generator.EnterScope($"namespace {ArrayNamespace}");
// Always generate Arrays for 1, 2 and 3
GenerateArray(generator, 1, sizesAvailable);
sizesAvailable.Add(1);
GenerateArray(generator, 2, sizesAvailable);
sizesAvailable.Add(2);
GenerateArray(generator, 3, sizesAvailable);
sizesAvailable.Add(3);
foreach (var size in arraySizes)
{
if (!sizesAvailable.Contains(size))
{
GenerateArray(generator, size, sizesAvailable);
sizesAvailable.Add(size);
}
}
generator.LeaveScope();
AddGeneratedSource(arraysFilePath, generator.ToString());
OutputFiles = _outputFiles.ToArray();
return true;
}
}
}

View file

@ -2,11 +2,12 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework> <TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>8</LangVersion>
<IsPackable>true</IsPackable> <IsPackable>true</IsPackable>
<PackageId>Ryujinx.CustomTasks</PackageId> <PackageId>Ryujinx.CustomTasks</PackageId>
<Title>Ryujinx.CustomTasks</Title> <Title>Ryujinx.CustomTasks</Title>
<Description>A collection of custom MSBuild tasks.</Description> <Description>A collection of custom MSBuild tasks.</Description>
<Version>1.0.0</Version>
<RepositoryUrl>https://github.com/Ryujinx/Ryujinx.CustomTasks</RepositoryUrl> <RepositoryUrl>https://github.com/Ryujinx/Ryujinx.CustomTasks</RepositoryUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression> <PackageLicenseExpression>MIT</PackageLicenseExpression>
@ -19,7 +20,6 @@
<NoWarn>NU5100</NoWarn> <NoWarn>NU5100</NoWarn>
<!-- Tell the SDK to generate a deps.json file --> <!-- Tell the SDK to generate a deps.json file -->
<GenerateDependencyFile>true</GenerateDependencyFile> <GenerateDependencyFile>true</GenerateDependencyFile>
<LangVersion>8</LangVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@ -34,9 +34,10 @@
<!-- This is the target we defined above. It's purpose is to add all of our PackageReference and ProjectReference's runtime assets to our package output. --> <!-- This is the target we defined above. It's purpose is to add all of our PackageReference and ProjectReference's runtime assets to our package output. -->
<!-- Currently we have to manually specify the runtime assets, because for some reason ReferenceCopyLocalPaths is always empty. --> <!-- Currently we have to manually specify the runtime assets, because for some reason ReferenceCopyLocalPaths is always empty. -->
<!-- TODO: Copy dependencies to the lib directory instead of the tasks directory -->
<Target Name="CopyProjectReferencesToPackage" DependsOnTargets="ResolveReferences"> <Target Name="CopyProjectReferencesToPackage" DependsOnTargets="ResolveReferences">
<ItemGroup> <ItemGroup>
<BuildOutputInPackage Include="bin\$(Configuration)\*\Microsoft.*.dll" /> <BuildOutputInPackage Include="bin\$(Configuration)\*\Microsoft.CodeAnalysis*.dll" />
</ItemGroup> </ItemGroup>
</Target> </Target>

View file

@ -0,0 +1,31 @@
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Collections.Generic;
namespace Ryujinx.CustomTasks.SyntaxWalker
{
class ArraySizeCollector : CSharpSyntaxWalker
{
public ICollection<int> ArraySizes { get; } = new List<int>();
private void AddArrayString(string name)
{
if (!name.StartsWith("Array") || !name.Contains("<"))
{
return;
}
string rawArrayType = name.Split('<')[0];
if (int.TryParse(rawArrayType.Substring(5), out int size) && !ArraySizes.Contains(size))
{
ArraySizes.Add(size);
}
}
// https://learn.microsoft.com/en-us/dotnet/api/microsoft.codeanalysis.csharp.syntax.genericnamesyntax?view=roslyn-dotnet-4.3.0
public override void VisitGenericName(GenericNameSyntax node) => AddArrayString(node.ToString());
// https://learn.microsoft.com/en-us/dotnet/api/microsoft.codeanalysis.csharp.syntax.identifiernamesyntax?view=roslyn-dotnet-4.3.0
public override void VisitIdentifierName(IdentifierNameSyntax node) => AddArrayString(node.ToString());
}
}