rpkbin 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rpkbin-0.1.0/LICENSE +21 -0
- rpkbin-0.1.0/PKG-INFO +114 -0
- rpkbin-0.1.0/README.md +78 -0
- rpkbin-0.1.0/pyproject.toml +51 -0
- rpkbin-0.1.0/rpkbin/__init__.py +5 -0
- rpkbin-0.1.0/rpkbin/cfg/__init__.py +85 -0
- rpkbin-0.1.0/rpkbin/cfg/analysis.py +281 -0
- rpkbin-0.1.0/rpkbin/cfg/block.py +141 -0
- rpkbin-0.1.0/rpkbin/cfg/cfg.py +1125 -0
- rpkbin-0.1.0/rpkbin/cfg/fsm.py +226 -0
- rpkbin-0.1.0/rpkbin/cfg/mcu.py +266 -0
- rpkbin-0.1.0/rpkbin/cfg/program.py +95 -0
- rpkbin-0.1.0/rpkbin/excel_extractor/__init__.py +13 -0
- rpkbin-0.1.0/rpkbin/excel_extractor/matcher.py +452 -0
- rpkbin-0.1.0/rpkbin/excel_extractor/normalizer.py +215 -0
- rpkbin-0.1.0/rpkbin/excel_extractor/result.py +51 -0
- rpkbin-0.1.0/rpkbin/excel_extractor/template.py +245 -0
- rpkbin-0.1.0/rpkbin/excel_extractor/types.py +126 -0
- rpkbin-0.1.0/rpkbin/job_manager/__init__.py +15 -0
- rpkbin-0.1.0/rpkbin/job_manager/cmd_job.py +184 -0
- rpkbin-0.1.0/rpkbin/job_manager/func_job.py +93 -0
- rpkbin-0.1.0/rpkbin/job_manager/job.py +340 -0
- rpkbin-0.1.0/rpkbin/job_manager/manager.py +573 -0
- rpkbin-0.1.0/rpkbin/mapbv.py +534 -0
- rpkbin-0.1.0/rpkbin/numbv/__init__.py +41 -0
- rpkbin-0.1.0/rpkbin/numbv/_backend.py +155 -0
- rpkbin-0.1.0/rpkbin/numbv/core.py +1536 -0
- rpkbin-0.1.0/rpkbin/utils/__init__.py +3 -0
- rpkbin-0.1.0/rpkbin/utils/stage_tracker.py +621 -0
- rpkbin-0.1.0/rpkbin/utils/text_diff.py +686 -0
- rpkbin-0.1.0/rpkbin/wave/__init__.py +15 -0
- rpkbin-0.1.0/rpkbin/wave/cli.py +42 -0
- rpkbin-0.1.0/rpkbin/wave/hook.py +370 -0
- rpkbin-0.1.0/rpkbin/wave/job.py +575 -0
- rpkbin-0.1.0/rpkbin/wave/runner.py +796 -0
- rpkbin-0.1.0/rpkbin/wave/session.py +570 -0
- rpkbin-0.1.0/rpkbin/wave/tui/__init__.py +5 -0
- rpkbin-0.1.0/rpkbin/wave/tui/app.py +1123 -0
- rpkbin-0.1.0/rpkbin.egg-info/PKG-INFO +114 -0
- rpkbin-0.1.0/rpkbin.egg-info/SOURCES.txt +43 -0
- rpkbin-0.1.0/rpkbin.egg-info/dependency_links.txt +1 -0
- rpkbin-0.1.0/rpkbin.egg-info/entry_points.txt +2 -0
- rpkbin-0.1.0/rpkbin.egg-info/requires.txt +23 -0
- rpkbin-0.1.0/rpkbin.egg-info/top_level.txt +1 -0
- rpkbin-0.1.0/setup.cfg +4 -0
rpkbin-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 redpigkiller
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
rpkbin-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rpkbin
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Personal collection of small scripts and helpers for work.
|
|
5
|
+
Author-email: redpigkiller <hongrunlu511@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/redpigkiller/rpkbin
|
|
8
|
+
Project-URL: Repository, https://github.com/redpigkiller/rpkbin
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Requires-Python: >=3.8
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
License-File: LICENSE
|
|
16
|
+
Requires-Dist: click>=8.3.1
|
|
17
|
+
Provides-Extra: cfg
|
|
18
|
+
Requires-Dist: networkx>=3.6.1; extra == "cfg"
|
|
19
|
+
Provides-Extra: excel
|
|
20
|
+
Requires-Dist: openpyxl>=3.1.5; extra == "excel"
|
|
21
|
+
Requires-Dist: xlrd>=2.0.2; extra == "excel"
|
|
22
|
+
Requires-Dist: rapidfuzz>=3.14.3; extra == "excel"
|
|
23
|
+
Provides-Extra: wave
|
|
24
|
+
Requires-Dist: textual>=8.0.2; extra == "wave"
|
|
25
|
+
Requires-Dist: prompt_toolkit>=3.0.52; extra == "wave"
|
|
26
|
+
Requires-Dist: rich>=14.2.0; extra == "wave"
|
|
27
|
+
Provides-Extra: all
|
|
28
|
+
Requires-Dist: networkx>=3.6.1; extra == "all"
|
|
29
|
+
Requires-Dist: openpyxl>=3.1.5; extra == "all"
|
|
30
|
+
Requires-Dist: xlrd>=2.0.2; extra == "all"
|
|
31
|
+
Requires-Dist: rapidfuzz>=3.14.3; extra == "all"
|
|
32
|
+
Requires-Dist: textual>=8.0.2; extra == "all"
|
|
33
|
+
Requires-Dist: prompt_toolkit>=3.0.52; extra == "all"
|
|
34
|
+
Requires-Dist: rich>=14.2.0; extra == "all"
|
|
35
|
+
Dynamic: license-file
|
|
36
|
+
|
|
37
|
+
# rpkbin — Core IC Design & Verification Utilities
|
|
38
|
+
|
|
39
|
+
[](README.md)
|
|
40
|
+
[](README_zh.md)
|
|
41
|
+
|
|
42
|
+
`rpkbin` is a comprehensive toolkit providing essential data types and utilities for hardware design and verification flows.
|
|
43
|
+
|
|
44
|
+
## Core Features
|
|
45
|
+
|
|
46
|
+
### 1. MapBV — BitVector with Bidirectional Mapping
|
|
47
|
+
MapBV allows you to define a hierarchy of bit-fields that stay synchronized automatically.
|
|
48
|
+
- **Hierarchical Slicing**: Define a 32-bit register and slice it into named fields.
|
|
49
|
+
- **Bidirectional Linking**: Use `concat` to build a new view from existing variables; updating the view updates the sources.
|
|
50
|
+
- **Symbolic Evaluation**: Use `.eval()` to test "what-if" scenarios without changing actual values.
|
|
51
|
+
|
|
52
|
+
[Learn more in MapBV Documentation](docs/mapbv/mapbv.md)
|
|
53
|
+
|
|
54
|
+
### 2. NumBV — Bit-True Fixed-Point Arithmetic
|
|
55
|
+
Bit-exact fixed-point simulation for DSP pipeline verification. Pure NumPy, no external dependency.
|
|
56
|
+
- **Two-layer API**: Operator layer (`+`, `*`) for convenience; function layer (`nbv.add()`, `nbv.mul()`) for explicit pipeline staging.
|
|
57
|
+
- **Five Rounding Modes**: `trunc`, `round`, `round_half_even` (convergent, Xilinx DSP48), `ceil`, `round_to_zero`.
|
|
58
|
+
- **Unified Operations**: One `NumBV` class handles both scalar and array computations. Backed by NumPy by default, with an optional drop-in JAX backend for transparent XLA hardware acceleration.
|
|
59
|
+
|
|
60
|
+
[Learn more in NumBV Documentation](docs/numbv/numbv.md)
|
|
61
|
+
|
|
62
|
+
### 3. Excel Extractor — Template-Based Extraction
|
|
63
|
+
Intelligently extract data from complex spreadsheets.
|
|
64
|
+
- **Layout Description**: Define the "shape" of data instead of hardcoded coordinates.
|
|
65
|
+
- **Fuzzy Matching**: Matches headers even with slight spelling variations.
|
|
66
|
+
- **Merged Cell Support**: Correctly resolves values spanning across merged rows/cols.
|
|
67
|
+
|
|
68
|
+
[Learn more in Excel Extractor Documentation](docs/excel_extractor/excel_extractor.md)
|
|
69
|
+
|
|
70
|
+
### 4. Job Manager
|
|
71
|
+
A practical, cross-platform job manager for running shell commands and Python callables safely in parallel.
|
|
72
|
+
|
|
73
|
+
- **One API for Common Workloads**: Run local functions (`FuncJob`) and CLI tasks (`CmdJob`) with the same manager.
|
|
74
|
+
- **Resource-Aware Scheduling**: Limit concurrency by global resources such as GPU count or license tokens.
|
|
75
|
+
- **Operationally Friendly**: Built-in cancellation, retries, live logs, and callbacks for automation pipelines.
|
|
76
|
+
|
|
77
|
+
[Job Manager Documentation](docs/job_manager/job_manager.md)
|
|
78
|
+
|
|
79
|
+
### 5. Wave — Batch Workflow Orchestration with Live TUI
|
|
80
|
+
A workflow layer built on top of Job Manager for declaring and observing long-running batch flows.
|
|
81
|
+
|
|
82
|
+
- **Wave File Pattern**: Declare jobs, parsers, and hooks in a plain Python file. Run it with `rpk-wave run`.
|
|
83
|
+
- **Live TUI**: A full-screen Textual interface with a job dashboard, per-job log/data/event panels, and a built-in command bar.
|
|
84
|
+
- **Headless REPL**: For CI or terminal sessions where a TUI isn't wanted — full inspection and control commands via stdin.
|
|
85
|
+
- **Hooks & Parsers**: React to log output, structured parsed data, elapsed time, or lifecycle events automatically.
|
|
86
|
+
- **Job Events with Source Tracking**: `job.emit()` records named events; `source="system"` marks internal errors (parser/hook failures) for easy filtering in the TUI.
|
|
87
|
+
- **Operational Control**: Pause/resume dispatch, gracefully stop jobs, force-cancel jobs, or target active jobs by tag from either the TUI command bar or headless REPL.
|
|
88
|
+
- **Fast Job Inspection**: Open `show`, `logs`, `data`, or `events <job>` directly in JOB DETAIL, then switch between jobs with `[` / `]` or between running jobs with `{` / `}`.
|
|
89
|
+
- **Intentional Cancellation Semantics**: User-cancelled jobs are reported as `cancelled` without turning the whole Wave run into a failed exit; real job failures and session timeouts still return a non-zero exit code.
|
|
90
|
+
|
|
91
|
+
[Wave Documentation](docs/wave/wave.md)
|
|
92
|
+
|
|
93
|
+
## Installation
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
# Core only (zero dependencies)
|
|
97
|
+
pip install -e .
|
|
98
|
+
|
|
99
|
+
# Install specific features
|
|
100
|
+
pip install -e .[wave] # Installs textual, prompt_toolkit, rich
|
|
101
|
+
pip install -e .[excel] # Installs openpyxl, xlrd, rapidfuzz
|
|
102
|
+
pip install -e .[cfg] # Installs networkx
|
|
103
|
+
|
|
104
|
+
# Install everything
|
|
105
|
+
pip install -e .[all]
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Testing
|
|
109
|
+
|
|
110
|
+
Run tests using `pytest` from the root directory:
|
|
111
|
+
```bash
|
|
112
|
+
pytest tests/ -v
|
|
113
|
+
```
|
|
114
|
+
*(All tests require only `numpy` — no optional dependencies needed.)*
|
rpkbin-0.1.0/README.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# rpkbin — Core IC Design & Verification Utilities
|
|
2
|
+
|
|
3
|
+
[](README.md)
|
|
4
|
+
[](README_zh.md)
|
|
5
|
+
|
|
6
|
+
`rpkbin` is a comprehensive toolkit providing essential data types and utilities for hardware design and verification flows.
|
|
7
|
+
|
|
8
|
+
## Core Features
|
|
9
|
+
|
|
10
|
+
### 1. MapBV — BitVector with Bidirectional Mapping
|
|
11
|
+
MapBV allows you to define a hierarchy of bit-fields that stay synchronized automatically.
|
|
12
|
+
- **Hierarchical Slicing**: Define a 32-bit register and slice it into named fields.
|
|
13
|
+
- **Bidirectional Linking**: Use `concat` to build a new view from existing variables; updating the view updates the sources.
|
|
14
|
+
- **Symbolic Evaluation**: Use `.eval()` to test "what-if" scenarios without changing actual values.
|
|
15
|
+
|
|
16
|
+
[Learn more in MapBV Documentation](docs/mapbv/mapbv.md)
|
|
17
|
+
|
|
18
|
+
### 2. NumBV — Bit-True Fixed-Point Arithmetic
|
|
19
|
+
Bit-exact fixed-point simulation for DSP pipeline verification. Pure NumPy, no external dependency.
|
|
20
|
+
- **Two-layer API**: Operator layer (`+`, `*`) for convenience; function layer (`nbv.add()`, `nbv.mul()`) for explicit pipeline staging.
|
|
21
|
+
- **Five Rounding Modes**: `trunc`, `round`, `round_half_even` (convergent, Xilinx DSP48), `ceil`, `round_to_zero`.
|
|
22
|
+
- **Unified Operations**: One `NumBV` class handles both scalar and array computations. Backed by NumPy by default, with an optional drop-in JAX backend for transparent XLA hardware acceleration.
|
|
23
|
+
|
|
24
|
+
[Learn more in NumBV Documentation](docs/numbv/numbv.md)
|
|
25
|
+
|
|
26
|
+
### 3. Excel Extractor — Template-Based Extraction
|
|
27
|
+
Intelligently extract data from complex spreadsheets.
|
|
28
|
+
- **Layout Description**: Define the "shape" of data instead of hardcoded coordinates.
|
|
29
|
+
- **Fuzzy Matching**: Matches headers even with slight spelling variations.
|
|
30
|
+
- **Merged Cell Support**: Correctly resolves values spanning across merged rows/cols.
|
|
31
|
+
|
|
32
|
+
[Learn more in Excel Extractor Documentation](docs/excel_extractor/excel_extractor.md)
|
|
33
|
+
|
|
34
|
+
### 4. Job Manager
|
|
35
|
+
A practical, cross-platform job manager for running shell commands and Python callables safely in parallel.
|
|
36
|
+
|
|
37
|
+
- **One API for Common Workloads**: Run local functions (`FuncJob`) and CLI tasks (`CmdJob`) with the same manager.
|
|
38
|
+
- **Resource-Aware Scheduling**: Limit concurrency by global resources such as GPU count or license tokens.
|
|
39
|
+
- **Operationally Friendly**: Built-in cancellation, retries, live logs, and callbacks for automation pipelines.
|
|
40
|
+
|
|
41
|
+
[Job Manager Documentation](docs/job_manager/job_manager.md)
|
|
42
|
+
|
|
43
|
+
### 5. Wave — Batch Workflow Orchestration with Live TUI
|
|
44
|
+
A workflow layer built on top of Job Manager for declaring and observing long-running batch flows.
|
|
45
|
+
|
|
46
|
+
- **Wave File Pattern**: Declare jobs, parsers, and hooks in a plain Python file. Run it with `rpk-wave run`.
|
|
47
|
+
- **Live TUI**: A full-screen Textual interface with a job dashboard, per-job log/data/event panels, and a built-in command bar.
|
|
48
|
+
- **Headless REPL**: For CI or terminal sessions where a TUI isn't wanted — full inspection and control commands via stdin.
|
|
49
|
+
- **Hooks & Parsers**: React to log output, structured parsed data, elapsed time, or lifecycle events automatically.
|
|
50
|
+
- **Job Events with Source Tracking**: `job.emit()` records named events; `source="system"` marks internal errors (parser/hook failures) for easy filtering in the TUI.
|
|
51
|
+
- **Operational Control**: Pause/resume dispatch, gracefully stop jobs, force-cancel jobs, or target active jobs by tag from either the TUI command bar or headless REPL.
|
|
52
|
+
- **Fast Job Inspection**: Open `show`, `logs`, `data`, or `events <job>` directly in JOB DETAIL, then switch between jobs with `[` / `]` or between running jobs with `{` / `}`.
|
|
53
|
+
- **Intentional Cancellation Semantics**: User-cancelled jobs are reported as `cancelled` without turning the whole Wave run into a failed exit; real job failures and session timeouts still return a non-zero exit code.
|
|
54
|
+
|
|
55
|
+
[Wave Documentation](docs/wave/wave.md)
|
|
56
|
+
|
|
57
|
+
## Installation
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
# Core only (zero dependencies)
|
|
61
|
+
pip install -e .
|
|
62
|
+
|
|
63
|
+
# Install specific features
|
|
64
|
+
pip install -e .[wave] # Installs textual, prompt_toolkit, rich
|
|
65
|
+
pip install -e .[excel] # Installs openpyxl, xlrd, rapidfuzz
|
|
66
|
+
pip install -e .[cfg] # Installs networkx
|
|
67
|
+
|
|
68
|
+
# Install everything
|
|
69
|
+
pip install -e .[all]
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Testing
|
|
73
|
+
|
|
74
|
+
Run tests using `pytest` from the root directory:
|
|
75
|
+
```bash
|
|
76
|
+
pytest tests/ -v
|
|
77
|
+
```
|
|
78
|
+
*(All tests require only `numpy` — no optional dependencies needed.)*
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "rpkbin"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Personal collection of small scripts and helpers for work."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
authors = [
|
|
11
|
+
{ name = "redpigkiller", email = "hongrunlu511@gmail.com" }
|
|
12
|
+
]
|
|
13
|
+
license = { text = "MIT" }
|
|
14
|
+
requires-python = ">=3.8"
|
|
15
|
+
dependencies = [
|
|
16
|
+
"click>=8.3.1",
|
|
17
|
+
]
|
|
18
|
+
classifiers = [
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"License :: OSI Approved :: MIT License",
|
|
21
|
+
"Operating System :: OS Independent",
|
|
22
|
+
"Development Status :: 3 - Alpha",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[project.optional-dependencies]
|
|
26
|
+
cfg = ["networkx>=3.6.1"]
|
|
27
|
+
excel = ["openpyxl>=3.1.5", "xlrd>=2.0.2", "rapidfuzz>=3.14.3"]
|
|
28
|
+
wave = ["textual>=8.0.2", "prompt_toolkit>=3.0.52", "rich>=14.2.0"]
|
|
29
|
+
all = [
|
|
30
|
+
"networkx>=3.6.1",
|
|
31
|
+
"openpyxl>=3.1.5",
|
|
32
|
+
"xlrd>=2.0.2",
|
|
33
|
+
"rapidfuzz>=3.14.3",
|
|
34
|
+
"textual>=8.0.2",
|
|
35
|
+
"prompt_toolkit>=3.0.52",
|
|
36
|
+
"rich>=14.2.0"
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
[project.urls]
|
|
40
|
+
Homepage = "https://github.com/redpigkiller/rpkbin"
|
|
41
|
+
Repository = "https://github.com/redpigkiller/rpkbin"
|
|
42
|
+
|
|
43
|
+
[project.scripts]
|
|
44
|
+
rpk-wave = "rpkbin.wave.cli:main"
|
|
45
|
+
|
|
46
|
+
[tool.setuptools.packages.find]
|
|
47
|
+
include = ["rpkbin*"]
|
|
48
|
+
|
|
49
|
+
[tool.setuptools]
|
|
50
|
+
include-package-data = true
|
|
51
|
+
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""rpkbin.cfg — Control Flow Graph IR, analysis, and domain-specific tools.
|
|
2
|
+
|
|
3
|
+
Public API
|
|
4
|
+
----------
|
|
5
|
+
|
|
6
|
+
Core IR::
|
|
7
|
+
|
|
8
|
+
from rpkbin.cfg import CFG, BasicBlock
|
|
9
|
+
from rpkbin.cfg import Assignment, CallRef, OtherInsn, Insn
|
|
10
|
+
from rpkbin.cfg import NaturalLoop, Program
|
|
11
|
+
|
|
12
|
+
Merging::
|
|
13
|
+
|
|
14
|
+
from rpkbin.cfg import merge_cfgs
|
|
15
|
+
from rpkbin.cfg import (
|
|
16
|
+
CFGMergeError, DuplicateLabelError, InsnConflictError,
|
|
17
|
+
EdgeConflictError, MetaConflictError,
|
|
18
|
+
)
|
|
19
|
+
merged = merge_cfgs(flow1, flow2, flow3)
|
|
20
|
+
merged.set_entry("IDLE")
|
|
21
|
+
|
|
22
|
+
Interprocedural analysis::
|
|
23
|
+
|
|
24
|
+
from rpkbin.cfg.analysis import (
|
|
25
|
+
build_call_graph,
|
|
26
|
+
check_call_depth,
|
|
27
|
+
interprocedural_liveness,
|
|
28
|
+
FunctionSummary,
|
|
29
|
+
LivenessResult,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
FSM tools::
|
|
33
|
+
|
|
34
|
+
from rpkbin.cfg import fsm
|
|
35
|
+
fsm.find_dead_states(program)
|
|
36
|
+
fsm.find_sink_sccs(program)
|
|
37
|
+
fsm.check_conditions_complete(program)
|
|
38
|
+
layout = fsm.linearize(program) # → FSMLayout
|
|
39
|
+
|
|
40
|
+
MCU tools::
|
|
41
|
+
|
|
42
|
+
from rpkbin.cfg import mcu
|
|
43
|
+
mcu.find_dead_loops(program, exit_block="HALT")
|
|
44
|
+
mcu.dead_code_elimination(cfg)
|
|
45
|
+
layout = mcu.linearize(program) # → MCULayout
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
from .block import Assignment, CallRef, Insn, OtherInsn, BasicBlock
|
|
49
|
+
from .cfg import (
|
|
50
|
+
CFG,
|
|
51
|
+
NaturalLoop,
|
|
52
|
+
CFGMergeError,
|
|
53
|
+
DuplicateLabelError,
|
|
54
|
+
InsnConflictError,
|
|
55
|
+
EdgeConflictError,
|
|
56
|
+
MetaConflictError,
|
|
57
|
+
merge_cfgs,
|
|
58
|
+
)
|
|
59
|
+
from .program import Program
|
|
60
|
+
from . import fsm
|
|
61
|
+
from . import mcu
|
|
62
|
+
|
|
63
|
+
__all__ = [
|
|
64
|
+
# IR types
|
|
65
|
+
"BasicBlock",
|
|
66
|
+
"Assignment",
|
|
67
|
+
"CallRef",
|
|
68
|
+
"OtherInsn",
|
|
69
|
+
"Insn",
|
|
70
|
+
# CFG
|
|
71
|
+
"CFG",
|
|
72
|
+
"NaturalLoop",
|
|
73
|
+
# Merge
|
|
74
|
+
"merge_cfgs",
|
|
75
|
+
"CFGMergeError",
|
|
76
|
+
"DuplicateLabelError",
|
|
77
|
+
"InsnConflictError",
|
|
78
|
+
"EdgeConflictError",
|
|
79
|
+
"MetaConflictError",
|
|
80
|
+
# Program container
|
|
81
|
+
"Program",
|
|
82
|
+
# Domain-specific sub-modules
|
|
83
|
+
"fsm",
|
|
84
|
+
"mcu",
|
|
85
|
+
]
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
"""Interprocedural analysis utilities for CFG Programs.
|
|
2
|
+
|
|
3
|
+
This module provides analysis functions that operate across multiple CFGs
|
|
4
|
+
(i.e. across function boundaries) using the :class:`~rpkbin.cfg.program.Program`
|
|
5
|
+
container.
|
|
6
|
+
|
|
7
|
+
Key features
|
|
8
|
+
------------
|
|
9
|
+
* :func:`build_call_graph` — scans all ``CallRef`` instructions to construct
|
|
10
|
+
the program's call graph automatically; no manual registration needed.
|
|
11
|
+
* :func:`check_call_depth` — verifies the call graph is a DAG and its depth
|
|
12
|
+
does not exceed a hardware limit.
|
|
13
|
+
* :func:`interprocedural_liveness` — computes live-in / live-out sets for
|
|
14
|
+
every block in every function using a bottom-up summary-based approach.
|
|
15
|
+
|
|
16
|
+
def/use derivation
|
|
17
|
+
------------------
|
|
18
|
+
``Assignment(lhs, rhs)``
|
|
19
|
+
def = {lhs}, use = set(rhs)
|
|
20
|
+
|
|
21
|
+
``CallRef(callee)``
|
|
22
|
+
def = callee's FunctionSummary.defs
|
|
23
|
+
use = callee's FunctionSummary.uses
|
|
24
|
+
|
|
25
|
+
``OtherInsn(defs, uses)``
|
|
26
|
+
def = defs, use = uses
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
from dataclasses import dataclass, field
|
|
32
|
+
from typing import FrozenSet
|
|
33
|
+
|
|
34
|
+
import networkx as nx
|
|
35
|
+
|
|
36
|
+
from .block import Assignment, CallRef, Insn, OtherInsn
|
|
37
|
+
from .cfg import CFG
|
|
38
|
+
from .program import Program
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# ---------------------------------------------------------------------------
|
|
42
|
+
# def/use helper
|
|
43
|
+
# ---------------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
def _block_def_use(
|
|
46
|
+
insn: Insn,
|
|
47
|
+
summaries: dict[str, "FunctionSummary"],
|
|
48
|
+
) -> tuple[set[str], set[str]]:
|
|
49
|
+
"""Return ``(defs, uses)`` for a single instruction.
|
|
50
|
+
|
|
51
|
+
For :class:`CallRef`, the summary of the callee is used if available;
|
|
52
|
+
otherwise both sets are empty (safe but imprecise — run bottom-up to avoid
|
|
53
|
+
this case).
|
|
54
|
+
"""
|
|
55
|
+
if isinstance(insn, Assignment):
|
|
56
|
+
return {insn.lhs}, set(insn.rhs)
|
|
57
|
+
if isinstance(insn, CallRef):
|
|
58
|
+
s = summaries.get(insn.callee)
|
|
59
|
+
if s is not None:
|
|
60
|
+
return set(s.defs), set(s.uses)
|
|
61
|
+
return set(), set() # callee not analysed yet (cycle guard)
|
|
62
|
+
if isinstance(insn, OtherInsn):
|
|
63
|
+
return set(insn.defs), set(insn.uses)
|
|
64
|
+
return set(), set() # unreachable with the current Insn union
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# ---------------------------------------------------------------------------
|
|
68
|
+
# Data structures
|
|
69
|
+
# ---------------------------------------------------------------------------
|
|
70
|
+
|
|
71
|
+
@dataclass
|
|
72
|
+
class FunctionSummary:
|
|
73
|
+
"""Liveness summary exported from a single function.
|
|
74
|
+
|
|
75
|
+
Attributes:
|
|
76
|
+
defs: Variables the function *may* write on any execution path.
|
|
77
|
+
uses: Variables the function reads before (possibly) writing them —
|
|
78
|
+
i.e. the live-in set at the function's entry block.
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
defs: set[str] = field(default_factory=set)
|
|
82
|
+
uses: set[str] = field(default_factory=set)
|
|
83
|
+
|
|
84
|
+
def __repr__(self) -> str:
|
|
85
|
+
return f"FunctionSummary(defs={sorted(self.defs)}, uses={sorted(self.uses)})"
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@dataclass
|
|
89
|
+
class LivenessResult:
|
|
90
|
+
"""Per-block liveness sets for a single function.
|
|
91
|
+
|
|
92
|
+
Attributes:
|
|
93
|
+
live_in: Mapping from block id to the set of variables live at the
|
|
94
|
+
*entry* of that block.
|
|
95
|
+
live_out: Mapping from block id to the set of variables live at the
|
|
96
|
+
*exit* of that block.
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
live_in: dict[str, FrozenSet[str]] = field(default_factory=dict)
|
|
100
|
+
live_out: dict[str, FrozenSet[str]] = field(default_factory=dict)
|
|
101
|
+
|
|
102
|
+
def is_live_at_entry(self, block_id: str, var: str) -> bool:
|
|
103
|
+
"""Return ``True`` if *var* is live at the entry of *block_id*."""
|
|
104
|
+
return var in self.live_in.get(block_id, frozenset())
|
|
105
|
+
|
|
106
|
+
def is_live_at_exit(self, block_id: str, var: str) -> bool:
|
|
107
|
+
"""Return ``True`` if *var* is live at the exit of *block_id*."""
|
|
108
|
+
return var in self.live_out.get(block_id, frozenset())
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
# ---------------------------------------------------------------------------
|
|
112
|
+
# Call graph
|
|
113
|
+
# ---------------------------------------------------------------------------
|
|
114
|
+
|
|
115
|
+
def build_call_graph(program: Program) -> nx.DiGraph:
|
|
116
|
+
"""Build a directed call graph by scanning ``CallRef`` instructions.
|
|
117
|
+
|
|
118
|
+
Each node in the returned graph is a function name (a key in
|
|
119
|
+
``program.cfgs``). An edge ``(caller, callee)`` is added for every
|
|
120
|
+
:class:`~rpkbin.cfg.block.CallRef` found in *caller*'s blocks.
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
A :class:`networkx.DiGraph` with one node per function.
|
|
124
|
+
|
|
125
|
+
Raises:
|
|
126
|
+
KeyError: If a ``CallRef.callee`` does not exist in ``program.cfgs``.
|
|
127
|
+
"""
|
|
128
|
+
cg: nx.DiGraph = nx.DiGraph()
|
|
129
|
+
cg.add_nodes_from(program.cfgs.keys())
|
|
130
|
+
|
|
131
|
+
for fn_name, cfg in program.cfgs.items():
|
|
132
|
+
for bb in cfg.blocks:
|
|
133
|
+
for insn in bb.insns:
|
|
134
|
+
if isinstance(insn, CallRef):
|
|
135
|
+
if insn.callee not in program.cfgs:
|
|
136
|
+
raise KeyError(
|
|
137
|
+
f"CallRef to unknown function {insn.callee!r} "
|
|
138
|
+
f"in block {bb.id!r} of function {fn_name!r}."
|
|
139
|
+
)
|
|
140
|
+
cg.add_edge(fn_name, insn.callee)
|
|
141
|
+
|
|
142
|
+
return cg
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def check_call_depth(program: Program, max_depth: int | None = None) -> int:
|
|
146
|
+
"""Verify the call graph is acyclic and within the depth limit.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
program: The program to analyse.
|
|
150
|
+
max_depth: Maximum allowed call-stack depth. If ``None``, only the
|
|
151
|
+
actual depth is returned without raising.
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
The actual maximum call depth (longest path length in the call graph).
|
|
155
|
+
|
|
156
|
+
Raises:
|
|
157
|
+
ValueError: If the call graph contains a cycle (recursive call).
|
|
158
|
+
ValueError: If the actual depth exceeds *max_depth*.
|
|
159
|
+
"""
|
|
160
|
+
cg = build_call_graph(program)
|
|
161
|
+
try:
|
|
162
|
+
depth: int = nx.dag_longest_path_length(cg)
|
|
163
|
+
except nx.NetworkXUnfeasible:
|
|
164
|
+
raise ValueError(
|
|
165
|
+
"Call graph contains a cycle — recursive calls are not allowed."
|
|
166
|
+
)
|
|
167
|
+
if max_depth is not None and depth > max_depth:
|
|
168
|
+
raise ValueError(
|
|
169
|
+
f"Call depth {depth} exceeds the allowed maximum of {max_depth}."
|
|
170
|
+
)
|
|
171
|
+
return depth
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
# ---------------------------------------------------------------------------
|
|
175
|
+
# Intraprocedural liveness (single CFG)
|
|
176
|
+
# ---------------------------------------------------------------------------
|
|
177
|
+
|
|
178
|
+
def _intraprocedural_liveness(
|
|
179
|
+
cfg: CFG,
|
|
180
|
+
summaries: dict[str, FunctionSummary],
|
|
181
|
+
) -> tuple[LivenessResult, FunctionSummary]:
|
|
182
|
+
"""Run liveness analysis on a single CFG using pre-computed summaries.
|
|
183
|
+
|
|
184
|
+
Returns the per-block :class:`LivenessResult` and the derived
|
|
185
|
+
:class:`FunctionSummary` for this function.
|
|
186
|
+
"""
|
|
187
|
+
g = cfg._graph
|
|
188
|
+
|
|
189
|
+
# Compute per-block def/use sets (in sequential instruction order)
|
|
190
|
+
block_def: dict[str, set[str]] = {}
|
|
191
|
+
block_use: dict[str, set[str]] = {}
|
|
192
|
+
for bb in cfg.blocks:
|
|
193
|
+
blk_def: set[str] = set()
|
|
194
|
+
blk_use: set[str] = set()
|
|
195
|
+
for insn in bb.insns:
|
|
196
|
+
d, u = _block_def_use(insn, summaries)
|
|
197
|
+
# A variable used before it is defined in this block is live-in
|
|
198
|
+
blk_use |= u - blk_def
|
|
199
|
+
blk_def |= d
|
|
200
|
+
block_def[bb.id] = blk_def
|
|
201
|
+
block_use[bb.id] = blk_use
|
|
202
|
+
|
|
203
|
+
# Initialize live sets
|
|
204
|
+
live_in: dict[str, FrozenSet[str]] = {bb.id: frozenset() for bb in cfg.blocks}
|
|
205
|
+
live_out: dict[str, FrozenSet[str]] = {bb.id: frozenset() for bb in cfg.blocks}
|
|
206
|
+
|
|
207
|
+
# Iterative worklist (backward)
|
|
208
|
+
worklist: set[str] = {bb.id for bb in cfg.blocks}
|
|
209
|
+
while worklist:
|
|
210
|
+
bid = worklist.pop()
|
|
211
|
+
|
|
212
|
+
new_out: set[str] = set()
|
|
213
|
+
for succ in g.successors(bid):
|
|
214
|
+
new_out |= live_in[succ]
|
|
215
|
+
new_out_f = frozenset(new_out)
|
|
216
|
+
|
|
217
|
+
new_in = frozenset(block_use[bid] | (new_out - block_def[bid]))
|
|
218
|
+
|
|
219
|
+
if new_out_f != live_out[bid] or new_in != live_in[bid]:
|
|
220
|
+
live_out[bid] = new_out_f
|
|
221
|
+
live_in[bid] = new_in
|
|
222
|
+
for pred in g.predecessors(bid):
|
|
223
|
+
worklist.add(pred)
|
|
224
|
+
|
|
225
|
+
result = LivenessResult(live_in=live_in, live_out=live_out)
|
|
226
|
+
|
|
227
|
+
# Build summary: defs = union of all block defs; uses = live_in at entry
|
|
228
|
+
all_defs: set[str] = set()
|
|
229
|
+
for d in block_def.values():
|
|
230
|
+
all_defs |= d
|
|
231
|
+
entry_uses: set[str] = set(live_in[cfg._entry]) if cfg._entry else set()
|
|
232
|
+
summary = FunctionSummary(defs=all_defs, uses=entry_uses)
|
|
233
|
+
|
|
234
|
+
return result, summary
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
# ---------------------------------------------------------------------------
|
|
238
|
+
# Interprocedural liveness
|
|
239
|
+
# ---------------------------------------------------------------------------
|
|
240
|
+
|
|
241
|
+
def interprocedural_liveness(
|
|
242
|
+
program: Program,
|
|
243
|
+
) -> dict[str, LivenessResult]:
|
|
244
|
+
"""Bottom-up interprocedural liveness analysis.
|
|
245
|
+
|
|
246
|
+
Analyses each function in reverse topological order of the call graph
|
|
247
|
+
(leaf functions first) so that every callee's :class:`FunctionSummary`
|
|
248
|
+
is available before its callers are analysed.
|
|
249
|
+
|
|
250
|
+
The call graph must be a DAG (no recursion). Use :func:`check_call_depth`
|
|
251
|
+
to verify this beforehand if needed.
|
|
252
|
+
|
|
253
|
+
Args:
|
|
254
|
+
program: The program to analyse.
|
|
255
|
+
|
|
256
|
+
Returns:
|
|
257
|
+
A mapping from function name to its :class:`LivenessResult`.
|
|
258
|
+
|
|
259
|
+
Raises:
|
|
260
|
+
ValueError: If the call graph contains a cycle.
|
|
261
|
+
"""
|
|
262
|
+
cg = build_call_graph(program)
|
|
263
|
+
|
|
264
|
+
# Topological order: process leaves (callees) before callers
|
|
265
|
+
try:
|
|
266
|
+
topo_order: list[str] = list(nx.topological_sort(cg))
|
|
267
|
+
except nx.NetworkXUnfeasible:
|
|
268
|
+
raise ValueError(
|
|
269
|
+
"Call graph contains a cycle — recursive calls are not allowed."
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
summaries: dict[str, FunctionSummary] = {}
|
|
273
|
+
results: dict[str, LivenessResult] = {}
|
|
274
|
+
|
|
275
|
+
for fn_name in reversed(topo_order): # reversed → leaves first
|
|
276
|
+
cfg = program.cfgs[fn_name]
|
|
277
|
+
result, summary = _intraprocedural_liveness(cfg, summaries)
|
|
278
|
+
results[fn_name] = result
|
|
279
|
+
summaries[fn_name] = summary
|
|
280
|
+
|
|
281
|
+
return results
|