qemu-cr16/scripts/qapi/commands.py
Markus Armbruster 5bd89761a4 qapi/command: Avoid generating unused qmp_marshal_output_T()
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 (commit
252dc3105f "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
commit 5dafaf4fbc).

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]
2025-11-04 13:34:20 +01:00

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)