diff options
-rw-r--r-- | .editorconfig | 4 | ||||
-rw-r--r-- | cmd/sbc_harness/CMakeLists.txt | 1 | ||||
-rw-r--r-- | cmd/sbc_harness/config/config.h | 2 | ||||
-rw-r--r-- | lib9p/.editorconfig | 8 | ||||
-rw-r--r-- | stack.py | 184 | ||||
-rwxr-xr-x | stack.sh | 2 |
6 files changed, 191 insertions, 10 deletions
diff --git a/.editorconfig b/.editorconfig index 339ad64..b2ffbdd 100644 --- a/.editorconfig +++ b/.editorconfig @@ -36,8 +36,10 @@ _mode = sh [libusb/include/libusb/tusb_helpers.h.gen] _mode = bash -[{lib9p/idl.gen,lib9p/include/lib9p/linux-errno.h.gen}] +[{lib9p/idl.gen,lib9p/include/lib9p/linux-errno.h.gen,stack.py}] _mode = python3 +indent_style = space +indent_size = 4 [{.editorconfig,.gitmodules}] _mode = ini diff --git a/cmd/sbc_harness/CMakeLists.txt b/cmd/sbc_harness/CMakeLists.txt index dbc7f24..d59e2b5 100644 --- a/cmd/sbc_harness/CMakeLists.txt +++ b/cmd/sbc_harness/CMakeLists.txt @@ -20,6 +20,7 @@ target_link_libraries(sbc_harness #libdhcp libhw ) +target_compile_options(sbc_harness PUBLIC "-fcallgraph-info=su,da") pico_enable_stdio_usb(sbc_harness 0) pico_enable_stdio_uart(sbc_harness 1) diff --git a/cmd/sbc_harness/config/config.h b/cmd/sbc_harness/config/config.h index 9e016bf..5ee8634 100644 --- a/cmd/sbc_harness/config/config.h +++ b/cmd/sbc_harness/config/config.h @@ -47,7 +47,7 @@ /* COROUTINE ******************************************************************/ -#define CONFIG_COROUTINE_DEFAULT_STACK_SIZE (32*1024) +#define CONFIG_COROUTINE_DEFAULT_STACK_SIZE 512 #define CONFIG_COROUTINE_MEASURE_STACK 1 /* bool */ #define CONFIG_COROUTINE_PROTECT_STACK 1 /* bool */ #define CONFIG_COROUTINE_DEBUG 0 /* bool */ diff --git a/lib9p/.editorconfig b/lib9p/.editorconfig deleted file mode 100644 index a585eb7..0000000 --- a/lib9p/.editorconfig +++ /dev/null @@ -1,8 +0,0 @@ -# lib9p/.editorconfig - How Python scripts should be formatted -# -# Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> -# SPDX-License-Identifier: AGPL-3.0-or-later - -[{idl.gen,linux-errno.h.gen}] -indent_style = space -indent_size = 4 diff --git a/stack.py b/stack.py new file mode 100644 index 0000000..c1e36d3 --- /dev/null +++ b/stack.py @@ -0,0 +1,184 @@ +#!/usr/bin/env python3 +import re +import sys +import typing + +# Parse the "VCG" language +# +# https://www.rw.cdl.uni-saarland.de/people/sander/private/html/gsvcg1.html +# +# The formal syntax is found at +# ftp://ftp.cs.uni-sb.de/pub/graphics/vcg/vcg.tgz `doc/grammar.txt`. + + +class VCGElem: + typ: str + lineno: int + attrs: dict[str, str] + + +def parse_vcg(reader: typing.TextIO) -> typing.Iterator[VCGElem]: + re_beg = re.compile(r"(edge|node):\s*\{\s*") + _re_tok = r"[a-zA-Z_][a-zA-Z0-9_]*" + _re_str = r'"(?:[^\"]|\\.)*"' + re_attr = re.compile( + "(" + _re_tok + r")\s*:\s*(" + _re_tok + "|" + _re_str + r")\s*" + ) + re_end = re.compile(r"\}\s*$") + re_skip = re.compile(r"(graph:\s*\{\s*title\s*:\s*" + _re_str + r"\s*|\})\s*") + re_esc = re.compile(r"\\.") + + for lineno, line in enumerate(reader): + pos = 0 + + def _raise(msg: str) -> typing.NoReturn: + nonlocal lineno + nonlocal line + nonlocal pos + e = SyntaxError(msg) + e.lineno = lineno + e.offset = pos + e.text = line + raise e + + if re_skip.fullmatch(line): + continue + + elem = VCGElem() + elem.lineno = lineno + + m = re_beg.match(line, pos=pos) + if not m: + _raise("does not look like a VCG line") + elem.typ = m.group(1) + pos = m.end() + + elem.attrs = {} + while True: + if re_end.match(line, pos=pos): + break + m = re_attr.match(line, pos=pos) + if not m: + _raise("unexpected character") + k = m.group(1) + v = m.group(2) + if k in elem.attrs: + _raise(f"duplicate key: {repr(k)}") + if v.startswith('"'): + + def unesc(esc: re.Match[str]) -> str: + match esc.group(0)[1:]: + case "n": + return "\n" + case '"': + return '"' + case "\\": + return "\\" + case _: + _raise(f"invalid escape code {repr(esc.group(0))}") + + v = re_esc.sub(unesc, v[1:-1]) + elem.attrs[k] = v + pos = m.end() + + yield elem + + +class Node: + # from .title (`static` functions are prefixed with the + # compilation unit .c file, which is fine, we'll just leave it). + funcname: str + # .label is "{funcname}\n{location}\n{nstatic} bytes (static}\n{ndynamic} dynamic objects" + location: str + nstatic: int + ndynamic: int + + # edges with .sourcename set to this node + calls: set[str] + +def main() -> None: + re_label = re.compile( + r"(?P<funcname>[^\n]+)\n" + + r"(?P<location>[^\n]+:[0-9]+:[0-9]+)\n" + + r"(?P<nstatic>[0-9]+) bytes \(static\)\n" + + r"(?P<ndynamic>[0-9]+) dynamic objects", + flags=re.MULTILINE, + ) + + graph: dict[str, Node] = dict() + + for elem in parse_vcg(sys.stdin): + match elem.typ: + case "node": + node = Node() + node.calls = set() + skip = False + for k, v in elem.attrs.items(): + match k: + case "title": + node.funcname = v + case "label": + if elem.attrs.get("shape", "") != "ellipse": + m = re_label.fullmatch(v) + if not m: + raise ValueError( + f"unexpected label value {repr(v)}" + ) + node.location = m.group("location") + node.nstatic = int(m.group("nstatic")) + node.ndynamic = int(m.group("ndynamic")) + case "shape": + if v != "ellipse": + raise ValueError(f"unexpected shape value {repr(v)}") + skip = True + case _: + raise ValueError(f"unknown edge key {repr(k)}") + if not skip: + if node.funcname in graph: + raise ValueError(f"duplicate node {repr(node.funcname)}") + graph[node.funcname] = node + case "edge": + caller: str | None = None + callee: str | None = None + for k, v in elem.attrs.items(): + match k: + case "sourcename": + caller = v + case "targetname": + callee = v + case "label": + pass + case _: + raise ValueError(f"unknown edge key {repr(k)}") + if caller is None or callee is None: + raise ValueError(f"incomplete edge: {repr(elem.attrs)}") + if caller not in graph: + raise ValueError(f"unknown caller: {caller}") + graph[caller].calls.add(callee) + case _: + raise ValueError(f"unknown elem type {repr(elem.typ)}") + + # x + + missing: set[str] = set() + def nstatic(funcname: str) -> int: + if funcname not in graph: + missing.add(funcname) + return 0 + node = graph[funcname] + return node.nstatic + max([0, *[nstatic(call) for call in node.calls]]) + + namelen = max(len(name) for name in graph if name.endswith("_cr")) + print(("="*namelen)+" =======") + + for funcname in graph: + if funcname.endswith("_cr"): + print(f"{funcname}\t{nstatic(funcname)}") + + print(("="*namelen)+" =======") + + for funcname in sorted(missing): + print(f"{funcname}\tmissing") + +if __name__ == "__main__": + main() diff --git a/stack.sh b/stack.sh new file mode 100755 index 0000000..4a8a12e --- /dev/null +++ b/stack.sh @@ -0,0 +1,2 @@ +#!/bin/sh +make >&2 && find build -name '*.ci' -exec cat -- {} + | python ./stack.py |