summaryrefslogtreecommitdiff
path: root/lib9p/idl.gen
diff options
context:
space:
mode:
authorLuke T. Shumaker <lukeshu@lukeshu.com>2025-01-13 23:20:13 -0700
committerLuke T. Shumaker <lukeshu@lukeshu.com>2025-02-12 20:55:19 -0700
commite9de61146146b250dd1c6977086a62435c21d771 (patch)
treee79b5dfd58403ba6c64a61617dfb26b86a84975d /lib9p/idl.gen
parentd65cbac746dcb9c1d5b32dc9f368eb32d322c2de (diff)
lib9p: idl.gen: Track more aspects of max object size
Diffstat (limited to 'lib9p/idl.gen')
-rwxr-xr-xlib9p/idl.gen200
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