guides Intermediate

Debugging Compiled Code

How to debug code compiled by Pyvorin Native 1.0.9: the support, explain and inspect commands, fallback reason chains, and cache forensics.

Published Apr 14, 2026

Debugging compiled code in Pyvorin Native is a local discipline. There is no remote pipeline to inspect and nothing to poll: the compiler, the diagnosis commands and the compiled artefacts all live on your machine. That changes the workflow for the better, because every question you can ask about a compilation can be answered from the machine that performed it. This page walks through the verified diagnosis surface of pyvorin-native 1.0.9 — support, explain, inspect and the cache commands — using output captured from real runs against the installed build.

The older version of this page was built around a remote job viewer and server-side error stages. None of that exists in the product. What follows is what actually runs.

Start with the support report

support is the fastest way to classify every function in a file. It compiles each function and prints one row per function with its status and a count of unsupported constructs:

python -m pyvorin support your_file.py

Against a file containing a generator, our run produced:

Support report for /tmp/gen2.py
Function                       Status               Unsupported
------------------------------------------------------------
yielder                        COMPILED_PARTIAL     1
entry                          COMPILED_PARTIAL     1

Two things to note before moving on. First, the status column uses the same four-state vocabulary as the rest of the toolchain — COMPILED_FULL, COMPILED_PARTIAL, COMPATIBILITY_EXECUTED and FAILED — so a support table reads the same way as a compile report. Second, callers inherit the taint: entry has one unsupported feature of its own only because it calls yielder. The unsupported count propagates through the call graph, which is your first hint about where the root cause lives. Add --json for machine-readable rows, or -v for the verbose form.

Reading explain output

When support flags a function, explain tells you why, per function, in the deepest view the product offers:

python -m pyvorin explain your_file.py

The same file gave us this capture (trimmed; the optimisation-timing list runs to thirty entries):

  yielder
    Status:  COMPILED_PARTIAL
    Compile: 273.878 ms
    Oracle:  needs_args
    Public claim allowed: False
    Error:   Runtime validation not performed: function requires arguments
    Unsupported features:
      - Yield ():
    Optimizations applied:
      - timing:escape_analysis=0.194ms
      - escape_analysis:vals=local
      - ...

Four fields do most of the diagnostic work. Status is the authoritative classification. Oracle tells you whether the function was executed against CPython ground truth during compilation: needs_args means the function takes arguments, so the oracle could not run it and the compile is unvalidated. Public claim allowed: False follows from that — the tooling refuses to let you cite the function as compiled until it has been verified against the interpreter. And Unsupported features names the construct; here, a yield. The Optimizations applied list is long and mostly timings, but entries like escape_analysis:vals=local are worth a glance when you are chasing a performance surprise rather than a failure.

The fix for the needs_args case is structural, not conceptual: give the file a zero-argument entrypoint that exercises the function with representative inputs, and the oracle runs. explain --json emits all of this as structured data, including fallback_reason, fallback_used, cpython_oracle_status and public_claim_allowed fields per function — the same fields the JSON compile report carries.

Fallback reason chains

For files where whole functions dropped to the compatibility path, compile --explain-fallback prints the causal chain. From a file whose hot function used a global statement:

python -m pyvorin compile your_file.py --explain-fallback
bump:
  Fallback reasons:
    - unsupported_ast_pattern (bump): unsupported AST pattern; calls a compat-mode function
entry:
  Fallback reasons:
    - unsupported_ast_pattern (entry): calls a compat-mode function

Read these bottom-up. entry fell back because it calls bump; bump fell back because of an unsupported AST pattern. Edit the root, and the leaves recover in the same run. The JSON report (via --report path.json) stores the same reason in the per-function fallback_reason field, which is what you want for tooling rather than eyeballs.

Timing tells: compile_time_ms and cache hits

Two fields in explain output answer the performance questions before you reach for a profiler. Compile: is wall time for this compilation; when it reads 0.000 ms, the disk cache served a previously compiled artefact, so that run tells you nothing about compile cost. And Optimizations applied names each pass that fired — in our capture, escape_analysis:vals=local alongside per-pass timings such as timing:loop_fusion=0.110ms. When a function compiles but runs slower than you expected, this list is the first place to look: a missing pass is a diagnostic. A loop you expected to vectorise that shows no vectorization_optimizer entry was not vectorised, and the reasons usually sit one field up in Unsupported features or in the simd_blocked_reason field of the JSON output. Cross-check with bench, which reports compile time on its own line, separate from the timed runs, so a cache-hit recompile cannot masquerade as fast compilation.

When the status words disagree

Here is a wrinkle we hit while writing this page, worth knowing before it costs you an afternoon. For the generator file above, support and explain both reported COMPILED_PARTIAL with one unsupported feature — but compile's summary line said COMPILED_FULL, annotated with the note Runtime validation not performed: function requires arguments. Same file, same build, two different status words.

The reconciliation is the validation note, not the status word. compile's line describes that native code was produced; the note admits it was never checked against CPython. Under --fail-on-fallback, that file exits 2 — the gate treats "compiled but unvalidated" with the same scepticism as a fallback. The habit that saves time: trust explain for per-function truth, and never read a status line without reading the note appended to it. That is the honest signal.

Inspecting compatibility and IR

inspect answers a coarser question — is this file ready for native compilation at all:

python -m pyvorin inspect your_file.py
Pyvorin Inspection Report: clean.py
============================================================
Functions found: 2

  square_sum                     COMPILED_FULL
  entry                          COMPILED_FULL

Summary:
  Native eligible:   2/2
  Fallback required: 0/2
  Failed:            0/2
  Risk level:        NONE

Recommendation: All functions compile natively. Ready for benchmark.

When compilation misbehaves at the machine-code level — a wrong result you cannot explain, a crash inside the native path — inspect --ir prints the generated LLVM IR, and --full widens the dump:

python -m pyvorin inspect your_file.py --ir
python -m pyvorin inspect your_file.py --ir --full

The IR begins with the module header and the runtime declarations the generated code links against, which is usually enough to confirm whether a call went through a nexus_rt_* runtime helper or stayed in generated code. One honest caveat from our runs: on the file containing the unsupported generator, inspect raised a TypeError: unhashable type: 'UnsupportedFeature' after printing its header. On files with unsupported constructs, use support and explain; treat inspect as the tool for clean files and IR dumps.

State problems: the caches

Sometimes the code is fine and the state is not — a stale entry after an upgrade, a cache you expected to hit. Two caches exist, and knowing which is which matters:

python -m pyvorin cache status
python -m pyvorin cache clear
Cache directory: /root/.pyvorin_cache
Total files: 158
Total size: 1.20 MB

These commands manage ~/.pyvorin_cache, the user cache (columnar kernel state, created at import time). They do not touch the compile artefact cache. The compiled machine code lives at <site-packages>/.pyvorin_cache/disk_compile — on our install, 177 index entries of SHA256-named shared objects plus a cache_index.json, capped by an LRU at 500 entries with per-entry checksum validation. Corrupt artefacts are detected on load, and editing your source invalidates the key anyway, because the key hashes the source along with the function name, options and toolchain modification times. If a number looks impossible after an upgrade, check you are not reading a cache-hit recompile: Compile: 0.000 ms in explain output means the disk cache served the artefact, not that compilation is free. Cache behaviour in build pipelines is covered in detail on the warm-up and caching strategies page.

A worked debugging session

Put it together. A nightly ETL job slows down after a refactor, and support shows the main transform at COMPATIBILITY_EXECUTED. explain names an unsupported construct on a helper and Oracle: needs_args on the transform itself. The reason chain from --explain-fallback confirms the helper is the root. Now the judgement call, and it is yours to make: if the helper's construct sits on the hot path, rewrite it and add a zero-argument entrypoint so the oracle validates the whole chain; if it is one cold call per batch, leave it and accept the partial result. We would spend the edit. A silent fallback that costs you five percent is a rounding error; one that costs you the compiled path on your dominant loop is the difference between buying the compiler and not. Correctness is guaranteed either way — the fallback executes your original source. The decision that remains is purely about where your speed lives.

Where to go next

Last reviewed 14 April 2026 against pyvorin-native 1.0.9 installed at /root/pvfinal. All command output on this page was captured from real runs on 13 September 2026; the inspect TypeError on files with unsupported features is a verified behaviour of this build, not a transcription error.