gdbus-codegen: Generate signal marshallers for each interface signal

We relied on g_cclosure_marshal_generic() to easily generate signal
marshallers, but this relies on inspecting each parameter type with ffi
and this implies a performance hit, other than breaking the stack-frame
unwinder used by Linux perf and so by sysprof.

Given that we know the types we work on, it's easy enough to generate
the marshallers ourself.

Helps with: https://gitlab.gnome.org/GNOME/glib/-/issues/3028
This commit is contained in:
Marco Trevisan (Treviño) 2023-06-30 02:52:11 +02:00
parent 51c023f189
commit 1cd5c5678a
3 changed files with 201 additions and 19 deletions

View File

@ -2187,7 +2187,7 @@ class CodeGenerator:
" G_STRUCT_OFFSET (%sIface, %s),\n"
" NULL,\n" # accumulator
" NULL,\n" # accu_data
" g_cclosure_marshal_generic,\n"
f" {i.name_lower}_signal_marshal_{s.name_lower},\n"
" G_TYPE_NONE,\n"
" %d"
% (s.name_hyphen, i.camel_name, s.name_lower, len(s.args))
@ -2525,6 +2525,77 @@ class CodeGenerator:
# ---------------------------------------------------------------------------------------------------
def generate_marshaller(self, func_name, in_args):
self.generate_marshaller_declaration(func_name)
self.outfile.write("{\n")
self.generate_marshaller_body(func_name, in_args)
self.outfile.write("}\n" "\n")
def generate_marshaller_declaration(self, func_name):
self.outfile.write(
"static void\n"
f"{func_name} (\n"
" GClosure *closure,\n"
" GValue *return_value G_GNUC_UNUSED,\n"
" unsigned int n_param_values,\n"
" const GValue *param_values,\n"
" void *invocation_hint G_GNUC_UNUSED,\n"
" void *marshal_data)\n"
)
def generate_marshaller_body(self, func_name, in_args=[]):
marshal_func_type = f"{utils.uscore_to_camel_case(func_name)}Func"
self.outfile.write(
f" typedef void (*{marshal_func_type})\n"
" (void *data1,\n"
+ "".join([f" {a.ctype_in}arg_{a.name},\n" for a in in_args])
+ " void *data2);\n"
f" {marshal_func_type} callback;\n"
" GCClosure *cc = (GCClosure*) closure;\n"
f" void *data1, *data2;\n"
"\n"
f" g_return_if_fail (n_param_values == {len(in_args) + 1});\n"
"\n"
" if (G_CCLOSURE_SWAP_DATA (closure))\n"
" {\n"
" data1 = closure->data;\n"
" data2 = g_value_peek_pointer (param_values + 0);\n"
" }\n"
" else\n"
" {\n"
" data1 = g_value_peek_pointer (param_values + 0);\n"
" data2 = closure->data;\n"
" }\n"
"\n"
f" callback = ({marshal_func_type})\n"
" (marshal_data ? marshal_data : cc->callback);\n"
"\n"
" callback (data1,\n"
+ "".join(
[
f" {in_args[i].gvalue_get} (param_values + {i+1}),\n"
for i in range(len(in_args))
]
)
+ " data2);\n"
)
def generate_marshaller_for_type(self, i, t):
assert isinstance(t, dbustypes.Signal)
kind_uscore = utils.camel_case_to_uscore(t.__class__.__name__.lower())
self.generate_marshaller(
func_name=f"{i.name_lower}_{kind_uscore}_marshal_{t.name_lower}",
in_args=t.args,
)
def generate_signal_marshallers(self, i):
for s in i.signals:
self.generate_marshaller_for_type(i, s)
# ---------------------------------------------------------------------------------------------------
def generate_method_calls(self, i):
for m in i.methods:
# async begin
@ -5249,6 +5320,7 @@ class CodeGenerator:
self.generate_interface_intro(i)
self.generate_signals_enum_for_interface(i)
self.generate_introspection_for_interface(i)
self.generate_signal_marshallers(i)
self.generate_interface(i)
self.generate_property_accessors(i)
self.generate_signal_emitters(i)

View File

@ -114,6 +114,10 @@ def camel_case_to_uscore(s):
return ret
def uscore_to_camel_case(s):
return "".join([s[0].upper() + s[1:].lower() if s else "_" for s in s.split("_")])
def is_ugly_case(s):
if s and s.find("_") > 0:
return True

View File

@ -61,24 +61,24 @@ class TestCodegen(unittest.TestCase):
cwd = ""
ARGUMENTS_TYPES = {
"b": {},
"y": {},
"n": {},
"q": {},
"i": {},
"u": {},
"x": {},
"t": {},
"d": {},
"s": {},
"o": {},
"g": {},
"h": {},
"ay": {},
"as": {},
"ao": {},
"aay": {},
"asv": {"variant_type": "a{sv}"},
"b": {"value_type": "boolean"},
"y": {"value_type": "uchar"},
"n": {"value_type": "int"},
"q": {"value_type": "uint"},
"i": {"value_type": "int"},
"u": {"value_type": "uint"},
"x": {"value_type": "int64"},
"t": {"value_type": "uint64"},
"d": {"value_type": "double"},
"s": {"value_type": "string"},
"o": {"value_type": "string"},
"g": {"value_type": "string"},
"h": {"value_type": "variant"},
"ay": {"value_type": "string"},
"as": {"value_type": "boxed"},
"ao": {"value_type": "boxed"},
"aay": {"value_type": "boxed"},
"asv": {"value_type": "variant", "variant_type": "a{sv}"},
}
def setUp(self):
@ -797,6 +797,112 @@ G_END_DECLS
1,
)
@unittest.skipIf(on_win32(), "requires /dev/stdout")
def test_generate_signals_marshaller_simple_signal(self):
"""Test that signals marshaller is generated for simple signal"""
interface_xml = """
<node>
<interface name="org.project.SignalingIface">
<signal name="SimpleSignal"/>
</interface>
<interface name="org.project.OtherSignalingIface">
<signal name="SimpleSignal"/>
</interface>
</node>"""
result = self.runCodegenWithInterface(
interface_xml, "--output", "/dev/stdout", "--body"
)
stripped_out = result.out.strip()
self.assertFalse(result.err)
self.assertIs(stripped_out.count("g_cclosure_marshal_generic"), 0)
func_name = "org_project_signaling_iface_signal_marshal_simple_signal"
self.assertIs(stripped_out.count(f"{func_name},"), 1)
self.assertIs(stripped_out.count(f"{func_name} ("), 1)
func_name = "org_project_other_signaling_iface_signal_marshal_simple_signal"
self.assertIs(stripped_out.count(f"{func_name},"), 1)
self.assertIs(stripped_out.count(f"{func_name} ("), 1)
@unittest.skipIf(on_win32(), "requires /dev/stdout")
def test_generate_signals_marshaller_single_typed_args(self):
"""Test that signals marshaller is generated for each known type"""
for t, props in self.ARGUMENTS_TYPES.items():
camel_type = t[0].upper() + t[1:]
interface_xml = f"""
<node>
<interface name="org.project.SignalingIface">
<signal name="SimpleSignal"/>
<signal name="SingleArgSignal{camel_type}">
<arg name="arg_{t}" type="{props.get("variant_type", t)}"/>
</signal>
</interface>
</node>"""
result = self.runCodegenWithInterface(
interface_xml, "--output", "/dev/stdout", "--body"
)
stripped_out = result.out.strip()
self.assertFalse(result.err)
self.assertEqual(stripped_out.count("g_cclosure_marshal_generic"), 0)
func_name = (
f"org_project_signaling_iface_signal_marshal_single_arg_signal_{t}"
)
self.assertIs(stripped_out.count(f"{func_name},"), 1)
self.assertIs(stripped_out.count(f"{func_name} ("), 1)
self.assertIs(
stripped_out.count(
f"g_value_get_{props['value_type']} (param_values + 1)"
),
1,
)
@unittest.skipIf(on_win32(), "requires /dev/stdout")
def test_generate_signals_marshallers_multiple_args(self):
"""Test that signals marshallers are generated"""
generated_args = [
f"<arg name='an_{t}' type='{props.get('variant_type', t)}'/>\n"
for t, props in self.ARGUMENTS_TYPES.items()
]
interface_xml = f"""
<node>
<interface name="org.project.SignalingIface">
<signal name="SimpleSignal"/>
<signal name="SignalWithManyArgs">
{''.join(generated_args)}
</signal>
</interface>
</node>"""
result = self.runCodegenWithInterface(
interface_xml, "--output", "/dev/stdout", "--body"
)
stripped_out = result.out.strip()
self.assertFalse(result.err)
self.assertIs(stripped_out.count("g_cclosure_marshal_generic"), 0)
func_name = f"org_project_signaling_iface_signal_marshal_simple_signal"
self.assertIs(stripped_out.count(f"{func_name},"), 1)
self.assertIs(stripped_out.count(f"{func_name} ("), 1)
func_name = f"org_project_signaling_iface_signal_marshal_signal_with_many_args"
self.assertIs(stripped_out.count(f"{func_name},"), 1)
self.assertIs(stripped_out.count(f"{func_name} ("), 1)
# Check access to MultipleArgsSignal arguments
index = 1
for props in self.ARGUMENTS_TYPES.values():
self.assertIs(
stripped_out.count(
f"g_value_get_{props['value_type']} (param_values + {index})"
),
1,
)
index += 1
def test_generate_valid_docbook(self):
"""Test the basic functionality of the docbook generator."""
xml_contents = """