Skip to main content

Feature Manifests

Feature behavior is declared in feature.toml files living under package feature folders. Those files are not standalone: the package root morph.toml must list them (usually via [include] globs such as features/*/feature.toml). Without that edge in the manifest graph, the host will not treat the folder as part of the composed plugin.

For how those files sit next to block.toml, [build], and the ABI, read Plugin-governed pipeline first—especially the Inside packages section with real paths.


Canonical locations

Feature declarations (typical):

morphs/<Package>/features/<group-or-feature>/feature.toml

Syntax-only surfaces often live next to them as blocks (separate manifests):

morphs/<Package>/blocks/<block>/block.toml

Many packages include both patterns from the root manifest, for example Core:

[include]
files = ["features/*/feature.toml", "blocks/*/block.toml"]

Some packages only own features (GPU includes features/*/feature.toml but implements GPU syntax mainly from [build]-listed sources). Always open the owning package’s morph.toml—it is the source of truth for what is loaded.


Why feature manifests exist

  • Discovery: The framework finds lowering components, routes, built-ins, and tooling without hardcoded registration tables in src/.
  • Ownership: The file lives next to the C++ that implements the feature; reviewers see “what” and “where” in one tree.
  • Composition: Exports, provider imports, and runtime families in the same morph.toml describe how this feature participates in the cross-package graph (LLVM host, GPU provider, etc.).

What a feature manifest can declare (conceptual)

There is no single schema for every feature.toml. Sections are domain-specific, but you will repeatedly see:

ConcernTypical manifest keys / sectionsNotes
Built-in callTop-level [core.input], [core.print], …symbol, component / component_class, source, returns, builtin_family
Backend routeNested [<feature>.route.hostllvm] (or other host)provider_id, route_id, artifact_kind, required_extensions, source for emit
REPL / IDE behavior[<feature>.repl], chain methods like [<feature>.chain.min]component_class, source
Binary / unary ops[operation.add] in packages like Opsnir_kind, token_request_id, vm_dispatch_id, precedence, semantic_family
ToolingSections consumed by CLI/LSP codegenDeclared under a feature folder and listed in [build]

The Morph ABI (MorphABI.h) names roles and phases (semantic, lowering, backend route, …). The manifest + generated glue map your C++ factories onto those roles.


Concrete patterns in the tree (read these files)

  1. morphs/Core/features/api/feature.toml — built-ins (Input, Print, …) with [core.*.route.hostllvm] subsections tying each to provider.core.host_llvm and LLVM emit sources.
  2. morphs/Ops/features/operations/feature.toml[operation.*] entries linking lexer token requests, NIR opcode kinds, and VM dispatch ids.
  3. morphs/Flow/features/... — control-flow and flow-specific features (open the folder list; each subfolder may carry its own feature.toml per [include]).

Syntax forms parallel to features:

  • morphs/Flow/blocks/branch_block/block.toml[forms.if_statement] with a rule DSL, produces, and source (grammar-only manifest shape).

If you add a new statement form, you usually touch block.toml; if you add a built-in, route, or op, you usually touch feature.toml—and you always update morph.toml [include] and [build] when new C++ must compile into the package library.


Implementation files: not optional

A feature.toml source field points at a concrete .cpp (or path) that implements the registered component. The root [build] section lists nir_sources, sema_sources, codegen_sources, cli_sources, etc., so those translation units are built into morphs/<Package>/bin/... morph library.

Rule of thumb: if the C++ is not on a manifest-driven list, assume it is dead or test-only until you wire it.


Discovery is manifest-driven

Morph does not treat a random .cpp under morphs/ as a feature hook just because it exists.

Correct order when adding behavior:

  1. Pick the owning package under morphs/<Package>/.
  2. Add or extend feature.toml (and/or block.toml).
  3. Ensure morph.toml [include] covers the file.
  4. Add [build] sources and rebuild with morphc (see Playbook).

A common mistake

Do not add an implementation file and stop there.

If the manifest graph does not declare the feature, the framework cannot register it, and the change drifts toward core-first patches. Likewise, do not copy a fictional minimal [feature] table from old docs—open a real package and mirror its section style.


Relation to project morph.toml (user repo)

The morph.toml at the project root (dependencies, targets, [modulecpp], …) is documented under Learn Morph → Toolchain → morph.toml. This page is about morphs/<Package>/morph.toml and nested feature.toml / block.toml inside language packages, not the end-user project file—though both use TOML, they answer different questions (application build vs compiler plugin graph).


Next steps