ltrzesniewski / InlineIL.Fody

Inject arbitrary IL code at compile time.
MIT License
240 stars 17 forks source link

Proposal: macroassembler-like support #24

Closed sakno closed 2 years ago

sakno commented 3 years ago

Hi Lucas!

Now I'm working on the set of factory methods that allow to create delegates from function pointers:

public static unsafe Action? CreateDelegate(delegate*<void> ptr)
        {
            if (ptr == null)
                return null;

            Ldnull();
            Push(ptr);
            Newobj(Constructor(Type<Action>(), Type<object>(), Type<IntPtr>()));
            return Return<Action>();
        }

public static unsafe Action<T>? CreateDelegate<T>(delegate*<T, void> ptr)
        {
            if (ptr == null)
                return null;

            Ldnull();
            Push(ptr);
            Newobj(Constructor(Type<Action<T>>(), Type<object>(), Type<IntPtr>()));
            return Return<Action<T>>();
        }

As you can see, I have to use code duplication. I would like to propose the support of macros by your library. The macro is a method with the following restrictions:

With the macro functionality, I'll be able to rewrite the methods shown above as follows:


[ILMacro]
private static unsafe TDelegate? CreateDelegateMacro<TDelegate>(void* ptr)
  where TDelegate : Delegate
{
  if (ptr == null)
    return null;

            Ldnull();
            Push(ptr);
            Newobj(Constructor(Type<TDelegate>(), Type<object>(), Type<IntPtr>()));
            return Return<TDelegate>();
}

public static unsafe Action? CreateDelegate(delegate*<void> ptr)
  => CreateDelegateMacro<Action>(ptr);

public static unsafe Action<T>? CreateDelegate<T>(delegate*<T, void> ptr)
  => CreateDelegateMacro<Action<T>(ptr);

I understand that the feature requires a lot of efforts.

ltrzesniewski commented 3 years ago

Hi Roman!

This is an interesting idea, but as you've pointed out, it would require quite a bit of work to implement. I feel that handling the arguments correctly wouldn't be easy (it would be nice to be able to call a macro multiple times with different parameters in a given method). Also, if the macro has locals, these would need to be handled in some way.

Therefore I'm not sure if this really belongs to the scope of this project, but I'll think about it a bit more.

In the meantime, I can suggest you to use a T4 template to keep code duplication under control. It helped me in many projects (see here for a good example, the template is here). It may not be as convenient as IL macros, but it should help solve the code duplication issue quite well. Roslyn generators are also an option, but I think T4 is more suitable for this usage.

ltrzesniewski commented 3 years ago

Here's what I'm thinking: this kind of feature seems out of scope for this project, but it could be implemented as a separate weaver.

Suppose there's a weaver which inlines the IL code of methods marked with a [ForceInline] attribute, and does the necessary mapping. If you execute this weaver before InlineIL is run, then calls like Constructor(Type<TDelegate>(), ...) would be mapped to Constructor(Type<Action>(), ...) for instance, and that would work fine.

There would be a few issues, but I think they can be worked around:

I may experiment with the idea, but I don't have a lot of time so I can't promise anything.

ltrzesniewski commented 2 years ago

I found this weaver which seems to implement what I described above (it also copy/pasted some of my code without attribution, but oh well 🙄). Maybe running it before InlineIL could be helpful.

In any case, I'd rather keep InlineIL itself pretty low-level, which makes this feature out of scope.