Make C# internal calls return structs as ref parameters

The Mono IL interpreter's WebAssembly to native trampolines don't support passing structs by value, so we need to do it this way.

Also now we pass and return long, ulong, float and double as ref parameters as well. This is due to missing trampolines for float and long types. This is likely a temporary workaround that will be reverted in the future. The correct solution would be to patch 'mono/mini/m2n-gen.cs' when building the Mono runtime for WASM in order to generate the trampolines we need.
This commit is contained in:
Ignacio Etcheverry 2019-11-14 21:48:49 +01:00
parent 2b67924a0b
commit 8cbe4a3db4
2 changed files with 148 additions and 49 deletions

View file

@ -97,7 +97,7 @@
#define C_METHOD_MONOARRAY_TO(m_type) C_NS_MONOMARSHAL "::mono_array_to_" #m_type
#define C_METHOD_MONOARRAY_FROM(m_type) C_NS_MONOMARSHAL "::" #m_type "_to_mono_array"
#define BINDINGS_GENERATOR_VERSION UINT32_C(9)
#define BINDINGS_GENERATOR_VERSION UINT32_C(11)
const char *BindingsGenerator::TypeInterface::DEFAULT_VARARG_C_IN("\t%0 %1_in = %1;\n");
@ -731,13 +731,26 @@ void BindingsGenerator::_generate_method_icalls(const TypeInterface &p_itype) {
i++;
}
String im_type_out = return_type->im_type_out;
if (return_type->ret_as_byref_arg) {
// Doesn't affect the unique signature
im_type_out = "void";
im_sig += ", ";
im_sig += return_type->im_type_out;
im_sig += " argRet";
i++;
}
// godot_icall_{argc}_{icallcount}
String icall_method = ICALL_PREFIX;
icall_method += itos(imethod.arguments.size());
icall_method += "_";
icall_method += itos(method_icalls.size());
InternalCall im_icall = InternalCall(p_itype.api_type, icall_method, return_type->im_type_out, im_sig, im_unique_sig);
InternalCall im_icall = InternalCall(p_itype.api_type, icall_method, im_type_out, im_sig, im_unique_sig);
List<InternalCall>::Element *match = method_icalls.find(im_icall);
@ -1685,17 +1698,18 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf
const InternalCall *im_icall = match->value();
String im_call = im_icall->editor_only ? BINDINGS_CLASS_NATIVECALLS_EDITOR : BINDINGS_CLASS_NATIVECALLS;
im_call += "." + im_icall->name + "(" + icall_params + ")";
im_call += ".";
im_call += im_icall->name;
if (p_imethod.arguments.size())
p_output.append(cs_in_statements);
if (return_type->cname == name_cache.type_void) {
p_output.append(im_call + ";\n");
p_output.append(im_call + "(" + icall_params + ");\n");
} else if (return_type->cs_out.empty()) {
p_output.append("return " + im_call + ";\n");
p_output.append("return " + im_call + "(" + icall_params + ");\n");
} else {
p_output.append(sformat(return_type->cs_out, im_call, return_type->cs_type, return_type->im_type_out));
p_output.append(sformat(return_type->cs_out, im_call, icall_params, return_type->cs_type, return_type->im_type_out));
p_output.append("\n");
}
@ -1937,6 +1951,15 @@ Error BindingsGenerator::_generate_glue_method(const BindingsGenerator::TypeInte
i++;
}
if (return_type->ret_as_byref_arg) {
c_func_sig += ", ";
c_func_sig += return_type->c_type_in;
c_func_sig += " ";
c_func_sig += "arg_ret";
i++;
}
const Map<const MethodInterface *, const InternalCall *>::Element *match = method_icalls_map.find(&p_imethod);
ERR_FAIL_NULL_V(match, ERR_BUG);
@ -1951,14 +1974,12 @@ Error BindingsGenerator::_generate_glue_method(const BindingsGenerator::TypeInte
// Generate icall function
p_output.append(ret_void ? "void " : return_type->c_type_out + " ");
p_output.append((ret_void || return_type->ret_as_byref_arg) ? "void " : return_type->c_type_out + " ");
p_output.append(icall_method);
p_output.append("(");
p_output.append(c_func_sig);
p_output.append(") " OPEN_BLOCK);
String fail_ret = ret_void ? "" : ", " + (return_type->c_type_out.ends_with("*") ? "NULL" : return_type->c_type_out + "()");
if (!ret_void) {
String ptrcall_return_type;
String initialization;
@ -1982,9 +2003,18 @@ Error BindingsGenerator::_generate_glue_method(const BindingsGenerator::TypeInte
p_output.append("\t" + ptrcall_return_type);
p_output.append(" " C_LOCAL_RET);
p_output.append(initialization + ";\n");
p_output.append("\tERR_FAIL_NULL_V(" CS_PARAM_INSTANCE);
p_output.append(fail_ret);
p_output.append(");\n");
String fail_ret = return_type->c_type_out.ends_with("*") && !return_type->ret_as_byref_arg ? "NULL" : return_type->c_type_out + "()";
if (return_type->ret_as_byref_arg) {
p_output.append("\tif (" CS_PARAM_INSTANCE " == NULL) { *arg_ret = ");
p_output.append(fail_ret);
p_output.append("; ERR_FAIL_MSG(\"Parameter ' arg_ret ' is null.\"); }\n");
} else {
p_output.append("\tERR_FAIL_NULL_V(" CS_PARAM_INSTANCE ", ");
p_output.append(fail_ret);
p_output.append(");\n");
}
} else {
p_output.append("\tERR_FAIL_NULL(" CS_PARAM_INSTANCE ");\n");
}
@ -2045,10 +2075,13 @@ Error BindingsGenerator::_generate_glue_method(const BindingsGenerator::TypeInte
}
if (!ret_void) {
if (return_type->c_out.empty())
if (return_type->c_out.empty()) {
p_output.append("\treturn " C_LOCAL_RET ";\n");
else
} else if (return_type->ret_as_byref_arg) {
p_output.append(sformat(return_type->c_out, return_type->c_type_out, C_LOCAL_RET, return_type->name, "arg_ret"));
} else {
p_output.append(sformat(return_type->c_out, return_type->c_type_out, C_LOCAL_RET, return_type->name));
}
}
p_output.append(CLOSE_BLOCK "\n");
@ -2620,30 +2653,32 @@ void BindingsGenerator::_populate_builtin_type_interfaces() {
TypeInterface itype;
#define INSERT_STRUCT_TYPE(m_type, m_type_in) \
#define INSERT_STRUCT_TYPE(m_type) \
{ \
itype = TypeInterface::create_value_type(String(#m_type)); \
itype.c_in = "\t%0 %1_in = MARSHALLED_IN(" #m_type ", %1);\n"; \
itype.c_out = "\treturn MARSHALLED_OUT(" #m_type ", %1);\n"; \
itype.c_out = "\t*%3 = MARSHALLED_OUT(" #m_type ", %1);\n"; \
itype.c_arg_in = "&%s_in"; \
itype.c_type_in = "GDMonoMarshal::M_" #m_type "*"; \
itype.c_type_out = "GDMonoMarshal::M_" #m_type; \
itype.cs_in = "ref %s"; \
itype.cs_out = "return (%1)%0;"; \
itype.im_type_out = itype.cs_type; \
/* in cs_out, im_type_out (%3) includes the 'out ' part */ \
itype.cs_out = "%0(%1, %3 argRet); return (%2)argRet;"; \
itype.im_type_out = "out " + itype.cs_type; \
itype.ret_as_byref_arg = true; \
builtin_types.insert(itype.cname, itype); \
}
INSERT_STRUCT_TYPE(Vector2, "real_t*")
INSERT_STRUCT_TYPE(Rect2, "real_t*")
INSERT_STRUCT_TYPE(Transform2D, "real_t*")
INSERT_STRUCT_TYPE(Vector3, "real_t*")
INSERT_STRUCT_TYPE(Basis, "real_t*")
INSERT_STRUCT_TYPE(Quat, "real_t*")
INSERT_STRUCT_TYPE(Transform, "real_t*")
INSERT_STRUCT_TYPE(AABB, "real_t*")
INSERT_STRUCT_TYPE(Color, "real_t*")
INSERT_STRUCT_TYPE(Plane, "real_t*")
INSERT_STRUCT_TYPE(Vector2)
INSERT_STRUCT_TYPE(Rect2)
INSERT_STRUCT_TYPE(Transform2D)
INSERT_STRUCT_TYPE(Vector3)
INSERT_STRUCT_TYPE(Basis)
INSERT_STRUCT_TYPE(Quat)
INSERT_STRUCT_TYPE(Transform)
INSERT_STRUCT_TYPE(AABB)
INSERT_STRUCT_TYPE(Color)
INSERT_STRUCT_TYPE(Plane)
#undef INSERT_STRUCT_TYPE
@ -2687,11 +2722,44 @@ void BindingsGenerator::_populate_builtin_type_interfaces() {
INSERT_INT_TYPE("sbyte", int8_t, int64_t);
INSERT_INT_TYPE("short", int16_t, int64_t);
INSERT_INT_TYPE("int", int32_t, int64_t);
INSERT_INT_TYPE("long", int64_t, int64_t);
INSERT_INT_TYPE("byte", uint8_t, int64_t);
INSERT_INT_TYPE("ushort", uint16_t, int64_t);
INSERT_INT_TYPE("uint", uint32_t, int64_t);
INSERT_INT_TYPE("ulong", uint64_t, int64_t);
itype = TypeInterface::create_value_type(String("long"));
{
itype.c_out = "\treturn (%0)%1;\n";
itype.c_in = "\t%0 %1_in = (%0)*%1;\n";
itype.c_out = "\t*%3 = (%0)%1;\n";
itype.c_type = "int64_t";
itype.c_arg_in = "&%s_in";
}
itype.c_type_in = "int64_t*";
itype.c_type_out = "int64_t";
itype.im_type_in = "ref " + itype.name;
itype.im_type_out = "out " + itype.name;
itype.cs_in = "ref %0";
/* in cs_out, im_type_out (%3) includes the 'out ' part */
itype.cs_out = "%0(%1, %3 argRet); return (%2)argRet;";
itype.ret_as_byref_arg = true;
builtin_types.insert(itype.cname, itype);
itype = TypeInterface::create_value_type(String("ulong"));
{
itype.c_in = "\t%0 %1_in = (%0)*%1;\n";
itype.c_out = "\t*%3 = (%0)%1;\n";
itype.c_type = "int64_t";
itype.c_arg_in = "&%s_in";
}
itype.c_type_in = "uint64_t*";
itype.c_type_out = "uint64_t";
itype.im_type_in = "ref " + itype.name;
itype.im_type_out = "out " + itype.name;
itype.cs_in = "ref %0";
/* in cs_out, im_type_out (%3) includes the 'out ' part */
itype.cs_out = "%0(%1, %3 argRet); return (%2)argRet;";
itype.ret_as_byref_arg = true;
builtin_types.insert(itype.cname, itype);
}
// Floating point types
@ -2703,16 +2771,20 @@ void BindingsGenerator::_populate_builtin_type_interfaces() {
itype.proxy_name = "float";
{
// The expected type for 'float' in ptrcall is 'double'
itype.c_in = "\t%0 %1_in = (%0)%1;\n";
itype.c_out = "\treturn (%0)%1;\n";
itype.c_in = "\t%0 %1_in = (%0)*%1;\n";
itype.c_out = "\t*%3 = (%0)%1;\n";
itype.c_type = "double";
itype.c_type_in = "float";
itype.c_type_in = "float*";
itype.c_type_out = "float";
itype.c_arg_in = "&%s_in";
}
itype.cs_type = itype.proxy_name;
itype.im_type_in = itype.proxy_name;
itype.im_type_out = itype.proxy_name;
itype.im_type_in = "ref " + itype.proxy_name;
itype.im_type_out = "out " + itype.proxy_name;
itype.cs_in = "ref %0";
/* in cs_out, im_type_out (%3) includes the 'out ' part */
itype.cs_out = "%0(%1, %3 argRet); return (%2)argRet;";
itype.ret_as_byref_arg = true;
builtin_types.insert(itype.cname, itype);
// double
@ -2720,13 +2792,21 @@ void BindingsGenerator::_populate_builtin_type_interfaces() {
itype.name = "double";
itype.cname = itype.name;
itype.proxy_name = "double";
itype.c_type = "double";
itype.c_type_in = "double";
itype.c_type_out = "double";
itype.c_arg_in = "&%s";
{
itype.c_in = "\t%0 %1_in = (%0)*%1;\n";
itype.c_out = "\t*%3 = (%0)%1;\n";
itype.c_type = "double";
itype.c_type_in = "double*";
itype.c_type_out = "double";
itype.c_arg_in = "&%s_in";
}
itype.cs_type = itype.proxy_name;
itype.im_type_in = itype.proxy_name;
itype.im_type_out = itype.proxy_name;
itype.im_type_in = "ref " + itype.proxy_name;
itype.im_type_out = "out " + itype.proxy_name;
itype.cs_in = "ref %0";
/* in cs_out, im_type_out (%3) includes the 'out ' part */
itype.cs_out = "%0(%1, %3 argRet); return (%2)argRet;";
itype.ret_as_byref_arg = true;
builtin_types.insert(itype.cname, itype);
}
@ -2757,7 +2837,7 @@ void BindingsGenerator::_populate_builtin_type_interfaces() {
itype.c_type_out = itype.c_type + "*";
itype.cs_type = itype.proxy_name;
itype.cs_in = "NodePath." CS_SMETHOD_GETINSTANCE "(%0)";
itype.cs_out = "return new %1(%0);";
itype.cs_out = "return new %2(%0(%1));";
itype.im_type_in = "IntPtr";
itype.im_type_out = "IntPtr";
builtin_types.insert(itype.cname, itype);
@ -2773,7 +2853,7 @@ void BindingsGenerator::_populate_builtin_type_interfaces() {
itype.c_type_out = itype.c_type + "*";
itype.cs_type = itype.proxy_name;
itype.cs_in = "RID." CS_SMETHOD_GETINSTANCE "(%0)";
itype.cs_out = "return new %1(%0);";
itype.cs_out = "return new %2(%0(%1));";
itype.im_type_in = "IntPtr";
itype.im_type_out = "IntPtr";
builtin_types.insert(itype.cname, itype);
@ -2855,7 +2935,7 @@ void BindingsGenerator::_populate_builtin_type_interfaces() {
itype.c_type_out = itype.c_type + "*";
itype.cs_type = BINDINGS_NAMESPACE_COLLECTIONS "." + itype.proxy_name;
itype.cs_in = "%0." CS_SMETHOD_GETINSTANCE "()";
itype.cs_out = "return new " + itype.cs_type + "(%0);";
itype.cs_out = "return new " + itype.cs_type + "(%0(%1));";
itype.im_type_in = "IntPtr";
itype.im_type_out = "IntPtr";
builtin_types.insert(itype.cname, itype);
@ -2871,7 +2951,7 @@ void BindingsGenerator::_populate_builtin_type_interfaces() {
itype.c_type_out = itype.c_type + "*";
itype.cs_type = BINDINGS_NAMESPACE_COLLECTIONS "." + itype.proxy_name;
itype.cs_in = "%0." CS_SMETHOD_GETINSTANCE "()";
itype.cs_out = "return new " + itype.cs_type + "(%0);";
itype.cs_out = "return new " + itype.cs_type + "(%0(%1));";
itype.im_type_in = "IntPtr";
itype.im_type_out = "IntPtr";
builtin_types.insert(itype.cname, itype);

View file

@ -214,6 +214,14 @@ class BindingsGenerator {
*/
bool memory_own;
/**
* This must be set to true for any struct bigger than 32-bits. Those cannot be passed/returned by value
* with internal calls, so we must use pointers instead. Returns must be replace with out parameters.
* In this case, [c_out] and [cs_out] must have a different format, explained below.
* The Mono IL interpreter icall trampolines don't support passing structs bigger than 32-bits by value (at least not on WASM).
*/
bool ret_as_byref_arg;
// !! The comments of the following fields make reference to other fields via square brackets, e.g.: [field_name]
// !! When renaming those fields, make sure to rename their references in the comments
@ -248,6 +256,14 @@ class BindingsGenerator {
* %0: [c_type_out] of the return type
* %1: name of the variable to be returned
* %2: [name] of the return type
* ---------------------------------------
* If [ret_as_byref_arg] is true, the format is different. Instead of using a return statement,
* the value must be assigned to a parameter. This type of this parameter is a pointer to [c_type_out].
* Formatting elements:
* %0: [c_type_out] of the return type
* %1: name of the variable to be returned
* %2: [name] of the return type
* %3: name of the parameter that must be assigned the return value
*/
String c_out;
@ -291,9 +307,10 @@ class BindingsGenerator {
* One or more statements that determine how a variable of this type is returned from a method.
* It must contain the return statement(s).
* Formatting elements:
* %0: internal method call statement
* %1: [cs_type] of the return type
* %2: [im_type_out] of the return type
* %0: internal method name
* %1: internal method call arguments without surrounding parenthesis
* %2: [cs_type] of the return type
* %3: [im_type_out] of the return type
*/
String cs_out;
@ -417,7 +434,7 @@ class BindingsGenerator {
r_enum_itype.cs_type = r_enum_itype.proxy_name;
r_enum_itype.cs_in = "(int)%s";
r_enum_itype.cs_out = "return (%1)%0;";
r_enum_itype.cs_out = "return (%2)%0(%1);";
r_enum_itype.im_type_in = "int";
r_enum_itype.im_type_out = "int";
r_enum_itype.class_doc = &EditorHelp::get_doc_data()->class_list[r_enum_itype.proxy_name];
@ -435,6 +452,8 @@ class BindingsGenerator {
memory_own = false;
ret_as_byref_arg = false;
c_arg_in = "%s";
class_doc = NULL;