Skip to content

Commit caea062

Browse files
committed
Introduce textual as a UI layer for the solve command, allowing better
statistics and understanding of what is going on
1 parent 3535c06 commit caea062

13 files changed

+1695
-235
lines changed

vxsort/smallsort/codegen/pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ dependencies = [
1010
"pytest-cov>=7.0.0",
1111
"tabulate>=0.9.0",
1212
"rich>=14.3.1",
13+
"textual>=0.87.1",
1314
"zstandard>=0.25.0",
1415
"z3-solver>=4.15.8.0",
1516
"psutil>=5.9.0",
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
# Reproducible audit for UI transition cleanup.
5+
# 1) Collect coverage from targeted UI tests.
6+
# 2) Append coverage from a real textual solve run.
7+
# 3) Report module-level coverage for UI-related modules.
8+
# 4) Run vulture over UI modules for dead-code hints.
9+
10+
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
11+
cd "${ROOT_DIR}"
12+
13+
echo "[ui-audit] Erasing old coverage data"
14+
uv run coverage erase
15+
16+
echo "[ui-audit] Running targeted UI tests with coverage"
17+
uv run pytest \
18+
tests/test_textual_progress_ui.py \
19+
tests/test_wave_engine.py \
20+
tests/test_instruction_stats.py \
21+
--cov=textual_progress_ui \
22+
--cov=wave_engine \
23+
--cov=instruction_stats \
24+
--cov=success_progress \
25+
--cov=bitonic_compiler \
26+
--cov-report=term-missing \
27+
-q
28+
29+
echo "[ui-audit] Running minimal textual solve and appending coverage"
30+
uv run coverage run --append \
31+
--source=bitonic_compiler,success_progress,wave_engine,textual_progress_ui,instruction_stats \
32+
src/bitonic_compiler.py \
33+
--num-vecs 2 \
34+
--vector-machine AVX2 \
35+
--datatype i64 \
36+
--gadget-depth 1 \
37+
--depth-limit 1 \
38+
--max-waves 1 \
39+
--wave-attempts 100 \
40+
--wave-outputs 20 \
41+
--runtime-ui textual
42+
43+
echo "[ui-audit] Combined coverage report"
44+
uv run coverage report -m
45+
46+
echo "[ui-audit] Dead-code hints (vulture)"
47+
set +e
48+
uv run vulture src/textual_progress_ui.py src/wave_engine.py src/success_progress.py src/bitonic_compiler.py src/instruction_stats.py
49+
VULTURE_EXIT=$?
50+
set -e
51+
if [[ ${VULTURE_EXIT} -ne 0 ]]; then
52+
echo "[ui-audit] vulture returned ${VULTURE_EXIT} (informational findings above)"
53+
fi

vxsort/smallsort/codegen/src/bitonic_compiler.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -843,6 +843,13 @@ def main():
843843
metavar="N",
844844
help="Budget per stage per wave in distinct outputs (default: 100).",
845845
)
846+
parser.add_argument(
847+
"--runtime-ui",
848+
type=str,
849+
default="auto",
850+
choices=["auto", "textual", "none"],
851+
help="Wave runtime UI mode. 'auto' uses Textual only on an interactive TTY (default: auto).",
852+
)
846853

847854
args = parser.parse_args()
848855

@@ -952,6 +959,7 @@ def main():
952959
llvm_mca_path=args.llvm_mca_path,
953960
max_waves=args.max_waves,
954961
top_k=args.top_k or 10,
962+
runtime_ui=args.runtime_ui,
955963
)
956964

957965
engine = WaveEngine(wave_config)

vxsort/smallsort/codegen/src/bitonic_verifier.py

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,7 @@ def _simulate_steps(
335335
"""
336336
N = 2 * self.elements_per_vector
337337
num_stages = len(steps)
338+
solver = Solver()
338339

339340
elems = [BitVec(f"e_{i}", self.lane_width) for i in range(N)]
340341

@@ -347,10 +348,18 @@ def _simulate_steps(
347348
is_last_stage = step_idx == num_stages - 1
348349

349350
new_top = self._apply_concrete_instructions(
350-
top_vec, bottom_vec, gadget.top_instructions, is_top=True
351+
top_vec,
352+
bottom_vec,
353+
gadget.top_instructions,
354+
is_top=True,
355+
solver=solver,
351356
)
352357
new_bottom = self._apply_concrete_instructions(
353-
top_vec, bottom_vec, gadget.bottom_instructions, is_top=False
358+
top_vec,
359+
bottom_vec,
360+
gadget.bottom_instructions,
361+
is_top=False,
362+
solver=solver,
354363
)
355364

356365
if natural_order and is_last_stage:
@@ -563,7 +572,12 @@ def _extract_output_elements(
563572
return sorted_elems
564573

565574
def _apply_concrete_instructions(
566-
self, top_reg, bottom_reg, instructions: list[InstructionSpec], is_top: bool
575+
self,
576+
top_reg,
577+
bottom_reg,
578+
instructions: list[InstructionSpec],
579+
is_top: bool,
580+
solver: Solver | None = None,
567581
):
568582
"""Apply concrete (non-symbolic) instructions to compute an output register.
569583
@@ -578,6 +592,8 @@ def _apply_concrete_instructions(
578592
3+ instruction chains to reference any earlier intermediate.
579593
"""
580594
current_reg = top_reg if is_top else bottom_reg
595+
if solver is None:
596+
solver = Solver()
581597
prev_output = None
582598
results: list = []
583599

@@ -598,7 +614,7 @@ def _apply_concrete_instructions(
598614
results,
599615
)
600616

601-
current_reg = self._dispatch_intrinsic(intrinsic, args)
617+
current_reg = self._dispatch_intrinsic(intrinsic, args, solver=solver)
602618
prev_output = current_reg
603619
results.append(current_reg)
604620

@@ -645,6 +661,6 @@ def _resolve_arg(
645661

646662
return value
647663

648-
def _dispatch_intrinsic(self, intrinsic, args: dict):
664+
def _dispatch_intrinsic(self, intrinsic, args: dict, solver: Solver | None = None):
649665
"""Dispatch an intrinsic call using shared signature patterns."""
650-
return _dispatch_intrinsic_by_signature(intrinsic, args)
666+
return _dispatch_intrinsic_by_signature(intrinsic, args, solver=solver)

vxsort/smallsort/codegen/src/gadget_synthesizer.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -248,23 +248,25 @@ def _accepts_solver(intrinsic) -> bool:
248248
return False
249249

250250

251-
def _dispatch_intrinsic_fallback(intrinsic, args: dict, solver: Solver):
251+
def _dispatch_intrinsic_fallback(intrinsic, args: dict, solver: Solver | None = None):
252252
"""Fallback positional dispatch that preserves incoming argument order."""
253253
arg_order = tuple(args.keys())
254-
if _accepts_solver(intrinsic):
254+
if solver is not None and _accepts_solver(intrinsic):
255255
return intrinsic(*(args[key] for key in arg_order), solver=solver)
256256
return intrinsic(*(args[key] for key in arg_order))
257257

258258

259-
def _dispatch_intrinsic_by_signature(intrinsic, args: dict, solver: Solver):
259+
def _dispatch_intrinsic_by_signature(
260+
intrinsic, args: dict, solver: Solver | None = None
261+
):
260262
"""Call an intrinsic using the first matching dispatch rule.
261263
262264
Falls back to legacy positional call order when no rule matches.
263265
"""
264266
rule = _match_dispatch_rule(frozenset(args.keys()))
265267
if rule is None:
266268
return _dispatch_intrinsic_fallback(intrinsic, args, solver=solver)
267-
if _accepts_solver(intrinsic):
269+
if solver is not None and _accepts_solver(intrinsic):
268270
return intrinsic(*(args[key] for key in rule.call_order), solver=solver)
269271
return intrinsic(*(args[key] for key in rule.call_order))
270272

vxsort/smallsort/codegen/src/instruction_stats.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,19 +32,19 @@ class InstructionAttemptStats:
3232
def normalize_instruction_stats_key(spec: InstructionSpec) -> str:
3333
"""Return a stable key for an instruction family.
3434
35-
The key is the intrinsic name, optionally suffixed with ``_imm`` for
36-
immediate-controlled instructions or ``_v`` for actual control-vector
35+
The key is the intrinsic name, optionally suffixed with ``/imm`` for
36+
immediate-controlled instructions or ``/v`` for actual control-vector
3737
instruction families.
3838
"""
3939
args = spec.args or {}
4040
arg_names = {str(name).lower() for name in args}
4141

4242
if any(any(marker in name for marker in _IMM_MARKERS) for name in arg_names):
43-
return f"{spec.intrinsic_name}_imm"
43+
return f"{spec.intrinsic_name}/imm"
4444

4545
intrinsic_name = spec.intrinsic_name.lower()
4646
if any(marker in intrinsic_name for marker in _CONTROL_VECTOR_MARKERS):
47-
return f"{spec.intrinsic_name}_v"
47+
return f"{spec.intrinsic_name}/v"
4848

4949
return spec.intrinsic_name
5050

0 commit comments

Comments
 (0)