qmp_marshal_output_T() is only ever called by qmp_marshal_C() for a command C that returns type T. We've always generated it as a static function on demand, i.e. when we generate a call. Since we split up monolithic generated code into modules (commit252dc3105f"qapi: Generate separate .h, .c for each module"), we do this per module. As noted in the commit message, this can result in identical (static) qmp_marshal_output_T() in several modules. Was deemed not worth avoiding. A bit later, we added 'if' conditionals to the schema language (merge commit5dafaf4fbc). When a conditional definition uses a type, then its condition must imply the type's condition. We made this the user's responsibility. Hasn't been an issue in practice. However, the sharing of qmp_marshal_output_T() among commands complicates matters. To avoid both undefined function errors and unused function warnings, qmp_marshal_output_T() must be defined exactly when it's used. It is used when any of the qmp_marshal_C() calling it is defined, i.e. when any C's condition holds. The generator uses T's condition instead. To avoid both error and warning, T's condition must be the conjunction of all C's conditions. Unfortunately, this can be impossible: * Conditional command returning a builtin type A builtin type cannot be conditional. This is noted in a FIXME comment. * Commands in multiple modules where the conjunction differs between modules An instance of this came up recently. we have unconditional commands returning HumanReadableText. If we add a conditional one to a module that does not have unconditional ones, compilation fails with "defined but not used". If we make HumanReadableText conditional to fix this module, we break the others. Instead of complicating the code to compute the conjunction, simplify it: generate the output marshaling code right into qmp_marshal_C(). This duplicates it when multiple commands return the same type. The impact on code size is negligible: qemu-system-x86_64's text segment grows by 1448 bytes. Signed-off-by: Markus Armbruster <armbru@redhat.com> Message-ID: <20250804130602.903904-1-armbru@redhat.com> Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org> [Commit message typos fixed]
396 lines
11 KiB
Python
396 lines
11 KiB
Python
"""
|
|
QAPI command marshaller generator
|
|
|
|
Copyright IBM, Corp. 2011
|
|
Copyright (C) 2014-2018 Red Hat, Inc.
|
|
|
|
Authors:
|
|
Anthony Liguori <aliguori@us.ibm.com>
|
|
Michael Roth <mdroth@linux.vnet.ibm.com>
|
|
Markus Armbruster <armbru@redhat.com>
|
|
|
|
This work is licensed under the terms of the GNU GPL, version 2.
|
|
See the COPYING file in the top-level directory.
|
|
"""
|
|
|
|
from typing import (
|
|
List,
|
|
Optional,
|
|
)
|
|
|
|
from .common import c_name, mcgen
|
|
from .gen import (
|
|
QAPISchemaModularCVisitor,
|
|
build_params,
|
|
gen_features,
|
|
ifcontext,
|
|
)
|
|
from .schema import (
|
|
QAPISchema,
|
|
QAPISchemaFeature,
|
|
QAPISchemaIfCond,
|
|
QAPISchemaObjectType,
|
|
QAPISchemaType,
|
|
)
|
|
from .source import QAPISourceInfo
|
|
|
|
|
|
def gen_command_decl(name: str,
|
|
arg_type: Optional[QAPISchemaObjectType],
|
|
boxed: bool,
|
|
ret_type: Optional[QAPISchemaType],
|
|
coroutine: bool) -> str:
|
|
return mcgen('''
|
|
%(c_type)s %(coroutine_fn)sqmp_%(c_name)s(%(params)s);
|
|
''',
|
|
c_type=(ret_type and ret_type.c_type()) or 'void',
|
|
coroutine_fn='coroutine_fn ' if coroutine else '',
|
|
c_name=c_name(name),
|
|
params=build_params(arg_type, boxed, 'Error **errp'))
|
|
|
|
|
|
def gen_call(name: str,
|
|
arg_type: Optional[QAPISchemaObjectType],
|
|
boxed: bool,
|
|
ret_type: Optional[QAPISchemaType],
|
|
gen_tracing: bool) -> str:
|
|
ret = ''
|
|
|
|
argstr = ''
|
|
if boxed:
|
|
assert arg_type
|
|
argstr = '&arg, '
|
|
elif arg_type:
|
|
assert not arg_type.branches
|
|
for memb in arg_type.members:
|
|
assert not memb.ifcond.is_present()
|
|
if memb.need_has():
|
|
argstr += 'arg.has_%s, ' % c_name(memb.name)
|
|
argstr += 'arg.%s, ' % c_name(memb.name)
|
|
|
|
lhs = ''
|
|
if ret_type:
|
|
lhs = 'retval = '
|
|
|
|
name = c_name(name)
|
|
upper = name.upper()
|
|
|
|
if gen_tracing:
|
|
ret += mcgen('''
|
|
|
|
if (trace_event_get_state_backends(TRACE_QMP_ENTER_%(upper)s)) {
|
|
g_autoptr(GString) req_json = qobject_to_json(QOBJECT(args));
|
|
|
|
trace_qmp_enter_%(name)s(req_json->str);
|
|
}
|
|
''',
|
|
upper=upper, name=name)
|
|
|
|
ret += mcgen('''
|
|
|
|
%(lhs)sqmp_%(name)s(%(args)s&err);
|
|
''',
|
|
name=name, args=argstr, lhs=lhs)
|
|
|
|
ret += mcgen('''
|
|
if (err) {
|
|
''')
|
|
|
|
if gen_tracing:
|
|
ret += mcgen('''
|
|
trace_qmp_exit_%(name)s(error_get_pretty(err), false);
|
|
''',
|
|
name=name)
|
|
|
|
ret += mcgen('''
|
|
error_propagate(errp, err);
|
|
goto out;
|
|
}
|
|
''')
|
|
|
|
if ret_type:
|
|
ret += gen_marshal_output(ret_type)
|
|
|
|
if gen_tracing:
|
|
if ret_type:
|
|
ret += mcgen('''
|
|
|
|
if (trace_event_get_state_backends(TRACE_QMP_EXIT_%(upper)s)) {
|
|
g_autoptr(GString) ret_json = qobject_to_json(*ret);
|
|
|
|
trace_qmp_exit_%(name)s(ret_json->str, true);
|
|
}
|
|
''',
|
|
upper=upper, name=name)
|
|
else:
|
|
ret += mcgen('''
|
|
|
|
trace_qmp_exit_%(name)s("{}", true);
|
|
''',
|
|
name=name)
|
|
|
|
return ret
|
|
|
|
|
|
def gen_marshal_output(ret_type: QAPISchemaType) -> str:
|
|
return mcgen('''
|
|
|
|
ov = qobject_output_visitor_new_qmp(ret);
|
|
if (visit_type_%(c_name)s(ov, "unused", &retval, errp)) {
|
|
visit_complete(ov, ret);
|
|
}
|
|
visit_free(ov);
|
|
ov = qapi_dealloc_visitor_new();
|
|
visit_type_%(c_name)s(ov, "unused", &retval, NULL);
|
|
visit_free(ov);
|
|
''',
|
|
c_name=ret_type.c_name())
|
|
|
|
|
|
def build_marshal_proto(name: str,
|
|
coroutine: bool) -> str:
|
|
return ('void %(coroutine_fn)sqmp_marshal_%(c_name)s(%(params)s)' % {
|
|
'coroutine_fn': 'coroutine_fn ' if coroutine else '',
|
|
'c_name': c_name(name),
|
|
'params': 'QDict *args, QObject **ret, Error **errp',
|
|
})
|
|
|
|
|
|
def gen_marshal_decl(name: str,
|
|
coroutine: bool) -> str:
|
|
return mcgen('''
|
|
%(proto)s;
|
|
''',
|
|
proto=build_marshal_proto(name, coroutine))
|
|
|
|
|
|
def gen_trace(name: str) -> str:
|
|
return mcgen('''
|
|
qmp_enter_%(name)s(const char *json) "%%s"
|
|
qmp_exit_%(name)s(const char *result, bool succeeded) "%%s %%d"
|
|
''',
|
|
name=c_name(name))
|
|
|
|
|
|
def gen_marshal(name: str,
|
|
arg_type: Optional[QAPISchemaObjectType],
|
|
boxed: bool,
|
|
ret_type: Optional[QAPISchemaType],
|
|
gen_tracing: bool,
|
|
coroutine: bool) -> str:
|
|
have_args = boxed or (arg_type and not arg_type.is_empty())
|
|
if have_args:
|
|
assert arg_type is not None
|
|
arg_type_c_name = arg_type.c_name()
|
|
|
|
ret = mcgen('''
|
|
|
|
%(proto)s
|
|
{
|
|
Error *err = NULL;
|
|
bool ok = false;
|
|
Visitor *v;
|
|
''',
|
|
proto=build_marshal_proto(name, coroutine))
|
|
|
|
if ret_type:
|
|
ret += mcgen('''
|
|
%(c_type)s retval;
|
|
Visitor *ov;
|
|
''',
|
|
c_type=ret_type.c_type())
|
|
|
|
if have_args:
|
|
ret += mcgen('''
|
|
%(c_name)s arg = {0};
|
|
''',
|
|
c_name=arg_type_c_name)
|
|
|
|
ret += mcgen('''
|
|
|
|
v = qobject_input_visitor_new_qmp(QOBJECT(args));
|
|
if (!visit_start_struct(v, NULL, NULL, 0, errp)) {
|
|
goto out;
|
|
}
|
|
''')
|
|
|
|
if have_args:
|
|
ret += mcgen('''
|
|
if (visit_type_%(c_arg_type)s_members(v, &arg, errp)) {
|
|
ok = visit_check_struct(v, errp);
|
|
}
|
|
''',
|
|
c_arg_type=arg_type_c_name)
|
|
else:
|
|
ret += mcgen('''
|
|
ok = visit_check_struct(v, errp);
|
|
''')
|
|
|
|
ret += mcgen('''
|
|
visit_end_struct(v, NULL);
|
|
if (!ok) {
|
|
goto out;
|
|
}
|
|
''')
|
|
|
|
ret += gen_call(name, arg_type, boxed, ret_type, gen_tracing)
|
|
|
|
ret += mcgen('''
|
|
|
|
out:
|
|
visit_free(v);
|
|
''')
|
|
|
|
ret += mcgen('''
|
|
v = qapi_dealloc_visitor_new();
|
|
visit_start_struct(v, NULL, NULL, 0, NULL);
|
|
''')
|
|
|
|
if have_args:
|
|
ret += mcgen('''
|
|
visit_type_%(c_arg_type)s_members(v, &arg, NULL);
|
|
''',
|
|
c_arg_type=arg_type_c_name)
|
|
|
|
ret += mcgen('''
|
|
visit_end_struct(v, NULL);
|
|
visit_free(v);
|
|
''')
|
|
|
|
ret += mcgen('''
|
|
}
|
|
''')
|
|
return ret
|
|
|
|
|
|
def gen_register_command(name: str,
|
|
features: List[QAPISchemaFeature],
|
|
success_response: bool,
|
|
allow_oob: bool,
|
|
allow_preconfig: bool,
|
|
coroutine: bool) -> str:
|
|
options = []
|
|
|
|
if not success_response:
|
|
options += ['QCO_NO_SUCCESS_RESP']
|
|
if allow_oob:
|
|
options += ['QCO_ALLOW_OOB']
|
|
if allow_preconfig:
|
|
options += ['QCO_ALLOW_PRECONFIG']
|
|
if coroutine:
|
|
options += ['QCO_COROUTINE']
|
|
|
|
ret = mcgen('''
|
|
qmp_register_command(cmds, "%(name)s",
|
|
qmp_marshal_%(c_name)s, %(opts)s, %(feats)s);
|
|
''',
|
|
name=name, c_name=c_name(name),
|
|
opts=' | '.join(options) or 0,
|
|
feats=gen_features(features))
|
|
return ret
|
|
|
|
|
|
class QAPISchemaGenCommandVisitor(QAPISchemaModularCVisitor):
|
|
def __init__(self, prefix: str, gen_tracing: bool):
|
|
super().__init__(
|
|
prefix, 'qapi-commands',
|
|
' * Schema-defined QAPI/QMP commands', None, __doc__,
|
|
gen_tracing=gen_tracing)
|
|
self._gen_tracing = gen_tracing
|
|
|
|
def _begin_user_module(self, name: str) -> None:
|
|
commands = self._module_basename('qapi-commands', name)
|
|
types = self._module_basename('qapi-types', name)
|
|
visit = self._module_basename('qapi-visit', name)
|
|
self._genc.add(mcgen('''
|
|
#include "qemu/osdep.h"
|
|
#include "qapi/compat-policy.h"
|
|
#include "qapi/visitor.h"
|
|
#include "qobject/qdict.h"
|
|
#include "qapi/dealloc-visitor.h"
|
|
#include "qapi/error.h"
|
|
#include "%(visit)s.h"
|
|
#include "%(commands)s.h"
|
|
''',
|
|
commands=commands, visit=visit))
|
|
|
|
if self._gen_tracing and commands != 'qapi-commands':
|
|
self._genc.add(mcgen('''
|
|
#include "qobject/qjson.h"
|
|
#include "trace/trace-%(nm)s_trace_events.h"
|
|
''',
|
|
nm=c_name(commands, protect=False)))
|
|
# We use c_name(commands, protect=False) to turn '-' into '_', to
|
|
# match .underscorify() in trace/meson.build
|
|
|
|
self._genh.add(mcgen('''
|
|
#include "%(types)s.h"
|
|
|
|
''',
|
|
types=types))
|
|
|
|
def visit_begin(self, schema: QAPISchema) -> None:
|
|
self._add_module('./init', ' * QAPI Commands initialization')
|
|
self._genh.add(mcgen('''
|
|
#include "qapi/qmp-registry.h"
|
|
|
|
void %(c_prefix)sqmp_init_marshal(QmpCommandList *cmds);
|
|
''',
|
|
c_prefix=c_name(self._prefix, protect=False)))
|
|
self._genc.add(mcgen('''
|
|
#include "qemu/osdep.h"
|
|
#include "%(prefix)sqapi-commands.h"
|
|
#include "%(prefix)sqapi-init-commands.h"
|
|
#include "%(prefix)sqapi-features.h"
|
|
|
|
void %(c_prefix)sqmp_init_marshal(QmpCommandList *cmds)
|
|
{
|
|
QTAILQ_INIT(cmds);
|
|
|
|
''',
|
|
prefix=self._prefix,
|
|
c_prefix=c_name(self._prefix, protect=False)))
|
|
|
|
def visit_end(self) -> None:
|
|
with self._temp_module('./init'):
|
|
self._genc.add(mcgen('''
|
|
}
|
|
'''))
|
|
|
|
def visit_command(self,
|
|
name: str,
|
|
info: Optional[QAPISourceInfo],
|
|
ifcond: QAPISchemaIfCond,
|
|
features: List[QAPISchemaFeature],
|
|
arg_type: Optional[QAPISchemaObjectType],
|
|
ret_type: Optional[QAPISchemaType],
|
|
gen: bool,
|
|
success_response: bool,
|
|
boxed: bool,
|
|
allow_oob: bool,
|
|
allow_preconfig: bool,
|
|
coroutine: bool) -> None:
|
|
if not gen:
|
|
return
|
|
with ifcontext(ifcond, self._genh, self._genc):
|
|
self._genh.add(gen_command_decl(name, arg_type, boxed,
|
|
ret_type, coroutine))
|
|
self._genh.add(gen_marshal_decl(name, coroutine))
|
|
self._genc.add(gen_marshal(name, arg_type, boxed, ret_type,
|
|
self._gen_tracing, coroutine))
|
|
if self._gen_tracing:
|
|
self._gen_trace_events.add(gen_trace(name))
|
|
with self._temp_module('./init'):
|
|
with ifcontext(ifcond, self._genh, self._genc):
|
|
self._genc.add(gen_register_command(
|
|
name, features, success_response, allow_oob,
|
|
allow_preconfig, coroutine))
|
|
|
|
|
|
def gen_commands(schema: QAPISchema,
|
|
output_dir: str,
|
|
prefix: str,
|
|
gen_tracing: bool) -> None:
|
|
vis = QAPISchemaGenCommandVisitor(prefix, gen_tracing)
|
|
schema.visit(vis)
|
|
vis.write(output_dir)
|