Skip to content

[eudsl-llvmpy] MIR: run codegen to a named pass and retain the MIR for inspection (e.g. post-RA) #653

Description

@makslevental

Follow-up to #648. Today MirModule.emit_object(scheduler=, regalloc=) runs the entire back half of codegen (-start-after=finalize-isel through object emission) and returns the object bytes. There is no way to run codegen up to a chosen pass and get the intermediate MachineFunctions back for inspection — so you can't, from Python, observe the MIR after a specific stage (e.g. after register allocation / virtregrewriter, where the function is no longer in SSA form and has no virtual registers), which is exactly what you want when debugging a custom RegAllocBase/MachineSchedStrategy.

Proposal

Add a "run to a named pass and retain the MIR" entry point, e.g.:

mmi2 = mmi.run_to("virtregrewriter", regalloc="my-alloc")   # or stop_before=
print(mmi2.to_mir())        # inspect post-RA, non-SSA MIR

Semantics: run the codegen pipeline from finalize-isel up to (and including / before) the named pass, then retain the resulting MachineFunctions in the returned MirModule for inspection via the existing to_mir() / machine_function() surface — rather than continuing to emission and freeing them.

Mechanism (mirrors what already exists)

  • LLVM already provides process-global -stop-after=<pass> / -stop-before=<pass> cl::opts, analogous to the -start-after=finalize-isel that emit_object already sets and restores (see the Restore RAII in MirModule::emitObject, src/MIR/Machine.cpp). Set/restore stop-after/stop-before the same way (GIL-serialized; no lock).
  • Retaining MIR after stopping is the same trick create_machine_function uses for the ISel boundary: don't append the object-emission passes (addPassesToEmitFile would add FreeMachineFunctionPass and free the functions), keep the MachineModuleInfoWrapperPass alive in the returned wrapper's owned state (the BuildOwned/EmittedOwned variant in Machine.cpp), so the MachineFunctions stay queryable — exactly like the current build/inspect path.
  • The scheduler/regalloc selection (-misched, RegisterRegAlloc::setDefault) and the pendingCodegenError stash/re-raise machinery should compose unchanged, so you can run a custom allocator and stop right after it.

Scope

  • New MirModule.run_to(pass_name, *, stop_before=False, scheduler=None, regalloc=None) (name TBD) that runs finalize-isel -> <pass> and returns a MirModule whose MachineFunctions are retained.
  • Validate the pass name against the registered pass pipeline; an unknown name raises a catchable Python error (as emit_object does for unknown scheduler/regalloc names).
  • Reuse the existing state machine so a module can be inspected and then still emit_object'd (or make the retain-vs-emit interaction explicit and one-shot, matching the current BuildOwned/EmittedOwned handling).
  • Tests: run to virtregrewriter with a custom allocator, assert the returned MIR has no virtual registers / uses physregs (i.e. is post-RA, non-SSA); run to a pre-RA pass and assert vregs are still present; unknown-pass-name raises. Keep the 100% C++ coverage gate.

Notes

  • This pairs naturally with the existing to_mir() and the -start-after=finalize-isel plumbing; it's mostly option set/restore + retaining the MMI wrapper, not new pipeline construction.
  • Useful well beyond regalloc (inspecting scheduling results, prologue/epilogue insertion, etc.).

cc #600

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions