CoffeDeveloper
CoffeDeveloper

Reputation: 8325

Use c# reflection to inline method calls "manually"

I'm running math heavy code on GPU with ILGPU, I organized it in modular classes segregated by interfaces to implement different algorithms that are modular.

Now ILGPU allows only to run static methods on GPU kernels. So I need to take the IL out of those nested classes, and inline manually, ouputting a single static method that will be called as Kernel.

In example

public interface IFunction
{
    public float DoSum(float[] input);
}


public class NormalSum
{
    public float(float[] input)
    {
        float sum=0.0f;
        for(int i=0;i<input.Lenght;i++)
            sum+=input[i];
    }
}

public interface IAlgorithm
{
    public void DoAlgorithm(float[] input, float[] output);
}

public class MyAlgorithm
{
    IFunction fun;
    public MyAlgorithm(IFunction fun)
    {
        this.fun = fun;
    }
    public void DoAlgorithm(float[] input, float[] output)
    {
          for(int j=0;j<output.Length;j++)
          {
               output[j] = 2.0f* fun.DoSum(input);
          }
    }
}

There are no stored variables, the classes and interfaces are just wrappers over algorithms so I need a code and expect it to inline the methods to something like that without bothering to resolve eventual references to instance members, i look to "fun" just to extract the method implementation, then "fun" can be discarded.

var staticMethod = Inline(new MyAlgorithm(new NormalSum));

 // generated IL
public static void GeneratedCode(float[] input, float[] output)
{
          for(int j=0;j<output.Length;j++)
          {
              float sum=0.0f;
              for(int i=0;i<input.Length;i++)
                  sum+=input[i];
              float result = sum;
              output[j] = 2.0f* result;
          }
}

So far i just copied a single static method, but was not able to go "inside the implementation".

    MethodInfo existingMethod = typeof(MyStaticClass).GetMethod("MethodToCopy", BindingFlags.Static | BindingFlags.Public);

    AssemblyName assemblyName = new AssemblyName("DynamicAssembly");
    AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);

    ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("DynamicModule");

    TypeBuilder typeBuilder = moduleBuilder.DefineType("DynamicClass", TypeAttributes.Public);

    MethodBuilder methodBuilder = typeBuilder.DefineMethod("DynamicMethod", MethodAttributes.Public | MethodAttributes.Static, existingMethod.ReturnType, new Type[] { });

    ILGenerator ilGenerator = methodBuilder.GetILGenerator();
    
    // Copy IL code
    foreach (var instruction in existingMethod.GetMethodBody().GetILAsByteArray())
    {
        ilGenerator.Emit(instruction);
    }
    
    Type type = typeBuilder.CreateType();

    type.GetMethod("DynamicMethod").Invoke(null, null);

Upvotes: 1

Views: 133

Answers (2)

Charlieface
Charlieface

Reputation: 72388

You can make each of those interfaces return a static Func rather than implicitly creating that delegate yourself.

public interface IFunction
{
    public Func<float[], float> GetDoSum();
}


public class NormalSum : IFunction
{
    public Func<float[], float> GetDoSum() => DoSum;

    public static float DoSum(float[] input)
    {
        float sum = 0.0f;
        for(int i=0; i < input.Length; i++)
            sum += input[i];
    }
}
public interface IAlgorithm
{
    public Action<float[], float[]> GetDoAlgorithm();
}

public class MyAlgorithm : IAlgorithm
{
    Func<float[], float> fun;

    public MyAlgorithm(IFunction fun)
    {
        this.fun = fun.GetDoSum();
    }

    public Action<float[], float[]> GetDoAlgorithm() => DoAlgorithm();

    public static void DoAlgorithm(float[] input, float[] output)
    {
          for(int j = 0; j < output.Length; j++)
          {
               output[j] = 2.0f * fun(input);
          }
    }
}

I must say, I don't know much about ILGPU, but what you are doing looks rather different from the documentation, which seems to indicate you are supposed to work with ArrayView and Kernel functions, not loops.

Upvotes: 1

Guru Stron
Guru Stron

Reputation: 143313

If you can use C# 11+ then you can try using the static abstract interface members and dump the reflection completely:

Declare the interfaces with static methods:

public interface IAlgorithm1
{
    public static abstract void DoAlgorithm(float[] input, float[] ouput) ;
}

public interface IFunction1
{
    public static abstract float DoSum(float[] input);
}

And then implement them:

public class MyAlgorithm1<T> : IAlgorithm1 where T : IFunction1
{
    public static void DoAlgorithm(float[] input, float[] output)
    {
        for(int j=0;j<output.Length;j++)
        {
            output[j] = 2.0f* T.DoSum(input); // call to the static method of the generic type
        }
    }
}

public class NormalSum1 : IFunction1
{
    public static float DoSum(float[] input)
    {
        float sum = 0.0f;
        for (int i = 0; i < input.Length; i++)
            sum += input[i];
        return sum;
    }
}

And then you can access the static method via MyAlgorithm1<NormalSum1>.DoAlgorithm. For example:

var output = new float[1];
MyAlgorithm1<NormalSum1>.DoAlgorithm(new []{1f}, output);

but GPU Kernels are not allowed to have a method call inside

Options I see:

  • Switching to struct's instead of classes (+ applying hints)
  • Use Rolsyn source generators to just generate the needed inlined code here (for example you can generate code for some static Generator.Call(MyAlgorithm.DoAlgorithm, new NormalSum()) method).

Upvotes: 1

Related Questions