[Rewrite] Refactored string prologue/epilogues

We currently have three categories of string parameters: `string`,
`string[]` and `StringBuilder`. (OpenTK 1.2 adds one more: `ref
string`.) Each category needs to be marshaled separately into a native
character array.

This commit implements the following changes:
- string[] epilogues are now correctly emitted, instead of being
ignored.
- string[] prologues and epilogues now use the same local variable name.
- all epilogues are now generated with a single pass over the function
parameters, instead of requiring a separate pass for each category.
- string prologues and epilogues now allocate local variables based on
the relevant parameter *name* rather than the parameter *type*.

Fixes issue #144.
This commit is contained in:
thefiddler 2014-07-07 13:16:17 +02:00
parent c8a5bf5e32
commit c32bf4ec5d

View file

@ -286,18 +286,8 @@ namespace OpenTK.Rewrite
{ {
EmitReturnTypeWrapper(wrapper, native, body, il); EmitReturnTypeWrapper(wrapper, native, body, il);
} }
if (wrapper.Parameters.Any(p => p.ParameterType.Name == "StringBuilder"))
{ EmitParameterEpilogues(wrapper, native, body, il);
EmitStringBuilderEpilogue(wrapper, native, body, il);
}
if (wrapper.Parameters.Any(p => p.ParameterType.Name == "String" && p.ParameterType.IsArray))
{
EmitStringArrayEpilogue(wrapper, body, il);
}
if (wrapper.Parameters.Any(p => p.ParameterType.Name == "String" && !p.ParameterType.IsArray))
{
EmitStringEpilogue(wrapper, body, il);
}
if (options.Contains("-debug")) if (options.Contains("-debug"))
{ {
@ -517,11 +507,63 @@ namespace OpenTK.Rewrite
} }
} }
static void EmitStringBuilderEpilogue(MethodDefinition wrapper, MethodDefinition native, MethodBody body, ILProcessor il) static void EmitParameterEpilogues(MethodDefinition wrapper, MethodDefinition native, MethodBody body, ILProcessor il)
{ {
for (int i = 0; i < wrapper.Parameters.Count; i++) foreach (var p in wrapper.Parameters)
{ {
var p = wrapper.Parameters[i].ParameterType; if (p.ParameterType.Name == "StringBuilder")
{
EmitStringBuilderEpilogue(wrapper, native, p, body, il);
}
if (!p.ParameterType.IsArray && p.ParameterType.Name == "String")
{
EmitStringEpilogue(wrapper, p, body, il);
}
if (p.ParameterType.IsArray && p.ParameterType.GetElementType().Name == "String")
{
EmitStringArrayEpilogue(wrapper, p, body, il);
}
}
}
static void EmitStringBuilderParameter(MethodDefinition method, ParameterDefinition parameter, MethodBody body, ILProcessor il)
{
var p = parameter.ParameterType;
// void GetShaderInfoLog(..., StringBuilder foo)
// IntPtr foo_sb_ptr;
// try {
// foo_sb_ptr = Marshal.AllocHGlobal(sb.Capacity + 1);
// glGetShaderInfoLog(..., foo_sb_ptr);
// MarshalPtrToStringBuilder(foo_sb_ptr, sb);
// }
// finally {
// Marshal.FreeHGlobal(sb_ptr);
// }
// Make sure we have imported StringBuilder::Capacity and Marshal::AllocHGlobal
var sb_get_capacity = method.Module.Import(TypeStringBuilder.Methods.First(m => m.Name == "get_Capacity"));
var alloc_hglobal = method.Module.Import(TypeMarshal.Methods.First(m => m.Name == "AllocHGlobal"));
// IntPtr ptr;
var variable_name = parameter.Name + " _sb_ptr";
body.Variables.Add(new VariableDefinition(variable_name, TypeIntPtr));
int index = body.Variables.Count - 1;
// ptr = Marshal.AllocHGlobal(sb.Capacity + 1);
il.Emit(OpCodes.Callvirt, sb_get_capacity);
il.Emit(OpCodes.Call, alloc_hglobal);
il.Emit(OpCodes.Stloc, index);
il.Emit(OpCodes.Ldloc, index);
// We'll emit the try-finally block in the epilogue implementation,
// because we haven't yet emitted all necessary instructions here.
}
static void EmitStringBuilderEpilogue(MethodDefinition wrapper, MethodDefinition native, ParameterDefinition parameter, MethodBody body, ILProcessor il)
{
var p = parameter.ParameterType;
if (p.Name == "StringBuilder") if (p.Name == "StringBuilder")
{ {
// void GetShaderInfoLog(..., StringBuilder foo) // void GetShaderInfoLog(..., StringBuilder foo)
@ -541,10 +583,10 @@ namespace OpenTK.Rewrite
var block = new ExceptionHandler(ExceptionHandlerType.Finally); var block = new ExceptionHandler(ExceptionHandlerType.Finally);
block.TryStart = body.Instructions[0]; block.TryStart = body.Instructions[0];
var variable_name = p.Name + " _sb_ptr"; var variable_name = parameter.Name + " _sb_ptr";
var v = body.Variables.First(m => m.Name == variable_name); var v = body.Variables.First(m => m.Name == variable_name);
il.Emit(OpCodes.Ldloc, v.Index); il.Emit(OpCodes.Ldloc, v.Index);
il.Emit(OpCodes.Ldarg, i); il.Emit(OpCodes.Ldarg, parameter.Index);
il.Emit(OpCodes.Call, ptr_to_sb); il.Emit(OpCodes.Call, ptr_to_sb);
block.TryEnd = body.Instructions.Last(); block.TryEnd = body.Instructions.Last();
@ -556,10 +598,11 @@ namespace OpenTK.Rewrite
block.HandlerEnd = body.Instructions.Last(); block.HandlerEnd = body.Instructions.Last();
} }
} }
}
static void EmitStringParameter(MethodDefinition wrapper, TypeReference p, MethodBody body, ILProcessor il) static void EmitStringParameter(MethodDefinition wrapper, ParameterDefinition parameter, MethodBody body, ILProcessor il)
{ {
var p = parameter.ParameterType;
// string marshaling: // string marshaling:
// IntPtr ptr = MarshalStringToPtr(str); // IntPtr ptr = MarshalStringToPtr(str);
// try { calli } // try { calli }
@ -567,7 +610,7 @@ namespace OpenTK.Rewrite
var marshal_str_to_ptr = wrapper.Module.Import(TypeBindingsBase.Methods.First(m => m.Name == "MarshalStringToPtr")); var marshal_str_to_ptr = wrapper.Module.Import(TypeBindingsBase.Methods.First(m => m.Name == "MarshalStringToPtr"));
// IntPtr ptr; // IntPtr ptr;
var variable_name = p.Name + "_string_ptr"; var variable_name = parameter.Name + "_string_ptr";
body.Variables.Add(new VariableDefinition(variable_name, TypeIntPtr)); body.Variables.Add(new VariableDefinition(variable_name, TypeIntPtr));
int index = body.Variables.Count - 1; int index = body.Variables.Count - 1;
@ -579,34 +622,30 @@ namespace OpenTK.Rewrite
// The finally block will be emitted in the function epilogue // The finally block will be emitted in the function epilogue
} }
static void EmitStringEpilogue(MethodDefinition wrapper, MethodBody body, ILProcessor il) static void EmitStringEpilogue(MethodDefinition wrapper, ParameterDefinition parameter, MethodBody body, ILProcessor il)
{
for (int i = 0; i < wrapper.Parameters.Count; i++)
{
var p = wrapper.Parameters[i].ParameterType;
if (p.Name == "String" && !p.IsArray)
{ {
var p = parameter.ParameterType;
var free = wrapper.Module.Import(TypeBindingsBase.Methods.First(m => m.Name == "FreeStringPtr")); var free = wrapper.Module.Import(TypeBindingsBase.Methods.First(m => m.Name == "FreeStringPtr"));
// FreeStringPtr(ptr) // FreeStringPtr(ptr)
var variable_name = p.Name + "_string_ptr"; var variable_name = parameter.Name + "_string_ptr";
var v = body.Variables.First(m => m.Name == variable_name); var v = body.Variables.First(m => m.Name == variable_name);
il.Emit(OpCodes.Ldloc, v.Index); il.Emit(OpCodes.Ldloc, v.Index);
il.Emit(OpCodes.Call, free); il.Emit(OpCodes.Call, free);
} }
}
}
static void EmitStringArrayParameter(MethodDefinition wrapper, TypeReference p, MethodBody body, ILProcessor il) static void EmitStringArrayParameter(MethodDefinition wrapper, ParameterDefinition parameter, MethodBody body, ILProcessor il)
{ {
var p = parameter.ParameterType;
// string[] masrhaling: // string[] masrhaling:
// IntPtr ptr = MarshalStringArrayToPtr(strings); // IntPtr ptr = MarshalStringArrayToPtr(strings);
// try { calli } // try { calli }
// finally { UnmarshalStringArray(ptr); } // finally { FreeStringArrayPtr(ptr); }
var marshal_str_array_to_ptr = wrapper.Module.Import(TypeBindingsBase.Methods.First(m => m.Name == "MarshalStringArrayToPtr")); var marshal_str_array_to_ptr = wrapper.Module.Import(TypeBindingsBase.Methods.First(m => m.Name == "MarshalStringArrayToPtr"));
// IntPtr ptr; // IntPtr ptr;
var variable_name = p.Name + " _string_array_ptr"; var variable_name = parameter.Name + "_string_array_ptr";
body.Variables.Add(new VariableDefinition(variable_name, TypeIntPtr)); body.Variables.Add(new VariableDefinition(variable_name, TypeIntPtr));
int index = body.Variables.Count - 1; int index = body.Variables.Count - 1;
@ -618,28 +657,30 @@ namespace OpenTK.Rewrite
// The finally block will be emitted in the function epilogue // The finally block will be emitted in the function epilogue
} }
static void EmitStringArrayEpilogue(MethodDefinition wrapper, MethodBody body, ILProcessor il) static void EmitStringArrayEpilogue(MethodDefinition wrapper, ParameterDefinition parameter, MethodBody body, ILProcessor il)
{
for (int i = 0; i < wrapper.Parameters.Count; i++)
{
var p = wrapper.Parameters[i].ParameterType;
if (p.Name == "String" && p.IsArray)
{ {
// Note: only works for string vectors (1d arrays).
// We do not (and will probably never) support 2d or higher string arrays
var p = parameter.ParameterType;
var free = wrapper.Module.Import(TypeBindingsBase.Methods.First(m => m.Name == "FreeStringArrayPtr")); var free = wrapper.Module.Import(TypeBindingsBase.Methods.First(m => m.Name == "FreeStringArrayPtr"));
var get_length = wrapper.Module.Import(TypeStringArray.Methods.First(m => m.Name == "get_Length"));
// FreeStringArrayPtr(string_array_ptr, string_array.Length) // FreeStringArrayPtr(string_array_ptr, string_array.Length)
var variable_name = p.Name + "_string_array_ptr"; var variable_name = parameter.Name + "_string_array_ptr";
var v = body.Variables.First(m => m.Name == variable_name); var v = body.Variables.First(m => m.Name == variable_name);
// load string_array_ptr
il.Emit(OpCodes.Ldloc, v.Index); il.Emit(OpCodes.Ldloc, v.Index);
il.Emit(OpCodes.Ldarg, i);
il.Emit(OpCodes.Callvirt, get_length); // load string_array.Length
il.Emit(OpCodes.Ldarg, parameter.Index);
il.Emit(OpCodes.Ldlen);
il.Emit(OpCodes.Conv_I4);
// call FreeStringArrayPtr
il.Emit(OpCodes.Call, free); il.Emit(OpCodes.Call, free);
} }
}
}
private static void EmitConvenienceWrapper(MethodDefinition wrapper, static void EmitConvenienceWrapper(MethodDefinition wrapper,
MethodDefinition native, int difference, MethodBody body, ILProcessor il) MethodDefinition native, int difference, MethodBody body, ILProcessor il)
{ {
if (wrapper.Parameters.Count > 2) if (wrapper.Parameters.Count > 2)
@ -707,43 +748,17 @@ namespace OpenTK.Rewrite
int i; int i;
for (i = 0; i < method.Parameters.Count; i++) for (i = 0; i < method.Parameters.Count; i++)
{ {
var parameter = method.Parameters[i];
var p = method.Module.Import(method.Parameters[i].ParameterType); var p = method.Module.Import(method.Parameters[i].ParameterType);
il.Emit(OpCodes.Ldarg, i); il.Emit(OpCodes.Ldarg, i);
if (p.Name == "StringBuilder") if (p.Name == "StringBuilder")
{ {
// void GetShaderInfoLog(..., StringBuilder foo) EmitStringBuilderParameter(method, parameter, body, il);
// IntPtr foo_sb_ptr;
// try {
// foo_sb_ptr = Marshal.AllocHGlobal(sb.Capacity + 1);
// glGetShaderInfoLog(..., foo_sb_ptr);
// MarshalPtrToStringBuilder(foo_sb_ptr, sb);
// }
// finally {
// Marshal.FreeHGlobal(sb_ptr);
// }
// Make sure we have imported StringBuilder::Capacity and Marshal::AllocHGlobal
var sb_get_capacity = method.Module.Import(TypeStringBuilder.Methods.First(m => m.Name == "get_Capacity"));
var alloc_hglobal = method.Module.Import(TypeMarshal.Methods.First(m => m.Name == "AllocHGlobal"));
// IntPtr ptr;
var variable_name = p.Name + " _sb_ptr";
body.Variables.Add(new VariableDefinition(variable_name, TypeIntPtr));
int index = body.Variables.Count - 1;
// ptr = Marshal.AllocHGlobal(sb.Capacity + 1);
il.Emit(OpCodes.Callvirt, sb_get_capacity);
il.Emit(OpCodes.Call, alloc_hglobal);
il.Emit(OpCodes.Stloc, index);
il.Emit(OpCodes.Ldloc, index);
// We'll emit the try-finally block in the epilogue implementation,
// because we haven't yet emitted all necessary instructions here.
} }
else if (p.Name == "String" && !p.IsArray) else if (p.Name == "String" && !p.IsArray)
{ {
EmitStringParameter(method, p, body, il); EmitStringParameter(method, parameter, body, il);
} }
else if (p.IsByReference) else if (p.IsByReference)
{ {
@ -845,7 +860,7 @@ namespace OpenTK.Rewrite
} }
else else
{ {
EmitStringArrayParameter(method, p, body, il); EmitStringArrayParameter(method, parameter, body, il);
} }
} }
} }