Skip to content

Commit

Permalink
Merge pull request #39 from Earthmark/wasm_marshaller
Browse files Browse the repository at this point in the history
Swapped from frame push and pop to marshallers.
  • Loading branch information
RobertBaruch authored Feb 4, 2024
2 parents 00ea1a6 + 8da17db commit 65bdf12
Show file tree
Hide file tree
Showing 9 changed files with 169 additions and 156 deletions.
2 changes: 1 addition & 1 deletion dergwasm_mod/Benchmarks/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ public int PopOutInt()
for (int i = 0; i < N; i++)
{
frame.Push(new Value { s32 = 1 });
frame.Pop(out y);
y = frame.Pop<int>();
x += y;
}
return x;
Expand Down
17 changes: 5 additions & 12 deletions dergwasm_mod/Dergwasm/Modules/ApiData.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Serialization;
using Derg.Runtime;

Expand All @@ -15,21 +16,13 @@ public class ApiFunc
{
public string Module { get; set; }
public string Name { get; set; }
public List<Parameter> Parameters { get; }
public List<Parameter> Returns { get; }
public List<Parameter> Parameters { get; } = new List<Parameter>();
public List<Parameter> Returns { get; } = new List<Parameter>();

[JsonIgnore]
public List<ValueType> ParameterValueTypes { get; }
public IEnumerable<ValueType> ParameterValueTypes => Parameters.SelectMany(p => p.Types);

[JsonIgnore]
public List<ValueType> ReturnValueTypes { get; }

public ApiFunc()
{
Parameters = new List<Parameter>();
Returns = new List<Parameter>();
ParameterValueTypes = new List<ValueType>();
ReturnValueTypes = new List<ValueType>();
}
public IEnumerable<ValueType> ReturnValueTypes => Returns.SelectMany(p => p.Types);
}
}
30 changes: 30 additions & 0 deletions dergwasm_mod/Dergwasm/Modules/IWasmMarshaller.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using Derg.Runtime;
using Dergwasm.Runtime;
using Elements.Core;
using System.Collections.Generic;

namespace Derg.Modules {
public interface IWasmMarshaller<T> {
// Get the type information for a parameter, Name will be filled out later.
void AddParams(string name, List<Parameter> parameters);
void To(Frame frame, Machine machine, T value);
T From(Frame frame, Machine machine);
}

public struct DirectMarshaller<T> : IWasmMarshaller<T>
{
public void AddParams(string name, List<Parameter> parameters)
{
parameters.Add(new Parameter
{
Name = name,
Types = ModuleReflector.ValueTypesFor(typeof(T)),
CSType = typeof(T).GetNiceName()
});
}

public T From(Frame frame, Machine machine) => frame.Pop().As<T>();

public void To(Frame frame, Machine machine, T value) => frame.Push(Value.From(value));
}
}
118 changes: 66 additions & 52 deletions dergwasm_mod/Dergwasm/Modules/ModuleReflector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,51 @@

namespace Derg.Modules
{
[AttributeUsage(AttributeTargets.Field | AttributeTargets.ReturnValue | AttributeTargets.Struct)]
public class MarshalWithAttribute : Attribute
{
public Type Marshaller { get; }

public MarshalWithAttribute(Type marshaller)
{
Marshaller = marshaller;
}
}

public static class ModuleReflector
{
private static readonly ConcurrentDictionary<
Type,
Func<object, (ApiFunc, HostFunc)[]>
> _reflectedFuncs = new ConcurrentDictionary<Type, Func<object, (ApiFunc, HostFunc)[]>>();

public static Runtime.ValueType[] ValueTypesFor(Type type)
{
var valueTypes = (Runtime.ValueType[])
typeof(Value)
.GetMethod(nameof(Value.ValueType))
.MakeGenericMethod(type)
.Invoke(null, null);
return valueTypes;
}

public static Type MarshallerFor(ParameterInfo info)
{
var marshaller = info.GetCustomAttribute<MarshalWithAttribute>()?.Marshaller ??
info.ParameterType.GetCustomAttribute<MarshalWithAttribute>()?.Marshaller ??
typeof(DirectMarshaller<>).MakeGenericType(info.ParameterType);
if (marshaller.IsByRef)
{
throw new InvalidOperationException("Marshallers must be structs.");
}
if (marshaller.IsGenericTypeDefinition)
{
// If the marshaller is a generic type, assume the marshaller has the same type arguments.
marshaller = marshaller.MakeGenericType(info.ParameterType.GenericTypeArguments);
}
return marshaller;
}

public static (ApiFunc, HostFunc)[] ReflectHostFuncs<T>(T ctx)
{
return ReflectHostFuncs((object)ctx);
Expand Down Expand Up @@ -92,16 +130,6 @@ var method in t.GetMethods(
return lambda.Compile();
}

private static Runtime.ValueType[] ValueTypesFor(Type type)
{
var valueTypes = (Runtime.ValueType[])
typeof(Value)
.GetMethod(nameof(Value.ValueType))
.MakeGenericMethod(type)
.Invoke(null, null);
return valueTypes;
}

private static Expression /*(ApiFunc, HostFunc)*/
ReflectHostFunc(string name, string module, MethodInfo method, ParameterExpression context)
{
Expand Down Expand Up @@ -157,7 +185,7 @@ ApiFunc funcData
var machine = Expression.Parameter(typeof(Machine), "machine");
var frame = Expression.Parameter(typeof(Frame), "frame");

var poppers = new List<Expression>();
var body = new List<Expression>();
var callParams = new List<ParameterExpression>();
var outVars = new List<ParameterExpression>();
foreach (var param in method.GetParameters())
Expand All @@ -182,35 +210,23 @@ ApiFunc funcData
outVars.Add(poppedValue);

// Call the appropriate pop method for the type.
string refTypeName = param.ParameterType.MakeByRefType().Name;
MethodInfo popper = typeof(Frame)
.GetMethods()
.Where(m => m.Name == "Pop")
.Where(m => m.GetParameters().Length == 1)
.Where(m => m.GetParameters()[0].IsOut)
.Where(m => m.GetParameters()[0].ParameterType.Name == refTypeName)
.First();
if (param.ParameterType.IsGenericType)
{
popper = popper.MakeGenericMethod(param.ParameterType.GetGenericArguments());
}
MethodCallExpression popperCaller = Expression.Call(frame, popper, poppedValue);
poppers.Add(popperCaller);
Type marshaller = MarshallerFor(param);
MethodInfo popper = marshaller.GetMethod(nameof(IWasmMarshaller<int>.From));
Expression popperCaller = Expression.Assign(poppedValue, Expression.Call(
Expression.Default(marshaller),
popper,
frame,
machine));
body.Add(popperCaller);

funcData.Parameters.Add(
new Parameter
{
Name = param.Name,
Types = ValueTypesFor(param.ParameterType),
CSType = param.ParameterType.GetNiceName()
}
);
funcData.ParameterValueTypes.AddRange(ValueTypesFor(param.ParameterType));
marshaller.GetMethod(nameof(IWasmMarshaller<int>.AddParams))
.Invoke(marshaller.GetDefault(),
new object[] { param.Name, funcData.Parameters });
}

// The poppers need to be reversed. The first value pushed is the first call arg,
// which means that the first value popped is the *last* call arg.
poppers.Reverse();
body.Reverse();

// The actual inner call to the host func.
var result = Expression.Call(method.IsStatic ? null : context, method, callParams);
Expand All @@ -220,29 +236,27 @@ ApiFunc funcData
{
returnsCount++;
// Process return value.
result = Expression.Call(
ParameterInfo param = method.ReturnParameter;
Type marshaller = MarshallerFor(param);
MethodInfo pusher = marshaller.GetMethod(nameof(IWasmMarshaller<int>.To));
MethodCallExpression pusherCaller = Expression.Call(
Expression.Default(marshaller),
pusher,
frame,
typeof(Frame)
.GetMethods()
.First(m => m.Name == nameof(Frame.Push) && m.IsGenericMethod)
.MakeGenericMethod(method.ReturnType),
result
);
machine,
result);

funcData.Returns.Add(
new Parameter
{
Types = ValueTypesFor(method.ReturnType),
CSType = method.ReturnType.GetNiceName()
}
);
funcData.ReturnValueTypes.AddRange(ValueTypesFor(method.ReturnType));
result = pusherCaller;

marshaller.GetMethod(nameof(IWasmMarshaller<int>.AddParams))
.Invoke(marshaller.GetDefault(),
new object[] { null, funcData.Returns });

// TODO: Add the ability to process value tuple based return values.
}
poppers.Add(result);
body.Add(result);

BlockExpression block = Expression.Block(outVars.ToArray(), poppers);
BlockExpression block = Expression.Block(outVars.ToArray(), body);

// This is the invoked callsite. To improve per-call performance, optimize the expression
// structure going into this compilation.
Expand Down
84 changes: 0 additions & 84 deletions dergwasm_mod/Dergwasm/Runtime/Frame.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,94 +102,10 @@ public List<Instruction> Code

public T Pop<T>() => Pop().As<T>();

public void Pop(out bool val) => val = (Pop().s32 != 0 ? true : false);

public void Pop(out int val) => val = Pop().s32;

public void Pop(out uint val) => val = Pop().u32;

public void Pop(out long val) => val = Pop().s64;

public void Pop(out ulong val) => val = Pop().u64;

public void Pop(out float val) => val = Pop().f32;

public void Pop(out double val) => val = Pop().f64;

public void Pop(out ResoniteError val) => val = (ResoniteError)Pop().s32;

public void Pop(out ResoniteEnv.ResoniteType val) =>
val = (ResoniteEnv.ResoniteType)Pop().s32;

public void Pop<T>(out WasmRefID<T> refId)
where T : class, IWorldElement => refId = new WasmRefID<T>(Pop().u64);

public void Pop<T>(out Ptr<T> ptr)
where T : struct => ptr = new Ptr<T>(Pop().s32);

public void Pop(out NullTerminatedString ptr) => ptr = new NullTerminatedString(Pop().s32);

public void Pop<T>(out Output<T> ptr)
where T : struct => ptr = new Output<T>(Pop().s32);

public void Pop<T>(out WasmArray<T> ptr)
where T : struct => ptr = new WasmArray<T>(Pop().s32);

public void Pop<T>(out Buff<T> buff)
where T : struct
{
// The first value pushed is the first call arg.
// The first value popped is the last call arg.
// A buff argument is (data, len), so in pop order, it's (len, data).
int len = Pop().s32;
int data = Pop().s32;
buff = new Buff<T>(data, len);
}

public void Push<T>(in T value) => value_stack.Push(Value.From(value));

public void Push(Value val) => value_stack.Push(val);

public void Push(bool val) => Push(new Value { u32 = val ? 1u : 0u });

public void Push(int val) => Push(new Value { s32 = val });

public void Push(uint val) => Push(new Value { u32 = val });

public void Push(long val) => Push(new Value { s64 = val });

public void Push(ulong val) => Push(new Value { u64 = val });

public void Push(float val) => Push(new Value { f32 = val });

public void Push(double val) => Push(new Value { f64 = val });

public void Push(ResoniteError val) => Push((int)val);

public void Push(ResoniteEnv.ResoniteType val) => Push((int)val);

public void Push<T>(WasmRefID<T> val)
where T : class, IWorldElement => Push(val.Id);

public void Push<T>(Ptr<T> val)
where T : struct => Push(val.Addr);

public void Push(NullTerminatedString val) => Push(val.Data.Addr);

public void Push<T>(Output<T> val)
where T : struct => Push(val.Ptr.Addr);

public void Push<T>(WasmArray<T> val)
where T : struct => Push(val.Data.Addr);

public void Push<T>(Buff<T> val)
where T : struct
{
// A buff argument is (data, len).
Push(val.Ptr.Addr);
Push(val.Length);
}

public int StackLevel() => value_stack.Count;

public Label PopLabel() => label_stack.Pop();
Expand Down
Loading

0 comments on commit 65bdf12

Please sign in to comment.