Skip to content

Latest commit

 

History

History
284 lines (247 loc) · 10.5 KB

File metadata and controls

284 lines (247 loc) · 10.5 KB
title Fortran Bridge Lowering
audience developers, maintainers, contributors
prerequisites Code Generation Stage guide, completed wrapper plan
related ../../architecture.md, ../codegen.md, ../planning.md, ../printers.md, c-binding.md
status maintained
publication reviewed

Fortran Bridge Lowering

Role And Boundary

prik/codegen/fortran/bridge.py lowers the native-entrypoint and bridge views of a completed ModulePlan into a FortranModule. The result is a typed Fortran syntax tree, not formatted source or a compiled library. FortranSourcePrinter serializes it later.

The entrypoint records define each public bind(C) symbol, ordered argument and result transport, and direct-return ABI. Bridge records define native imports and interfaces, adapter-local declarations and representation conversion, the ordered original-Fortran call, writeback, cleanup, and derived-value lifecycles. The generator does not infer a native interface, argument optionality, ownership, or result projection from source-language details.

Generated support procedures obtain their exported symbol and public parameter/result contract from the same module support-procedure registry used by the C binding. Derived-field bodies, origin storage manipulation, descriptor association, destruction, and callback adaptation remain Fortran-local implementation. The lowerer does not reconstruct a support ABI from those local facts. The registry also marks callback trampolines as binding-implemented, so Fortran consumes their signatures as interfaces rather than emitting duplicate implementations.

Fortran-local typed-holder definitions and holder field bodies follow explicit bridge-module inventories projected beside that registry. The lowerer joins a planned owner path to its derived declaration for spelling, but does not walk function results, argument call cases, or storage policy to rediscover which holder types exist. Per-argument lowering still dispatches the already-planned derived call case selected for that one invocation.

Input And Output

ModulePlan.entrypoint + ModulePlan.bridge
  + namespaces + function entrypoint/bridge views
  -> FortranBridgeGenerator.require_supported()
  -> FortranBridgeGenerator.visit()
  -> FortranModule
  -> FortranSourcePrinter
  -> Fortran bridge source

require_supported() verifies only that selected primitive representations can be emitted. It does not validate or complete the cross-backend plan; the normal WrapperGenerator handoff does that before it calls this backend.

Lowering Algorithm

_visit_ModulePlan() collects the iso_c_binding symbols, required native module uses, interfaces, holder definitions, and procedures for every namespace. It adds derived and callback support only when plan facts require them.

_visit_FunctionPlan() preserves the plan's execution order:

  1. It lowers the entrypoint result form and ordered C ABI parameters into the public bind(C) declaration.
  2. It emits declarations and representation initializers, then forms the native invocation from the ordered call slots.
  3. It runs the selected writeback and cleanup finalizers, wrapping derived result or carrier lifecycles when the plan requires them.

The entrypoint record exposes a bind(C) name shared with the C binding. For a standalone native procedure, the bridge record explicitly selects its external declaration; for a module procedure, it supplies the native module use. Those are completed plan facts, not heuristics in the generator.

Run A Minimal Manual Plan

This is the same complete no-argument PING plan used by the C binding deep dive. With no transfers, results, slots, or lifecycle actions, it is small enough to construct explicitly while still containing both mandatory module views and a complete bridge record.

Real builds must obtain plans from WrapperPlanner after policy completion and must pass through WrapperGenerator for freezing and cross-backend validation. Use direct construction only to inspect a backend lowering path.

Plan Shape

This abbreviated, non-runnable sketch shows the records in construction order. Expand the full source to run the complete example.

binding = BindingFunctionPlan(...)
entrypoint = NativeEntrypointFunctionPlan(...)
bridge = BridgeFunctionPlan(...)
function = FunctionPlan(
    ..., binding=binding, entrypoint=entrypoint, bridge=bridge
)
namespace = NamespacePlan(..., functions=(function,))
plan = ModulePlan(
    binding=BindingModulePlan(...),
    entrypoint=NativeEntrypointModulePlan(...),
    bridge=BridgeModulePlan(...),
    namespaces=(namespace,),
)

generator = FortranBridgeGenerator()
bridge_module = generator.visit(plan)
print(FortranSourcePrinter().doprint(...))
Full runnable source
from prik.codegen.fortran.bridge import FortranBridgeGenerator
from prik.planning.models import (
    BindingFunctionPlan, BindingModulePlan, BridgeFunctionPlan,
    BridgeModulePlan, FunctionPlan, ModulePlan,
    NativeEntrypointFunctionPlan, NativeEntrypointModulePlan,
    NativeGeneratedCodeGroupKind, NativeGeneratedCodeGroupPlan, NamespacePlan,
)
from prik.policy.models import (
    ExternalDeclarationMode, NativeEntrypointAction, NativeInvocationKind,
)
from prik.printers.fortran import FortranSourcePrinter

binding = BindingFunctionPlan(
    python_name="ping",
    docstring="Call PING.",
    release_gil=False,
    status_error=None,
    argument_conversion_order=(),
)
bridge = BridgeFunctionPlan(
    native_name="PING",
    native_invocation=NativeInvocationKind.PROCEDURE,
    native_operator=None,
    standalone=True,
    external_declaration=ExternalDeclarationMode.IMPLICIT_EXTERNAL,
    native_module=None,
    native_is_subroutine=True,
)
entrypoint = NativeEntrypointFunctionPlan(
    symbol_name="bind_c_ping",
    action=NativeEntrypointAction.GENERATED_FORTRAN_ADAPTER,
    parameters=(),
    results=(),
    projected_slots=(),
)
function = FunctionPlan(
    owner_path="demo.ping",
    symbol_name="ping",
    binding=binding,
    entrypoint=entrypoint,
    bridge=bridge,
    class_call=None,
    arguments=(),
    results=(),
    declaration_callables=(),
    available_roles=(),
)
namespace = NamespacePlan(
    owner_path="demo",
    python_path=(),
    functions=(function,),
    docstring="Manual codegen demonstration.",
)
plan = ModulePlan(
    owner_path="demo",
    binding=BindingModulePlan(owner_path="demo"),
    entrypoint=NativeEntrypointModulePlan(owner_path="demo"),
    bridge=BridgeModulePlan(owner_path="demo"),
    namespaces=(namespace,),
    native_generated_code_groups=(
        NativeGeneratedCodeGroupPlan(
            kind=NativeGeneratedCodeGroupKind.FORTRAN_ADAPTERS,
            language="fortran",
            member_keys=("demo.ping",),
            source_paths=("bind_c_demo_wrapper.f90",),
        ),
    ),
)

generator = FortranBridgeGenerator()
generator.require_supported(plan)
bridge_module = generator.visit(plan)
print(FortranSourcePrinter().doprint(bridge_module.procedures[0]))
subroutine bind_c_ping() bind(c, name="bind_c_ping")
  external :: PING
  call PING()
end subroutine bind_c_ping

The entrypoint record fixes the public C-ABI name; the bridge record marks PING as an external subroutine and original native target. The generator contributes the adapter declaration and call syntax; it does not decide whether PING is callable or how values cross the boundary.

Run The Module Demonstration

bridge.py also contains a direct demonstration of its normal input route. It constructs one scalar semantic function, completes policy, builds its plan, preflights bridge scalar support, lowers the Fortran nodes, and prints the module through FortranSourcePrinter. Expand Example source on the published site to see that exact __main__ setup.

python3 prik/codegen/fortran/bridge.py
Rendered Fortran bridge source:
module bind_c_bridge_demo_wrapper
  use iso_c_binding, only: &
    c_associated, &
    c_bool, &
    c_char, &
    c_double, &
    c_double_complex, &
    c_f_pointer, &
    c_float, &
    c_float_complex, &
    c_int8_t, &
    c_int16_t, &
    c_int, &
    c_int32_t, &
    c_int64_t, &
    c_loc, &
    c_null_char, &
    c_ptr, &
    c_null_ptr, &
    c_size_t, &
    c_sizeof
  use bridge_demo, only: native_double_value => DOUBLE_VALUE
  implicit none
contains
  function bind_c_double_value(value) result(result) bind(c, name="bind_c_double_value")
    real(c_double), value :: value
    real(c_double) :: result
    result = native_double_value(value)
  end function bind_c_double_value
end module bind_c_bridge_demo_wrapper

The module and procedure names identify the planned C-ABI boundary. The final assignment is the printed native call that the completed bridge plan selected. DOUBLE_VALUE remains the procedure exported by bridge_demo; the use statement imports it locally as native_double_value, so the bridge calls that alias. This gives every imported module procedure a distinct bridge-local name.

Change Routes And Evidence

  • Change entrypoint declaration lowering or bridge-local native invocation, conversion, writeback, or cleanup in bridge.py.
  • Change a primitive's Fortran spelling in primitive_scalar_types.py.
  • If a C ABI fact is absent from an entrypoint record, or an adapter-local fact is absent from a bridge record, add the completed policy and plan fact upstream; do not inspect semantic source or invent a default here.
Evidence What it establishes
Wrapper-generator handoff Frozen-plan validation and generated Fortran bridge assembly.
Module-variable lowering Matched C and Fortran scalar module-variable operations and their bridge procedures.
Array lowering Plan-selected specialized array roles lower through the bridge ABI.

Failure Boundary

The bridge reports unavailable primitive spellings, unsupported completed actions, invalid plan references, and missing node visitors. It delegates missing semantic choices to policy/ and planning/, source formatting to printers/, and compilation to compiler/. Start with the first invalid plan record or bridge procedure, not with a later Fortran compiler error.