diff --git a/Samples/DynamicILInjectionKernel/App.config b/Samples/DynamicILInjectionKernel/App.config new file mode 100644 index 0000000000..9d2c7adf3b --- /dev/null +++ b/Samples/DynamicILInjectionKernel/App.config @@ -0,0 +1,6 @@ + + + + + + diff --git a/Samples/DynamicILInjectionKernel/AssemblyAttributes.cs b/Samples/DynamicILInjectionKernel/AssemblyAttributes.cs new file mode 100644 index 0000000000..8c114539a1 --- /dev/null +++ b/Samples/DynamicILInjectionKernel/AssemblyAttributes.cs @@ -0,0 +1,3 @@ +using System; + +[assembly: CLSCompliant(true)] diff --git a/Samples/DynamicILInjectionKernel/DynamicILInjectionKernel.csproj b/Samples/DynamicILInjectionKernel/DynamicILInjectionKernel.csproj new file mode 100644 index 0000000000..b7e3975f4b --- /dev/null +++ b/Samples/DynamicILInjectionKernel/DynamicILInjectionKernel.csproj @@ -0,0 +1,16 @@ + + + $(LibrarySamplesTargetFrameworks) + Exe + 8.0 + + + + true + AllEnabledByDefault + + + + + + \ No newline at end of file diff --git a/Samples/DynamicILInjectionKernel/Program.cs b/Samples/DynamicILInjectionKernel/Program.cs new file mode 100644 index 0000000000..7bf5483799 --- /dev/null +++ b/Samples/DynamicILInjectionKernel/Program.cs @@ -0,0 +1,204 @@ +// --------------------------------------------------------------------------------------- +// ILGPU Samples +// Copyright (c) 2021 ILGPU Project +// www.ilgpu.net +// +// File: Program.cs +// +// This file is part of ILGPU and is distributed under the University of Illinois Open +// Source License. See LICENSE.txt for details. +// --------------------------------------------------------------------------------------- + +#pragma warning disable CA1031 +using ILGPU; +using ILGPU.Backends; +using ILGPU.Backends.EntryPoints; +using ILGPU.Frontend; +using ILGPU.IR; +using ILGPU.Runtime; +using ILGPU.Runtime.Cuda; +using System; +using System.Collections.Immutable; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; +using System.Runtime.CompilerServices; + +namespace SimpleKernel +{ + class Program + { + private static float[] GetInputs() => + Enumerable.Range(0, 1024).Select(i => MathF.Tau * i / 1024f).ToArray(); + + /// + /// It's a simple method that calls the sine function but contains + /// internal branching for generic specialization. Because the method + /// uses the type and throw instructions, + /// it cannot be compiled by ILGPU's normal compilation process. + /// + /// The type of input and output. + /// The input number. + /// Sine of . + /// + static T GenericSin(T x) + where T : unmanaged + { + if (typeof(T) == typeof(float)) + return Unsafe.BitCast(MathF.Sin(Unsafe.BitCast(x))); + else if (typeof(T) == typeof(double)) + return Unsafe.BitCast(Math.Cos(Unsafe.BitCast(x))); + else + throw new NotSupportedException(); + } + + /// + /// The first case that uses . The method + /// itself is not generic, but it internally references the type- + /// parameterized . + /// + /// + /// + static void MyKernel1( + Index1D index, + ArrayView dataView) + { + dataView[index] = GenericSin(dataView[index]); + } + + /// + /// The another case that uses . The + /// method itself is generic, and it binds + /// when this method is called. + /// + /// + /// + /// + static void MyKernel2( + Index1D index, + ArrayView dataView) + where T : unmanaged + { + dataView[index] = GenericSin(dataView[index]); + } + + /// + /// Experiments invoking as ILGPU compiled kernel. + /// + /// + static void TestInvokeMyKernel1(Accelerator accelerator) + { + try + { + var kernel = accelerator.LoadAutoGroupedStreamKernel>(MyKernel1); + using var buffer = accelerator.Allocate1D(1024); + buffer.CopyFromCPU(GetInputs()); + kernel((int)buffer.Length, buffer.View); + var data1 = buffer.GetAsArray1D(); + Console.WriteLine($"[{string.Join(", ", data1.Take(10))}, ...]"); + } + catch (Exception exc) + { + Console.WriteLine($"An exception occurred: {exc.Message}"); + } + } + + /// + /// Experiments invoking as ILGPU compiled kernel. + /// + /// + static void TestInvokeMyKernel2(Accelerator accelerator) + { + try + { + var kernel = accelerator.LoadAutoGroupedStreamKernel>(MyKernel2); + using var buffer = accelerator.Allocate1D(1024); + buffer.CopyFromCPU(GetInputs()); + kernel((int)buffer.Length, buffer.View); + var data1 = buffer.GetAsArray1D(); + Console.WriteLine($"[{string.Join(", ", data1.Take(10))}, ...]"); + } + catch (Exception exc) + { + Console.WriteLine($"An exception occurred: {exc.Message}"); + } + } + + + /// + /// Launches a simple 1D kernel. + /// + static void Main() + { + // Due to using `GenericSin`, this operation will be failed. + Console.WriteLine("<<< without dynamic IL injection for `GenericSin` >>>"); + using (var context = Context.CreateDefault()) + { + using var accelerator = context + .GetPreferredDevice(false) + .CreateAccelerator(context); + Console.WriteLine($"Performing operations on {accelerator}"); + + Console.Write("Results from MyKernel1: "); + TestInvokeMyKernel1(accelerator); + } + Console.WriteLine(); + + // Since `GenericSin` has been replaced with IL that contains no compilation errors, + // `MyKernel1` and `MyKernel2` can be compiled and executed without errors. + Console.WriteLine("<<< with dynamic IL injection for `GenericSin` >>>"); + using (var context = Context.CreateDefault()) + { + using var accelerator = context + .GetPreferredDevice(false) + .CreateAccelerator(context); + Console.WriteLine($"Performing operations on {accelerator}"); + + // Injects a dynamic IL implementation for `GenericSin`. + // When the type parameter `T` is instantiated as `float`, the conditional branches + // can be removed and the method can be optimized to a direct call to `MathF.Sin(float)`. + // Therefore, we inject the precomputed optimized IL here. + using (var codeGenerationPhase = accelerator.GetBackend().Context.BeginCodeGeneration()) + { + var methodInfoGenericSin = typeof(Program).GetMethod(nameof(GenericSin), BindingFlags.NonPublic | BindingFlags.Static); + var genericSinEPDesc = EntryPointDescription.FromExplicitlyGroupedKernel(methodInfoGenericSin.MakeGenericMethod(typeof(float))); + var mainContext = codeGenerationPhase.IRContext; + + CodeGenerationResult generationResult; + using (var frontendPhase = codeGenerationPhase.BeginFrontendCodeGeneration()) + { + var methodInfoMathFSin = typeof(MathF).GetMethod(nameof(MathF.Sin), BindingFlags.Public | BindingFlags.Static); + var disassembledMethod = new DisassembledMethod( + genericSinEPDesc.MethodSource, + ImmutableArray.Create( + new ILInstruction(0, ILInstructionType.Ldarg, default, popCount: 0, pushCount: 1, argument: 0, Location.Unknown), + new ILInstruction(1, ILInstructionType.Call, default, popCount: 1, pushCount: 1, argument: methodInfoMathFSin, Location.Unknown), + new ILInstruction(6, ILInstructionType.Ret, default, popCount: 1, pushCount: 0, argument: OpCodes.Ret, Location.Unknown) + ), + 8); + generationResult = frontendPhase.GenerateCode(genericSinEPDesc.MethodSource, disassembledMethod); + } + + if (codeGenerationPhase.IsFaulted) + { + throw codeGenerationPhase.LastException!; + } + + var result = generationResult; + if (!result.HasResult) + { + throw new InvalidOperationException(); + } + codeGenerationPhase.Optimize(); + } + + Console.WriteLine("<<< after dynamic IL injection for `GenericSin` >>>"); + Console.Write("Results from MyKernel1: "); + TestInvokeMyKernel1(accelerator); + + Console.Write("Results from MyKernel2: "); + TestInvokeMyKernel2(accelerator); + } + } + } +} diff --git a/Samples/ILGPU.Samples.sln b/Samples/ILGPU.Samples.sln index ce9098eb9e..6c149be11b 100644 --- a/Samples/ILGPU.Samples.sln +++ b/Samples/ILGPU.Samples.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.1.32319.34 +# Visual Studio Version 18 +VisualStudioVersion = 18.0.11111.16 d18.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DeviceInfo", "DeviceInfo\DeviceInfo.csproj", "{715584CD-87DA-4F1F-86C3-A3D9AB50EE53}" EndProject @@ -135,6 +135,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ILGPU.Analyzers", "..\Src\I EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InterleaveFields", "InterleaveFields\InterleaveFields.csproj", "{1E6D0BC6-CFA1-4F52-9EB9-CAA62DD2F33A}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DynamicILInjectionKernel", "DynamicILInjectionKernel\DynamicILInjectionKernel.csproj", "{493A5D16-AE8D-5E0B-4CB1-4BA2F3A17099}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -381,6 +383,10 @@ Global {1E6D0BC6-CFA1-4F52-9EB9-CAA62DD2F33A}.Debug|Any CPU.Build.0 = Debug|Any CPU {1E6D0BC6-CFA1-4F52-9EB9-CAA62DD2F33A}.Release|Any CPU.ActiveCfg = Release|Any CPU {1E6D0BC6-CFA1-4F52-9EB9-CAA62DD2F33A}.Release|Any CPU.Build.0 = Release|Any CPU + {493A5D16-AE8D-5E0B-4CB1-4BA2F3A17099}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {493A5D16-AE8D-5E0B-4CB1-4BA2F3A17099}.Debug|Any CPU.Build.0 = Debug|Any CPU + {493A5D16-AE8D-5E0B-4CB1-4BA2F3A17099}.Release|Any CPU.ActiveCfg = Release|Any CPU + {493A5D16-AE8D-5E0B-4CB1-4BA2F3A17099}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -449,6 +455,7 @@ Global {70B69CE3-24A9-463C-B14C-E2934988BBEE} = {25BA2234-5778-40BC-9386-9CE87AB87D1F} {1C5E9E39-3C14-4B52-8D97-04555D5F6331} = {03FCC663-945D-4982-90D8-B14BE52D8FCD} {1E6D0BC6-CFA1-4F52-9EB9-CAA62DD2F33A} = {C1D99632-ED4A-4B08-A14D-4C8DB375934F} + {493A5D16-AE8D-5E0B-4CB1-4BA2F3A17099} = {C1D99632-ED4A-4B08-A14D-4C8DB375934F} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {30E502BD-3826-417F-888F-1CE19CF5C6DA} diff --git a/Src/ILGPU/Frontend/DisassembledMethod.cs b/Src/ILGPU/Frontend/DisassembledMethod.cs index 321e500aa9..50d8175551 100644 --- a/Src/ILGPU/Frontend/DisassembledMethod.cs +++ b/Src/ILGPU/Frontend/DisassembledMethod.cs @@ -1,4 +1,4 @@ -// --------------------------------------------------------------------------------------- +// --------------------------------------------------------------------------------------- // ILGPU // Copyright (c) 2018-2021 ILGPU Project // www.ilgpu.net @@ -26,7 +26,16 @@ public sealed class DisassembledMethod { #region Instance - internal DisassembledMethod( + /// + /// Constructs a new IL instructions stream. + /// + /// The method who has been disassembled. Must not be null. + /// + /// The collection of intermediate language (IL) instructions associated with the method. + /// Must not be null and must contain at least one instruction. + /// + /// The maximum stack size required by the method during execution. + public DisassembledMethod( MethodBase method, ImmutableArray instructions, int maxStackSize) diff --git a/Src/ILGPU/Frontend/ILFrontend.cs b/Src/ILGPU/Frontend/ILFrontend.cs index 657924f987..65859a9a3f 100644 --- a/Src/ILGPU/Frontend/ILFrontend.cs +++ b/Src/ILGPU/Frontend/ILFrontend.cs @@ -1,4 +1,4 @@ -// --------------------------------------------------------------------------------------- +// --------------------------------------------------------------------------------------- // ILGPU // Copyright (c) 2018-2023 ILGPU Project // www.ilgpu.net @@ -36,10 +36,12 @@ private readonly struct ProcessingEntry { public ProcessingEntry( MethodBase method, + DisassembledMethod? disassembledMethod, CompilationStackLocation compilationStackLocation, CodeGenerationResult? result) { Method = method; + DisassembledMethod = disassembledMethod; CompilationStackLocation = compilationStackLocation; Result = result; } @@ -49,6 +51,11 @@ public ProcessingEntry( /// public MethodBase Method { get; } + /// + /// Returns the disassembled method if exists. + /// + public DisassembledMethod? DisassembledMethod { get; } + /// /// Returns the source location. /// @@ -191,6 +198,7 @@ private void DoWork() { codeGenerationPhase.GenerateCodeInternal( current.Method, + current.DisassembledMethod, current.IsExternalRequest, current.CompilationStackLocation, detectedMethods, @@ -214,6 +222,7 @@ private void DoWork() { processing.Push(new ProcessingEntry( detectedMethod.Key, + null, detectedMethod.Value, null)); } @@ -243,7 +252,16 @@ private void DoWork() /// /// The method. /// The generation future. - internal CodeGenerationResult GenerateCode(MethodBase method) + internal CodeGenerationResult GenerateCode(MethodBase method) => + GenerateCode(method, null); + + /// + /// Internal method used for code generation. + /// + /// The method. + /// The disassembled method if exists. + /// The generation future. + internal CodeGenerationResult GenerateCode(MethodBase method, DisassembledMethod? disassembledMethod) { var result = new CodeGenerationResult(method); lock (processingSyncObject) @@ -251,6 +269,7 @@ internal CodeGenerationResult GenerateCode(MethodBase method) driverNotifier.Reset(); processing.Push(new ProcessingEntry( method, + disassembledMethod, new CompilationStackLocation(new Method.MethodLocation(method)), result)); Monitor.Pulse(processingSyncObject); @@ -436,6 +455,9 @@ internal CodeGenerationPhase( /// Performs the actual (asynchronous) code generation. /// /// The method. + /// + /// The disassembled method corresponding to if exists. + /// /// /// True, if processing of this method was requested by a user. /// @@ -444,10 +466,10 @@ internal CodeGenerationPhase( /// The resolved IR method. internal void GenerateCodeInternal( MethodBase method, + DisassembledMethod? disassembledMethod, bool isExternalRequest, CompilationStackLocation compilationStackLocation, - Dictionary detectedMethods, - out Method generatedMethod) + Dictionary detectedMethods, out Method generatedMethod) { ILocation? location = null; try @@ -460,11 +482,14 @@ internal void GenerateCodeInternal( SequencePointEnumerator sequencePoints = DebugInformationManager?.LoadSequencePoints(method) ?? SequencePointEnumerator.Empty; - var disassembler = new Disassembler( - method, - sequencePoints, - compilationStackLocation); - var disassembledMethod = disassembler.Disassemble(); + if (disassembledMethod is null) + { + var disassembler = new Disassembler( + method, + sequencePoints, + compilationStackLocation); + disassembledMethod = disassembler.Disassemble(); + } using (var builder = generatedMethod.CreateBuilder()) { @@ -504,12 +529,23 @@ internal void GenerateCodeInternal( /// /// The method. /// A completion future. - public CodeGenerationResult GenerateCode(MethodBase method) + public CodeGenerationResult GenerateCode(MethodBase method) => + GenerateCode(method, null); + + /// + /// Generates code for the given method. + /// + /// The method. + /// + /// The disassembled method corresponding to if exists. + /// + /// A completion future. + public CodeGenerationResult GenerateCode(MethodBase method, DisassembledMethod? disassembledMethod) { if (method == null) throw new ArgumentNullException(nameof(method)); hadWorkToDo = true; - return Frontend.GenerateCode(method); + return Frontend.GenerateCode(method, disassembledMethod); } ///