Backend API¶
This is the backend interface, version 1 (BACKEND_API_VERSION).
A backend takes the canonical IR after the shared passes and returns text, bytes, or built artifacts. It:
- may hang passes at the
backendstage - must refuse in
validatewhat it cannot take - says whether its toolchain is present
An external backend is a package with an entry point in the ppy.backends
group. The compiler finds it without importing it, and loads it when it is
asked for. Backends explains how the pieces fit
and walks through a whole example package.
A backend package imports this module and ppy_compiler.ir.
ppy_compiler.backend.base
¶
The backend interface: what a code generator is to the compiler.
A backend consumes the canonical IR after the shared passes and nothing
else -- never the Python AST, never the checker's tables -- and answers
with text, bytes, or built artifacts. It may hang passes of its own at the
backend stage, must refuse IR it cannot take in validate, and says
whether its toolchain is here. Plugins are the other extension: a plugin
models a library's semantics and lowering; a backend makes code. Neither
does the other's job.
An external backend is a package with an entry point in the ppy.backends
group whose factory takes the backend's options from pyproject.toml
([tool.ppy.backends.<name>]) and returns a Backend::
[project.entry-points."ppy.backends"]
toy = "ppy_toy:create_backend"
The interface is versioned by BACKEND_API_VERSION, independently of the
compiler's version: a backend written against another one is refused with
the reason, never loaded and hoped about.
BackendConfig(name, options=dict())
dataclass
¶
The [tool.ppy.backends.<name>] table, as the backend receives it.
fingerprint()
¶
A digest of the options: part of every artifact key, so a changed setting is a different artifact.
BackendError
¶
Bases: Exception
A backend's refusal; the message is the reason.
BackendUnavailable
¶
Bases: BackendError
The backend's toolchain is not here; what is missing is named.
BackendValidationError(backend, what, *, capability='', location=None)
¶
Bases: BackendError
IR the backend does not take: the backend, what it cannot take, the capability it lacks, and the source location when the IR carries one.
EmitFormat(name, suffix, binary=False, description='', scope='module', requires_toolchain=True)
dataclass
¶
One thing a backend can write for ppy emit <name>.
ToolchainStatus(available, detail='')
dataclass
¶
Whether the backend can build here, and what it found or missed.
BuildResult(outputs=(), notes=())
dataclass
¶
What build produced.
BackendPassRegistrar(manager, backend)
¶
Where a backend hangs its passes: the backend stage, and nowhere else.
The stages before it belong to the shared pipeline and to the plugins --
a pass of a backend's own running among them would decide for every other
backend what the canonical IR is. A backend is handed this rather than the
PassManager so that the invalid thing cannot be spelled.
BackendContext(root, config, backend_config, opt_level, target, registry, plugin_fingerprints=(), identity=dict(), entry=None, notes=list())
dataclass
¶
Everything a backend is told besides the IR.
The IR is already through the shared passes and the backend's own;
nothing here reaches back into the frontend. identity is the cache
key the driver computed for the module (compiler, IR, backend, its
fingerprint and configuration, target, optimization level, plugins),
for a backend that keeps artifacts of its own.
Backend(options=None)
¶
The class an external backend extends. Every method has a default: a
backend that emits registers formats and implements emit; one that
builds implements build; the rest is optional.
fingerprint()
¶
What identifies this backend's code generation for the cache: its version, its SDK's, anything whose change makes old artifacts wrong. The default is the class and the interface version -- a backend with a toolchain under it should include that toolchain's version.
emit_formats()
¶
The formats ppy emit may ask this backend for.
register_passes(passes)
¶
Hang the backend's own passes: passes.add(MyPass).
They run at the backend stage, after every shared pass and every
plugin's, and the module is verified after each of them.
validate(module, context)
¶
Refuse IR the backend cannot take, with a BackendValidationError
naming what and why. Called after every pass, before emit or build.
emit(module, format, context)
¶
format (one of emit_formats) for one module: text, or bytes for
a binary format.
emit_program(modules, format, context)
¶
A program-scoped format: every module at once, one artifact back.
Only a format that declared scope="program" arrives here; a
module-scoped one goes to emit, once per module.
build(modules, output, context)
¶
Build every module into output, which exists, and say what was written.
toolchain_status()
¶
Whether the backend can work here; ppy doctor prints the detail.
The registry¶
ppy_compiler.backend.registry
¶
Which backends there are, and loading one when it is asked for.
The builtin backends are always here. An external one is a distribution
with an entry point in the ppy.backends group; discovery reads the entry
points without importing anything, and the package is imported only when
its backend is selected -- by --backend, by a format it registers, or by
ppy doctor. One broken package does not break the others: its failure
is a reported problem, or the error of the command that asked for it. A
name registered twice is never settled by luck: it is reported, and asking
for it is an error naming both distributions.
BackendLoadError
¶
Bases: BackendError
A backend that cannot be used: unknown, duplicated, incompatible, or failing to load; the message says which and names what is available.
BackendInfo(name, builtin, origin, distribution=('', ''), entry=None)
dataclass
¶
One backend as discovery sees it, loaded or not.
BackendCatalog(backends=dict(), problems=list(), duplicates=dict())
dataclass
¶
Every backend by name, and what discovery had to refuse.
FormatOwner(backend, format)
dataclass
¶
The backend that emits a format, and the format as it declared it.
discover_external_backends()
¶
Every installed external backend by name, without importing any, and the names that were registered more than once (each is a problem).
available_backends()
¶
The builtin backends and every discovered external one, nothing imported.
load_backend(name, options=None)
¶
The backend called name, constructed with its options: a builtin one
directly, an external one through its entry point, imported now.
Refused with a BackendLoadError that says why: no such backend (and
which there are), registered twice, a factory that fails or answers
something that is not a Backend, or an interface version other than
BACKEND_API_VERSION.
discover_format_owners()
¶
Which backend each installed distribution says owns which emit format,
from ppy.backend-formats alone: no backend package is imported.
emit_format_owner(kind, options_for=None)
¶
The backend that emits kind.
A builtin format is answered without loading anything. For any other
name every external backend is loaded (each with the options
options_for(name) gives it) and asked; a backend that fails to load
is reported in the error only when the format is not found elsewhere.
Two backends claiming one format is an error naming both.