diff options
author | Luke T. Shumaker <lukeshu@lukeshu.com> | 2024-12-13 18:49:15 -0500 |
---|---|---|
committer | Luke T. Shumaker <lukeshu@lukeshu.com> | 2024-12-13 18:49:15 -0500 |
commit | c578a300c7d0d46662fcd0bdce69af95a821bc18 (patch) | |
tree | a99de333f2812d7c018820f39d78b8c4e744f705 /build-aux | |
parent | 6a719c63ecb83a850c317ea94b8932aa0c857770 (diff) | |
parent | 57cc0523f600575feda09bd9fae13f685ce85b0f (diff) |
Merge commit '57cc0523f600575feda09bd9fae13f685ce85b0f'
Diffstat (limited to 'build-aux')
-rwxr-xr-x | build-aux/stack.c.gen | 660 |
1 files changed, 381 insertions, 279 deletions
diff --git a/build-aux/stack.c.gen b/build-aux/stack.c.gen index 22b18d5..92bd8c2 100755 --- a/build-aux/stack.c.gen +++ b/build-aux/stack.c.gen @@ -95,6 +95,8 @@ def parse_vcg(reader: typing.TextIO) -> typing.Iterator[VCGElem]: ################################################################################ # Main analysis +UsageKind: typing.TypeAlias = typing.Literal["static", "dynamic", "dynamic,bounded"] + class Node: # from .title (`static` and `__weak` functions are prefixed with @@ -103,6 +105,7 @@ class Node: funcname: str # .label is "{funcname}\n{location}\n{nstatic} bytes (static}\n{ndynamic} dynamic objects" location: str + usage_kind: UsageKind nstatic: int ndynamic: int @@ -116,6 +119,7 @@ def synthetic_node(name: str, nstatic: int, calls: set[str] = set()) -> Node: n.funcname = name n.location = "<synthetic>" + n.usage_kind = "static" n.nstatic = nstatic n.ndynamic = 0 @@ -137,8 +141,9 @@ def analyze( re_node_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", + + r"(?P<nstatic>[0-9]+) bytes \((?P<usage_kind>static|dynamic|dynamic,bounded)\)\n" + + r"(?P<ndynamic>[0-9]+) dynamic objects" + + r"(?:\n.*)?", flags=re.MULTILINE, ) @@ -163,6 +168,9 @@ def analyze( f"unexpected label value {repr(v)}" ) node.location = m.group("location") + node.usage_kind = typing.cast( + UsageKind, m.group("usage_kind") + ) node.nstatic = int(m.group("nstatic")) node.ndynamic = int(m.group("ndynamic")) case "shape": @@ -216,8 +224,7 @@ def analyze( graph[node.funcname] = node missing: set[str] = set() - - print("/*") + dynamic: set[str] = set() dbg = False @@ -255,41 +262,46 @@ def analyze( node = graph[funcname] if dbg: print(f"//dbg: {funcname}\t{node.nstatic}") + if node.usage_kind == "dynamic" or node.ndynamic > 0: + dynamic.add(app_location_xform(funcname)) return node.nstatic + max( [0, *[nstatic(call, chain + [funcname]) for call in node.calls]] ) - for grp_name, grp_filter in app_func_filters.items(): - namelen = max( - [len(app_location_xform(name)) for name in graph if grp_filter(name)] - + [len(grp_name) + 4] - ) - numlen = max(len(str(nstatic(name))) for name in graph if name.endswith("_cr")) - sep1 = ("=" * namelen) + " " + "=" * numlen - sep2 = ("-" * namelen) + " " + "-" * numlen - - print("= " + grp_name + " " + sep1[len(grp_name) + 3 :]) + print("/*") + for grp_name, grp_filter in app_func_filters.items(): + # Gather the data. nmax = 0 nsum = 0 + rows: dict[str, int] = {} for funcname in graph: if grp_filter(funcname): n = nstatic(funcname) - print( - f"{app_location_xform(funcname).ljust(namelen)} {str(n).rjust(numlen)}" - ) + rows[app_location_xform(funcname)] = n if n > nmax: nmax = n nsum += n + # Figure sizes. + namelen = max([len(k) for k in rows.keys()] + [len(grp_name) + 4]) + numlen = len(str(nsum)) + sep1 = ("=" * namelen) + " " + "=" * numlen + sep2 = ("-" * namelen) + " " + "-" * numlen + + # Print. + print("= " + grp_name + " " + sep1[len(grp_name) + 3 :]) + for name, num in rows.items(): + print(f"{name.ljust(namelen)} {str(num).rjust(numlen)}") print(sep2) print(f"{'Total'.ljust(namelen)} {str(nsum).rjust(numlen)}") print(f"{'Maximum'.ljust(namelen)} {str(nmax).rjust(numlen)}") - print(sep1) for funcname in sorted(missing): print(f"warning: missing: {funcname}") + for funcname in sorted(dynamic): + print(f"warning: dynamic: {funcname}") print("*/") @@ -312,11 +324,20 @@ def read_source(location: str) -> str: def main( - *, arg_base_dir: str, arg_ci_fnames: list[str], arg_c_fnames: list[str] + *, + arg_pico_platform: str, + arg_base_dir: str, + arg_ci_fnames: list[str], + arg_c_fnames: list[str], ) -> None: re_call_other = re.compile(r"(?P<func>[^(]+)\(.*") + all_nodes: list[Node] = [] + hooks_is_intrhandler: list[typing.Callable[[str], bool]] = [] + hooks_indirect_callees: list[typing.Callable[[str, str], list[str] | None]] = [] + hooks_skip_call: list[typing.Callable[[list[str], str], bool]] = [] + # The sbc-harness codebase ####################################### vcalls: dict[str, set[str]] = {} @@ -341,6 +362,39 @@ def main( elif re_vtable_start.search(line): in_vtable = True + tmessage_handlers: set[str] | None = None + if any(fname.endswith("lib9p/srv.c") for fname in c_fnames): + srv_c = next(fname for fname in c_fnames if fname.endswith("lib9p/srv.c")) + re_tmessage_handler = re.compile( + r"^\s*\[LIB9P_TYP_T[^]]+\]\s*=\s*\(tmessage_handler\)\s*(?P<handler>\S+),\s*$" + ) + tmessage_handlers = set() + with open(srv_c, "r") as fh: + for line in fh: + line = line.rstrip() + if m := re_tmessage_handler.fullmatch(line): + tmessage_handlers.add(m.group("handler")) + + lib9p_versions: dict[str, set[str]] | None = None + if any(fname.endswith("lib9p/9p.c") for fname in c_fnames): + generated_c = next( + fname for fname in c_fnames if fname.endswith("lib9p/9p.generated.c") + ) + re_lib9p_msg_entry = re.compile(r"^\s*_MSG\((?P<typ>\S+)\),$") + lib9p_versions = { + "validate": set(), + "marshal": set(), + "unmarshal": set(), + } + with open(generated_c, "r") as fh: + for line in fh: + line = line.rstrip() + if m := re_lib9p_msg_entry.fullmatch(line): + typ = m.group("typ") + lib9p_versions["validate"].add(f"validate_{typ}") + lib9p_versions["unmarshal"].add(f"unmarshal_{typ}") + lib9p_versions["marshal"].add(f"marshal_{typ}") + re_call_vcall = re.compile(r"VCALL\((?P<obj>[^,]+), (?P<meth>[^,)]+)[,)].*") def sbc_indirect_callees(loc: str, line: str) -> list[str] | None: @@ -361,10 +415,22 @@ def main( "_cr_chan_dequeue", "_cr_select_dequeue", ] + if tmessage_handlers and "/srv.c:" in loc and "tmessage_handlers[typ](" in line: + return sorted(tmessage_handlers) + if lib9p_versions and "/9p.c:" in loc: + for meth in lib9p_versions.keys(): + if line.startswith(f"table.{meth}("): + return sorted(lib9p_versions[meth]) return None + hooks_indirect_callees += [sbc_indirect_callees] + def sbc_is_thread(name: str) -> bool: - return name.endswith("_cr") or name == "main" + if name.endswith("_cr") and name != "lib9p_srv_read_cr": + return True + if name == "main": + return True + return False def sbc_is_intrhandler(name: str) -> bool: return name in [ @@ -374,10 +440,19 @@ def main( "hostnet_handle_sig_io", ] + hooks_is_intrhandler += [sbc_is_intrhandler] + sbc_gpio_handlers = [ "w5500_intrhandler", ] + # 1=just root directory + # 2=just files in root directory + # 3=just 1 level of subdirectories + # 4=just 2 levels of subdirectories + # ... + sbc_9p_max_depth = 3 + def sbc_skip_call(chain: list[str], call: str) -> bool: if ( len(chain) > 1 @@ -386,268 +461,299 @@ def main( and any(c.endswith(":__assert_msg_fail") for c in chain[:-1]) ): return True - if call == "_cr_select_dequeue": + if ( + len(chain) >= sbc_9p_max_depth + and call.endswith("/srv.c:util_release") + and all( + c.endswith("/srv.c:util_release") for c in chain[-sbc_9p_max_depth:] + ) + ): return True return False + hooks_skip_call += [sbc_skip_call] + # pico-sdk ####################################################### - def pico_is_intrhandler(name: str) -> bool: - return name in [ - "gpio_default_irq_handler", - ] + if arg_pico_platform == "rp2040": + + def pico_is_intrhandler(name: str) -> bool: + return name in [ + "gpio_default_irq_handler", + ] - def pico_indirect_callees(loc: str, line: str) -> list[str] | None: - if "/3rd-party/pico-sdk/" not in loc or "/3rd-party/pico-sdk/lib/" in loc: + hooks_is_intrhandler += [pico_is_intrhandler] + + def pico_indirect_callees(loc: str, line: str) -> list[str] | None: + if "/3rd-party/pico-sdk/" not in loc or "/3rd-party/pico-sdk/lib/" in loc: + return None + m = re_call_other.fullmatch(line) + call: str | None = m.group("func") if m else None + + match call: + case "connect_internal_flash_func": + return ["rom_func_lookup(ROM_FUNC_CONNECT_INTERNAL_FLASH)"] + case "flash_exit_xip_func": + return ["rom_func_lookup(ROM_FUNC_FLASH_EXIT_XIP)"] + case "flash_range_erase_func": + return ["rom_func_lookup(ROM_FUNC_FLASH_RANGE_ERASE)"] + case "flash_flush_cache_func": + return ["rom_func_lookup(ROM_FUNC_FLASH_FLUSH_CACHE)"] + case "rom_table_lookup": + return ["rom_hword_as_ptr(BOOTROM_TABLE_LOOKUP_OFFSET)"] + if "/flash.c:" in loc and "boot2_copyout" in line: + return ["_stage2_boot"] + if "/gpio.c:" in loc and call == "callback": + return sbc_gpio_handlers + if "/printf.c:" in loc: + if call == "out": + return [ + "_out_buffer", + "_out_null", + "_out_fct", + ] + if "->fct(" in line: + return ["stdio_buffered_printer"] + if "/stdio.c:" in loc: + if call == "out_func": + return [ + "stdio_out_chars_crlf", + "stdio_out_chars_no_crlf", + ] + if call and (call.startswith("d->") or call.startswith("driver->")): + _, meth = call.split("->", 1) + match meth: + case "out_chars": + return ["stdio_uart_out_chars"] + case "out_flush": + return ["stdio_uart_out_flush"] + case "in_chars": + return ["stdio_uart_in_chars"] return None - m = re_call_other.fullmatch(line) - call: str | None = m.group("func") if m else None - - match call: - case "connect_internal_flash_func": - return ["rom_func_lookup(ROM_FUNC_CONNECT_INTERNAL_FLASH)"] - case "flash_exit_xip_func": - return ["rom_func_lookup(ROM_FUNC_FLASH_EXIT_XIP)"] - case "flash_range_erase_func": - return ["rom_func_lookup(ROM_FUNC_FLASH_RANGE_ERASE)"] - case "flash_flush_cache_func": - return ["rom_func_lookup(ROM_FUNC_FLASH_FLUSH_CACHE)"] - case "rom_table_lookup": - return ["rom_hword_as_ptr(BOOTROM_TABLE_LOOKUP_OFFSET)"] - if "/flash.c:" in loc and "boot2_copyout" in line: - return ["_stage2_boot"] - if "/gpio.c:" in loc and call == "callback": - return sbc_gpio_handlers - if "/printf.c:" in loc: - if call == "out": - return [ - "_out_buffer", - "_out_null", - "_out_fct", - ] - if "->fct(" in line: - return ["stdio_buffered_printer"] - if "/stdio.c:" in loc: - if call == "out_func": - return [ - "stdio_out_chars_crlf", - "stdio_out_chars_no_crlf", - ] - if call and (call.startswith("d->") or call.startswith("driver->")): - _, meth = call.split("->", 1) - match meth: - case "out_chars": - return ["stdio_uart_out_chars"] - case "out_flush": - return ["stdio_uart_out_flush"] - case "in_chars": - return ["stdio_uart_in_chars"] - return None - def pico_skip_call(chain: list[str], call: str) -> bool: - if call == "_out_buffer" or call == "_out_fct": - last = "" - for pcall in chain: - if pcall in [ - "__wrap_sprintf", - "__wrap_snprintf", - "__wrap_vsnprintf", - "vfctprintf", - ]: - last = pcall - if last == "vfctprintf": - return call != "_out_fct" - else: - return call == "_out_buffer" - return False + hooks_indirect_callees += [pico_indirect_callees] + + def pico_skip_call(chain: list[str], call: str) -> bool: + if call == "_out_buffer" or call == "_out_fct": + last = "" + for pcall in chain: + if pcall in [ + "__wrap_sprintf", + "__wrap_snprintf", + "__wrap_vsnprintf", + "vfctprintf", + ]: + last = pcall + if last == "vfctprintf": + return call != "_out_fct" + else: + return call == "_out_buffer" + return False + + hooks_skip_call += [pico_skip_call] - # src/rp2_common/hardware_divider/include/hardware/divider_helper.S - save_div_state_and_lr = 5 * 4 - # src/rp2_common/pico_divider/divider_hardware.S - save_div_state_and_lr_64 = 5 * 4 - pico_nodes: list[Node] = [ - # src/rp2_common/pico_int64_ops/pico_int64_ops_aeabi.S - synthetic_node("__aeabi_lmul", 4), + # src/rp2_common/hardware_divider/include/hardware/divider_helper.S + save_div_state_and_lr = 5 * 4 # src/rp2_common/pico_divider/divider_hardware.S - # s32 aliases - synthetic_node("div_s32s32", 0, {"divmod_s32s32"}), - synthetic_node("__aeabi_idiv", 0, {"divmod_s32s32"}), - synthetic_node("__aeabi_idivmod", 0, {"divmod_s32s32"}), - # s32 impl - synthetic_node("divmod_s32s32", 0, {"divmod_s32s32_savestate"}), - synthetic_node( - "divmod_s32s32_savestate", save_div_state_and_lr, {"divmod_s32s32_unsafe"} - ), - synthetic_node("divmod_s32s32_unsafe", 2 * 4, {"__aeabi_idiv0"}), - # u32 aliases - synthetic_node("div_u32u32", 0, {"divmod_u32u32"}), - synthetic_node("__aeabi_uidiv", 0, {"divmod_u32u32"}), - synthetic_node("__aeabi_uidivmod", 0, {"divmod_u32u32"}), - # u32 impl - synthetic_node("divmod_u32u32", 0, {"divmod_u32u32_savestate"}), - synthetic_node( - "divmod_u32u32_savestate", save_div_state_and_lr, {"divmod_u32u32_unsafe"} - ), - synthetic_node("divmod_u32u32_unsafe", 2 * 4, {"__aeabi_idiv0"}), - # s64 aliases - synthetic_node("div_s64s64", 0, {"divmod_s64s64"}), - synthetic_node("__aeabi_ldiv", 0, {"divmod_s64s64"}), - synthetic_node("__aeabi_ldivmod", 0, {"divmod_s64s64"}), - # s64 impl - synthetic_node("divmod_s64s64", 0, {"divmod_s64s64_savestate"}), - synthetic_node( - "divmod_s64s64_savestate", - save_div_state_and_lr_64 + (2 * 4), - {"divmod_s64s64_unsafe"}, - ), - synthetic_node( - "divmod_s64s64_unsafe", 4, {"divmod_u64u64_unsafe", "__aeabi_ldiv0"} - ), - # u64 aliases - synthetic_node("div_u64u64", 0, {"divmod_u64u64"}), - synthetic_node("__aeabi_uldiv", 0, {"divmod_u64u64"}), - synthetic_node("__aeabi_uldivmod", 0, {"divmod_u64u64"}), - # u64 impl - synthetic_node("divmod_u64u64", 0, {"divmod_u64u64_savestate"}), - synthetic_node( - "divmod_u64u64_savestate", - save_div_state_and_lr_64 + (2 * 4), - {"divmod_u64u64_unsafe"}, - ), - synthetic_node( - "divmod_u64u64_unsafe", (1 + 1 + 2 + 5 + 5 + 2) * 4, {"__aeabi_ldiv0"} - ), - # *_rem - synthetic_node("divod_s64s64_rem", 2 * 4, {"divmod_s64s64"}), - synthetic_node("divod_u64u64_rem", 2 * 4, {"divmod_u64u64"}), - # src/rp2040/boot_stage2/boot2_${name,,}.S for name=W25Q080, - # controlled by `#define PICO_BOOT_STAGE2_{name} 1` in - # src/boards/include/boards/pico.h - synthetic_node("_stage2_boot", 0), # TODO - # https://github.com/raspberrypi/pico-bootrom-rp2040 - synthetic_node("rom_func_lookup(ROM_FUNC_CONNECT_INTERNAL_FLASH)", 0), # TODO - synthetic_node("rom_func_lookup(ROM_FUNC_FLASH_EXIT_XIP)", 0), # TODO - synthetic_node("rom_func_lookup(ROM_FUNC_FLASH_FLUSH_CACHE)", 0), # TODO - synthetic_node("rom_hword_as_ptr(BOOTROM_TABLE_LOOKUP_OFFSET)", 0), # TODO - ] + save_div_state_and_lr_64 = 5 * 4 + all_nodes += [ + # src/rp2_common/pico_int64_ops/pico_int64_ops_aeabi.S + synthetic_node("__aeabi_lmul", 4), + # src/rp2_common/pico_divider/divider_hardware.S + # s32 aliases + synthetic_node("div_s32s32", 0, {"divmod_s32s32"}), + synthetic_node("__aeabi_idiv", 0, {"divmod_s32s32"}), + synthetic_node("__aeabi_idivmod", 0, {"divmod_s32s32"}), + # s32 impl + synthetic_node("divmod_s32s32", 0, {"divmod_s32s32_savestate"}), + synthetic_node( + "divmod_s32s32_savestate", + save_div_state_and_lr, + {"divmod_s32s32_unsafe"}, + ), + synthetic_node("divmod_s32s32_unsafe", 2 * 4, {"__aeabi_idiv0"}), + # u32 aliases + synthetic_node("div_u32u32", 0, {"divmod_u32u32"}), + synthetic_node("__aeabi_uidiv", 0, {"divmod_u32u32"}), + synthetic_node("__aeabi_uidivmod", 0, {"divmod_u32u32"}), + # u32 impl + synthetic_node("divmod_u32u32", 0, {"divmod_u32u32_savestate"}), + synthetic_node( + "divmod_u32u32_savestate", + save_div_state_and_lr, + {"divmod_u32u32_unsafe"}, + ), + synthetic_node("divmod_u32u32_unsafe", 2 * 4, {"__aeabi_idiv0"}), + # s64 aliases + synthetic_node("div_s64s64", 0, {"divmod_s64s64"}), + synthetic_node("__aeabi_ldiv", 0, {"divmod_s64s64"}), + synthetic_node("__aeabi_ldivmod", 0, {"divmod_s64s64"}), + # s64 impl + synthetic_node("divmod_s64s64", 0, {"divmod_s64s64_savestate"}), + synthetic_node( + "divmod_s64s64_savestate", + save_div_state_and_lr_64 + (2 * 4), + {"divmod_s64s64_unsafe"}, + ), + synthetic_node( + "divmod_s64s64_unsafe", 4, {"divmod_u64u64_unsafe", "__aeabi_ldiv0"} + ), + # u64 aliases + synthetic_node("div_u64u64", 0, {"divmod_u64u64"}), + synthetic_node("__aeabi_uldiv", 0, {"divmod_u64u64"}), + synthetic_node("__aeabi_uldivmod", 0, {"divmod_u64u64"}), + # u64 impl + synthetic_node("divmod_u64u64", 0, {"divmod_u64u64_savestate"}), + synthetic_node( + "divmod_u64u64_savestate", + save_div_state_and_lr_64 + (2 * 4), + {"divmod_u64u64_unsafe"}, + ), + synthetic_node( + "divmod_u64u64_unsafe", (1 + 1 + 2 + 5 + 5 + 2) * 4, {"__aeabi_ldiv0"} + ), + # *_rem + synthetic_node("divod_s64s64_rem", 2 * 4, {"divmod_s64s64"}), + synthetic_node("divod_u64u64_rem", 2 * 4, {"divmod_u64u64"}), + # src/rp2040/boot_stage2/boot2_${name,,}.S for name=W25Q080, + # controlled by `#define PICO_BOOT_STAGE2_{name} 1` in + # src/boards/include/boards/pico.h + synthetic_node("_stage2_boot", 0), # TODO + # https://github.com/raspberrypi/pico-bootrom-rp2040 + synthetic_node( + "rom_func_lookup(ROM_FUNC_CONNECT_INTERNAL_FLASH)", 0 + ), # TODO + synthetic_node("rom_func_lookup(ROM_FUNC_FLASH_EXIT_XIP)", 0), # TODO + synthetic_node("rom_func_lookup(ROM_FUNC_FLASH_FLUSH_CACHE)", 0), # TODO + synthetic_node("rom_hword_as_ptr(BOOTROM_TABLE_LOOKUP_OFFSET)", 0), # TODO + ] - # TinyUSB ######################################################## + # TinyUSB device ################################################# - tusb_config_fname = ( - arg_base_dir + "/cmd/sbc_harness/config/tusb_config.h" - ) # TODO: FIXME - re_tud_class = re.compile( - r"^\s*#\s*define\s+(?P<k>CFG_TUD_(?:\S{3}|AUDIO|VIDEO|MIDI|VENDOR|USBTMC|DFU_RUNTIME|ECM_RNDIS))\s+(?P<v>\S+).*" - ) - tusb_config: dict[str, bool] = {} - with open(tusb_config_fname, "r") as fh: - in_table = False - for line in fh: - line = line.rstrip() - if m := re_tud_class.fullmatch(line): - k = m.group("k") - v = m.group("v") - tusb_config[k] = bool(int(v)) - - usbd_fname = next( - fname for fname in c_fnames if fname.endswith("/tinyusb/src/device/usbd.c") - ) - tud_drivers: dict[str, set[str]] = {} - re_tud_entry = re.compile( - r"^\s+\.(?P<meth>\S+)\s*=\s*(?P<impl>[a-zA-Z0-9_]+)(?:,.*)?" - ) - re_tud_if1 = re.compile(r"^\s*#\s*if (\S+)\s*") - re_tud_if2 = re.compile(r"^\s*#\s*if (\S+)\s*\|\|\s*(\S+)\s*") - re_tud_endif = re.compile(r"^\s*#\s*endif\s*") - with open(usbd_fname, "r") as fh: - in_table = False - enabled = True - for line in fh: - line = line.rstrip() - if in_table: - if m := re_tud_if1.fullmatch(line): - enabled = tusb_config[m.group(1)] - elif m := re_tud_if2.fullmatch(line): - enabled = tusb_config[m.group(1)] or tusb_config[m.group(2)] - elif re_tud_endif.fullmatch(line): - enabled = True - if m := re_tud_entry.fullmatch(line): - meth = m.group("meth") - impl = m.group("impl") - if meth == "name" or not enabled: - continue - if meth not in tud_drivers: - tud_drivers[meth] = set() - if impl != "NULL": - tud_drivers[meth].add(impl) - if line.startswith("}"): - in_table = False - elif " _usbd_driver[] = {" in line: - in_table = True - - def tud_indirect_callees(loc: str, line: str) -> list[str] | None: - if "/tinyusb/" not in loc or "/tinyusb/src/host/" in loc or "_host.c:" in loc: - return None - m = re_call_other.fullmatch(line) - assert m - call = m.group("func") - if call == "_ctrl_xfer.complete_cb": - return [ - # "process_test_mode_cb", - "tud_vendor_control_xfer_cb", - *sorted(tud_drivers["control_xfer_cb"]), - ] - elif call.startswith("driver->"): - return sorted(tud_drivers[call[len("driver->") :]]) - elif call == "event.func_call.func": - # callback from usb_defer_func() - return [] + if any(fname.endswith("/tinyusb/src/device/usbd.c") for fname in c_fnames): + tusb_config_fname = ( + arg_base_dir + "/cmd/sbc_harness/config/tusb_config.h" + ) # TODO: FIXME + re_tud_class = re.compile( + r"^\s*#\s*define\s+(?P<k>CFG_TUD_(?:\S{3}|AUDIO|VIDEO|MIDI|VENDOR|USBTMC|DFU_RUNTIME|ECM_RNDIS))\s+(?P<v>\S+).*" + ) + tusb_config: dict[str, bool] = {} + with open(tusb_config_fname, "r") as fh: + in_table = False + for line in fh: + line = line.rstrip() + if m := re_tud_class.fullmatch(line): + k = m.group("k") + v = m.group("v") + tusb_config[k] = bool(int(v)) - return None + usbd_fname = next( + fname for fname in c_fnames if fname.endswith("/tinyusb/src/device/usbd.c") + ) + tud_drivers: dict[str, set[str]] = {} + re_tud_entry = re.compile( + r"^\s+\.(?P<meth>\S+)\s*=\s*(?P<impl>[a-zA-Z0-9_]+)(?:,.*)?" + ) + re_tud_if1 = re.compile(r"^\s*#\s*if (\S+)\s*") + re_tud_if2 = re.compile(r"^\s*#\s*if (\S+)\s*\|\|\s*(\S+)\s*") + re_tud_endif = re.compile(r"^\s*#\s*endif\s*") + with open(usbd_fname, "r") as fh: + in_table = False + enabled = True + for line in fh: + line = line.rstrip() + if in_table: + if m := re_tud_if1.fullmatch(line): + enabled = tusb_config[m.group(1)] + elif m := re_tud_if2.fullmatch(line): + enabled = tusb_config[m.group(1)] or tusb_config[m.group(2)] + elif re_tud_endif.fullmatch(line): + enabled = True + if m := re_tud_entry.fullmatch(line): + meth = m.group("meth") + impl = m.group("impl") + if meth == "name" or not enabled: + continue + if meth not in tud_drivers: + tud_drivers[meth] = set() + if impl != "NULL": + tud_drivers[meth].add(impl) + if line.startswith("}"): + in_table = False + elif " _usbd_driver[] = {" in line: + in_table = True + + def tud_indirect_callees(loc: str, line: str) -> list[str] | None: + if ( + "/tinyusb/" not in loc + or "/tinyusb/src/host/" in loc + or "_host.c:" in loc + ): + return None + m = re_call_other.fullmatch(line) + assert m + call = m.group("func") + if call == "_ctrl_xfer.complete_cb": + return [ + # "process_test_mode_cb", + "tud_vendor_control_xfer_cb", + *sorted(tud_drivers["control_xfer_cb"]), + ] + elif call.startswith("driver->"): + return sorted(tud_drivers[call[len("driver->") :]]) + elif call == "event.func_call.func": + # callback from usb_defer_func() + return [] - def tud_skip_call(chain: list[str], call: str) -> bool: - if call == "usbd_app_driver_get_cb": - return True - return False + return None + + hooks_indirect_callees += [tud_indirect_callees] # newlib ######################################################### - newlib_nodes: list[Node] = [ - # malloc - synthetic_node("free", 0), # TODO - synthetic_node("malloc", 0), # TODO - synthetic_node("realloc", 0), # TODO - synthetic_node("aligned_alloc", 0), # TODO - synthetic_node("reallocarray", 0), # TODO - # execution - synthetic_node("abort", 0), # TODO - synthetic_node("longjmp", 0), # TODO - synthetic_node("setjmp", 0), # TODO - # <strings.h> - synthetic_node("memcmp", 0), # TODO - synthetic_node("memcpy", 0), # TODO - synthetic_node("memset", 0), # TODO - synthetic_node("strlen", 0), # TODO - synthetic_node("strncpy", 0), # TODO - # other - synthetic_node("random", 0), # TODO - ] + if arg_pico_platform == "rp2040": + all_nodes += [ + # malloc + synthetic_node("free", 0), # TODO + synthetic_node("malloc", 0), # TODO + synthetic_node("realloc", 0), # TODO + synthetic_node("aligned_alloc", 0), # TODO + synthetic_node("reallocarray", 0), # TODO + # execution + synthetic_node("abort", 0), # TODO + synthetic_node("longjmp", 0), # TODO + synthetic_node("setjmp", 0), # TODO + # <strings.h> + synthetic_node("memcmp", 0), # TODO + synthetic_node("memcpy", 0), # TODO + synthetic_node("memset", 0), # TODO + synthetic_node("strcmp", 0), # TODO + synthetic_node("strlen", 0), # TODO + synthetic_node("strncpy", 0), # TODO + synthetic_node("strnlen", 0), # TODO + # other + synthetic_node("random", 0), # TODO + ] # libgcc ######################################################### - gcc_nodes: list[Node] = [ - synthetic_node("__aeabi_idiv0", 0), # TODO - synthetic_node("__aeabi_ldiv0", 0), # TODO - ] + if arg_pico_platform == "rp2040": + all_nodes += [ + synthetic_node("__aeabi_idiv0", 0), # TODO + synthetic_node("__aeabi_ldiv0", 0), # TODO + ] - # main ########################################################### + # Tie it all together ############################################ def thread_filter(name: str) -> bool: return sbc_is_thread(name) def intrhandler_filter(name: str) -> bool: name = name.rsplit(":", 1)[-1] - return sbc_is_intrhandler(name) or pico_is_intrhandler(name) + for hook in hooks_is_intrhandler: + if hook(name): + return True + return False def location_xform(loc: str) -> str: if not loc.startswith("/"): @@ -660,30 +766,26 @@ def main( loc = elem.attrs.get("label", "") line = read_source(loc) - ret = sbc_indirect_callees(loc, line) - if ret is not None: - return ret + for hook in hooks_indirect_callees: + ret = hook(loc, line) + if ret is not None: + return ret - ret = pico_indirect_callees(loc, line) - if ret is not None: - return ret - - ret = tud_indirect_callees(loc, line) - if ret is not None: - return ret - - return [f"__indirect_call:" + location_xform(elem.attrs.get("label", ""))] + placeholder = "__indirect_call" + if m := re_call_other.fullmatch(line): + placeholder += ":" + m.group("func") + placeholder += " at " + location_xform(elem.attrs.get("label", "")) + return [placeholder] def skip_call(chain: list[str], call: str) -> bool: - return ( - sbc_skip_call(chain, call) - or pico_skip_call(chain, call) - or tud_skip_call(chain, call) - ) + for hook in hooks_skip_call: + if hook(chain, call): + return True + return False analyze( ci_fnames=arg_ci_fnames, - extra_nodes=pico_nodes + newlib_nodes + gcc_nodes, + extra_nodes=all_nodes, app_func_filters={ "Threads": thread_filter, "Interrupt handlers": intrhandler_filter, @@ -696,19 +798,19 @@ def main( if __name__ == "__main__": - base_dir = sys.argv[1] + pico_platform = sys.argv[1] + base_dir = sys.argv[2] + fnames = sys.argv[3:] re_suffix = re.compile(r"\.c\.o(bj)?$") - ci_fnames = [ - re_suffix.sub(".c.ci", fname) - for fname in sys.argv[2:] - if re_suffix.search(fname) + re_suffix.sub(".c.ci", fname) for fname in fnames if re_suffix.search(fname) ] - c_fnames = [fname for fname in sys.argv[2:] if fname.endswith(".c")] + c_fnames = [fname for fname in fnames if fname.endswith(".c")] main( + arg_pico_platform=pico_platform, arg_base_dir=base_dir, arg_ci_fnames=ci_fnames, arg_c_fnames=c_fnames, |