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);
}
///