Skip to content

swig, generator: unify accessor annotations and add custom-access groundwork#3315

Open
martin-belanger wants to merge 2 commits intolinux-nvme:masterfrom
martin-belanger:libnvme-python-bindings-redesign-plan-1
Open

swig, generator: unify accessor annotations and add custom-access groundwork#3315
martin-belanger wants to merge 2 commits intolinux-nvme:masterfrom
martin-belanger:libnvme-python-bindings-redesign-plan-1

Conversation

@martin-belanger
Copy link
Copy Markdown

Summary

This PR contains two small, incremental patches that prepare the codebase for upcoming work on Python/SWIG binding generation.

The goal is to make accessor semantics explicit and machine-readable so downstream tooling can reason about read/write behavior and custom implementations.

Patch 1: Rename annotation

  • Rename !accessors:*!access:*
  • Update generator, headers, and documentation
  • Purely mechanical change (no behavior change)

Patch 2: Explicit access model + custom support

  • Introduce a unified annotation model:

    • !access:read=<mode>,write=<mode>
    • !generate-accessors:read=<mode>,write=<mode>
  • Modes: generated, custom, none

  • Struct-level annotations define defaults; member-level overrides per axis

  • Add support in the generator

  • Update documentation

  • Annotate existing fields that already rely on custom accessors

Rationale

The previous annotation scheme could not clearly express:

  • Direction (read vs write)
  • Implementation (generated vs custom)

This becomes important for:

  • Python/SWIG binding generation
  • Consistency checks between C and bindings

These changes establish a clear and extensible model without altering existing runtime behavior.

Notes

  • No functional changes intended
  • Changes are kept incremental for easier review
  • This is groundwork for follow-up patches adding binding generation

Feedback welcome before building on top of this.

Martin Belanger added 2 commits April 23, 2026 11:09
Rename the member-level annotation prefix from !accessors: to !access:
to decouple it from the !generate-accessors struct-level annotation.

Until now, the !accessors:<value> member annotation was consumed only
by the accessor generator, and its name reflected that — it read as a
natural companion to the struct-level !generate-accessors annotation.

That one-to-one relationship is about to change. An upcoming
!generate-python struct-level annotation will also need to read the
same member annotations, both to drive the generated SWIG layer and to
validate consistency between the C and Python sides. The annotation is
no longer the private concern of the accessor generator.

Keeping the name !accessors: would make that relationship confusing:

  - The visual similarity to !generate-accessors suggests the member
    annotation is owned by accessor generation, when in fact multiple
    generators will consume it.
  - Readers (and future contributors adding more generators) would
    reasonably — but wrongly — assume the annotation is accessor
    specific and only relevant when !generate-accessors is set.

Renaming to !access: breaks that false association. The new name also
reads more naturally as English:

  char *name;  // !access:readonly
  char *addr;  // !access:none

vs.

  char *name;  // !accessors:readonly
  char *addr;  // !accessors:none

This patch is a pure rename with no behavioural change:

  - generate-accessors.py: update has_annotation() lookups and the
    docstring examples
  - private.h: rename all 47 member annotations
  - generate-accessors.md: update every example, table row and note;
    re-pad the affected table rows to preserve column alignment

Generator output (accessors.h / accessors.c / accessors.ld) is byte
identical before and after this commit.

Signed-off-by: Martin Belanger <[email protected]>
Assisted-by: Claude Opus 4.7 <[email protected]>
Replace the single-mode !access:<value> annotation with an orthogonal
two-axis spec:

  !access:read=<mode>,write=<mode>

where read and write are independent and each takes one of:
generated (generator emits the accessor), custom (hand-written
accessor in the public API, generator emits nothing), or none (no
accessor exists).  The same spec is accepted at the struct level via
!generate-accessors:read=<mode>,write=<mode>, with per-axis
inheritance from the struct-level default to member-level overrides.

The new model lets downstream consumers (the Python-binding generator,
the nvme.i consistency check) distinguish "no accessor" from
"hand-written accessor", and cleanly expresses mixed cases such as
"generated getter + hand-written setter" that the old one-dimensional
annotation could not name.  This is a clean break: the old :none /
:readonly / :writeonly / :readwrite tokens are no longer recognised.

See libnvme/tools/generator/generate-accessors.md for the full syntax,
inheritance rules, and examples.

All 47 !access: annotations in private.h are updated accordingly.  The
regenerated accessors.{h,c,ld} are byte-identical to the pre-change
baseline.

Signed-off-by: Martin Belanger <[email protected]>
Assisted-by: Claude Opus 4.7 <[email protected]>
struct libnvme_stat stat[2]; /* gendisk I/O stat */
unsigned int curr_idx; /* current index into the stat[] */
bool diffstat; // !access:none
bool diffstat; // !access:read=none,write=none
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this change necessary? It looks like a no-op. The same comment on diffstat below

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — it does look like a no-op at first glance.

The behavior isn’t defined only at the member level though — it comes from the struct-level default, and member annotations act as overrides.

In this case:

    struct libnvme_path { // !generate-accessors

implies the default:

read=generated,write=generated

So:

    bool diffstat; // !access:read=none,write=none

is explicitly overriding that default to disable both directions.

That said, I agree this adds noise when we’re just “turning things off”. One thing we can improve is choosing struct-level defaults that better match the majority of members, which would reduce the need for these overrides.

int queue_depth; // !access:read=custom,write=none
long multipath_failover_count; // !access:read=custom,write=none
long command_retry_count; // !access:read=custom,write=none
long command_error_count; // !access:read=custom,write=none
Copy link
Copy Markdown
Collaborator

@igaw igaw Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just wondering if the default should always be none when not listed, e.g.

// !access:read=custom

or would this be confusing? I'd like to reduce the 'noise' level if possible

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current behavior is that struct-level annotations define defaults, and member-level annotations override per axis.

So something like:

    // !access:read=custom

would inherit the struct-level default for write, not imply write=none.

That’s why we currently need to spell out:

    // !access:read=custom,write=none

I do agree with the goal of reducing noise though. The best way to do that is probably to pick struct-level defaults that match the common case, so we only annotate exceptions at the member level.

@martin-belanger
Copy link
Copy Markdown
Author

If it helps, we could also consider making write=none the default in some structs where setters are rarely needed, which would allow shorter forms like !access:read=custom without ambiguity.

I can make that change if you'd like.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants