Skip to content

Plugin API

This is the library plugin interface, version 2. A plugin extends the compiler's semantics only through explicit hooks. A plugin:

  • types the calls and attributes of the modules it claims
  • declares their effects
  • says how each recognized operation lowers, as a backend-neutral spec rather than backend code
  • may register dialects, passes, patterns, and lowerings for the IR

Each hook has a no-op default, so the compiler calls them directly. Plugins describes what each builtin plugin does.

ppy_compiler.plugins.base

Library plugin interface, version 2 (spec 18).

A plugin extends the compiler's semantics through explicit hooks and nothing else: it types the calls and attributes of the modules it claims, declares their effects, says how each recognized operation lowers -- as a backend-neutral spec, never as backend code -- and may register dialects, passes, patterns, and lowerings for the IR. Every hook has a no-op default here, so the compiler calls them directly; there is no probing for optional capabilities.

A plugin is loaded for a project, never for the process: discovery reads entry points without importing anything, and a plugin's module is imported only when a project enables it.

PluginError

Bases: Exception

A plugin the project cannot use: the wrong interface, or a collision.

Lowering

Bases: StrEnum

The kind of lowering a plugin selects for a recognized operation (spec 18.2).

ArgumentOwnership

Bases: StrEnum

What a plugin call does with one argument.

CallArgument(argument, ownership=ArgumentOwnership.BORROWED, reason='') dataclass

One positional or keyword argument's ownership contract.

IntrinsicSpec(name) dataclass

A named intrinsic the backends know: math.sqrt, a fused kernel.

DialectOperationSpec(dialect, operation, attributes=(), keyword_operands=(), keyword_attributes=()) dataclass

An operation of a dialect: special.erf, columnar.filter.

DirectCallSpec(symbol, library='', abi='c') dataclass

A call into a library through its documented C symbol.

GraphRegionSpec(framework, region='') dataclass

A region a framework compiles as one graph: an ATen region, a staged export.

FallbackSpec(reason='') dataclass

The Python implementation runs; reason says why that is right.

RejectSpec(reason='') dataclass

The construct is refused outright, with the reason.

CallResult(type, facts=Facts(), effects=EffectSet(), lowering=Lowering.PYTHON_FALLBACK, reason='', guards=(), arguments=()) dataclass

A plugin's verdict for one recognized call.

kind property

The kind of lowering, whether the plugin answered a kind or a spec.

spec property

The lowering as a spec; a bare kind becomes the spec with no detail.

CallAdjustment(qualname, replace_first_argument=None, add_keywords=(), reason='') dataclass

A framework-level rewrite of one call's arguments (spec 15.4, 22.2).

PluginContext(options=dict(), enabled=True) dataclass

Per-project state a plugin may consult.

Plugin(options=None)

A library plugin: the base every plugin extends.

Every hook has a default that claims nothing, so a plugin implements what it knows and the compiler calls the rest without asking whether it exists. A plugin never guesses a native signature: every direct path comes from a documented public API, a binding manifest, or a version-pinned adapter (spec 18.3).

fingerprint()

ABI/version identity that enters every cache key (spec 18.3).

The default names the interface and the plugin; a plugin over a library adds the library's version and build.

external_types()

Qualified names this plugin can type, mapped to display names.

attribute_type(qualname)

Type of a module-level attribute such as numpy.pi.

instance_attribute(type_name, attribute, facts=None)

Type of an attribute or method of an instance of an external class.

subscript(type_name, *, is_slice, tupled)

Type of indexing an instance of an external class.

call(qualname, args, keywords)

Type, effects, and lowering for a recognized call.

operator(symbol)

The library function a Python operator on this plugin's types runs.

call_alias(type_name)

The method that calling an instance of an external class runs, if the plugin knows one: forward for torch.nn.Module.

decorator_semantics(name)

What a decorator this plugin vouches for does to a function.

adjust_call(qualname, node, symbols)

A framework-level rewrite of one call's arguments (spec 22.2).

stage(decorators)

The build-time stage a decorated function enters, if this plugin stages it: a JAX export, an ATen region.

tensor_operation(qualname)

The shared tensor operation this call converges onto, if any (spec 42).

numpy.add, torch.add, and jax.numpy.add are all tensor.add: the compiler reads the answer to fuse expressions across libraries and to build their kernels from the tensor dialect, whatever the call's own lowering is.

lower_type(type_, facts)

Canonical representation of a source type, retaining its proven facts.

Return None when this plugin does not provide a representation. This hook describes IR types, independently of any backend's native ABI.

lower_type_for_backend(type_, facts, backend)

A backend-selected representation, or None for the common type.

register_dialects(registry)

Dialects this plugin defines: registry.register(MyDialect()).

register_passes(manager)

Passes this plugin adds: manager.register_stage_pass(stage, factory).

register_patterns(registry)

Rewrite patterns this plugin adds: registry.add_pattern(pattern).

register_lowerings(registry)

Lowerings of this plugin's dialects to others or to a backend.

PluginRegistry()

Holds the plugins enabled for one project.

fingerprints(modules=None)

Version fingerprints of the plugins that can affect a compilation.

Computing a fingerprint imports the library, which for an accelerator runtime costs seconds. A module that never imports torch cannot be affected by torch's version, so restricting this to the modules actually imported keeps that cost off every other compilation. Pass None to fingerprint everything.

lower_type(type_, facts, backend=None)

The common or explicitly backend-selected representation of a type.

dialect_registry()

The IR registry for this project: the builtin dialects plus every dialect, pattern, and lowering the enabled plugins register.

Tensor ownership and backend types (PPy 0.3.3)

Plugin interface 2 remains compatible. Plugins using the APIs in this section must require compiler version 0.3.3 or newer.

CallResult.arguments describes what a recognized call does with individual arguments. Address a positional argument by its zero-based index or a keyword argument by its name:

CallResult(
    T.NONE,
    arguments=(
        CallArgument(0, ArgumentOwnership.BORROWED),
        CallArgument("out", ArgumentOwnership.MUT, "out must be writable"),
    ),
)
ownership meaning
BORROWED reads an argument only for the duration of the call
MUT permits call-scoped writes; requires a ppy.Mut[...] or ppy.Owned[...] value
OWNED the callee may retain the value, so it requires ppy.Owned[...]

Mutations follow aliases back to the function parameter.

Arguments without a contract keep the conservative behavior: the compiler assumes the callee may retain them. Starred positional arguments and unpacked keyword arguments are also conservative, because their runtime positions or names are unknown.

An ownership violation reports E1802 at the argument. Its explanation comes from the first of these that is set: CallArgument.reason, CallResult.reason, a compiler default. A rejected call uses CallResult.reason or RejectSpec.reason in its E1802 diagnostic.

Plugin.lower_type_for_backend(type_, facts, backend) -> IRType | None lets a plugin claim a representation only when that backend is explicitly selected. The default returns None.

PluginRegistry.lower_type accepts the optional backend argument, rejects competing backend claims, and otherwise preserves the registration-order behavior of Plugin.lower_type.

The shared ppy.Tensor type has no legacy plugin-specific representation when no backend is selected. The compiler handles its neutral canonical form.

Canonical types and operations (PPy 0.3.2)

Plugin interface 2 remains compatible. Plugins using the following additions must require compiler version 0.3.2 or newer.

Plugin.lower_type

Plugin.lower_type(type_, facts) -> IRType | None chooses a canonical IR representation. The registry asks enabled plugins in registration order and uses the first non-None answer. Returning None lets the builtin type conversion try next.

The frontend supplies the actual Facts, including dtype and shape, for parameters, returns and plugin call results. Register each custom type's dialect through register_dialects.

Canonical signatures

Canonical signatures, local values and function calls use IR types. A custom tensor or pointer does not need a CPU NativeSignature; CPU emission checks its own ABI and preserves Python fallback when that boundary is unavailable. An explicit external backend request reports functions it cannot lower with the function name, source location and reason.

DialectOperationSpec

A CallResult with DialectOperationSpec becomes the named operation:

  • Positional arguments become SSA operands in source order.
  • The result type comes from lower_type. A CallResult with the analysis type T.NONE produces zero results, allowing a store as a statement.
  • Effects, guards and the call's source location are retained on the operation.
  • Observable writes cannot be removed by dead-code elimination.

Keyword arguments have explicit roles:

DialectOperationSpec(
    "example",
    "scale",
    attributes=(("mode", "linear"),),
    keyword_operands=("factor",),
    keyword_attributes=("axis",),
)

For scale(x, factor=n, axis=1), x and n are operands and axis=1 is an attribute.

  • Keyword operand values are evaluated in Python source order and appended in the declared order.
  • Omitted keywords are omitted from the operation. The plugin must supply any required defaults in its contract.
  • Keyword attributes must be literals or names with proven constant values.
  • Undeclared keyword roles, unpacked arguments and nonconstant attributes are rejected.
  • Fixed attributes must agree with corresponding keyword attributes.
  • Attribute values use the IR's serializable scalar, type, tuple and dictionary forms.

guards are contract metadata for backend validation/lowering. A backend must implement required checks or reject the operation.