diff options
author | Luke T. Shumaker <lukeshu@lukeshu.com> | 2025-03-23 02:05:13 -0600 |
---|---|---|
committer | Luke T. Shumaker <lukeshu@lukeshu.com> | 2025-03-23 03:05:06 -0600 |
commit | ee356d885a984d5e79da0da20ce608787f1426f3 (patch) | |
tree | 18599858087855f299ba7e7c3aaa65ef43b77e73 | |
parent | 77249bb45c44ec88c96cd00da0805e1a58a1bfd6 (diff) |
lib9p: protogen: pull idlutil.py out of __init__.py
-rw-r--r-- | lib9p/protogen/__init__.py | 156 | ||||
-rw-r--r-- | lib9p/protogen/idlutil.py | 112 |
2 files changed, 144 insertions, 124 deletions
diff --git a/lib9p/protogen/__init__.py b/lib9p/protogen/__init__.py index 73542a2..76e80f3 100644 --- a/lib9p/protogen/__init__.py +++ b/lib9p/protogen/__init__.py @@ -4,15 +4,13 @@ # Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> # SPDX-License-Identifier: AGPL-3.0-or-later -import enum -import graphlib import os.path import sys import typing import idl -from . import c9util, cutil +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 @@ -23,100 +21,6 @@ from . import c9util, cutil __all__ = ["main"] -# 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) - - # get_buffer_size() ############################################################ @@ -170,12 +74,12 @@ def _get_buffer_size(typ: idl.Type, version: str) -> TmpBufferSize: ret.tmp_ends_with_copy = True return ret - def handle(path: Path) -> tuple[WalkCmd, None]: + 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 WalkCmd.DONT_RECURSE, None + return idlutil.WalkCmd.DONT_RECURSE, None if child.cnt: if child.typ.static_size == 1: # SPECIAL (zerocopy) ret.max_iov += 1 @@ -183,7 +87,7 @@ def _get_buffer_size(typ: idl.Type, version: str) -> TmpBufferSize: ret.exp_size += 27 if child.membname == "utf8" else 8192 ret.max_size += child.max_cnt ret.tmp_ends_with_copy = False - return WalkCmd.DONT_RECURSE, None + 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 @@ -218,7 +122,7 @@ def _get_buffer_size(typ: idl.Type, version: str) -> TmpBufferSize: # we can merge these ret.max_iov -= child.max_cnt - 1 ret.tmp_ends_with_copy = sub.tmp_ends_with_copy - return WalkCmd.DONT_RECURSE, None + 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: @@ -230,9 +134,9 @@ def _get_buffer_size(typ: idl.Type, version: str) -> TmpBufferSize: ret.exp_size += child.typ.static_size ret.max_size += child.typ.static_size ret.max_copy += child.typ.static_size - return WalkCmd.KEEP_GOING, None + return idlutil.WalkCmd.KEEP_GOING, None - walk(typ, handle) + idlutil.walk(typ, handle) assert ret.min_size == typ.min_size(version) assert ret.max_size == typ.max_size(version) return ret @@ -343,7 +247,7 @@ enum {c9util.ident('version')} {{ ret += f"/* {c9util.ver_enum(version):<{v_width}}: {line} */\n" return ret - for typ in topo_sorted(typs): + for typ in idlutil.topo_sorted(typs): ret += "\n" ret += cutil.ifdef_push(1, c9util.ver_ifdef(typ.in_versions)) @@ -712,7 +616,7 @@ LM_ALWAYS_INLINE static bool validate_8(struct _validate_ctx *ctx) { return _val member.max or member.val or any(m.cnt == member for m in typ.members) ) - for typ in topo_sorted(typs): + for typ in idlutil.topo_sorted(typs): inline = "LM_FLATTEN" if isinstance(typ, idl.Message) else "LM_ALWAYS_INLINE" argfn = unused if (isinstance(typ, idl.Struct) and not typ.members) else used ret += "\n" @@ -841,7 +745,7 @@ LM_ALWAYS_INLINE static void unmarshal_8(struct _unmarshal_ctx *ctx, uint64_t *o \tctx->net_offset += 8; } """ - for typ in topo_sorted(typs): + for typ in idlutil.topo_sorted(typs): inline = "LM_FLATTEN" if isinstance(typ, idl.Message) else "LM_ALWAYS_INLINE" argfn = unused if (isinstance(typ, idl.Struct) and not typ.members) else used ret += "\n" @@ -943,7 +847,7 @@ LM_ALWAYS_INLINE static void unmarshal_8(struct _unmarshal_ctx *ctx, uint64_t *o class OffsetExpr: static: int cond: dict[frozenset[str], "OffsetExpr"] - rep: list[tuple[Path, "OffsetExpr"]] + rep: list[tuple[idlutil.Path, "OffsetExpr"]] def __init__(self) -> None: self.static = 0 @@ -999,7 +903,7 @@ LM_ALWAYS_INLINE static void unmarshal_8(struct _unmarshal_ctx *ctx, uint64_t *o ret += multiline return ret - type OffsetExprRecursion = typing.Callable[[Path], WalkCmd] + type OffsetExprRecursion = typing.Callable[[idlutil.Path], idlutil.WalkCmd] def get_offset_expr(typ: idl.UserType, recurse: OffsetExprRecursion) -> OffsetExpr: if not isinstance(typ, idl.Struct): @@ -1008,7 +912,7 @@ LM_ALWAYS_INLINE static void unmarshal_8(struct _unmarshal_ctx *ctx, uint64_t *o ret.static = typ.static_size return ret - stack: list[tuple[Path, OffsetExpr, typing.Callable[[], None]]] + stack: list[tuple[idlutil.Path, OffsetExpr, typing.Callable[[], None]]] def pop_root() -> None: assert False @@ -1031,11 +935,13 @@ LM_ALWAYS_INLINE static void unmarshal_8(struct _unmarshal_ctx *ctx, uint64_t *o stack[-2][1].rep.append((cnt_path, stack[-1][1])) stack = stack[:-1] - def handle(path: Path) -> tuple[WalkCmd, typing.Callable[[], None] | None]: + def handle( + path: idlutil.Path, + ) -> tuple[idlutil.WalkCmd, typing.Callable[[], None] | None]: nonlocal recurse ret = recurse(path) - if ret != WalkCmd.KEEP_GOING: + if ret != idlutil.WalkCmd.KEEP_GOING: return ret, None nonlocal stack @@ -1059,18 +965,18 @@ LM_ALWAYS_INLINE static void unmarshal_8(struct _unmarshal_ctx *ctx, uint64_t *o stack[-1][1].static += child.typ.static_size return ret, pop - stack = [(Path(typ), OffsetExpr(), pop_root)] - walk(typ, handle) + stack = [(idlutil.Path(typ), OffsetExpr(), pop_root)] + idlutil.walk(typ, handle) return stack[0][1] - def go_to_end(path: Path) -> WalkCmd: - return WalkCmd.KEEP_GOING + def go_to_end(path: idlutil.Path) -> idlutil.WalkCmd: + return idlutil.WalkCmd.KEEP_GOING - def go_to_tok(name: str) -> typing.Callable[[Path], WalkCmd]: - def ret(path: Path) -> WalkCmd: + 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 WalkCmd.ABORT - return WalkCmd.KEEP_GOING + return idlutil.WalkCmd.ABORT + return idlutil.WalkCmd.KEEP_GOING return ret @@ -1107,9 +1013,11 @@ LM_ALWAYS_INLINE static void unmarshal_8(struct _unmarshal_ctx *ctx, uint64_t *o # Pass 2 - write data ifdef_depth = 1 - stack: list[tuple[Path, bool]] = [(Path(typ), False)] + stack: list[tuple[idlutil.Path, bool]] = [(idlutil.Path(typ), False)] - def handle(path: Path) -> tuple[WalkCmd, typing.Callable[[], None]]: + def handle( + path: idlutil.Path, + ) -> tuple[idlutil.WalkCmd, typing.Callable[[], None]]: nonlocal ret nonlocal ifdef_depth nonlocal stack @@ -1185,7 +1093,7 @@ LM_ALWAYS_INLINE static void unmarshal_8(struct _unmarshal_ctx *ctx, uint64_t *o ret += f"{'\t'*len(stack)}MARSHAL_BYTES(ctx, {path.c_str('val->')[:-3]}, {cnt_path.c_str('val->')});\n" else: ret += f"{'\t'*len(stack)}MARSHAL_BYTES_ZEROCOPY(ctx, {path.c_str('val->')[:-3]}, {cnt_path.c_str('val->')});\n" - return WalkCmd.KEEP_GOING, pop + return idlutil.WalkCmd.KEEP_GOING, pop loopvar = chr(ord("i") + loopdepth - 1) ret += f"{'\t'*len(stack)}for ({c9util.typename(child.cnt.typ)} {loopvar} = 0; {loopvar} < {cnt_path.c_str('val->')}; {loopvar}++) {{\n" stack.append((path, False)) @@ -1208,9 +1116,9 @@ LM_ALWAYS_INLINE static void unmarshal_8(struct _unmarshal_ctx *ctx, uint64_t *o if isinstance(child.typ, idl.Bitfield): val += f" & {child.typ.typname}_masks[ctx->ctx->version]" ret += f"{'\t'*len(stack)}MARSHAL_U{child.typ.static_size*8}LE(ctx, {val});\n" - return WalkCmd.KEEP_GOING, pop + return idlutil.WalkCmd.KEEP_GOING, pop - walk(typ, handle) + idlutil.walk(typ, handle) del handle del stack del max_size diff --git a/lib9p/protogen/idlutil.py b/lib9p/protogen/idlutil.py new file mode 100644 index 0000000..dc4d012 --- /dev/null +++ b/lib9p/protogen/idlutil.py @@ -0,0 +1,112 @@ +# 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) |