diff options
Diffstat (limited to 'lib9p/protogen')
-rw-r--r-- | lib9p/protogen/c.py | 125 | ||||
-rw-r--r-- | lib9p/protogen/c9util.py | 47 | ||||
-rw-r--r-- | lib9p/protogen/c_format.py | 142 | ||||
-rw-r--r-- | lib9p/protogen/c_marshal.py | 47 | ||||
-rw-r--r-- | lib9p/protogen/c_unmarshal.py | 13 | ||||
-rw-r--r-- | lib9p/protogen/c_validate.py | 28 | ||||
-rw-r--r-- | lib9p/protogen/h.py | 270 |
7 files changed, 481 insertions, 191 deletions
diff --git a/lib9p/protogen/c.py b/lib9p/protogen/c.py index 5e67939..a6824ce 100644 --- a/lib9p/protogen/c.py +++ b/lib9p/protogen/c.py @@ -7,7 +7,7 @@ import sys import idl -from . import c9util, c_marshal, c_unmarshal, c_validate, cutil +from . import c9util, c_format, c_marshal, c_unmarshal, c_validate, cutil # This strives to be "general-purpose" in that it just acts on the # *.9p inputs; but (unfortunately?) there are a few special-cases in @@ -29,11 +29,21 @@ def gen_c(versions: set[str], typs: list[idl.UserType]) -> str: #include <string.h> /* for memset() */ #include <libmisc/assert.h> +#include <libmisc/endian.h> #include <lib9p/9p.h> -#include "internal.h" +#include "tables.h" +#include "utf8.h" """ + # libobj vtables ########################################################### + ret += """ +/* libobj vtables *************************************************************/ +""" + for typ in typs: + ret += cutil.ifdef_push(1, c9util.ver_ifdef(typ.in_versions)) + ret += f"LO_IMPLEMENTATION_C(fmt_formatter, {c9util.typename(typ)}, {c9util.basename(typ)}, static);\n" + ret += cutil.ifdef_pop(0) # utilities ################################################################ ret += """ @@ -44,28 +54,6 @@ def gen_c(versions: set[str], typs: list[idl.UserType]) -> str: for msg in [msg for msg in typs if isinstance(msg, idl.Message)]: id2typ[msg.msgid] = msg - def msg_table(grp: str, meth: str, tentry: str, rng: tuple[int, int, int]) -> str: - ret = f"const {tentry} {c9util.ident(f'_table_{grp}_{meth}')}[{c9util.ver_enum('NUM')}][{hex(len(range(*rng)))}] = {{\n" - for ver in ["unknown", *sorted(versions)]: - if ver != "unknown": - ret += cutil.ifdef_push(1, c9util.ver_ifdef({ver})) - ret += f"\t[{c9util.ver_enum(ver)}] = {{\n" - for n in range(*rng): - xmsg: idl.Message | None = id2typ.get(n, None) - if xmsg: - if ver == "unknown": # SPECIAL (initialization) - if xmsg.typname not in ["Tversion", "Rversion", "Rerror"]: - xmsg = None - else: - if ver not in xmsg.in_versions: - xmsg = None - if xmsg: - ret += f"\t\t_MSG_{meth.upper()}({xmsg.typname}),\n" - ret += "\t},\n" - ret += cutil.ifdef_pop(0) - ret += "};\n" - return ret - for v in sorted(versions): ret += f"#if CONFIG_9P_ENABLE_{v.replace('.', '_')}\n" ret += ( @@ -83,23 +71,6 @@ def gen_c(versions: set[str], typs: list[idl.UserType]) -> str: ret += " */\n" ret += "#define is_ver(ctx, ver) _is_ver_##ver((ctx)->version)\n" - # strings ################################################################## - ret += f""" -/* strings ********************************************************************/ - -const char *const {c9util.ident('_table_ver_name')}[{c9util.ver_enum('NUM')}] = {{ -""" - for ver in ["unknown", *sorted(versions)]: - if ver in versions: - ret += cutil.ifdef_push(1, c9util.ver_ifdef({ver})) - ret += f'\t[{c9util.ver_enum(ver)}] = "{ver}",\n' - ret += cutil.ifdef_pop(0) - ret += "};\n" - - ret += "\n" - ret += f"#define _MSG_NAME(typ) [{c9util.Ident('TYP_')}##typ] = #typ\n" - ret += msg_table("msg", "name", "char *const", (0, 0x100, 1)) - # bitmasks ################################################################# ret += """ /* bitmasks *******************************************************************/ @@ -118,7 +89,7 @@ const char *const {c9util.ident('_table_ver_name')}[{c9util.ver_enum('NUM')}] = + "".join( ( "1" - if bit.cat in (idl.BitCat.USED, idl.BitCat.SUBFIELD) + if (bit.cat == "USED" or isinstance(bit.cat, idl.BitNum)) and ver in bit.in_versions else "0" ) @@ -139,25 +110,63 @@ const char *const {c9util.ident('_table_ver_name')}[{c9util.ver_enum('NUM')}] = # marshal_* ################################################################ ret += c_marshal.gen_c_marshal(versions, typs) - # function tables ########################################################## + # *_format ################################################################# + ret += c_format.gen_c_format(versions, typs) + + # tables.h ################################################################# ret += """ -/* function tables ************************************************************/ +/* tables.h *******************************************************************/ """ ret += "\n" - ret += f"const uint32_t {c9util.ident('_table_msg_min_size')}[{c9util.ver_enum('NUM')}] = {{\n" + ret += f"const struct {c9util.ident('_ver_tentry')} {c9util.ident('_table_ver')}[{c9util.ver_enum('NUM')}] = {{\n" rerror = next(typ for typ in typs if typ.typname == "Rerror") - ret += f"\t[{c9util.ver_enum('unknown')}] = {rerror.min_size('9P2000')},\n" # SPECIAL (initialization) - for ver in sorted(versions): - ret += cutil.ifdef_push(1, c9util.ver_ifdef({ver})) - ret += f"\t[{c9util.ver_enum(ver)}] = {rerror.min_size(ver)},\n" + for ver in ["unknown", *sorted(versions)]: + if ver == "unknown": + min_msg_size = rerror.min_size("9P2000") # SPECIAL (initialization) + else: + ret += cutil.ifdef_push(1, c9util.ver_ifdef({ver})) + min_msg_size = rerror.min_size(ver) + ret += f'\t[{c9util.ver_enum(ver)}] = {{.name="{ver}", .min_msg_size={min_msg_size}}},\n' ret += cutil.ifdef_pop(0) ret += "};\n" + def msg_table( + cstruct: str, cname: str, each: str, _range: tuple[int, int, int] + ) -> str: + ret = f"const struct {c9util.ident(cstruct)} {c9util.ident(cname)}[{c9util.ver_enum('NUM')}][{hex(len(range(*_range)))}] = {{\n" + for ver in ["unknown", *sorted(versions)]: + if ver != "unknown": + ret += cutil.ifdef_push(1, c9util.ver_ifdef({ver})) + ret += f"\t[{c9util.ver_enum(ver)}] = {{\n" + for n in range(*_range): + xmsg: idl.Message | None = id2typ.get(n, None) + if xmsg: + if ver == "unknown": # SPECIAL (initialization) + if xmsg.typname not in ["Tversion", "Rversion", "Rerror"]: + xmsg = None + else: + if ver not in xmsg.in_versions: + xmsg = None + if xmsg: + ret += f"\t\t{each}({xmsg.typname}),\n" + ret += "\t},\n" + ret += cutil.ifdef_pop(0) + ret += "};\n" + return ret + + ret += "\n" + ret += cutil.macro( + f"#define _MSG(typ) [{c9util.Ident('TYP_')}##typ] = {{\n" + f"\t\t.name = #typ,\n" + f"\t\t.box_as_fmt_formatter = (_box_as_fmt_formatter_fn_t)lo_box_{c9util.ident('msg_')}##typ##_as_fmt_formatter,\n" + f"\t}}\n" + ) + ret += msg_table("_msg_tentry", "_table_msg", "_MSG", (0, 0x100, 1)) + ret += "\n" ret += cutil.macro( f"#define _MSG_RECV(typ) [{c9util.Ident('TYP_')}##typ/2] = {{\n" - f"\t\t.basesize = sizeof(struct {c9util.ident('msg_')}##typ),\n" f"\t\t.validate = validate_##typ,\n" f"\t\t.unmarshal = (_unmarshal_fn_t)unmarshal_##typ,\n" f"\t}}\n" @@ -168,21 +177,13 @@ const char *const {c9util.ident('_table_ver_name')}[{c9util.ver_enum('NUM')}] = f"\t}}\n" ) ret += "\n" - ret += msg_table( - "Tmsg", "recv", f"struct {c9util.ident('_recv_tentry')}", (0, 0x100, 2) - ) + ret += msg_table("_recv_tentry", "_table_Tmsg_recv", "_MSG_RECV", (0, 0x100, 2)) ret += "\n" - ret += msg_table( - "Rmsg", "recv", f"struct {c9util.ident('_recv_tentry')}", (1, 0x100, 2) - ) + ret += msg_table("_recv_tentry", "_table_Rmsg_recv", "_MSG_RECV", (1, 0x100, 2)) ret += "\n" - ret += msg_table( - "Tmsg", "send", f"struct {c9util.ident('_send_tentry')}", (0, 0x100, 2) - ) + ret += msg_table("_send_tentry", "_table_Tmsg_send", "_MSG_SEND", (0, 0x100, 2)) ret += "\n" - ret += msg_table( - "Rmsg", "send", f"struct {c9util.ident('_send_tentry')}", (1, 0x100, 2) - ) + ret += msg_table("_send_tentry", "_table_Rmsg_send", "_MSG_SEND", (1, 0x100, 2)) ret += f""" LM_FLATTEN ssize_t {c9util.ident('_stat_validate')}(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes, uint32_t *ret_net_size) {{ diff --git a/lib9p/protogen/c9util.py b/lib9p/protogen/c9util.py index e7ad999..cf91951 100644 --- a/lib9p/protogen/c9util.py +++ b/lib9p/protogen/c9util.py @@ -3,6 +3,7 @@ # Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> # SPDX-License-Identifier: AGPL-3.0-or-later +import re import typing import idl @@ -72,6 +73,20 @@ def ver_cond(versions: typing.Collection[str]) -> str: # misc ######################################################################### +def basename(typ: idl.UserType) -> str: + match typ: + case idl.Number(): + return ident(typ.typname) + case idl.Bitfield(): + return ident(typ.typname) + case idl.Message(): + return ident(f"msg_{typ.typname}") + case idl.Struct(): + return ident(typ.typname) + case _: + raise ValueError(f"not a defined type: {typ.__class__.__name__}") + + def typename(typ: idl.Type, parent: idl.StructMember | None = None) -> str: match typ: case idl.Primitive(): @@ -79,31 +94,41 @@ def typename(typ: idl.Type, parent: idl.StructMember | None = None) -> str: return "[[gnu::nonstring]] char" return f"uint{typ.value*8}_t" case idl.Number(): - return ident(f"{typ.typname}_t") + return f"{basename(typ)}_t" case idl.Bitfield(): - return ident(f"{typ.typname}_t") + return f"{basename(typ)}_t" case idl.Message(): - return f"struct {ident(f'msg_{typ.typname}')}" + return f"struct {basename(typ)}" case idl.Struct(): - return f"struct {ident(typ.typname)}" + return f"struct {basename(typ)}" case _: raise ValueError(f"not a type: {typ.__class__.__name__}") -def idl_expr(expr: idl.Expr, lookup_sym: typing.Callable[[str], str]) -> str: +def idl_expr( + expr: idl.Expr, lookup_sym: typing.Callable[[str], str], bitwidth: int = 0 +) -> str: ret: list[str] = [] for tok in expr.tokens: match tok: case idl.ExprOp(): ret.append(tok.op) case idl.ExprLit(): - ret.append(str(tok.val)) - case idl.ExprSym(symname="s32_max"): - ret.append("INT32_MAX") - case idl.ExprSym(symname="s64_max"): - ret.append("INT64_MAX") + if bitwidth: + ret.append(f"{tok.val:#0{bitwidth}b}") + else: + ret.append(str(tok.val)) case idl.ExprSym(): - ret.append(lookup_sym(tok.symname)) + if m := re.fullmatch(r"^u(8|16|32|64)_max$", tok.symname): + ret.append(f"UINT{m.group(1)}_MAX") + elif m := re.fullmatch(r"^s(8|16|32|64)_max$", tok.symname): + ret.append(f"INT{m.group(1)}_MAX") + else: + ret.append(lookup_sym(tok.symname)) + case idl.ExprOff(): + ret.append(lookup_sym("&" + tok.membname)) + case idl.ExprNum(): + ret.append(Ident(add_prefix(f"{tok.numname}_".upper(), tok.valname))) case _: assert False return " ".join(ret) diff --git a/lib9p/protogen/c_format.py b/lib9p/protogen/c_format.py new file mode 100644 index 0000000..a1bcbf3 --- /dev/null +++ b/lib9p/protogen/c_format.py @@ -0,0 +1,142 @@ +# lib9p/protogen/c_format.py - Generate C pretty-print functions +# +# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> +# SPDX-License-Identifier: AGPL-3.0-or-later + + +import idl + +from . import c9util, cutil + +# This strives to be "general-purpose" in that it just acts on the +# *.9p inputs; but (unfortunately?) there are a few special-cases in +# this script, marked with "SPECIAL". + +# pylint: disable=unused-variable +__all__ = ["gen_c_format"] + + +def bf_numname(typ: idl.Bitfield, num: idl.BitNum, base: str) -> str: + prefix = f"{typ.typname}_{num.numname}_".upper() + return c9util.Ident(c9util.add_prefix(prefix, base)) + + +def gen_c_format(versions: set[str], typs: list[idl.UserType]) -> str: + ret = """ +/* *_format *******************************************************************/ +""" + for typ in typs: + ret += "\n" + ret += cutil.ifdef_push(1, c9util.ver_ifdef(typ.in_versions)) + ret += f"static void {c9util.basename(typ)}_format({c9util.typename(typ)} *self, struct fmt_state *state) {{\n" + match typ: + case idl.Number(): + if typ.vals: + ret += "\tswitch (*self) {\n" + for name in typ.vals: + ret += f"\tcase {c9util.Ident(c9util.add_prefix(f'{typ.typname}_'.upper(), name))}:\n" + ret += f'\t\tfmt_state_puts(state, "{name}");\n' + ret += "\t\tbreak;\n" + ret += "\tdefault:\n" + ret += f'\t\tfmt_state_printf(state, "%"PRIu{typ.static_size*8}, *self);\n' + ret += "\t}\n" + else: + ret += f'\t\tfmt_state_printf(state, "%"PRIu{typ.static_size*8}, *self);\n' + case idl.Bitfield(): + val = "*self" + if typ.typname == "dm": # SPECIAL (pretty file permissions) + val = f"(*self & ~(({c9util.typename(typ)})0777))" + ret += "\tbool empty = true;\n" + ret += "\tfmt_state_putchar(state, '(');\n" + nums: set[str] = set() + + for bit in reversed(typ.bits): + match bit.cat: + case "UNUSED" | "USED" | "RESERVED": + if bit.cat == "UNUSED": + bitname = f"1<<{bit.num}" + else: + bitname = bit.bitname + ret += f"\tif ({val} & (UINT{typ.static_size*8}_C(1)<<{bit.num})) {{\n" + ret += "\t\tif (!empty)\n" + ret += "\t\t\tfmt_state_putchar(state, '|');\n" + ret += f'\t\tfmt_state_puts(state, "{bitname}");\n' + ret += "\t\tempty = false;\n" + ret += "\t}\n" + case idl.BitNum(): + if bit.cat.numname in nums: + continue + ret += f"\tswitch ({val} & {bf_numname(typ, bit.cat, 'MASK')}) {{\n" + for name in bit.cat.vals: + ret += f"\tcase {bf_numname(typ, bit.cat, name)}:\n" + bitname = c9util.add_prefix( + f"{bit.cat.numname}_".upper(), name + ) + ret += "\t\tif (!empty)\n" + ret += "\t\t\tfmt_state_putchar(state, '|');\n" + ret += f'\t\tfmt_state_puts(state, "{bitname}");\n' + ret += "\t\tempty = false;\n" + ret += "\t\tbreak;\n" + ret += "\tdefault:\n" + ret += "\t\tif (!empty)\n" + ret += "\t\t\tfmt_state_putchar(state, '|');\n" + ret += f'\t\tfmt_state_printf(state, "%"PRIu{typ.static_size*8}, {val} & {bf_numname(typ, bit.cat, 'MASK')});\n' + ret += "\t\tempty = false;\n" + ret += "\t}\n" + nums.add(bit.cat.numname) + if typ.typname == "dm": # SPECIAL (pretty file permissions) + ret += "\tif (!empty)\n" + ret += "\t\tfmt_state_putchar(state, '|');\n" + ret += f'\tfmt_state_printf(state, "%#04"PRIo{typ.static_size*8}, *self & 0777);\n' + else: + ret += "\tif (empty)\n" + ret += "\t\tfmt_state_putchar(state, '0');\n" + ret += "\tfmt_state_putchar(state, ')');\n" + case idl.Struct(typname="s"): # SPECIAL(string) + ret += "\t/* https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47781 */\n" + ret += "#pragma GCC diagnostic push\n" + ret += '#pragma GCC diagnostic ignored "-Wformat"\n' + ret += '#pragma GCC diagnostic ignored "-Wformat-extra-args"\n' + ret += '\tfmt_state_printf(state, "%.*q", self->len, self->utf8);\n' + ret += "#pragma GCC diagnostic pop\n" + case idl.Struct(): # and idl.Message(): + if isinstance(typ, idl.Message): + ret += f'\tfmt_state_puts(state, "{typ.typname} {{");\n' + else: + ret += "\tfmt_state_putchar(state, '{');\n" + for member in typ.members: + if member.val: + continue + ret += cutil.ifdef_push(2, c9util.ver_ifdef(member.in_versions)) + if member.cnt: + if member.typ.static_size == 1: # SPECIAL (data) + ret += f'\tfmt_state_puts(state, " {member.membname}=<bytedata>");\n' + continue + if isinstance(member.cnt, int): + cnt_str = str(member.cnt) + cnt_typ = "size_t" + else: + cnt_str = f"self->{member.cnt.membname}" + cnt_typ = c9util.typename(member.cnt.typ) + ret += f'\tfmt_state_puts(state, " {member.membname}=[");\n' + ret += f"\tfor ({cnt_typ} i = 0; i < {cnt_str}; i++) {{\n" + ret += "\t\tif (i)\n" + ret += '\t\t\tfmt_state_puts(state, ", ");\n' + if isinstance(member.typ, idl.Primitive): + ret += f'\t\tfmt_state_printf(state, "%"PRIu{member.typ.static_size*8}, self->{member.membname}[i]);\n' + else: + ret += f"\t\t{c9util.basename(member.typ)}_format(&self->{member.membname}[i], state);\n" + ret += "\t}\n" + ret += '\tfmt_state_puts(state, " ]");\n' + else: + ret += f'\tfmt_state_puts(state, " {member.membname}=");\n' + if isinstance(member.typ, idl.Primitive): + ret += f'\tfmt_state_printf(state, "%"PRIu{member.typ.static_size*8}, self->{member.membname});\n' + else: + ret += f"\t{c9util.basename(member.typ)}_format(&self->{member.membname}, state);\n" + ret += cutil.ifdef_pop(1) + ret += '\tfmt_state_puts(state, " }");\n' + ret += "}\n" + ret += cutil.ifdef_pop(0) + + return ret diff --git a/lib9p/protogen/c_marshal.py b/lib9p/protogen/c_marshal.py index 74b64f5..4dab864 100644 --- a/lib9p/protogen/c_marshal.py +++ b/lib9p/protogen/c_marshal.py @@ -23,7 +23,7 @@ __all__ = ["gen_c_marshal"] class OffsetExpr: static: int cond: dict[frozenset[str], "OffsetExpr"] - rep: list[tuple[idlutil.Path, "OffsetExpr"]] + rep: list[tuple[idlutil.Path | int, "OffsetExpr"]] def __init__(self) -> None: self.static = 0 @@ -52,14 +52,20 @@ class OffsetExpr: if self.static: oneline.append(str(self.static)) for cnt, sub in self.rep: + if isinstance(cnt, int): + cnt_str = str(cnt) + cnt_typ = "size_t" + else: + cnt_str = cnt.c_str(root) + cnt_typ = c9util.typename(cnt.elems[-1].typ) if not sub.cond and not sub.rep: if sub.static == 1: - oneline.append(cnt.c_str(root)) + oneline.append(cnt_str) else: - oneline.append(f"({cnt.c_str(root)})*{sub.static}") + oneline.append(f"({cnt_str})*{sub.static}") continue loopvar = chr(ord("i") + loop_depth) - multiline += f"{'\t'*indent_depth}for ({c9util.typename(cnt.elems[-1].typ)} {loopvar} = 0; {loopvar} < {cnt.c_str(root)}; {loopvar}++) {{\n" + multiline += f"{'\t'*indent_depth}for ({cnt_typ} {loopvar} = 0; {loopvar} < {cnt_str}; {loopvar}++) {{\n" multiline += sub.gen_c("", dstvar, root, indent_depth + 1, loop_depth + 1) multiline += f"{'\t'*indent_depth}}}\n" for vers, sub in self.cond.items(): @@ -113,8 +119,12 @@ def get_offset_expr(typ: idl.UserType, recurse: OffsetExprRecursion) -> OffsetEx member_path = expr_stack[-1].path member = member_path.elems[-1] assert member.cnt - cnt_path = member_path.parent().add(member.cnt) - expr_stack[-2].expr.rep.append((cnt_path, expr_stack[-1].expr)) + cnt: idlutil.Path | int + if isinstance(member.cnt, int): + cnt = member.cnt + else: + cnt = member_path.parent().add(member.cnt) + expr_stack[-2].expr.rep.append((cnt, expr_stack[-1].expr)) expr_stack = expr_stack[:-1] def handle( @@ -268,11 +278,13 @@ def gen_c_marshal(versions: set[str], typs: list[idl.UserType]) -> str: if not member.val: continue for tok in member.val.tokens: - if not isinstance(tok, idl.ExprSym): - continue - if tok.symname == "end" or tok.symname.startswith("&"): - if tok.symname not in offsets: - offsets.append(tok.symname) + match tok: + case idl.ExprSym(symname="end"): + if tok.symname not in offsets: + offsets.append(tok.symname) + case idl.ExprOff(): + if f"&{tok.membname}" not in offsets: + offsets.append(f"&{tok.membname}") for name in offsets: name_prefix = f"offsetof{''.join('_'+m.membname for m in path.elems)}_" if name == "end": @@ -313,15 +325,20 @@ def gen_c_marshal(versions: set[str], typs: list[idl.UserType]) -> str: ) indent_stack.append(IndentLevel(ifdef=True)) if child.cnt: - cnt_path = path.parent().add(child.cnt) + if isinstance(child.cnt, int): + cnt_str = str(child.cnt) + cnt_typ = "size_t" + else: + cnt_str = path.parent().add(child.cnt).c_str("val->") + cnt_typ = c9util.typename(child.cnt.typ) if child.typ.static_size == 1: # SPECIAL (zerocopy) if path.root.typname == "stat": # SPECIAL (stat) - ret += f"{'\t'*indent_lvl()}MARSHAL_BYTES(ctx, {path.c_str('val->')[:-3]}, {cnt_path.c_str('val->')});\n" + ret += f"{'\t'*indent_lvl()}MARSHAL_BYTES(ctx, {path.c_str('val->')[:-3]}, {cnt_str});\n" else: - ret += f"{'\t'*indent_lvl()}MARSHAL_BYTES_ZEROCOPY(ctx, {path.c_str('val->')[:-3]}, {cnt_path.c_str('val->')});\n" + ret += f"{'\t'*indent_lvl()}MARSHAL_BYTES_ZEROCOPY(ctx, {path.c_str('val->')[:-3]}, {cnt_str});\n" return idlutil.WalkCmd.KEEP_GOING, pop loopvar = chr(ord("i") + loopdepth - 1) - ret += f"{'\t'*indent_lvl()}for ({c9util.typename(child.cnt.typ)} {loopvar} = 0; {loopvar} < {cnt_path.c_str('val->')}; {loopvar}++) {{\n" + ret += f"{'\t'*indent_lvl()}for ({cnt_typ} {loopvar} = 0; {loopvar} < {cnt_str}; {loopvar}++) {{\n" indent_stack.append(IndentLevel(ifdef=False)) if not isinstance(child.typ, idl.Struct): if child.val: diff --git a/lib9p/protogen/c_unmarshal.py b/lib9p/protogen/c_unmarshal.py index 018d750..34635f9 100644 --- a/lib9p/protogen/c_unmarshal.py +++ b/lib9p/protogen/c_unmarshal.py @@ -93,15 +93,20 @@ def gen_c_unmarshal(versions: set[str], typs: list[idl.UserType]) -> str: ) indent_stack.append(IndentLevel(ifdef=True)) if child.cnt: - cnt_path = path.parent().add(child.cnt) + if isinstance(child.cnt, int): + cnt_str = str(child.cnt) + cnt_typ = "size_t" + else: + cnt_str = path.parent().add(child.cnt).c_str("out->") + cnt_typ = c9util.typename(child.cnt.typ) if child.typ.static_size == 1: # SPECIAL (zerocopy) - ret += f"{'\t'*indent_lvl()}UNMARSHAL_BYTES(ctx, {path.c_str('out->')[:-3]}, {cnt_path.c_str('out->')});\n" + ret += f"{'\t'*indent_lvl()}UNMARSHAL_BYTES(ctx, {path.c_str('out->')[:-3]}, {cnt_str});\n" return idlutil.WalkCmd.KEEP_GOING, pop ret += f"{'\t'*indent_lvl()}{path.c_str('out->')[:-3]} = extra;\n" - ret += f"{'\t'*indent_lvl()}extra += sizeof({path.c_str('out->')[:-3]}[0]) * {cnt_path.c_str('out->')};\n" + ret += f"{'\t'*indent_lvl()}extra += sizeof({path.c_str('out->')[:-3]}[0]) * {cnt_str};\n" loopdepth = sum(1 for elem in path.elems if elem.cnt) loopvar = chr(ord("i") + loopdepth - 1) - ret += f"{'\t'*indent_lvl()}for ({c9util.typename(child.cnt.typ)} {loopvar} = 0; {loopvar} < {cnt_path.c_str('out->')}; {loopvar}++) {{\n" + ret += f"{'\t'*indent_lvl()}for ({cnt_typ} {loopvar} = 0; {loopvar} < {cnt_str}; {loopvar}++) {{\n" indent_stack.append(IndentLevel(ifdef=False)) if not isinstance(child.typ, idl.Struct): if child.val: diff --git a/lib9p/protogen/c_validate.py b/lib9p/protogen/c_validate.py index e315b60..535a750 100644 --- a/lib9p/protogen/c_validate.py +++ b/lib9p/protogen/c_validate.py @@ -24,11 +24,11 @@ def should_save_offset(parent: idl.Struct, child: idl.StructMember) -> bool: for sibling in parent.members: if sibling.val: for tok in sibling.val.tokens: - if isinstance(tok, idl.ExprSym) and tok.symname == f"&{child.membname}": + if isinstance(tok, idl.ExprOff) and tok.membname == child.membname: return True if sibling.max: for tok in sibling.max.tokens: - if isinstance(tok, idl.ExprSym) and tok.symname == f"&{child.membname}": + if isinstance(tok, idl.ExprOff) and tok.membname == child.membname: return True return False @@ -132,21 +132,33 @@ def gen_c_validate(versions: set[str], typs: list[idl.UserType]) -> str: if should_save_offset(parent, child): ret += f"{'\t'*indent_lvl()}uint32_t offsetof{''.join('_'+m.membname for m in path.elems)} = net_offset + {incr_buf};\n" if child.cnt: - assert child.cnt.typ.static_size - cnt_path = path.parent().add(child.cnt) - incr_flush() + if isinstance(child.cnt, int): + cnt_str = str(child.cnt) + cnt_typ = "size_t" + else: + assert child.cnt.typ.static_size + incr_flush() + cnt_str = f"LAST_U{child.cnt.typ.static_size*8}LE()" + cnt_typ = c9util.typename(child.cnt.typ) if child.membname == "utf8": # SPECIAL (string) + assert child.typ.static_size == 1 # Yes, this is content-validation and "belongs" in # gen_validate_content(), not here. But it's just # easier this way. - ret += f"{'\t'*indent_lvl()}VALIDATE_NET_UTF8(LAST_U{child.cnt.typ.static_size*8}LE());\n" + incr_flush() + ret += f"{'\t'*indent_lvl()}VALIDATE_NET_UTF8({cnt_str});\n" return if child.typ.static_size == 1: # SPECIAL (zerocopy) - ret += f"{'\t'*indent_lvl()}VALIDATE_NET_BYTES(LAST_U{child.cnt.typ.static_size*8}LE());\n" + if isinstance(child.cnt, int): + incr_buf += child.cnt + return + incr_flush() + ret += f"{'\t'*indent_lvl()}VALIDATE_NET_BYTES({cnt_str});\n" return loopdepth = sum(1 for elem in path.elems if elem.cnt) loopvar = chr(ord("i") + loopdepth - 1) - ret += f"{'\t'*indent_lvl()}for ({c9util.typename(child.cnt.typ)} {loopvar} = 0, cnt = LAST_U{child.cnt.typ.static_size*8}LE(); {loopvar} < cnt; {loopvar}++) {{\n" + incr_flush() + ret += f"{'\t'*indent_lvl()}for ({cnt_typ} {loopvar} = 0, cnt = {cnt_str}; {loopvar} < cnt; {loopvar}++) {{\n" indent_stack.append(IndentLevel(ifdef=False)) ret += f"{'\t'*indent_lvl()}RESERVE_HOST_BYTES(sizeof({c9util.typename(child.typ)}));\n" if not isinstance(child.typ, idl.Struct): diff --git a/lib9p/protogen/h.py b/lib9p/protogen/h.py index 7785ca1..3b33419 100644 --- a/lib9p/protogen/h.py +++ b/lib9p/protogen/h.py @@ -165,6 +165,7 @@ def gen_h(versions: set[str], typs: list[idl.UserType]) -> str: #include <stdint.h> /* for uint{{n}}_t types */ +#include <libfmt/fmt.h> /* for fmt_formatter */ #include <libhw/generic/net.h> /* for struct iovec */ """ @@ -206,6 +207,7 @@ enum {c9util.ident('version')} {{ ret += cutil.ifdef_pop(0) ret += f"\t{c9util.ver_enum('NUM')},\n" ret += "};\n" + ret += f"LO_IMPLEMENTATION_H(fmt_formatter, enum {c9util.ident('version')}, {c9util.ident('version')});\n" ret += """ /* enum msg_type **************************************************************/ @@ -221,6 +223,7 @@ enum {c9util.ident('version')} {{ ret += f"\t{c9util.Ident(f'TYP_{msg.typname:<{namewidth}}')} = {msg.msgid},\n" ret += cutil.ifdef_pop(0) ret += "};\n" + ret += f"LO_IMPLEMENTATION_H(fmt_formatter, enum {c9util.ident('msg_type')}, {c9util.ident('msg_type')});\n" ret += """ /* payload types **************************************************************/ @@ -268,99 +271,11 @@ enum {c9util.ident('version')} {{ match typ: case idl.Number(): - ret += f"typedef {c9util.typename(typ.prim)} {c9util.typename(typ)};\n" - prefix = f"{c9util.IDENT(typ.typname)}_" - namewidth = max(len(name) for name in typ.vals) - for name, val in typ.vals.items(): - ret += f"#define {prefix}{name:<{namewidth}} (({c9util.typename(typ)})UINT{typ.static_size*8}_C({val}))\n" + ret += gen_number(typ) case idl.Bitfield(): - ret += f"typedef {c9util.typename(typ.prim)} {c9util.typename(typ)};\n" - - def bitname(val: idl.Bit | idl.BitAlias) -> str: - s = val.bitname - match val: - case idl.Bit(cat=idl.BitCat.RESERVED): - s = "_RESERVED_" + s - case idl.Bit(cat=idl.BitCat.SUBFIELD): - assert isinstance(typ, idl.Bitfield) - n = sum( - 1 - for b in typ.bits[: val.num] - if b.cat == idl.BitCat.SUBFIELD - and b.bitname == val.bitname - ) - s = f"_{s}_{n}" - case idl.Bit(cat=idl.BitCat.UNUSED): - return "" - return c9util.Ident(c9util.add_prefix(typ.typname.upper() + "_", s)) - - namewidth = max( - len(bitname(val)) for val in [*typ.bits, *typ.names.values()] - ) - - ret += "\n" - for bit in reversed(typ.bits): - vers = bit.in_versions - if bit.cat == idl.BitCat.UNUSED: - vers = typ.in_versions - ret += cutil.ifdef_push(2, c9util.ver_ifdef(vers)) - - # It is important all of the `beg` strings have - # the same length. - end = "" - match bit.cat: - case ( - idl.BitCat.USED | idl.BitCat.RESERVED | idl.BitCat.SUBFIELD - ): - if cutil.ifdef_leaf_is_noop(): - beg = "#define " - else: - beg = "# define" - case idl.BitCat.UNUSED: - beg = "/* unused" - end = " */" - - c_name = bitname(bit) - c_val = f"1<<{bit.num}" - ret += f"{beg} {c_name:<{namewidth}} (({c9util.typename(typ)})({c_val})){end}\n" - if aliases := [ - alias - for alias in typ.names.values() - if isinstance(alias, idl.BitAlias) - ]: - ret += "\n" - - for alias in aliases: - ret += cutil.ifdef_push(2, c9util.ver_ifdef(alias.in_versions)) - - end = "" - if cutil.ifdef_leaf_is_noop(): - beg = "#define " - else: - beg = "# define" - - c_name = bitname(alias) - c_val = alias.val - ret += f"{beg} {c_name:<{namewidth}} (({c9util.typename(typ)})({c_val})){end}\n" - ret += cutil.ifdef_pop(1) - del bitname + ret += gen_bitfield(typ) case idl.Struct(): # and idl.Message(): - ret += c9util.typename(typ) + " {" - if not typ.members: - ret += "};\n" - continue - ret += "\n" - - typewidth = max(len(c9util.typename(m.typ, m)) for m in typ.members) - - for member in typ.members: - if member.val: - continue - ret += cutil.ifdef_push(2, c9util.ver_ifdef(member.in_versions)) - ret += f"\t{c9util.typename(member.typ, member):<{typewidth}} {'*' if member.cnt else ' '}{member.membname};\n" - ret += cutil.ifdef_pop(1) - ret += "};\n" - del typ + ret += gen_struct(typ) ret += cutil.ifdef_pop(0) ret += """ @@ -445,3 +360,176 @@ enum {c9util.ident('version')} {{ ret += "};\n" return ret + + +def gen_number(typ: idl.Number) -> str: + ret = f"typedef {c9util.typename(typ.prim)} {c9util.typename(typ)};\n" + ret += f"LO_IMPLEMENTATION_H(fmt_formatter, {c9util.typename(typ)}, {c9util.basename(typ)});\n" + + def lookup_sym(sym: str) -> str: + assert False + + def cname(base: str) -> str: + prefix = f"{typ.typname}_".upper() + return c9util.Ident(c9util.add_prefix(prefix, base)) + + namewidth = max(len(cname(name)) for name in typ.vals) + for name, val in typ.vals.items(): + c_name = cname(name) + c_val = c9util.idl_expr(val, lookup_sym) + ret += f"#define {c_name:<{namewidth}} (({c9util.typename(typ)})({c_val}))\n" + return ret + + +def gen_bitfield(typ: idl.Bitfield) -> str: + ret = f"typedef {c9util.typename(typ.prim)} {c9util.typename(typ)};\n" + ret += f"LO_IMPLEMENTATION_H(fmt_formatter, {c9util.typename(typ)}, {c9util.basename(typ)});\n" + + def lookup_sym(sym: str) -> str: + assert False + + # There are 4 parts here: bits, aliases, masks, and numbers. + + # 1. bits + + def bitname(bit: idl.Bit) -> str: + prefix = f"{typ.typname}_".upper() + base = bit.bitname + match bit: + case idl.Bit(cat="RESERVED"): + base = "_RESERVED_" + base + case idl.Bit(cat=idl.BitNum()): + base += "_*" + case idl.Bit(cat="UNUSED"): + base = f"_UNUSED_{bit.num}" + return c9util.Ident(c9util.add_prefix(prefix, base)) + + namewidth = max(len(bitname(bit)) for bit in typ.bits) + + ret += "/* bits */\n" + for bit in reversed(typ.bits): + vers = bit.in_versions + if bit.cat == "UNUSED": + vers = typ.in_versions + ret += cutil.ifdef_push(2, c9util.ver_ifdef(vers)) + + # It is important all of the `beg` strings have + # the same length. + end = "" + match bit.cat: + case "USED" | "RESERVED" | "UNUSED": + if cutil.ifdef_leaf_is_noop(): + beg = "#define " + else: + beg = "# define" + case idl.BitNum(): + beg = "/* number" + end = " */" + + c_name = bitname(bit) + c_val = f"UINT{typ.static_size*8}_C(1)<<{bit.num}" + ret += ( + f"{beg} {c_name:<{namewidth}} (({c9util.typename(typ)})({c_val})){end}\n" + ) + ret += cutil.ifdef_pop(1) + + # 2. aliases + if typ.aliases: + + def aliasname(alias: idl.BitAlias) -> str: + prefix = f"{typ.typname}_".upper() + base = alias.bitname + return c9util.Ident(c9util.add_prefix(prefix, base)) + + ret += "/* aliases */\n" + for alias in typ.aliases.values(): + ret += cutil.ifdef_push(2, c9util.ver_ifdef(alias.in_versions)) + + end = "" + if cutil.ifdef_leaf_is_noop(): + beg = "#define " + else: + beg = "# define" + + c_name = aliasname(alias) + c_val = c9util.idl_expr(alias.val, lookup_sym) + ret += f"{beg} {c_name:<{namewidth}} (({c9util.typename(typ)})({c_val})){end}\n" + + ret += cutil.ifdef_pop(1) + + # 3. masks + if typ.masks: + + def maskname(mask: idl.BitAlias) -> str: + prefix = f"{typ.typname}_".upper() + base = mask.bitname + return c9util.Ident(c9util.add_prefix(prefix, base) + "_MASK") + + ret += "/* masks */\n" + for mask in typ.masks.values(): + ret += cutil.ifdef_push(2, c9util.ver_ifdef(mask.in_versions)) + + end = "" + if cutil.ifdef_leaf_is_noop(): + beg = "#define " + else: + beg = "# define" + + c_name = maskname(mask) + c_val = c9util.idl_expr(mask.val, lookup_sym, bitwidth=typ.static_size * 8) + ret += f"{beg} {c_name:<{namewidth}} (({c9util.typename(typ)})({c_val})){end}\n" + + ret += cutil.ifdef_pop(1) + + # 4. numbers + def numname(num: idl.BitNum, base: str) -> str: + prefix = f"{typ.typname}_{num.numname}_".upper() + return c9util.Ident(c9util.add_prefix(prefix, base)) + + for num in typ.nums.values(): + namewidth = max( + len(numname(num, base)) + for base in [ + *[alias.bitname for alias in num.vals.values()], + "MASK", + ] + ) + ret += f"/* number: {num.numname} */\n" + for alias in num.vals.values(): + ret += cutil.ifdef_push(2, c9util.ver_ifdef(alias.in_versions)) + + end = "" + if cutil.ifdef_leaf_is_noop(): + beg = "#define " + else: + beg = "# define" + + c_name = numname(num, alias.bitname) + c_val = c9util.idl_expr(alias.val, lookup_sym) + ret += f"{beg} {c_name:<{namewidth}} (({c9util.typename(typ)})({c_val})){end}\n" + ret += cutil.ifdef_pop(1) + c_name = numname(num, "MASK") + c_val = f"{num.mask:#0{typ.static_size*8}b}" + ret += ( + f"{beg} {c_name:<{namewidth}} (({c9util.typename(typ)})({c_val})){end}\n" + ) + + return ret + + +def gen_struct(typ: idl.Struct) -> str: # and idl.Message + ret = c9util.typename(typ) + " {" + if typ.members: + ret += "\n" + + typewidth = max(len(c9util.typename(m.typ, m)) for m in typ.members) + + for member in typ.members: + if member.val: + continue + ret += cutil.ifdef_push(2, c9util.ver_ifdef(member.in_versions)) + ret += f"\t{c9util.typename(member.typ, member):<{typewidth}} {'*' if member.cnt else ' '}{member.membname};\n" + ret += cutil.ifdef_pop(1) + ret += "};\n" + ret += f"LO_IMPLEMENTATION_H(fmt_formatter, {c9util.typename(typ)}, {c9util.basename(typ)});\n" + return ret |