diff options
Diffstat (limited to 'lib9p/protogen')
-rw-r--r-- | lib9p/protogen/__init__.py | 57 | ||||
-rw-r--r-- | lib9p/protogen/c.py | 201 | ||||
-rw-r--r-- | lib9p/protogen/c9util.py | 134 | ||||
-rw-r--r-- | lib9p/protogen/c_format.py | 142 | ||||
-rw-r--r-- | lib9p/protogen/c_marshal.py | 403 | ||||
-rw-r--r-- | lib9p/protogen/c_unmarshal.py | 138 | ||||
-rw-r--r-- | lib9p/protogen/c_validate.py | 299 | ||||
-rw-r--r-- | lib9p/protogen/cutil.py | 84 | ||||
-rw-r--r-- | lib9p/protogen/h.py | 535 | ||||
-rw-r--r-- | lib9p/protogen/idlutil.py | 112 |
10 files changed, 0 insertions, 2105 deletions
diff --git a/lib9p/protogen/__init__.py b/lib9p/protogen/__init__.py deleted file mode 100644 index c2c6173..0000000 --- a/lib9p/protogen/__init__.py +++ /dev/null @@ -1,57 +0,0 @@ -# lib9p/protogen/__init__.py - Generate C marshalers/unmarshalers for -# .9p files defining 9P protocol variants -# -# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> -# SPDX-License-Identifier: AGPL-3.0-or-later - -import os.path -import sys -import typing - -import idl - -from . import c, h - -# pylint: disable=unused-variable -__all__ = ["main"] - - -def main() -> None: - if typing.TYPE_CHECKING: - - class ANSIColors: - MAGENTA = "\x1b[35m" - RED = "\x1b[31m" - RESET = "\x1b[0m" - - else: - from _colorize import ANSIColors # Present in Python 3.13+ - - if len(sys.argv) < 2: - raise ValueError("requires at least 1 .9p filename") - parser = idl.Parser() - for txtname in sys.argv[1:]: - try: - parser.parse_file(txtname) - except SyntaxError as e: - print( - f"{ANSIColors.RED}{e.filename}{ANSIColors.RESET}:{ANSIColors.MAGENTA}{e.lineno}{ANSIColors.RESET}: {e.msg}", - file=sys.stderr, - ) - assert e.text - print(f"\t{e.text}", file=sys.stderr) - text_suffix = e.text.lstrip() - text_prefix = e.text[: -len(text_suffix)] - print( - f"\t{text_prefix}{ANSIColors.RED}{'~'*len(text_suffix)}{ANSIColors.RESET}", - file=sys.stderr, - ) - sys.exit(2) - versions, typs = parser.all() - outdir = os.path.normpath(os.path.join(sys.argv[0], "..")) - with open( - os.path.join(outdir, "include/lib9p/9p.generated.h"), "w", encoding="utf-8" - ) as fh: - fh.write(h.gen_h(versions, typs)) - with open(os.path.join(outdir, "9p.generated.c"), "w", encoding="utf-8") as fh: - fh.write(c.gen_c(versions, typs)) diff --git a/lib9p/protogen/c.py b/lib9p/protogen/c.py deleted file mode 100644 index a6824ce..0000000 --- a/lib9p/protogen/c.py +++ /dev/null @@ -1,201 +0,0 @@ -# lib9p/protogen/c.py - Generate 9p.generated.c -# -# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> -# SPDX-License-Identifier: AGPL-3.0-or-later - -import sys - -import idl - -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 -# this script, marked with "SPECIAL". - - -# pylint: disable=unused-variable -__all__ = ["gen_c"] - - -def gen_c(versions: set[str], typs: list[idl.UserType]) -> str: - cutil.ifdef_init() - - ret = f"""/* Generated by `{' '.join(sys.argv)}`. DO NOT EDIT! */ - -#include <stdbool.h> -#include <stddef.h> /* for size_t */ -#include <inttypes.h> /* for PRI* macros */ -#include <string.h> /* for memset() */ - -#include <libmisc/assert.h> -#include <libmisc/endian.h> - -#include <lib9p/9p.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 += """ -/* utilities ******************************************************************/ -""" - - id2typ: dict[int, idl.Message] = {} - for msg in [msg for msg in typs if isinstance(msg, idl.Message)]: - id2typ[msg.msgid] = msg - - for v in sorted(versions): - ret += f"#if CONFIG_9P_ENABLE_{v.replace('.', '_')}\n" - ret += ( - f"\t#define _is_ver_{v.replace('.', '_')}(v) (v == {c9util.ver_enum(v)})\n" - ) - ret += "#else\n" - ret += f"\t#define _is_ver_{v.replace('.', '_')}(v) false\n" - ret += "#endif\n" - ret += "\n" - ret += "/**\n" - ret += f" * is_ver(ctx, ver) is essentially `(ctx->version == {c9util.Ident('VER_')}##ver)`, but\n" - ret += f" * compiles correctly (to `false`) even if `{c9util.Ident('VER_')}##ver` isn't defined\n" - ret += " * (because `!CONFIG_9P_ENABLE_##ver`). This is useful when `||`ing\n" - ret += " * several version checks together.\n" - ret += " */\n" - ret += "#define is_ver(ctx, ver) _is_ver_##ver((ctx)->version)\n" - - # bitmasks ################################################################# - ret += """ -/* bitmasks *******************************************************************/ -""" - for typ in typs: - if not isinstance(typ, idl.Bitfield): - continue - ret += "\n" - ret += cutil.ifdef_push(1, c9util.ver_ifdef(typ.in_versions)) - ret += f"static const {c9util.typename(typ)} {typ.typname}_masks[{c9util.ver_enum('NUM')}] = {{\n" - verwidth = max(len(ver) for ver in versions) - for ver in sorted(versions): - ret += cutil.ifdef_push(2, c9util.ver_ifdef({ver})) - ret += ( - f"\t[{c9util.ver_enum(ver)}]{' '*(verwidth-len(ver))} = 0b" - + "".join( - ( - "1" - if (bit.cat == "USED" or isinstance(bit.cat, idl.BitNum)) - and ver in bit.in_versions - else "0" - ) - for bit in reversed(typ.bits) - ) - + ",\n" - ) - ret += cutil.ifdef_pop(1) - ret += "};\n" - ret += cutil.ifdef_pop(0) - - # validate_* ############################################################### - ret += c_validate.gen_c_validate(versions, typs) - - # unmarshal_* ############################################################## - ret += c_unmarshal.gen_c_unmarshal(versions, typs) - - # marshal_* ################################################################ - ret += c_marshal.gen_c_marshal(versions, typs) - - # *_format ################################################################# - ret += c_format.gen_c_format(versions, typs) - - # tables.h ################################################################# - ret += """ -/* tables.h *******************************************************************/ -""" - - ret += "\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") - 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.validate = validate_##typ,\n" - f"\t\t.unmarshal = (_unmarshal_fn_t)unmarshal_##typ,\n" - f"\t}}\n" - ) - ret += cutil.macro( - f"#define _MSG_SEND(typ) [{c9util.Ident('TYP_')}##typ/2] = {{\n" - f"\t\t.marshal = (_marshal_fn_t)marshal_##typ,\n" - f"\t}}\n" - ) - ret += "\n" - ret += msg_table("_recv_tentry", "_table_Tmsg_recv", "_MSG_RECV", (0, 0x100, 2)) - ret += "\n" - ret += msg_table("_recv_tentry", "_table_Rmsg_recv", "_MSG_RECV", (1, 0x100, 2)) - ret += "\n" - ret += msg_table("_send_tentry", "_table_Tmsg_send", "_MSG_SEND", (0, 0x100, 2)) - ret += "\n" - 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) {{ -\treturn validate_stat(ctx, net_size, net_bytes, ret_net_size); -}} -LM_FLATTEN void {c9util.ident('_stat_unmarshal')}(struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out) {{ -\tunmarshal_stat(ctx, net_bytes, out); -}} -LM_FLATTEN bool {c9util.ident('_stat_marshal')}(struct lib9p_ctx *ctx, struct {c9util.ident('stat')} *val, struct _marshal_ret *ret) {{ -\treturn marshal_stat(ctx, val, ret); -}} -""" - - ############################################################################ - return ret diff --git a/lib9p/protogen/c9util.py b/lib9p/protogen/c9util.py deleted file mode 100644 index cf91951..0000000 --- a/lib9p/protogen/c9util.py +++ /dev/null @@ -1,134 +0,0 @@ -# lib9p/protogen/c9util.py - Utilities for generating lib9p-specific C -# -# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> -# SPDX-License-Identifier: AGPL-3.0-or-later - -import re -import typing - -import idl - -# 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__ = [ - "add_prefix", - "ident", - "Ident", - "IDENT", - "ver_enum", - "ver_ifdef", - "ver_cond", - "typename", - "idl_expr", -] - -# idents ####################################################################### - - -def add_prefix(p: str, s: str) -> str: - if s.startswith("_"): - return "_" + p + s[1:] - return p + s - - -def _ident(p: str, s: str) -> str: - return add_prefix(p, s.replace(".", "_")) - - -def ident(s: str) -> str: - return _ident("lib9p_", s) - - -def Ident(s: str) -> str: - return _ident("lib9p_".upper(), s) - - -def IDENT(s: str) -> str: - return _ident("lib9p_", s).upper() - - -# versions ##################################################################### - - -def ver_enum(ver: str) -> str: - return Ident("VER_" + ver) - - -def ver_ifdef(versions: typing.Collection[str]) -> str: - return " || ".join( - f"CONFIG_9P_ENABLE_{v.replace('.', '_')}" for v in sorted(versions) - ) - - -def ver_cond(versions: typing.Collection[str]) -> str: - if len(versions) == 1: - v = next(v for v in versions) - return f"is_ver(ctx, {v.replace('.', '_')})" - return "( " + (" || ".join(ver_cond({v}) for v in sorted(versions))) + " )" - - -# 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(): - if typ.value == 1 and parent and parent.cnt: # SPECIAL (string) - return "[[gnu::nonstring]] char" - return f"uint{typ.value*8}_t" - case idl.Number(): - return f"{basename(typ)}_t" - case idl.Bitfield(): - return f"{basename(typ)}_t" - case idl.Message(): - return f"struct {basename(typ)}" - case idl.Struct(): - 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], bitwidth: int = 0 -) -> str: - ret: list[str] = [] - for tok in expr.tokens: - match tok: - case idl.ExprOp(): - ret.append(tok.op) - case idl.ExprLit(): - if bitwidth: - ret.append(f"{tok.val:#0{bitwidth}b}") - else: - ret.append(str(tok.val)) - case idl.ExprSym(): - 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 deleted file mode 100644 index a1bcbf3..0000000 --- a/lib9p/protogen/c_format.py +++ /dev/null @@ -1,142 +0,0 @@ -# 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 deleted file mode 100644 index 4dab864..0000000 --- a/lib9p/protogen/c_marshal.py +++ /dev/null @@ -1,403 +0,0 @@ -# lib9p/protogen/c_marshal.py - Generate C marshal functions -# -# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> -# SPDX-License-Identifier: AGPL-3.0-or-later - -import typing - -import idl - -from . import c9util, cutil, idlutil - -# 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_marshal"] - -# get_offset_expr() ############################################################ - - -class OffsetExpr: - static: int - cond: dict[frozenset[str], "OffsetExpr"] - rep: list[tuple[idlutil.Path | int, "OffsetExpr"]] - - def __init__(self) -> None: - self.static = 0 - self.rep = [] - self.cond = {} - - def add(self, other: "OffsetExpr") -> None: - self.static += other.static - self.rep += other.rep - for k, v in other.cond.items(): - if k in self.cond: - self.cond[k].add(v) - else: - self.cond[k] = v - - def gen_c( - self, - dsttyp: str, - dstvar: str, - root: str, - indent_depth: int, - loop_depth: int, - ) -> str: - oneline: list[str] = [] - multiline = "" - 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_str) - else: - oneline.append(f"({cnt_str})*{sub.static}") - continue - loopvar = chr(ord("i") + loop_depth) - 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(): - multiline += cutil.ifdef_push(indent_depth + 1, c9util.ver_ifdef(vers)) - multiline += f"{'\t'*indent_depth}if {c9util.ver_cond(vers)} {{\n" - multiline += sub.gen_c("", dstvar, root, indent_depth + 1, loop_depth) - multiline += f"{'\t'*indent_depth}}}\n" - multiline += cutil.ifdef_pop(indent_depth) - ret = "" - if dsttyp: - if not oneline: - oneline.append("0") - ret += f"{'\t'*indent_depth}{dsttyp} {dstvar} = {' + '.join(oneline)};\n" - elif oneline: - ret += f"{'\t'*indent_depth}{dstvar} += {' + '.join(oneline)};\n" - ret += multiline - return ret - - -type OffsetExprRecursion = typing.Callable[[idlutil.Path], idlutil.WalkCmd] - - -def get_offset_expr(typ: idl.UserType, recurse: OffsetExprRecursion) -> OffsetExpr: - if not isinstance(typ, idl.Struct): - assert typ.static_size - ret = OffsetExpr() - ret.static = typ.static_size - return ret - - class ExprStackItem(typing.NamedTuple): - path: idlutil.Path - expr: OffsetExpr - pop: typing.Callable[[], None] - - expr_stack: list[ExprStackItem] - - def pop_root() -> None: - assert False - - def pop_cond() -> None: - nonlocal expr_stack - key = frozenset(expr_stack[-1].path.elems[-1].in_versions) - if key in expr_stack[-2].expr.cond: - expr_stack[-2].expr.cond[key].add(expr_stack[-1].expr) - else: - expr_stack[-2].expr.cond[key] = expr_stack[-1].expr - expr_stack = expr_stack[:-1] - - def pop_rep() -> None: - nonlocal expr_stack - member_path = expr_stack[-1].path - member = member_path.elems[-1] - assert member.cnt - 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( - path: idlutil.Path, - ) -> tuple[idlutil.WalkCmd, typing.Callable[[], None] | None]: - nonlocal recurse - - ret = recurse(path) - if ret != idlutil.WalkCmd.KEEP_GOING: - return ret, None - - nonlocal expr_stack - expr_stack_len = len(expr_stack) - - def pop() -> None: - nonlocal expr_stack - nonlocal expr_stack_len - while len(expr_stack) > expr_stack_len: - expr_stack[-1].pop() - - if path.elems: - child = path.elems[-1] - parent = path.elems[-2].typ if len(path.elems) > 1 else path.root - if child.in_versions < parent.in_versions: - expr_stack.append( - ExprStackItem(path=path, expr=OffsetExpr(), pop=pop_cond) - ) - if child.cnt: - expr_stack.append( - ExprStackItem(path=path, expr=OffsetExpr(), pop=pop_rep) - ) - if not isinstance(child.typ, idl.Struct): - assert child.typ.static_size - expr_stack[-1].expr.static += child.typ.static_size - return ret, pop - - expr_stack = [ - ExprStackItem(path=idlutil.Path(typ), expr=OffsetExpr(), pop=pop_root) - ] - idlutil.walk(typ, handle) - return expr_stack[0].expr - - -def go_to_end(path: idlutil.Path) -> idlutil.WalkCmd: - return idlutil.WalkCmd.KEEP_GOING - - -def go_to_tok(name: str) -> typing.Callable[[idlutil.Path], idlutil.WalkCmd]: - def ret(path: idlutil.Path) -> idlutil.WalkCmd: - if len(path.elems) == 1 and path.elems[0].membname == name: - return idlutil.WalkCmd.ABORT - return idlutil.WalkCmd.KEEP_GOING - - return ret - - -# Generate .c ################################################################## - - -def gen_c_marshal(versions: set[str], typs: list[idl.UserType]) -> str: - ret = """ -/* marshal_* ******************************************************************/ - -""" - ret += cutil.macro( - "#define MARSHAL_BYTES_ZEROCOPY(ctx, data, len)\n" - "\tif (ret->net_iov[ret->net_iov_cnt-1].iov_len)\n" - "\t\tret->net_iov_cnt++;\n" - "\tret->net_iov[ret->net_iov_cnt-1].iov_base = data;\n" - "\tret->net_iov[ret->net_iov_cnt-1].iov_len = len;\n" - "\tret->net_iov_cnt++;\n" - ) - ret += cutil.macro( - "#define MARSHAL_BYTES(ctx, data, len)\n" - "\tif (!ret->net_iov[ret->net_iov_cnt-1].iov_base)\n" - "\t\tret->net_iov[ret->net_iov_cnt-1].iov_base = &ret->net_copied[ret->net_copied_size];\n" - "\tmemcpy(&ret->net_copied[ret->net_copied_size], data, len);\n" - "\tret->net_copied_size += len;\n" - "\tret->net_iov[ret->net_iov_cnt-1].iov_len += len;\n" - ) - ret += cutil.macro( - "#define MARSHAL_U8LE(ctx, val)\n" - "\tif (!ret->net_iov[ret->net_iov_cnt-1].iov_base)\n" - "\t\tret->net_iov[ret->net_iov_cnt-1].iov_base = &ret->net_copied[ret->net_copied_size];\n" - "\tret->net_copied[ret->net_copied_size] = val;\n" - "\tret->net_copied_size += 1;\n" - "\tret->net_iov[ret->net_iov_cnt-1].iov_len += 1;\n" - ) - ret += cutil.macro( - "#define MARSHAL_U16LE(ctx, val)\n" - "\tif (!ret->net_iov[ret->net_iov_cnt-1].iov_base)\n" - "\t\tret->net_iov[ret->net_iov_cnt-1].iov_base = &ret->net_copied[ret->net_copied_size];\n" - "\tuint16le_encode(&ret->net_copied[ret->net_copied_size], val);\n" - "\tret->net_copied_size += 2;\n" - "\tret->net_iov[ret->net_iov_cnt-1].iov_len += 2;\n" - ) - ret += cutil.macro( - "#define MARSHAL_U32LE(ctx, val)\n" - "\tif (!ret->net_iov[ret->net_iov_cnt-1].iov_base)\n" - "\t\tret->net_iov[ret->net_iov_cnt-1].iov_base = &ret->net_copied[ret->net_copied_size];\n" - "\tuint32le_encode(&ret->net_copied[ret->net_copied_size], val);\n" - "\tret->net_copied_size += 4;\n" - "\tret->net_iov[ret->net_iov_cnt-1].iov_len += 4;\n" - ) - ret += cutil.macro( - "#define MARSHAL_U64LE(ctx, val)\n" - "\tif (!ret->net_iov[ret->net_iov_cnt-1].iov_base)\n" - "\t\tret->net_iov[ret->net_iov_cnt-1].iov_base = &ret->net_copied[ret->net_copied_size];\n" - "\tuint64le_encode(&ret->net_copied[ret->net_copied_size], val);\n" - "\tret->net_copied_size += 8;\n" - "\tret->net_iov[ret->net_iov_cnt-1].iov_len += 8;\n" - ) - - class IndentLevel(typing.NamedTuple): - ifdef: bool # whether this is both `{` and `#if`, or just `{` - - indent_stack: list[IndentLevel] - - def ifdef_lvl() -> int: - return sum(1 if lvl.ifdef else 0 for lvl in indent_stack) - - def indent_lvl() -> int: - return len(indent_stack) - - max_size: int - - def handle( - path: idlutil.Path, - ) -> tuple[idlutil.WalkCmd, typing.Callable[[], None]]: - nonlocal ret - nonlocal indent_stack - nonlocal max_size - indent_stack_len = len(indent_stack) - - def pop() -> None: - nonlocal ret - nonlocal indent_stack - nonlocal indent_stack_len - while len(indent_stack) > indent_stack_len: - if len(indent_stack) == indent_stack_len + 1 and indent_stack[-1].ifdef: - break - ret += f"{'\t'*(indent_lvl()-1)}}}\n" - if indent_stack.pop().ifdef: - ret += cutil.ifdef_pop(ifdef_lvl()) - - loopdepth = sum(1 for elem in path.elems if elem.cnt) - struct = path.elems[-1].typ if path.elems else path.root - if isinstance(struct, idl.Struct): - offsets: list[str] = [] - for member in struct.members: - if not member.val: - continue - for tok in member.val.tokens: - 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": - if not path.elems: - if max_size > cutil.UINT32_MAX: - ret += f"{'\t'*indent_lvl()}uint32_t {name_prefix}end = (uint32_t)needed_size;\n" - else: - ret += f"{'\t'*indent_lvl()}uint32_t {name_prefix}end = needed_size;\n" - continue - recurse: OffsetExprRecursion = go_to_end - else: - assert name.startswith("&") - name = name[1:] - recurse = go_to_tok(name) - expr = get_offset_expr(struct, recurse) - expr_prefix = path.c_str("val->", loopdepth) - if not expr_prefix.endswith(">"): - expr_prefix += "." - ret += expr.gen_c( - "uint32_t", - name_prefix + name, - expr_prefix, - indent_lvl(), - loopdepth, - ) - if not path.elems: - return idlutil.WalkCmd.KEEP_GOING, pop - - child = path.elems[-1] - parent = path.elems[-2].typ if len(path.elems) > 1 else path.root - if child.in_versions < parent.in_versions: - if line := cutil.ifdef_push( - ifdef_lvl() + 1, c9util.ver_ifdef(child.in_versions) - ): - ret += line - ret += ( - f"{'\t'*indent_lvl()}if ({c9util.ver_cond(child.in_versions)}) {{\n" - ) - indent_stack.append(IndentLevel(ifdef=True)) - if 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_str});\n" - else: - 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 ({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: - - def lookup_sym(sym: str) -> str: - nonlocal path - if sym.startswith("&"): - sym = sym[1:] - return f"offsetof{''.join('_'+m.membname for m in path.elems[:-1])}_{sym}" - - val = c9util.idl_expr(child.val, lookup_sym) - else: - val = path.c_str("val->") - if isinstance(child.typ, idl.Bitfield): - val += f" & {child.typ.typname}_masks[ctx->version]" - ret += f"{'\t'*indent_lvl()}MARSHAL_U{child.typ.static_size*8}LE(ctx, {val});\n" - return idlutil.WalkCmd.KEEP_GOING, pop - - for typ in typs: - if not ( - isinstance(typ, idl.Message) or typ.typname == "stat" - ): # SPECIAL (include stat) - continue - assert isinstance(typ, idl.Struct) - ret += "\n" - ret += cutil.ifdef_push(1, c9util.ver_ifdef(typ.in_versions)) - ret += f"static bool marshal_{typ.typname}(struct lib9p_ctx *ctx, {c9util.typename(typ)} *val, struct _marshal_ret *ret) {{\n" - - # Pass 1 - check size - max_size = max(typ.max_size(v) for v in typ.in_versions) - - if max_size > cutil.UINT32_MAX: # SPECIAL (9P2000.e) - ret += get_offset_expr(typ, go_to_end).gen_c( - "uint64_t", "needed_size", "val->", 1, 0 - ) - ret += "\tif (needed_size > (uint64_t)(ctx->max_msg_size)) {\n" - else: - ret += get_offset_expr(typ, go_to_end).gen_c( - "uint32_t", "needed_size", "val->", 1, 0 - ) - ret += "\tif (needed_size > ctx->max_msg_size) {\n" - if isinstance(typ, idl.Message): # SPECIAL (disable for stat) - ret += '\t\tlib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")",\n' - ret += f'\t\t\t"{typ.typname}",\n' - ret += f'\t\t\tctx->version ? "negotiated" : "{'client' if typ.msgid % 2 == 0 else 'server'}",\n' - ret += "\t\t\tctx->max_msg_size);\n" - ret += "\t\treturn true;\n" - ret += "\t}\n" - - # Pass 2 - write data - indent_stack = [IndentLevel(ifdef=True)] - idlutil.walk(typ, handle) - while len(indent_stack) > 1: - ret += f"{'\t'*(indent_lvl()-1)}}}\n" - if indent_stack.pop().ifdef: - ret += cutil.ifdef_pop(ifdef_lvl()) - - # Return - ret += "\treturn false;\n" - ret += "}\n" - ret += cutil.ifdef_pop(0) - return ret diff --git a/lib9p/protogen/c_unmarshal.py b/lib9p/protogen/c_unmarshal.py deleted file mode 100644 index 34635f9..0000000 --- a/lib9p/protogen/c_unmarshal.py +++ /dev/null @@ -1,138 +0,0 @@ -# lib9p/protogen/c_unmarshal.py - Generate C unmarshal functions -# -# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> -# SPDX-License-Identifier: AGPL-3.0-or-later - -import typing - -import idl - -from . import c9util, cutil, idlutil - -# 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_unmarshal"] - - -def gen_c_unmarshal(versions: set[str], typs: list[idl.UserType]) -> str: - ret = """ -/* unmarshal_* ****************************************************************/ - -""" - ret += cutil.macro( - "#define UNMARSHAL_BYTES(ctx, data_lvalue, len)\n" - "\tdata_lvalue = (char *)&net_bytes[net_offset];\n" - "\tnet_offset += len;\n" - ) - ret += cutil.macro( - "#define UNMARSHAL_U8LE(ctx, val_lvalue)\n" - "\tval_lvalue = net_bytes[net_offset];\n" - "\tnet_offset += 1;\n" - ) - ret += cutil.macro( - "#define UNMARSHAL_U16LE(ctx, val_lvalue)\n" - "\tval_lvalue = uint16le_decode(&net_bytes[net_offset]);\n" - "\tnet_offset += 2;\n" - ) - ret += cutil.macro( - "#define UNMARSHAL_U32LE(ctx, val_lvalue)\n" - "\tval_lvalue = uint32le_decode(&net_bytes[net_offset]);\n" - "\tnet_offset += 4;\n" - ) - ret += cutil.macro( - "#define UNMARSHAL_U64LE(ctx, val_lvalue)\n" - "\tval_lvalue = uint64le_decode(&net_bytes[net_offset]);\n" - "\tnet_offset += 8;\n" - ) - - class IndentLevel(typing.NamedTuple): - ifdef: bool # whether this is both `{` and `#if`, or just `{` - - indent_stack: list[IndentLevel] - - def ifdef_lvl() -> int: - return sum(1 if lvl.ifdef else 0 for lvl in indent_stack) - - def indent_lvl() -> int: - return len(indent_stack) - - def handle( - path: idlutil.Path, - ) -> tuple[idlutil.WalkCmd, typing.Callable[[], None]]: - nonlocal ret - nonlocal indent_stack - indent_stack_len = len(indent_stack) - - def pop() -> None: - nonlocal ret - nonlocal indent_stack - nonlocal indent_stack_len - while len(indent_stack) > indent_stack_len: - if len(indent_stack) == indent_stack_len + 1 and indent_stack[-1].ifdef: - break - ret += f"{'\t'*(indent_lvl()-1)}}}\n" - if indent_stack.pop().ifdef: - ret += cutil.ifdef_pop(ifdef_lvl()) - - if not path.elems: - return idlutil.WalkCmd.KEEP_GOING, pop - - child = path.elems[-1] - parent = path.elems[-2].typ if len(path.elems) > 1 else path.root - if child.in_versions < parent.in_versions: - if line := cutil.ifdef_push( - ifdef_lvl() + 1, c9util.ver_ifdef(child.in_versions) - ): - ret += line - ret += ( - f"{'\t'*indent_lvl()}if ({c9util.ver_cond(child.in_versions)}) {{\n" - ) - indent_stack.append(IndentLevel(ifdef=True)) - if 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_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_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 ({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: - ret += f"{'\t'*indent_lvl()}net_offset += {child.typ.static_size};\n" - else: - ret += f"{'\t'*indent_lvl()}UNMARSHAL_U{child.typ.static_size*8}LE(ctx, {path.c_str('out->')});\n" - return idlutil.WalkCmd.KEEP_GOING, pop - - for typ in typs: - if not ( - isinstance(typ, idl.Message) or typ.typname == "stat" - ): # SPECIAL (include stat) - continue - assert isinstance(typ, idl.Struct) - ret += "\n" - ret += cutil.ifdef_push(1, c9util.ver_ifdef(typ.in_versions)) - ret += f"static void unmarshal_{typ.typname}([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) {{\n" - ret += f"\t{c9util.typename(typ)} *out = out_buf;\n" - ret += "\t[[gnu::unused]] void *extra = &out[1];\n" - ret += "\tuint32_t net_offset = 0;\n" - - indent_stack = [IndentLevel(ifdef=True)] - idlutil.walk(typ, handle) - while len(indent_stack) > 0: - ret += f"{'\t'*(indent_lvl()-1)}}}\n" - if indent_stack.pop().ifdef and indent_stack: - ret += cutil.ifdef_pop(ifdef_lvl()) - ret += cutil.ifdef_pop(0) - return ret diff --git a/lib9p/protogen/c_validate.py b/lib9p/protogen/c_validate.py deleted file mode 100644 index 535a750..0000000 --- a/lib9p/protogen/c_validate.py +++ /dev/null @@ -1,299 +0,0 @@ -# lib9p/protogen/c_validate.py - Generate C validation functions -# -# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> -# SPDX-License-Identifier: AGPL-3.0-or-later - -import typing - -import idl - -from . import c9util, cutil, idlutil - -# 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_validate"] - - -def should_save_offset(parent: idl.Struct, child: idl.StructMember) -> bool: - if child.val or child.max or isinstance(child.typ, idl.Bitfield): - return True - for sibling in parent.members: - if sibling.val: - for tok in sibling.val.tokens: - 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.ExprOff) and tok.membname == child.membname: - return True - return False - - -def should_save_end_offset(struct: idl.Struct) -> bool: - for memb in struct.members: - if memb.val: - for tok in memb.val.tokens: - if isinstance(tok, idl.ExprSym) and tok.symname == "end": - return True - if memb.max: - for tok in memb.max.tokens: - if isinstance(tok, idl.ExprSym) and tok.symname == "end": - return True - return False - - -def gen_c_validate(versions: set[str], typs: list[idl.UserType]) -> str: - ret = """ -/* validate_* *****************************************************************/ - -""" - ret += cutil.macro( - "#define VALIDATE_NET_BYTES(n)\n" - "\tif (__builtin_add_overflow(net_offset, n, &net_offset))\n" - "\t\t/* If needed-net-size overflowed uint32_t, then\n" - "\t\t * there's no way that actual-net-size will live up to\n" - "\t\t * that. */\n" - '\t\treturn lib9p_error(ctx, LINUX_EBADMSG, "message is too short for content");\n' - "\tif (net_offset > net_size)\n" - '\t\treturn lib9p_errorf(ctx, LINUX_EBADMSG, "message is too short for content (%"PRIu32" > %"PRIu32") @ %d", net_offset, net_size, __LINE__);\n' - ) - ret += cutil.macro( - "#define VALIDATE_NET_UTF8(n)\n" - "\t{\n" - "\t\tsize_t len = n;\n" - "\t\tVALIDATE_NET_BYTES(len);\n" - "\t\tif (!is_valid_utf8_without_nul(&net_bytes[net_offset-len], len))\n" - '\t\t\treturn lib9p_error(ctx, LINUX_EBADMSG, "message contains invalid UTF-8");\n' - "\t}\n" - ) - ret += cutil.macro( - "#define RESERVE_HOST_BYTES(n)\n" - "\tif (__builtin_add_overflow(host_size, n, &host_size))\n" - "\t\t/* If needed-host-size overflowed ssize_t, then there's\n" - "\t\t * no way that actual-net-size will live up to\n" - "\t\t * that. */\n" - '\t\treturn lib9p_error(ctx, LINUX_EBADMSG, "message is too short for content");\n' - ) - - ret += "#define GET_U8LE(off) (net_bytes[off])\n" - ret += "#define GET_U16LE(off) uint16le_decode(&net_bytes[off])\n" - ret += "#define GET_U32LE(off) uint32le_decode(&net_bytes[off])\n" - ret += "#define GET_U64LE(off) uint64le_decode(&net_bytes[off])\n" - - ret += "#define LAST_U8LE() GET_U8LE(net_offset-1)\n" - ret += "#define LAST_U16LE() GET_U16LE(net_offset-2)\n" - ret += "#define LAST_U32LE() GET_U32LE(net_offset-4)\n" - ret += "#define LAST_U64LE() GET_U64LE(net_offset-8)\n" - - class IndentLevel(typing.NamedTuple): - ifdef: bool # whether this is both `{` and `#if`, or just `{` - - indent_stack: list[IndentLevel] - - def ifdef_lvl() -> int: - return sum(1 if lvl.ifdef else 0 for lvl in indent_stack) - - def indent_lvl() -> int: - return len(indent_stack) - - incr_buf: int - - def incr_flush() -> None: - nonlocal ret - nonlocal incr_buf - if incr_buf: - ret += f"{'\t'*indent_lvl()}VALIDATE_NET_BYTES({incr_buf});\n" - incr_buf = 0 - - def gen_validate_size(path: idlutil.Path) -> None: - nonlocal ret - nonlocal incr_buf - nonlocal indent_stack - - assert path.elems - child = path.elems[-1] - parent = path.elems[-2].typ if len(path.elems) > 1 else path.root - assert isinstance(parent, idl.Struct) - - if child.in_versions < parent.in_versions: - if line := cutil.ifdef_push( - ifdef_lvl() + 1, c9util.ver_ifdef(child.in_versions) - ): - incr_flush() - ret += line - ret += ( - f"{'\t'*indent_lvl()}if ({c9util.ver_cond(child.in_versions)}) {{\n" - ) - indent_stack.append(IndentLevel(ifdef=True)) - 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: - 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. - incr_flush() - ret += f"{'\t'*indent_lvl()}VALIDATE_NET_UTF8({cnt_str});\n" - return - if child.typ.static_size == 1: # SPECIAL (zerocopy) - 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) - 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): - incr_buf += child.typ.static_size - - def gen_validate_content(path: idlutil.Path) -> None: - nonlocal ret - nonlocal incr_buf - nonlocal indent_stack - - assert path.elems - child = path.elems[-1] - parent = path.elems[-2].typ if len(path.elems) > 1 else path.root - assert isinstance(parent, idl.Struct) - - def lookup_sym(sym: str) -> str: - if sym.startswith("&"): - sym = sym[1:] - return f"offsetof{''.join('_'+m.membname for m in path.elems[:-1])}_{sym}" - - if child.val: - incr_flush() - assert child.typ.static_size - nbits = child.typ.static_size * 8 - nbits = child.typ.static_size * 8 - if nbits < 32 and any( - isinstance(tok, idl.ExprSym) - and (tok.symname == "end" or tok.symname.startswith("&")) - for tok in child.val.tokens - ): - nbits = 32 - act = f"(uint{nbits}_t)GET_U{nbits}LE({lookup_sym(f'&{child.membname}')})" - exp = f"(uint{nbits}_t)({c9util.idl_expr(child.val, lookup_sym)})" - ret += f"{'\t'*indent_lvl()}if ({act} != {exp})\n" - ret += f'{"\t"*(indent_lvl()+1)}return lib9p_errorf(ctx, LINUX_EBADMSG, "{path} value is wrong: actual: %"PRIu{nbits}" != correct:%"PRIu{nbits},\n' - ret += f"{'\t'*(indent_lvl()+2)}{act}, {exp});\n" - if child.max: - incr_flush() - assert child.typ.static_size - nbits = child.typ.static_size * 8 - if nbits < 32 and any( - isinstance(tok, idl.ExprSym) - and (tok.symname == "end" or tok.symname.startswith("&")) - for tok in child.max.tokens - ): - nbits = 32 - act = f"(uint{nbits}_t)GET_U{nbits}LE({lookup_sym(f'&{child.membname}')})" - exp = f"(uint{nbits}_t)({c9util.idl_expr(child.max, lookup_sym)})" - ret += f"{'\t'*indent_lvl()}if ({act} > {exp})\n" - ret += f'{"\t"*(indent_lvl()+1)}return lib9p_errorf(ctx, LINUX_EBADMSG, "{path} value is too large: %"PRIu{nbits}" > %"PRIu{nbits},\n' - ret += f"{'\t'*(indent_lvl()+2)}{act}, {exp});\n" - if isinstance(child.typ, idl.Bitfield): - incr_flush() - nbytes = child.typ.static_size - nbits = nbytes * 8 - act = f"GET_U{nbits}LE({lookup_sym(f'&{child.membname}')})" - ret += f"{'\t'*indent_lvl()}if ({act} & ~{child.typ.typname}_masks[ctx->version])\n" - ret += f'{"\t"*(indent_lvl()+1)}return lib9p_errorf(ctx, LINUX_EBADMSG, "unknown bits in {child.typ.typname} bitfield: %#0{nbytes*2}"PRIx{nbits},\n' - ret += f"{'\t'*(indent_lvl()+2)}{act} & ~{child.typ.typname}_masks[ctx->version]);\n" - - def handle( - path: idlutil.Path, - ) -> tuple[idlutil.WalkCmd, typing.Callable[[], None]]: - nonlocal ret - nonlocal incr_buf - nonlocal indent_stack - indent_stack_len = len(indent_stack) - pop_struct = path.elems[-1].typ if path.elems else path.root - pop_path = path - pop_indent_stack_len: int - - def pop() -> None: - nonlocal ret - nonlocal indent_stack - nonlocal indent_stack_len - nonlocal pop_struct - nonlocal pop_path - nonlocal pop_indent_stack_len - if isinstance(pop_struct, idl.Struct): - while len(indent_stack) > pop_indent_stack_len: - incr_flush() - ret += f"{'\t'*(indent_lvl()-1)}}}\n" - if indent_stack.pop().ifdef: - ret += cutil.ifdef_pop(ifdef_lvl()) - parent = pop_struct - path = pop_path - if should_save_end_offset(parent): - ret += f"{'\t'*indent_lvl()}uint32_t offsetof{''.join('_'+m.membname for m in path.elems)}_end = net_offset + {incr_buf};\n" - for child in parent.members: - gen_validate_content(pop_path.add(child)) - while len(indent_stack) > indent_stack_len: - if len(indent_stack) == indent_stack_len + 1 and indent_stack[-1].ifdef: - break - incr_flush() - ret += f"{'\t'*(indent_lvl()-1)}}}\n" - if indent_stack.pop().ifdef: - ret += cutil.ifdef_pop(ifdef_lvl()) - - if path.elems: - gen_validate_size(path) - - pop_indent_stack_len = len(indent_stack) - - return idlutil.WalkCmd.KEEP_GOING, pop - - for typ in typs: - if not ( - isinstance(typ, idl.Message) or typ.typname == "stat" - ): # SPECIAL (include stat) - continue - assert isinstance(typ, idl.Struct) - ret += "\n" - ret += cutil.ifdef_push(1, c9util.ver_ifdef(typ.in_versions)) - if typ.typname == "stat": # SPECIAL (stat) - ret += f"static ssize_t validate_{typ.typname}(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes, uint32_t *ret_net_size) {{\n" - else: - ret += f"static ssize_t validate_{typ.typname}(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) {{\n" - - ret += "\tuint32_t net_offset = 0;\n" - ret += f"\tssize_t host_size = sizeof({c9util.typename(typ)});\n" - - incr_buf = 0 - indent_stack = [IndentLevel(ifdef=True)] - idlutil.walk(typ, handle) - while len(indent_stack) > 1: - incr_flush() - ret += f"{'\t'*(indent_lvl()-1)}}}\n" - if indent_stack.pop().ifdef: - ret += cutil.ifdef_pop(ifdef_lvl()) - - incr_flush() - if typ.typname == "stat": # SPECIAL (stat) - ret += "\tif (ret_net_size)\n" - ret += "\t\t*ret_net_size = net_offset;\n" - ret += "\treturn (ssize_t)host_size;\n" - ret += "}\n" - ret += cutil.ifdef_pop(0) - return ret diff --git a/lib9p/protogen/cutil.py b/lib9p/protogen/cutil.py deleted file mode 100644 index 8df6db9..0000000 --- a/lib9p/protogen/cutil.py +++ /dev/null @@ -1,84 +0,0 @@ -# lib9p/protogen/cutil.py - Utilities for generating C code -# -# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> -# SPDX-License-Identifier: AGPL-3.0-or-later - -# pylint: disable=unused-variable -__all__ = [ - "UINT32_MAX", - "UINT64_MAX", - "macro", - "ifdef_init", - "ifdef_push", - "ifdef_pop", - "ifdef_leaf_is_noop", -] - -UINT32_MAX = (1 << 32) - 1 -UINT64_MAX = (1 << 64) - 1 - - -def tab_ljust(s: str, width: int) -> str: - cur = len(s.expandtabs(tabsize=8)) - if cur >= width: - return s - return s + " " * (width - cur) - - -def macro(full: str) -> str: - full = full.rstrip() - assert "\n" in full - lines = [l.rstrip() for l in full.split("\n")] - width = max(len(l.expandtabs(tabsize=8)) for l in lines[:-1]) - lines = [tab_ljust(l, width) for l in lines] - return " \\\n".join(lines).rstrip() + "\n" - - -_ifdef_stack: list[str | None] = [] - - -def ifdef_init() -> None: - global _ifdef_stack - _ifdef_stack = [] - - -def ifdef_push(n: int, _newval: str) -> str: - # Grow the stack as needed - while len(_ifdef_stack) < n: - _ifdef_stack.append(None) - - # Set some variables - parentval: str | None = None - for x in _ifdef_stack[:-1]: - if x is not None: - parentval = x - oldval = _ifdef_stack[-1] - newval: str | None = _newval - if newval == parentval: - newval = None - - # Put newval on the stack. - _ifdef_stack[-1] = newval - - # Build output. - ret = "" - if newval != oldval: - if oldval is not None: - ret += f"#endif /* {oldval} */\n" - if newval is not None: - ret += f"#if {newval}\n" - return ret - - -def ifdef_pop(n: int) -> str: - global _ifdef_stack - ret = "" - while len(_ifdef_stack) > n: - if _ifdef_stack[-1] is not None: - ret += f"#endif /* {_ifdef_stack[-1]} */\n" - _ifdef_stack = _ifdef_stack[:-1] - return ret - - -def ifdef_leaf_is_noop() -> bool: - return not _ifdef_stack[-1] diff --git a/lib9p/protogen/h.py b/lib9p/protogen/h.py deleted file mode 100644 index 3b33419..0000000 --- a/lib9p/protogen/h.py +++ /dev/null @@ -1,535 +0,0 @@ -# lib9p/protogen/h.py - Generate 9p.generated.h -# -# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> -# SPDX-License-Identifier: AGPL-3.0-or-later - -import sys -import typing - -import idl - -from . import c9util, cutil, idlutil - -# 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_h"] - -# get_buffer_size() ############################################################ - - -class BufferSize(typing.NamedTuple): - min_size: int # really just here to sanity-check against typ.min_size(version) - exp_size: int # "expected" or max-reasonable size - max_size: int # really just here to sanity-check against typ.max_size(version) - max_copy: int - max_copy_extra: str - max_iov: int - max_iov_extra: str - - -class TmpBufferSize: - min_size: int - exp_size: int - max_size: int - max_copy: int - max_copy_extra: str - max_iov: int - max_iov_extra: str - - tmp_starts_with_copy: bool - tmp_ends_with_copy: bool - - def __init__(self) -> None: - self.min_size = 0 - self.exp_size = 0 - self.max_size = 0 - self.max_copy = 0 - self.max_copy_extra = "" - self.max_iov = 0 - self.max_iov_extra = "" - self.tmp_starts_with_copy = False - self.tmp_ends_with_copy = False - - -def _get_buffer_size(typ: idl.Type, version: str) -> TmpBufferSize: - assert isinstance(typ, idl.Primitive) or (version in typ.in_versions) - - ret = TmpBufferSize() - - if not isinstance(typ, idl.Struct): - assert typ.static_size - ret.min_size = typ.static_size - ret.exp_size = typ.static_size - ret.max_size = typ.static_size - ret.max_copy = typ.static_size - ret.max_iov = 1 - ret.tmp_starts_with_copy = True - ret.tmp_ends_with_copy = True - return ret - - def handle(path: idlutil.Path) -> tuple[idlutil.WalkCmd, None]: - nonlocal ret - if path.elems: - child = path.elems[-1] - if version not in child.in_versions: - return idlutil.WalkCmd.DONT_RECURSE, None - if child.cnt: - if child.typ.static_size == 1: # SPECIAL (zerocopy) - ret.max_iov += 1 - # HEURISTIC: 27 for strings (max-strlen from 9P1), 8KiB for other data - ret.exp_size += 27 if child.membname == "utf8" else 8192 - ret.max_size += child.max_cnt - ret.tmp_ends_with_copy = False - return idlutil.WalkCmd.DONT_RECURSE, None - sub = _get_buffer_size(child.typ, version) - ret.exp_size += sub.exp_size * 16 # HEURISTIC: MAXWELEM - ret.max_size += sub.max_size * child.max_cnt - if child.membname == "wname" and path.root.typname in ( - "Tsread", - "Tswrite", - ): # SPECIAL (9P2000.e) - assert ret.tmp_ends_with_copy - assert sub.tmp_starts_with_copy - assert not sub.tmp_ends_with_copy - ret.max_copy_extra = ( - f" + (CONFIG_9P_MAX_9P2000_e_WELEM * {sub.max_copy})" - ) - ret.max_iov_extra = ( - f" + (CONFIG_9P_MAX_9P2000_e_WELEM * {sub.max_iov})" - ) - ret.max_iov -= 1 - else: - ret.max_copy += sub.max_copy * child.max_cnt - if sub.max_iov == 1 and sub.tmp_starts_with_copy: # is purely copy - ret.max_iov += 1 - else: # contains zero-copy segments - ret.max_iov += sub.max_iov * child.max_cnt - if ret.tmp_ends_with_copy and sub.tmp_starts_with_copy: - # we can merge this one - ret.max_iov -= 1 - if ( - sub.tmp_ends_with_copy - and sub.tmp_starts_with_copy - and sub.max_iov > 1 - ): - # we can merge these - ret.max_iov -= child.max_cnt - 1 - ret.tmp_ends_with_copy = sub.tmp_ends_with_copy - return idlutil.WalkCmd.DONT_RECURSE, None - if not isinstance(child.typ, idl.Struct): - assert child.typ.static_size - if not ret.tmp_ends_with_copy: - if ret.max_size == 0: - ret.tmp_starts_with_copy = True - ret.max_iov += 1 - ret.tmp_ends_with_copy = True - ret.min_size += child.typ.static_size - ret.exp_size += child.typ.static_size - ret.max_size += child.typ.static_size - ret.max_copy += child.typ.static_size - return idlutil.WalkCmd.KEEP_GOING, None - - idlutil.walk(typ, handle) - assert ret.min_size == typ.min_size(version) - assert ret.max_size == typ.max_size(version) - return ret - - -def get_buffer_size(typ: idl.Type, version: str) -> BufferSize: - tmp = _get_buffer_size(typ, version) - return BufferSize( - min_size=tmp.min_size, - exp_size=tmp.exp_size, - max_size=tmp.max_size, - max_copy=tmp.max_copy, - max_copy_extra=tmp.max_copy_extra, - max_iov=tmp.max_iov, - max_iov_extra=tmp.max_iov_extra, - ) - - -# Generate .h ################################################################## - - -def gen_h(versions: set[str], typs: list[idl.UserType]) -> str: - cutil.ifdef_init() - - ret = f"""/* Generated by `{' '.join(sys.argv)}`. DO NOT EDIT! */ - -#ifndef _LIB9P_9P_H_ -\t#error Do not include <lib9p/9p.generated.h> directly; include <lib9p/9p.h> instead -#endif - -#include <stdint.h> /* for uint{{n}}_t types */ - -#include <libfmt/fmt.h> /* for fmt_formatter */ -#include <libhw/generic/net.h> /* for struct iovec */ -""" - - id2typ: dict[int, idl.Message] = {} - for msg in [msg for msg in typs if isinstance(msg, idl.Message)]: - id2typ[msg.msgid] = msg - - ret += """ -/* config *********************************************************************/ - -#include "config.h" -""" - for ver in sorted(versions): - ret += "\n" - ret += f"#ifndef {c9util.ver_ifdef({ver})}\n" - ret += f"\t#error config.h must define {c9util.ver_ifdef({ver})}\n" - if ver == "9P2000.e": # SPECIAL (9P2000.e) - ret += "#else\n" - ret += f"\t#if {c9util.ver_ifdef({ver})}\n" - ret += "\t\t#ifndef CONFIG_9P_MAX_9P2000_e_WELEM\n" - ret += f"\t\t\t#error if {c9util.ver_ifdef({ver})} then config.h must define CONFIG_9P_MAX_9P2000_e_WELEM\n" - ret += "\t\t#endif\n" - ret += "\t\tstatic_assert(CONFIG_9P_MAX_9P2000_e_WELEM > 0);\n" - ret += "\t#endif\n" - ret += "#endif\n" - - ret += f""" -/* enum version ***************************************************************/ - -enum {c9util.ident('version')} {{ -""" - fullversions = ["unknown = 0", *sorted(versions)] - verwidth = max(len(v) for v in fullversions) - for ver in fullversions: - if ver in versions: - ret += cutil.ifdef_push(1, c9util.ver_ifdef({ver})) - ret += f"\t{c9util.ver_enum(ver)}," - ret += (" " * (verwidth - len(ver))) + ' /* "' + ver.split()[0] + '" */\n' - 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 **************************************************************/ - -""" - ret += f"enum {c9util.ident('msg_type')} {{ /* uint8_t */\n" - namewidth = max(len(msg.typname) for msg in typs if isinstance(msg, idl.Message)) - for n in range(0x100): - if n not in id2typ: - continue - msg = id2typ[n] - ret += cutil.ifdef_push(1, c9util.ver_ifdef(msg.in_versions)) - 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 **************************************************************/ -""" - - def per_version_comment( - typ: idl.UserType, fn: typing.Callable[[idl.UserType, str], str] - ) -> str: - lines: dict[str, str] = {} - for version in sorted(typ.in_versions): - lines[version] = fn(typ, version) - if len(set(lines.values())) == 1: - for _, line in lines.items(): - return f"/* {line} */\n" - assert False - else: - ret = "" - v_width = max(len(c9util.ver_enum(v)) for v in typ.in_versions) - for version, line in lines.items(): - ret += f"/* {c9util.ver_enum(version):<{v_width}}: {line} */\n" - return ret - - for typ in idlutil.topo_sorted(typs): - ret += "\n" - ret += cutil.ifdef_push(1, c9util.ver_ifdef(typ.in_versions)) - - def sum_size(typ: idl.UserType, version: str) -> str: - sz = get_buffer_size(typ, version) - assert ( - sz.min_size <= sz.exp_size - and sz.exp_size <= sz.max_size - and sz.max_size < cutil.UINT64_MAX - ) - ret = "" - if sz.min_size == sz.max_size: - ret += f"size = {sz.min_size:,}" - else: - ret += f"min_size = {sz.min_size:,} ; exp_size = {sz.exp_size:,} ; max_size = {sz.max_size:,}" - if sz.max_size > cutil.UINT32_MAX: - ret += " (warning: >UINT32_MAX)" - ret += f" ; max_iov = {sz.max_iov:,}{sz.max_iov_extra} ; max_copy = {sz.max_copy:,}{sz.max_copy_extra}" - return ret - - ret += per_version_comment(typ, sum_size) - - match typ: - case idl.Number(): - ret += gen_number(typ) - case idl.Bitfield(): - ret += gen_bitfield(typ) - case idl.Struct(): # and idl.Message(): - ret += gen_struct(typ) - ret += cutil.ifdef_pop(0) - - ret += """ -/* containers *****************************************************************/ -""" - ret += "\n" - ret += f"#define {c9util.IDENT('_MAX')}(a, b) ((a) > (b)) ? (a) : (b)\n" - - tmsg_max_iov: dict[str, int] = {} - tmsg_max_copy: dict[str, int] = {} - rmsg_max_iov: dict[str, int] = {} - rmsg_max_copy: dict[str, int] = {} - for typ in typs: - if not isinstance(typ, idl.Message): - continue - if typ.typname in ("Tsread", "Tswrite"): # SPECIAL (9P2000.e) - continue - max_iov = tmsg_max_iov if typ.msgid % 2 == 0 else rmsg_max_iov - max_copy = tmsg_max_copy if typ.msgid % 2 == 0 else rmsg_max_copy - for version in typ.in_versions: - if version not in max_iov: - max_iov[version] = 0 - max_copy[version] = 0 - sz = get_buffer_size(typ, version) - if sz.max_iov > max_iov[version]: - max_iov[version] = sz.max_iov - if sz.max_copy > max_copy[version]: - max_copy[version] = sz.max_copy - - for name, table in [ - ("tmsg_max_iov", tmsg_max_iov), - ("tmsg_max_copy", tmsg_max_copy), - ("rmsg_max_iov", rmsg_max_iov), - ("rmsg_max_copy", rmsg_max_copy), - ]: - inv: dict[int, set[str]] = {} - for version, maxval in table.items(): - if maxval not in inv: - inv[maxval] = set() - inv[maxval].add(version) - - ret += "\n" - directive = "if" - seen_e = False # SPECIAL (9P2000.e) - for maxval in sorted(inv, reverse=True): - ret += f"#{directive} {c9util.ver_ifdef(inv[maxval])}\n" - indent = 1 - if name.startswith("tmsg") and not seen_e: # SPECIAL (9P2000.e) - typ = next(typ for typ in typs if typ.typname == "Tswrite") - sz = get_buffer_size(typ, "9P2000.e") - match name: - case "tmsg_max_iov": - maxexpr = f"{sz.max_iov}{sz.max_iov_extra}" - case "tmsg_max_copy": - maxexpr = f"{sz.max_copy}{sz.max_copy_extra}" - case _: - assert False - ret += f"\t#if {c9util.ver_ifdef({"9P2000.e"})}\n" - ret += f"\t\t#define {c9util.IDENT(name)} {c9util.IDENT('_MAX')}({maxval}, {maxexpr})\n" - ret += "\t#else\n" - indent += 1 - ret += f"{'\t'*indent}#define {c9util.IDENT(name)} {maxval}\n" - if name.startswith("tmsg") and not seen_e: # SPECIAL (9P2000.e) - ret += "\t#endif\n" - if "9P2000.e" in inv[maxval]: - seen_e = True - directive = "elif" - ret += "#endif\n" - - ret += "\n" - ret += f"struct {c9util.ident('Tmsg_send_buf')} {{\n" - ret += "\tsize_t iov_cnt;\n" - ret += f"\tstruct iovec iov[{c9util.IDENT('TMSG_MAX_IOV')}];\n" - ret += f"\tuint8_t copied[{c9util.IDENT('TMSG_MAX_COPY')}];\n" - ret += "};\n" - - ret += "\n" - ret += f"struct {c9util.ident('Rmsg_send_buf')} {{\n" - ret += "\tsize_t iov_cnt;\n" - ret += f"\tstruct iovec iov[{c9util.IDENT('RMSG_MAX_IOV')}];\n" - ret += f"\tuint8_t copied[{c9util.IDENT('RMSG_MAX_COPY')}];\n" - 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 diff --git a/lib9p/protogen/idlutil.py b/lib9p/protogen/idlutil.py deleted file mode 100644 index dc4d012..0000000 --- a/lib9p/protogen/idlutil.py +++ /dev/null @@ -1,112 +0,0 @@ -# lib9p/protogen/idlutil.py - Utilities for working with the 9P idl package -# -# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> -# SPDX-License-Identifier: AGPL-3.0-or-later - -import enum -import graphlib -import typing - -import idl - -# pylint: disable=unused-variable -__all__ = [ - "topo_sorted", - "Path", - "WalkCmd", - "WalkHandler", - "walk", -] - -# topo_sorted() ################################################################ - - -def topo_sorted(typs: list[idl.UserType]) -> typing.Iterable[idl.UserType]: - ts: graphlib.TopologicalSorter[idl.UserType] = graphlib.TopologicalSorter() - for typ in typs: - match typ: - case idl.Number(): - ts.add(typ) - case idl.Bitfield(): - ts.add(typ) - case idl.Struct(): # and idl.Message(): - deps = [ - member.typ - for member in typ.members - if not isinstance(member.typ, idl.Primitive) - ] - ts.add(typ, *deps) - return ts.static_order() - - -# walk() ####################################################################### - - -class Path: - root: idl.Type - elems: list[idl.StructMember] - - def __init__( - self, root: idl.Type, elems: list[idl.StructMember] | None = None - ) -> None: - self.root = root - self.elems = elems if elems is not None else [] - - def add(self, elem: idl.StructMember) -> "Path": - return Path(self.root, self.elems + [elem]) - - def parent(self) -> "Path": - return Path(self.root, self.elems[:-1]) - - def c_str(self, base: str, loopdepth: int = 0) -> str: - ret = base - for i, elem in enumerate(self.elems): - if i > 0: - ret += "." - ret += elem.membname - if elem.cnt: - ret += f"[{chr(ord('i')+loopdepth)}]" - loopdepth += 1 - return ret - - def __str__(self) -> str: - return self.c_str(self.root.typname + "->") - - -class WalkCmd(enum.Enum): - KEEP_GOING = 1 - DONT_RECURSE = 2 - ABORT = 3 - - -type WalkHandler = typing.Callable[ - [Path], tuple[WalkCmd, typing.Callable[[], None] | None] -] - - -def _walk(path: Path, handle: WalkHandler) -> WalkCmd: - typ = path.elems[-1].typ if path.elems else path.root - - ret, atexit = handle(path) - - if isinstance(typ, idl.Struct): - match ret: - case WalkCmd.KEEP_GOING: - for member in typ.members: - if _walk(path.add(member), handle) == WalkCmd.ABORT: - ret = WalkCmd.ABORT - break - case WalkCmd.DONT_RECURSE: - ret = WalkCmd.KEEP_GOING - case WalkCmd.ABORT: - ret = WalkCmd.ABORT - case _: - assert False, f"invalid cmd: {ret}" - - if atexit: - atexit() - return ret - - -def walk(typ: idl.Type, handle: WalkHandler) -> None: - _walk(Path(typ), handle) |