Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
f7e65e5
Expose non-target module arrays as views and interoperable logicals
saidctb Sep 2, 2026
bbee891
Borrow the runtime descriptor for an allocatable actual
saidctb Sep 3, 2026
92af0d2
Publish a native entry-point table for module array handles
saidctb Sep 3, 2026
c3b272d
Take the published entry-point table when placing a descriptor argument
saidctb Sep 3, 2026
bde7120
Publish entry-point tables for field and owned result handles
saidctb Sep 3, 2026
3cc23e4
Detach a borrowed descriptor from the capsule that carried it
saidctb Sep 3, 2026
ab2b6b7
Refuse a PROTECTED module array before generating its accessors
saidctb Sep 3, 2026
24a01f9
Let a descriptor consumer define the module array it receives
saidctb Sep 3, 2026
248ff1e
Call inside the descriptor consumer for a writable allocatable dummy
saidctb Sep 4, 2026
d742fec
Place every descriptor argument inside its consumer
saidctb Sep 4, 2026
941b901
Remove the machinery for handing a copied descriptor to a binding
saidctb Sep 4, 2026
c15013f
Reach a pointer dummy through its consumer, as an allocatable one is
saidctb Sep 4, 2026
ff8d32d
Place a present optional descriptor argument like any other
saidctb Sep 4, 2026
a73b08b
Delete the machinery for rebuilding a descriptor in C
saidctb Sep 4, 2026
97ff88f
Publish an entry-point table for wrapper-owned descriptor storage
saidctb Sep 4, 2026
f6d29bf
Build an allocatable handle's NumPy view where its descriptor is
saidctb Sep 4, 2026
eb48fda
Read an array handle's storage from its table for an ordinary dummy
saidctb Sep 4, 2026
7ec432e
Normalize an empty dimension when a handle reaches an ordinary dummy
saidctb Sep 4, 2026
00e9bf6
Answer an owned handle's inquiries from the descriptor it holds
saidctb Sep 4, 2026
16d92b0
Reach an array handle's storage from C for more dummy forms
saidctb Sep 4, 2026
c19ade5
Report a refused array handle from the binding, and cover characters
saidctb Sep 4, 2026
79e7652
Publish one versioned backend for every array handle
saidctb Sep 5, 2026
abc7b3a
codex: Answer every handle inquiry from the descriptor it already has
saidctb Sep 5, 2026
b76447a
codex: Delete the lowering the descriptor route replaced, and cover t…
saidctb Sep 5, 2026
e10f5f2
Dispatch every array handle through one backend and one call
saidctb Sep 5, 2026
a2cf473
Refuse a borrowed descriptor a call cannot hold open
saidctb Sep 5, 2026
9d02d1b
Make the call inside the descriptors, always
saidctb Sep 5, 2026
3479b32
codex: Keep native array documentation current
saidctb Sep 5, 2026
3e586a4
Say which storage an owned handle operation needs
saidctb Sep 5, 2026
127e3c4
Record the capsule ABI break for extension builders
saidctb Sep 5, 2026
daa8f29
codex: Complete native array backend audit
saidctb Sep 5, 2026
b9e2d3f
Prove the two things the capsule name and an absent optional promise
saidctb Sep 5, 2026
7feba1c
Hold the record still while the version name does
saidctb Sep 5, 2026
e7a3a52
Name the capsule after the record it carries
saidctb Sep 5, 2026
ab6e467
Drop the version number the tag made redundant
saidctb Sep 5, 2026
72c1031
Describe the capsule name the code actually publishes
saidctb Sep 5, 2026
7a812cb
codex: Version native array backend semantics
saidctb Sep 6, 2026
78ef2cd
codex: Classify how each array dummy is reached, and say why a layout…
saidctb Sep 6, 2026
3ab611f
codex: Describe a NumPy array to a Fortran descriptor dummy
saidctb Sep 6, 2026
75717c9
codex: Format the descriptor constructor
saidctb Sep 6, 2026
6e431b5
codex: Reach a descriptor dummy from a handle and from NumPy alike
saidctb Sep 6, 2026
2aa5423
codex: Hand every descriptor-capable array over as a descriptor
saidctb Sep 6, 2026
ebad154
codex: Cover every source and shape a signed-stride handoff accepts
saidctb Sep 6, 2026
20d4f74
codex: Complete and harden descriptor array handoff
saidctb Sep 6, 2026
795717c
codex: Take a fixed-width character array by the only dummy that can …
saidctb Sep 6, 2026
9b46574
codex: Distinguish native entities from descriptor projections
saidctb Sep 6, 2026
22a2c81
codex: Take a default-kind LOGICAL array at its real width in the LAP…
saidctb Sep 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ compilation should use the focused owners under
`tests/fortran/infrastructure/building/compiling/` as applicable. Include the
relevant end-to-end feature tests whenever a generated or compiled mechanism
changes; run a broader suite when behavior spans multiple stages.
Run pytest with at most `-n 2`. Never `-n 4`, `-n 8`, or `-n auto`. The development machine has 12 cores but only about 7 GB of RAM, and every xdist worker loads NumPy while the Fortran end-to-end tests fork gfortran and cc per test on top of `pytest-monitor` profiling each one. Higher parallelism exhausts memory and thrashes swap, which has hard-frozen the machine and forced a reboot. Prefer the narrowest owning test path over a full suite run, and commit verified work promptly rather than batching it behind a long run.
Do not run LAPACK wrapper tests locally unless the user explicitly asks for them. Local verification may run everything else, including BLAS-only real-library tests; leave LAPACK coverage to GitHub Actions by default.
Do not run the full coverage workflow for routine changes. Run focused tests plus the required static-analysis suite. Reserve the complete CI-style coverage workflow for explicit pre-merge or pull-request verification, or when the user specifically requests it.
When investigating coverage failures, mirror the GitHub Actions workflow before deciding the fix: run coverage with `COVERAGE_PROCESS_START=pyproject.toml`, combine parallel data with `python3 -m coverage combine`, then run `python3 -m coverage report`. Do not assume a plain local coverage run matches CI, especially when subprocess tests are involved.
Expand Down
59 changes: 59 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,65 @@ release tags add a leading `v` to the package version.

## Unreleased

- Fixed Fortran allocatable and pointer descriptor arguments to use the
compiler's live descriptor. This works on Intel ifx as well as GNU Fortran,
preserves lower bounds, allocation and association changes, and covers
required and optional dummies, multiple descriptor dummies per call,
`intent(out)` and status-bearing calls, and handles from module variables,
fields, results, and caller-created storage.

- Descriptor-backed native calls honor `@nogil` while the native procedure is
running, including calls with multiple allocatable or pointer arguments.

- Deferred-length character pointer-array arguments are rejected before code
generation; module and field handles expose only operations that do not
require an unsupported `bind(C)` descriptor interface.

- Generated array handles share descriptors between extensions through the
versioned `prik.native_array_backend.v1.<layout tag>` capsule. It runs a
consumer while the handle's descriptor is live and refuses incompatible ABI
versions or record layouts before reading the backend. Rebuild generated
extensions together after upgrading PRIK.

- Fixed-width character module and field handles expose their storage through
an ordinary descriptor. They support inquiries, NumPy views, and ordinary
array arguments. Fixed-width character allocatable and pointer array
arguments are rejected because no interoperable descriptor interface carries
their allocation or association semantics.

- Generated Fortran allocatable and pointer handles can be passed directly to
matching ordinary array arguments. Numeric assumed-shape and assumed-rank
arguments accept representable forward or reversed Fortran sections from
either handles or NumPy arrays, including direct `bind(C)` procedures.
Optional arrays apply the same layout rules when present and accept omission
or `None` as absence. Explicit-shape, assumed-size, and fixed- or
assumed-width character arrays retain their declared layout. C array
arguments accept NumPy arrays.

- Array-valued functions whose result extents depend on descriptor arguments
return initialized NumPy arrays on Intel ifx and GNU Fortran.

- Descriptor-backed NumPy views preserve native byte strides, including
negative strides, non-contiguous pointer targets, and zero-sized dimensions.

- Writable module and derived-field allocatable arrays support allocation and
reassignment. PRIK rejects `protected` module arrays when the generated API
would require writable access.

- Module array views cover fixed, target, allocatable, pointer, shifted-bound,
character, and logical storage. Ordinary fixed-shape module arrays expose
live NumPy views with or without `target`.

- PRIK now selects interoperable Fortran logical storage on Intel and
PGI/NVIDIA compilers. Use `--no-standard-logicals` or
`standard_logicals=False` only when linking Intel objects compiled without
that option. Wider logical arrays are exposed with their matching integer
dtype, including caller-created allocatable and pointer handles;
`logical(c_bool)` remains `numpy.bool_`. A wider logical array argument is
now aliased rather than converted, so it takes that integer dtype where a
one-byte `numpy.bool_` buffer was previously copied in and out. Logical
scalars are unaffected and stay Python `bool` in every kind.

## 0.4.3 — 2026-08-31

- Republishes 0.4.2. That tag carried the previous package version, so the
Expand Down
132 changes: 117 additions & 15 deletions docs/developer/packages/runtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,114 @@ select a different view behavior from local descriptor facts.
## A Native Array Handle At Runtime

```text
generated operation dictionary + dtype, rank, ownership, and view policy
generated dispatcher + completed capability set + native backend capsule
+ dtype, rank, ownership, and view policy
-> NativeArrayHandleBase validation and owner retention
-> AllocatableArray or PointerArray
-> state, lifecycle, association, and to_numpy() operations
```

The operation dictionary is the boundary between generated extension code and
the stable Python handle API. An operation exists only when the completed plan
allows the generator to expose it. Missing operations fail explicitly rather
than being inferred from `allocatable` or `pointer` alone.
The dispatcher is the single Python call boundary between generated extension
code and the stable handle API. Its immutable capability set comes from the
completed plan and states which operation names the dispatcher accepts. The
runtime validates that set when it creates the handle and before dispatching an
operation.

### The Backend Capsule

Every generated handle publishes one versioned capsule,
`prik.native_array_backend.v1`, on `_native_backend`. It is the whole
cross-extension ABI for an array handle:

```c
typedef struct {
uint32_t descriptor_kind;
uint32_t descriptor_attribute;
uint32_t rank;
uint32_t descriptor_size;
int32_t cfi_type;
size_t element_size;
void *context;
prik_native_array_with_descriptor_fn with_descriptor;
prik_native_array_release_fn release;
} prik_native_array_backend;
```

`with_descriptor(context, consumer, consumer_context)` supplies a live
descriptor and runs the consumer on it:

- **Borrowed** — a module variable or a derived-type field. The entry point
enters Fortran and supplies the plan-selected descriptor for that call. The
descriptor is gone when the consumer returns and must never be retained,
copied, or serialized. An ordinary projection does not invoke the consumer
while its allocatable or pointer entity has no storage; inquiries return the
corresponding absent value.
- **Owned** — a native result, or a contract handle that has been given
storage. The binding allocated a descriptor and keeps it for the handle's
life, so the entry point hands that storage straight to the consumer.

Consumers use the same contract for both forms. `context` is the parent's
address for a field, descriptor storage for an owned handle, and `NULL` for a
module variable. `release` is non-`NULL` when the extension owns `context`.
Clearing `context` after release makes `close()` and finalization idempotent.

The capsule name is `prik.native_array_backend.v1.<tag>`. The version identifies
the callback contract and field meanings. Change it when either changes without
changing the C record layout. The tag folds the record size and each field's
name, offset, and width, so a layout mismatch also changes the name.
`PyCapsule_GetPointer` compares that name before returning the record pointer;
an incompatible producer is therefore refused before its fields are read.

`descriptor_size` stays in the record because it attests the producer's
`CFI_CDESC_T(rank)` layout, which is the compiler's, not this header's, and so
is not folded into the tag. `descriptor_kind` identifies the native entity;
`descriptor_attribute` identifies the descriptor supplied to a consumer. A
descriptor-dummy call requires both to match, while ordinary-array consumers
can use a descriptor with the `other` attribute. Readers also validate `rank`,
`cfi_type` and `element_size`. `element_size` is `0` for widths determined at
run time, such as deferred-length character arrays.

### Inquiries Read The Descriptor

`shape`, `allocated`, `associated`, `contiguous`, `element_length` and
`to_numpy` are all answered by small shared C consumers run through
`with_descriptor`, for borrowed and owned handles alike. Each consumer returns
the completed Python value. The bridge provides the descriptor entry point and
the mutations that act on the entity itself: `allocate`, `resize`,
`deallocate`, `nullify`, `associate`, and `destroy`.

A pointer additionally reports `descriptor` as a flat fact tuple: base address,
element width, rank, then a lower bound, extent, and byte stride per axis. A
pointer assignment uses this snapshot. A handle created from a `.pyi` contract
retains the snapshot until a call attaches native storage and replays the
association.

A call with more than one allocatable or pointer dummy enters each argument's
backend in turn. Each consumer records its descriptor and enters the next, and
the call runs inside the last consumer while every descriptor is live. Borrowed
and owned handles use the same placement; an owned backend hands the consumer
its persistent storage. An absent optional argument contributes an unallocated
placeholder to the chain. Each descriptor remains scoped to the consumer that
supplied it.

Ordinary numeric assumed-shape and assumed-rank arguments use the same C
descriptor entrypoint for both direct and adapted calls. The binding describes
a NumPy array with call-local descriptor storage, or enters a handle's live
descriptor through the consumer chain. A direct `bind(C)` procedure receives
that descriptor itself; a non-`bind(C)` procedure has an interoperable bridge
dummy that passes the array through unchanged. Explicit-shape, assumed-size,
raw C-pointer, and character-array entrypoints keep their planned address ABI.

### Views And Ownership

`to_numpy()` builds the view in C while the descriptor is live, over the
storage the descriptor names, with the descriptor's own byte strides — so
negative strides, non-contiguous pointer targets and zero-sized dimensions all
come through unchanged. The view's base is what keeps that storage valid: the
parent object for a derived-type field, the backend capsule for an owned
handle, and nothing for a module variable, whose storage outlives every view of
it. An owned handle's view additionally retains the handle, because closing the
handle is what releases the storage.

## Local Structure

Expand All @@ -45,21 +143,25 @@ prik/runtime/
```

- [`handles.py`](../../../prik/runtime/handles.py) contains the Python runtime.
`NativeArrayHandleBase` validates common metadata and operations.
`NativeArrayHandleBase` validates common metadata, the dispatcher, and its
completed capabilities.
`AllocatableArray` adds allocation state, resize, and deallocation;
`PointerArray` adds association, nullification, allocation, resize, and
deallocation when supplied. Internal adapters translate generated call
signatures and descriptor handoffs.
deallocation when supplied. A handle created from a `.pyi` contract answers
from a fact tuple of its own until a call attaches generated storage; every
other handle answers from its descriptor.
- `native_support/prik_binding.h` contains header-only CPython/NumPy
conversion, descriptor, validation, capsule, and release support. Change it
only with its generated C users and `prik/compiler/native_support.py`.
- `native_support/LICENSE` is distributed with the native payload.

`to_numpy()` returns `None` for an absent allocatable or pointer and otherwise
validates the completed view policy, dtype, rank, and any required contiguity.
Native argument handoff performs the additional expected shape, layout,
alignment, byte-order, and writeability checks. A returned NumPy array is a
view of native storage; a caller that needs independent storage must copy it.
Native argument handoff is performed in the binding, against the live
descriptor, and refuses a mismatched dtype, rank, fixed shape, character width,
layout, byte order, alignment, writeability, or contiguity there. A returned
NumPy array is a view of native storage; a caller that needs independent
storage must copy it.

## Run The Handle Demonstration

Expand All @@ -75,10 +177,10 @@ Resized shape: (4,)
Generated resize received NumPy extents: True
```

The example supplies the same operation-dictionary shape as generated code.
It creates an allocatable handle, reads its live NumPy view, and routes a
resize through the adapter. The native header has no standalone Python route;
the compiler installs it into a generated `binding_support/` directory.
The example supplies the same dispatcher and capability set as generated code.
It creates an allocatable handle, reads its live NumPy view, and routes a resize
through the dispatcher. The compiler installs the native header into the
generated `binding_support/` directory.

## Change Routes And Evidence

Expand Down
11 changes: 11 additions & 0 deletions docs/user/guide/allocatables.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,21 @@ Use ordinary `T[...]` when the callable needs only array data:
def sum_values(values: Float64[:]) -> Float64: ...
```

An allocated handle may be passed to that ordinary Fortran array argument;
PRIK reads the live storage from its descriptor and applies the ordinary array
contract. This also works for optional, strided, flattened, and assumed-rank
ordinary arguments when their declared constraints match the handle.

A plain NumPy array cannot satisfy an `Allocatable[T[...]]` parameter because
it does not carry native allocation state. Use `to_numpy()` when Python needs
the current array data held by an allocatable handle.

PRIK does not wrap `Allocatable[String[N][...]]` parameters: a fixed-width
character allocatable array has no interoperable allocatable descriptor
interface that preserves its allocation semantics. A fixed-width character
module or field handle can still be viewed with `to_numpy()` and passed to an
ordinary `String[N][...]` array parameter.

---

## Allocatable Array Handle API
Expand Down
28 changes: 23 additions & 5 deletions docs/user/guide/arrays.md
Original file line number Diff line number Diff line change
Expand Up @@ -481,7 +481,7 @@ This checks the final Python axis and flattens the leading axes.
## Strided Views

Use `::` for an assumed-shape axis that accepts F-contiguous arrays and
positive-stride views without copying:
Fortran-ordered strided views without copying:

```python
from prik.contracts import Float64
Expand Down Expand Up @@ -511,9 +511,16 @@ print(out)
# [21. 45. 69.]]
```

PRIK passes the base address, extents, and positive element strides. Reversed
slices, broadcasted views, and C-order strided matrices are rejected for this
Fortran-oriented contract. Strides are not an order workaround.
Numeric assumed-shape and assumed-rank arguments receive a Fortran descriptor,
so an axis may run forward or backward. For example,
`scale_visible_rows(visible_rows[::-1, :], out[::-1, :])` updates the same
selected rows in reverse order.

The view must still be a non-overlapping Fortran-ordered array section.
Broadcasted views, overlapping views, and C-order strided matrices are
rejected. Strides are not an order workaround. Explicit-shape, assumed-size,
and character arrays use an address-based entrypoint and therefore require a
forward layout that their contract can express.

---

Expand Down Expand Up @@ -548,7 +555,18 @@ Use this list when reading or editing a generated `.pyi` contract:
- `T[rows, Flat]`: Fortran-contiguous; checked prefix, remaining axes flattened
- `Annotated[T[Flat, columns], ORDER_C]`: C-contiguous; checked suffix,
leading axes flattened
- `T[...]`: assumed-rank, currently rank 1-15
- `T[...]`: assumed-rank, currently rank 1-15, including supported reversed
sections

An allocated `Allocatable[T[...]]` handle or associated `Pointer[T[...]]`
handle can also satisfy a matching ordinary Fortran array argument. The same
element type, rank, shape, layout, contiguity, and writeability requirements
apply as for a NumPy array. This includes explicit-shape, assumed-shape,
signed-strided, assumed-size/`Flat`, and assumed-rank arguments, plus
fixed-width and assumed-width character arrays. Reversed targets are accepted
by matching numeric assumed-shape and assumed-rank arguments. An absent handle
is rejected; pass `None` only when the ordinary argument itself is optional.
C array arguments accept NumPy arrays, not Fortran descriptor handles.

Generated contracts may describe a shape with visible arguments, such as
`T[rows, columns]`. Most users should keep those generated relationships
Expand Down
7 changes: 7 additions & 0 deletions docs/user/guide/building-shared-library.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,13 @@ GNU, IFX, and Flang are tested on Linux. See
[Compiler Toolchains](../getting-started/installation.md#compiler-toolchains)
for versions and other recognized options.

On Intel PRIK adds `-standard-semantics` (and `-Munixlogical` on PGI/NVIDIA) so
a Fortran `logical` has the representation C expects. Pass
`--no-standard-logicals` to leave it out — needed only when you link prebuilt
Intel objects that were themselves compiled without it, since the option also
changes module symbol mangling. See
[Compiler options for interoperable logicals](data-types.md#compiler-options-for-interoperable-logicals).

## Build a primitive C API directly

PRIK supports C source as well. Start with the [C User Guide](c/index.md) for
Expand Down
Loading
Loading