diff options
author | Luke T. Shumaker <lukeshu@lukeshu.com> | 2025-01-13 23:20:13 -0700 |
---|---|---|
committer | Luke T. Shumaker <lukeshu@lukeshu.com> | 2025-02-12 20:55:19 -0700 |
commit | e9de61146146b250dd1c6977086a62435c21d771 (patch) | |
tree | e79b5dfd58403ba6c64a61617dfb26b86a84975d /lib9p/idl.gen | |
parent | d65cbac746dcb9c1d5b32dc9f368eb32d322c2de (diff) |
lib9p: idl.gen: Track more aspects of max object size
Diffstat (limited to 'lib9p/idl.gen')
-rwxr-xr-x | lib9p/idl.gen | 200 |
1 files changed, 193 insertions, 7 deletions
diff --git a/lib9p/idl.gen b/lib9p/idl.gen index adb15ce..ead9bc5 100755 --- a/lib9p/idl.gen +++ b/lib9p/idl.gen @@ -5,6 +5,7 @@ # 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 @@ -145,6 +146,9 @@ def ifdef_pop(n: int) -> str: return ret +# topo_sorted() ################################################################ + + def topo_sorted(typs: list[idl.Type]) -> typing.Iterable[idl.Type]: ts: graphlib.TopologicalSorter[idl.Type] = graphlib.TopologicalSorter() for typ in typs: @@ -163,6 +167,142 @@ def topo_sorted(typs: list[idl.Type]) -> typing.Iterable[idl.Type]: 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]) + + +class WalkCmd(enum.Enum): + KEEP_GOING = 1 + DONT_RECURSE = 2 + ABORT = 3 + + +def _walk(path: Path, handle: typing.Callable[[Path], WalkCmd]) -> WalkCmd: + typ = path.elems[-1].typ if path.elems else path.root + + ret = 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}" + + return ret + + +def walk(typ: idl.Type, handle: typing.Callable[[Path], WalkCmd]) -> None: + _walk(Path(typ), handle) + + +# get_buffer_size() ############################################################ + + +class BufferSize: + 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_iov: int + _starts_with_copy: bool + _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_iov = 0 + self._starts_with_copy = False + self._ends_with_copy = False + + +def get_buffer_size(typ: idl.Type, version: str) -> BufferSize: + assert isinstance(typ, idl.Primitive) or (version in typ.in_versions) + + ret = BufferSize() + + 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._starts_with_copy = True + ret._ends_with_copy = True + return ret + + def handle(path: Path) -> WalkCmd: + nonlocal ret + if path.elems: + child = path.elems[-1] + if version not in child.in_versions: + return WalkCmd.DONT_RECURSE + 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.name == "utf8" else 8192 + ret.max_size += child.max_cnt + ret._ends_with_copy = False + return WalkCmd.DONT_RECURSE + 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 + ret.max_copy += sub.max_copy * child.max_cnt + if sub.max_iov == 1 and sub._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._ends_with_copy and sub._starts_with_copy: + # we can merge this one + ret.max_iov -= 1 + if sub._ends_with_copy and sub._starts_with_copy and sub.max_iov > 1: + # we can merge these + ret.max_iov -= child.max_cnt - 1 + ret._ends_with_copy = sub._ends_with_copy + return WalkCmd.DONT_RECURSE + elif not isinstance(child.typ, idl.Struct): + assert child.typ.static_size + if not ret._ends_with_copy: + if ret.max_size == 0: + ret._starts_with_copy = True + ret.max_iov += 1 + ret._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 WalkCmd.KEEP_GOING + + walk(typ, handle) + assert ret.min_size == typ.min_size(version) + assert ret.max_size == typ.max_size(version) + return ret + + # Generate .h ################################################################## @@ -251,16 +391,20 @@ enum {idprefix}version {{ ret += ifdef_push(1, c_ver_ifdef(typ.in_versions)) def sum_size(typ: idl.Type, version: str) -> str: - min_size = typ.min_size(version) - max_size = typ.max_size(version) - assert min_size <= max_size and max_size < u64max + sz = get_buffer_size(typ, version) + assert ( + sz.min_size <= sz.exp_size + and sz.exp_size <= sz.max_size + and sz.max_size < u64max + ) ret = "" - if min_size == max_size: - ret += f"size = {min_size:,}" + if sz.min_size == sz.max_size: + ret += f"size = {sz.min_size:,}" else: - ret += f"min_size = {min_size:,} ; max_size = {max_size:,}" - if max_size > u32max: + ret += f"min_size = {sz.min_size:,} ; exp_size = {sz.exp_size:,} ; max_size = {sz.max_size:,}" + if sz.max_size > u32max: ret += " (warning: >UINT32_MAX)" + ret += f" ; max_iov = {sz.max_iov:,} ; max_copy = {sz.max_copy:,}" return ret ret += per_version_comment(typ, sum_size) @@ -331,6 +475,48 @@ enum {idprefix}version {{ ret += "};\n" ret += ifdef_pop(0) + ret += """ +/* sizes **********************************************************************/ +""" + 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 + 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" + for maxval in sorted(inv, reverse=True): + ret += f"#{directive} {c_ver_ifdef(inv[maxval])}\n" + ret += f"\t#define {idprefix.upper()}{name.upper()} {maxval}\n" + directive = "elif" + ret += "#endif\n" + return ret |