Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

ROUP

ROUP is a strict, safe Rust parser for OpenMP and OpenACC directives in C, C++, and Fortran source forms. It returns typed syntax trees suitable for compiler frontends and source tools.

The parser has four deliberate rules:

  • A successful result contains typed directive, clause, selector, locator, and host-expression data.
  • A problem is returned immediately as one structured diagnostic. Trailing input, unknown fields, incompatible clauses, and unavailable features are not ignored.
  • Version selection is cumulative. An exact specification mode rejects syntax introduced later, while continuing to accept standardized syntax from older specifications even if a later specification deprecated or removed it.
  • Standard spelling aliases are accepted at the grammar boundary and map to a canonical semantic representation.

The root roup package is the complete parser and contains no unsafe Rust. A separate optional roup-capi package provides an opaque-handle C ABI. The ompparser and accparser compatibility libraries are consumers of that ABI, not alternate parser implementations.

Getting started

Rust-only parser

Build and test the safe parser without the C ABI:

cargo build -p roup
cargo test -p roup

A parser configuration always names the host-language standard and source form. VersionPolicy::Any accepts the union of standardized historical syntax; an exact configuration enforces an introduction ceiling.

use roup::api::OpenMpConfig;
use roup::version::{CStandard, HostLanguageProfile, SourceForm};

let parser = OpenMpConfig::new(
    HostLanguageProfile::C(CStandard::C23),
    SourceForm::Pragma,
)?
.parser();

let parsed = parser.parse("#pragma omp parallel private(value)")?;
assert_eq!(parsed.directive().kind().as_str(), "parallel");
assert_eq!(parsed.directive().clauses().len(), 1);
Ok::<(), Box<dyn std::error::Error>>(())

Optional C ABI

Build the ABI explicitly:

cargo build -p roup-capi --release

Include crates/roup-capi/include/roup.h and link the generated libroup_capi shared or static library. The ABI copies UTF-8 input, returns opaque handles, and requires callers to release every successful parser, directive, and error handle.

Complete repository validation

./test.sh is fail-fast. It requires initialized pinned submodules and all native toolchains, then checks formatting, lints, Rust tests, documentation, the C ABI, both compatibility adapters, every test in their pinned upstream suites, and all language examples. The current compatibility totals are 1,537/1,537 for ompparser and 920/920 for accparser, including five local contract/audit tests.

Building

ROUP is a Cargo workspace with two packages:

  • roup: the complete safe Rust parser (rlib only)
  • roup-capi: an optional C ABI (rlib, staticlib, and cdylib)

Both packages use the Rust 2024 edition. The repository MSRV is Rust 1.88, which covers the complete required dependency and tooling graph, including the pinned mdBook 0.5.4 documentation tool.

The root package is the default workspace member, so an ordinary build does not compile or link the ABI:

cargo build --locked

Build both packages when the ABI is required:

cargo build --locked --workspace
cargo build --locked --release -p roup-capi

The public C header is checked in at crates/roup-capi/include/roup.h. The ABI build copies that exact file to its Cargo output directory; it does not generate constants by scraping Rust source.

Compatibility libraries

Initialize the two pinned submodules once:

git submodule update --init --recursive

Then build and test either adapter in a separate directory:

cargo build --locked --release -p roup-capi

cmake -S compat/ompparser -B target/compat/ompparser -DCMAKE_BUILD_TYPE=Release
cmake --build target/compat/ompparser --parallel
ctest --test-dir target/compat/ompparser --output-on-failure --no-tests=error

cmake -S compat/accparser -B target/compat/accparser -DCMAKE_BUILD_TYPE=Release
cmake --build target/compat/accparser --parallel
ctest --test-dir target/compat/accparser --output-on-failure --no-tests=error

CMake treats a missing or mismatched prerequisite as an error. It never changes the recorded submodule revisions or silently substitutes a previously built library. Each adapter imports its pinned upstream test directory unchanged; at the current revisions this is 1,534 ompparser tests and 918 accparser tests, plus five repository-owned contract/audit tests across the two builds.

Rust tutorial

Configure explicitly

Parser configuration fixes the directive dialect, specification policy, host language standard, and physical source form.

use roup::api::OpenMpConfig;
use roup::version::{CStandard, HostLanguageProfile, OpenMpVersion, SourceForm};

let current = OpenMpConfig::exact(
    OpenMpVersion::V6_0,
    HostLanguageProfile::C(CStandard::C23),
    SourceForm::Pragma,
)?
.parser();

let source = "#pragma omp master";
let historical = current.parse(source)?;
assert!(historical
    .compatible_versions()
    .contains(OpenMpVersion::V6_0));
assert_eq!(historical.directive().span().slice(source), Ok("master"));
Ok::<(), Box<dyn std::error::Error>>(())

Exact mode is cumulative: it rejects syntax introduced after the selected version, but accepts older standardized syntax even if the selected specification no longer documents that spelling.

Inspect typed data

use roup::api::OpenAccConfig;
use roup::ast::{AccClausePayload, AccDirectiveKind};
use roup::version::{CStandard, HostLanguageProfile, SourceForm};

let parser = OpenAccConfig::new(
    HostLanguageProfile::C(CStandard::C23),
    SourceForm::Pragma,
)?
.parser();
let parsed = parser.parse("#pragma acc parallel async(queue)")?;
let directive = parsed.directive();

assert_eq!(directive.kind(), AccDirectiveKind::Parallel);
assert!(matches!(
    directive.clauses()[0].payload(),
    AccClausePayload::Expression(_)
));
Ok::<(), Box<dyn std::error::Error>>(())

Expressions expose a typed host-language tree through Expression::ast(). Canonical formatting walks that tree; source text is retained only as backing for checked locations. Directive and clause span() values always refer to the original physical directive, including across line continuations.

Context supplied by a compiler

Plain parse performs syntax, version, and context-independent semantic validation. When a compiler can answer declaration, association, or constant-expression questions, use parse_with_facts. Applicable facts are mandatory in that mode; an omitted fact is a hard MissingSemanticFact or MissingContext diagnostic.

use roup::validation::{AssociationKind, SemanticFacts};

let facts = SemanticFacts::new()
    .with_association(AssociationKind::SectionRegion, true);
let parsed = current.parse_with_facts("#pragma omp section", &facts)?;
assert_eq!(parsed.directive().kind().as_str(), "section");
Ok::<(), Box<dyn std::error::Error>>(())

For a sequence of paired regions, use ContextValidator with each directive’s checked source span. Mismatched or unclosed regions are errors and include the related opening location.

C tutorial

Build the optional ABI and include its checked-in header:

cargo build --release -p roup-capi
cc -std=c11 -Icrates/roup-capi/include app.c \
  -Ltarget/release -lroup_capi -o app

The exact additional system libraries required for a static link are platform specific. A shared-library run must make target/release visible to the dynamic loader.

Create, parse, query, release

#include "roup.h"

#include <stdint.h>
#include <stdlib.h>

static int check(RoupCallResult result) {
    if (result.status == ROUP_STATUS_OK) {
        return 1;
    }
    if (result.error.generation != 0) {
        RoupCallResult released = roup_error_release(result.error);
        if (released.status != ROUP_STATUS_OK) {
            abort();
        }
    }
    return 0;
}

int main(void) {
    RoupParserOptions options = {0};
    options.abi_version = ROUP_ABI_VERSION;
    options.struct_size = (uint32_t)sizeof(options);
    options.dialect = ROUP_DIALECT_OPENMP;
    options.version_policy = ROUP_VERSION_ANY;
    options.host_language = ROUP_HOST_C;
    options.host_standard = ROUP_C_23;
    options.source_form = ROUP_SOURCE_PRAGMA;

    RoupParserResult parser = roup_parser_create(options);
    if (!check(parser.result)) {
        return EXIT_FAILURE;
    }

    const uint8_t input[] = "#pragma omp parallel private(value)";
    RoupDirectiveResult parsed =
        roup_parse(parser.value, input, sizeof(input) - 1U);
    if (!check(parsed.result)) {
        (void)roup_parser_release(parser.value);
        return EXIT_FAILURE;
    }

    RoupSizeResult clauses = roup_directive_clause_count(parsed.value);
    if (!check(clauses.result) || clauses.value != 1U) {
        (void)roup_directive_release(parsed.value);
        (void)roup_parser_release(parser.value);
        return EXIT_FAILURE;
    }

    if (!check(roup_directive_release(parsed.value)) ||
        !check(roup_parser_release(parser.value))) {
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}

Production code should query and print the error message before releasing its handle. Messages use two calls: roup_error_message_length, followed by roup_error_message_copy into a buffer of exactly that many bytes. The copy is all-or-nothing and does not append \0.

Typed fields and child nodes

Each clause and directive parameter has an indexed field schema:

  1. Query the field count.
  2. Query RoupFieldInfo for each field.
  3. Dispatch on value_kind.
  4. Read boolean leaves with *_field_bool, closed numeric tags with *_field_u32, and UTF-8 leaves with the paired length/copy operations.
  5. Acquire NODE or NODE_LIST values with *_field_node, recursively query the node, then release its independent handle.

Tagged records such as apply loop modifiers, complete applied directives, induction identifiers, interoperability preferences, requirements, iterators, allocator specifications, and selectors are child nodes. They are never flattened into a clause payload string.

roup_directive_span and roup_clause_span report half-open byte ranges and one-based line and column positions in the original UTF-8 input. This mapping is preserved across C/C++ line splices and Fortran continuations.

Every non-OK result owns an error handle. Every successful parser, directive, node, and error handle must be released exactly once; stale and wrong-kind handles are hard errors.

API reference

Rust

The stable entry points are organized by responsibility:

  • roup::api: configured OpenMP and OpenACC parser facades and parse results
  • roup::ast: canonical typed directive and clause nodes
  • roup::host: typed C, C++, and Fortran expression syntax
  • roup::ir: shared typed clause components such as expressions, variables, locators, modifiers, and reduction operators
  • roup::version: specification policies, host profiles, and source forms
  • roup::diagnostic and roup::source: stable hard errors and checked spans
  • roup::validation: semantic facts and stateful region validation

The parser requires an explicit host profile and source form. A parse returns one dialect-specific result or one Diagnostic; it never returns a partial AST. Generate the complete item-level API with:

RUSTDOCFLAGS="-D warnings" cargo doc --locked -p roup --no-deps

C

The optional ABI is declared entirely by crates/roup-capi/include/roup.h. Its operations fall into five groups:

  • parser creation and release
  • parsing and directive release
  • directive kind, compatibility, checked span, parameter, and clause queries
  • typed field metadata and scalar/string/list element queries
  • diagnostic code, checked span, message, and release operations

Every fallible operation returns a result structure whose first member is a RoupCallResult. A non-OK status owns a queryable error handle. String queries use a length call followed by an all-or-nothing copy into caller-owned bytes; the ABI does not append a NUL terminator.

Kinds are returned as (dialect, ordinal) ABI values. They are explicit ABI mappings, not Rust enum layouts. Payloads are exposed only through typed field descriptors. roup_directive_span, roup_clause_span, and roup_error_span return physical UTF-8 byte, line, and column locations.

OpenMP support

ROUP models standardized OpenMP syntax from 1.0 through 6.0 for C, C++, and Fortran. These chapters describe the strict public behavior:

Version behavior

The default policy accepts the union of standardized historical syntax. An exact version rejects syntax introduced later while continuing to accept all older standardized syntax, including syntax deprecated or removed by the selected later specification.

Standard aliases are accepted and canonicalized into one semantic AST shape. Their introduction provenance participates in availability checks, and their checked source spans retain the exact physical location.

Strict typed behavior

A successful parse contains typed directive parameters, clause payloads, selectors, locators, allocator and induction records, and host expressions. Unknown syntax, malformed payloads, nonstandard extensions, invalid directive-clause combinations, duplicates, unavailable features, and trailing input are hard errors.

Optional semantic arguments added in OpenMP 6.0 are named Option<Expression> fields in their typed payloads. Historical bare forms remain accepted; empty parentheses never stand in for an omitted argument. Atomic compound spellings canonicalize to an atomic directive plus typed operation and memory-order clauses.

parse applies context-independent rules. parse_with_facts applies rules whose facts must come from an embedding compiler, and ContextValidator checks stateful region pairing across a sequence.

Regression coverage uses the configured public API in the feature-availability, context-validation, host-profile, source-span, directive-parameter, strict-payload, and strict-error test suites.

OpenACC support

ROUP models standardized OpenACC syntax from 1.0 through 3.4 for C, C++, and Fortran. These reference chapters cross-check the 3.4 vocabulary and restrictions against the official specification:

Version behavior

OpenAccConfig::new accepts the union of standardized historical syntax. OpenAccConfig::exact selects an introduction ceiling from OpenACC 1.0, 2.0, 2.5, 2.6, 2.7, 3.0, 3.1, 3.2, 3.3, or 3.4. Syntax introduced later is rejected; older standardized syntax remains accepted.

Typed and canonical results

Directive parameters, clauses, data modifiers, reduction operators, locators, queue expressions, and embedded host expressions are typed before success is returned. Standard aliases such as dtype, pcopy*, pcreate, and present_or_* are accepted and canonicalized. The host(var-list) action on update is likewise represented as the canonical self clause and typed item-list payload while keeping its OpenACC 1.0 spelling floor. Multiword directives and the standardized host_data spelling are parsed exactly; fabricated space/underscore variants are rejected. The exact source spelling remains available through its checked span, not as a competing semantic kind.

Unknown syntax, malformed payloads, nonstandard extensions, invalid directive-clause combinations, duplicate singleton clauses, unavailable features, and trailing input are hard errors. The parser never returns opaque payload text or retries with a permissive grammar.

Public behavior is covered by tests/openacc_public_api.rs, tests/openacc_directive_parameters.rs, tests/openacc_line_continuations.rs, tests/feature_availability.rs, and the strict payload and error suites. C ABI and accparser adapter checks are separate consumers of the safe parser.

OpenACC 3.4 Directives and Clauses

This reference catalogue documents the OpenACC 3.4 vocabulary from the OpenACC Application Programming Interface Version 3.4. ROUP also accepts syntax standardized by earlier OpenACC specifications. Exact version modes use introduction ceilings, so an older standardized form remains accepted by later modes even when a later document no longer lists it.

Purpose

This document serves as a complete keyword inventory for development and reference. Each entry includes:

  • Specification section and page numbers
  • Categorization and properties
  • No duplication - each keyword appears once

Coverage

  • Directives/Constructs - Compute, data, loop, synchronization, declaration, and runtime directives
  • Clauses - Standard clause keywords and their typed payload families
  • Modifiers - Data clause modifiers, gang/worker/vector modifiers, collapse modifiers
  • Special Values - Async values, device types, default values
  • Reduction Operators - All supported reduction operations
  • Parallelism Levels - Gang, worker, vector, seq

Directives and Constructs

Compute Constructs

  • parallel (§2.5.1; p.33; category: compute; association: block; properties: creates gang-worker-vector parallelism)
  • serial (§2.5.2; p.34; category: compute; association: block; properties: serialized execution on device)
  • kernels (§2.5.3; p.35; category: compute; association: block; properties: compiler-optimized kernel launch)

Data Constructs

  • data (§2.6.5; p.43; category: data; association: block; properties: structured data lifetime)
  • enter data (§2.6.6; p.45; category: data; association: executable; properties: dynamic data region entry)
  • exit data (§2.6.6; p.45; category: data; association: executable; properties: dynamic data region exit)
  • host_data (§2.8; p.62; category: data; association: block; properties: host pointer mapping)

Loop Constructs

  • loop (§2.9; p.64; category: loop; association: loop nest; properties: loop parallelization)
  • parallel loop (§2.11; p.75; category: combined; association: loop nest; properties: parallel + loop combined)
  • serial loop (§2.11; p.75; category: combined; association: loop nest; properties: serial + loop combined)
  • kernels loop (§2.11; p.75; category: combined; association: loop nest; properties: kernels + loop combined)

Synchronization Constructs

  • atomic (§2.12; p.77; category: synchronization; association: statement; properties: atomic memory operations)
  • cache (§2.10; p.75; category: synchronization; association: loop; properties: cache hint)
  • wait (§2.16.3; p.100; category: synchronization; association: executable; properties: async queue synchronization)

Declaration Directives

  • declare (§2.13; p.81; category: declarative; association: scope; properties: device data declaration)
  • routine (§2.15.1; p.91; category: declarative; association: function; properties: device routine declaration)

Runtime Directives

  • init (§2.14.1; p.84; category: runtime; association: executable; properties: device initialization)
  • shutdown (§2.14.2; p.85; category: runtime; association: executable; properties: device shutdown)
  • set (§2.14.3; p.87; category: runtime; association: executable; properties: runtime configuration)
  • update (§2.14.4; p.88; category: runtime; association: executable; properties: explicit data transfer)

Special Constructs

  • do concurrent (§2.17.2; p.102; category: integration; association: Fortran; properties: Fortran do concurrent mapping)

Clauses

Compute Clauses

  • if (§2.5.6; p.37; category: conditional; applicable to: parallel, serial, kernels, host_data, atomic, init, set, update)
  • self (§2.5.7; p.37; category: conditional; applicable to: parallel, serial, kernels; properties: execute on host without data movement)
  • async (§2.5.8, §2.16.1; pp.37, 99; category: synchronization; applicable to: parallel, serial, kernels, data, enter data, exit data, update, wait)
  • wait (§2.5.9, §2.16.2; pp.37, 100; category: synchronization; applicable to: parallel, serial, kernels, data, enter data, exit data, update)
  • num_gangs (§2.5.10; p.37; category: parallelism; applicable to: parallel, kernels; properties: specifies number of gangs)
  • num_workers (§2.5.11; p.38; category: parallelism; applicable to: parallel, kernels; properties: specifies workers per gang)
  • vector_length (§2.5.12; p.38; category: parallelism; applicable to: parallel, kernels; properties: specifies vector length per worker)
  • private (§2.5.13, §2.9.10; pp.38, 70; category: data sharing; applicable to: parallel, serial, kernels, loop)
  • firstprivate (§2.5.14; p.38; category: data sharing; applicable to: parallel, serial, kernels; properties: initialized private variables)
  • reduction (§2.5.15, §2.9.11; pp.39, 71; category: data sharing; applicable to: parallel, kernels, loop; properties: reduction operations)
  • default (§2.5.16; p.40; category: data sharing; applicable to: parallel, serial, kernels, data; properties: values are none or present)

Data Clauses

  • copy (§2.7.7; p.54; category: data movement; properties: copy in and copy out)
  • copyin (§2.7.8; p.55; category: data movement; properties: copy to device)
  • copyout (§2.7.9; p.56; category: data movement; properties: copy from device)
  • create (§2.7.10, §2.13.2; pp.57, 83; category: data allocation; properties: allocate on device)
  • no_create (§2.7.11; p.57; category: data allocation; properties: use if present, don’t create)
  • delete (§2.7.12; p.58; category: data allocation; properties: deallocate from device)
  • present (§2.7.6; p.53; category: data presence; properties: data must be present on device)
  • deviceptr (§2.7.5; p.53; category: data presence; properties: device pointer)
  • attach (§2.7.13; p.59; category: pointer; properties: attach pointer to device address)
  • detach (§2.7.14; p.59; category: pointer; properties: detach pointer from device address)

Synonyms: The specification preserves historical aliases such as pcopy, pcopyin, pcopyout, pcreate and their present_or_* counterparts. ROUP accepts these spellings and canonicalizes them to the corresponding typed data-clause kind. The checked source span still selects the exact spelling written by the caller.

Host-Device Interaction Clauses

  • use_device (§2.8.1; p.63; category: host access; applicable to: host_data; properties: map device pointers to host)
  • if_present (§2.8.3; p.63; category: conditional; applicable to: host_data, update; properties: conditional on presence)

Loop Clauses

  • collapse (§2.9.1; p.65; category: loop transformation; applicable to: loop; properties: collapse nested loops)
  • gang (§2.9.2; p.66; category: parallelism; applicable to: loop; properties: gang-level parallelism)
  • worker (§2.9.3; p.68; category: parallelism; applicable to: loop; properties: worker-level parallelism)
  • vector (§2.9.4; p.68; category: parallelism; applicable to: loop; properties: vector-level parallelism)
  • seq (§2.9.5; p.68; category: parallelism; applicable to: loop; properties: sequential execution)
  • independent (§2.9.6; p.69; category: parallelism; applicable to: loop; properties: loop iterations are independent)
  • auto (§2.9.7; p.69; category: parallelism; applicable to: loop; properties: compiler decides parallelism)
  • tile (§2.9.8; p.69; category: loop transformation; applicable to: loop; properties: tile nested loops)
  • device_type (§2.9.9; p.70; category: device-specific; applicable to: loop, compute constructs; properties: device-specific clauses)

Declaration Clauses

  • device_resident (§2.13.1; p.82; category: data declaration; applicable to: declare; properties: data resides on device)
  • link (§2.13.3; p.84; category: data declaration; applicable to: declare; properties: static device linkage)

Special Clauses

  • finalize (§2.6.6; p.46; category: data management; applicable to: exit data; properties: force deallocation)
  • bind (§2.15.1; p.92; category: routine; applicable to: routine; properties: specify device routine name)
  • nohost (§2.15.1; p.93; category: routine; applicable to: routine; properties: routine only on device)

Modifiers

Modifiers are keywords that modify the behavior of clauses. They appear as part of clause syntax to refine clause semantics.

Data Clause Modifiers

  • always (§2.7.4; p.52; data clause modifier; forces data transfer even if present)
  • zero (§2.7.4; p.52; data clause modifier; zero memory on allocation)
  • readonly (§2.7.4; p.52; data clause modifier; read-only access)

Gang Clause Modifiers

  • num (§2.9.2; p.66; gang modifier; specifies number of gangs)
  • dim (§2.9.2; p.67; gang modifier; specifies gang dimension)
  • static (§2.9.2; p.67; gang modifier; static gang distribution)

Worker Clause Modifiers

  • num (§2.9.3; p.68; worker modifier; specifies number of workers)

Vector Clause Modifiers

  • length (§2.9.4; p.68; vector modifier; specifies vector length)

Collapse Clause Modifiers

  • force (§2.9.1; p.65; collapse modifier; force collapse even with dependencies)

Cache Clause Modifiers

  • readonly (§2.10; p.75; cache modifier; read-only cache hint)

Special Values and Constants

Async Values

  • acc_async_default (§2.16; p.98; async value; default async queue)
  • acc_async_noval (§2.16; p.98; async value; no async queue specified)
  • acc_async_sync (§2.16; p.98; async value; synchronous execution)

Device Types

  • * (§2.4; p.31; device type; all device types)
  • host (§2.4; p.31; device type; host device)
  • nvidia (§2.4; p.31; device type; NVIDIA devices)
  • radeon (§2.4; p.31; device type; AMD Radeon devices)
  • default (§2.4; p.31; device type; implementation default)

Default Clause Values

  • none (§2.5.16; p.40; default value; no implicit data sharing)
  • present (§2.5.16; p.40; default value; assume all data present)

Reduction Operators

Operators used with the reduction clause for parallel reduction operations.

Arithmetic Operators

  • + (§2.5.15, §2.9.11; pp.39, 71; addition)
  • * (§2.5.15, §2.9.11; pp.39, 71; multiplication)
  • max (§2.5.15, §2.9.11; pp.39, 71; maximum value)
  • min (§2.5.15, §2.9.11; pp.39, 71; minimum value)

Bitwise Operators

  • & (§2.5.15, §2.9.11; pp.39, 71; bitwise AND)
  • | (§2.5.15, §2.9.11; pp.39, 71; bitwise OR)
  • ^ (§2.5.15, §2.9.11; pp.39, 71; bitwise XOR)

Logical Operators

  • && (§2.5.15, §2.9.11; pp.39, 71; logical AND)
  • || (§2.5.15, §2.9.11; pp.39, 71; logical OR)

Fortran-Specific Operators

  • .and. (§2.5.15, §2.9.11; pp.39, 71; Fortran logical AND)
  • .or. (§2.5.15, §2.9.11; pp.39, 71; Fortran logical OR)
  • .eqv. (§2.5.15, §2.9.11; pp.39, 71; Fortran logical equivalence)
  • .neqv. (§2.5.15, §2.9.11; pp.39, 71; Fortran logical non-equivalence)
  • iand (§2.5.15, §2.9.11; pp.39, 71; Fortran bitwise AND)
  • ior (§2.5.15, §2.9.11; pp.39, 71; Fortran bitwise OR)
  • ieor (§2.5.15, §2.9.11; pp.39, 71; Fortran bitwise XOR)

Parallelism Levels

OpenACC defines a three-level parallelism hierarchy:

  • gang (§2.2.3; p.23; parallelism level; coarse-grain parallelism, analogous to thread blocks)
  • worker (§2.2.3; p.23; parallelism level; medium-grain parallelism, analogous to threads)
  • vector (§2.2.3; p.23; parallelism level; fine-grain parallelism, analogous to SIMD lanes)
  • seq (§2.9.5; p.68; parallelism level; sequential execution, no parallelism)

Atomic Operation Keywords

  • read (§2.12; p.77; atomic operation; atomic read)
  • write (§2.12; p.78; atomic operation; atomic write)
  • update (§2.12; p.78; atomic operation; atomic update)
  • capture (§2.12; p.79; atomic operation; atomic capture)

Runtime Clause Keywords

Set Directive Clauses

  • device_type (§2.14.3; p.87; applicable to: set; specifies device type)
  • device_num (§2.14.3; p.87; applicable to: set; specifies device number)
  • default_async (§2.14.3; p.87; applicable to: set; sets default async queue)

Update Directive Clauses

  • self (§2.14.4; p.88; applicable to: update; copy to host)
  • host (§2.14.4; p.88; applicable to: update; alias for self)
  • device (§2.14.4; p.88; applicable to: update; copy to device)

Routine Directive Clauses

  • gang (§2.15.1; p.92; applicable to: routine; routine contains gang-level parallelism)
  • worker (§2.15.1; p.92; applicable to: routine; routine contains worker-level parallelism)
  • vector (§2.15.1; p.92; applicable to: routine; routine contains vector-level parallelism)
  • seq (§2.15.1; p.92; applicable to: routine; routine is sequential)

OpenACC 3.4 Directive–Clause Matrix

This matrix cross-references OpenACC 3.4 directives with their allowed clauses and enumerates clause-level modifiers and arguments. Section numbers and page references point back to the canonical OpenACC 3.4 PDF. ROUP applies these context-independent combinations as hard validation rules; rules requiring program facts are checked by parse_with_facts when the embedding compiler supplies those facts.

Directive coverage

Parallel construct (§2.5.1, p.33)

  • async [(async-argument)] — asynchronous queue selection; semantics defined in §2.16.1 (p.99) with async-argument rules in §2.16 (p.98).
  • wait [(wait-argument)] — queue synchronization; wait-argument syntax in §2.16 (p.99) and clause behavior in §2.16.2 (p.100).
  • num_gangs(int-expr-list) — up to three gang dimensions (missing entries default to 1) for parallel regions; details in §2.5.10 (p.37).
  • num_workers(int-expr) — worker count per gang (§2.5.11, p.38).
  • vector_length(int-expr) — vector lane count per worker (§2.5.12, p.38).
  • device_type(device-type-list) — device-specific clause selection (§2.4, p.31).
  • if(condition) — host vs device execution control (§2.5.6, p.37).
  • self[(condition)] — execute region on host without moving data (§2.5.7, p.37).
  • reduction(operator:var-list) — reduction variables imply copy semantics (§2.5.15, p.39).
  • Data clauses copy, copyin, copyout, create, no_create, present, deviceptr, attach each accept optional modifier lists from §2.7.4 (p.52) and actions defined in §§2.7.1–2.7.14 (pp.48–60).
  • private(var-list) — private instances per gang (§2.5.13, p.38).
  • firstprivate(var-list) — initialize privates from host values (§2.5.14, p.38).
  • default(none|present) — default data scoping (§2.5.16, p.40).

Serial construct (§2.5.2, p.34)

  • Permits the same clauses as the parallel construct except that num_gangs, num_workers, and vector_length are forbidden (§2.5.2, p.34). Other clause semantics match the sections cited above.

Kernels construct (§2.5.3, p.35)

  • async[(async-argument)] and wait[(wait-argument)] per §§2.16.1–2.16.2 (pp.99–100).
  • num_gangs(int-expr) — single argument specifying gangs per kernel (§2.5.10, p.37).
  • num_workers(int-expr) and vector_length(int-expr) as in §§2.5.11–2.5.12 (p.38).
  • device_type, if, self, and all data clauses (copy, copyin, copyout, create, no_create, present, deviceptr, attach) with modifiers per §§2.4 and 2.7.
  • default(none|present) per §2.5.16 (p.40).

Data construct (§2.6.5, p.43)

  • if(condition) for conditional region creation (§2.6.5, p.43).
  • async[(async-argument)] and wait[(wait-argument)] per §§2.16.1–2.16.2 (pp.99–100).
  • device_type(device-type-list) per §2.4 (p.31).
  • Data movement clauses copy, copyin, copyout, create, no_create, present, deviceptr, attach with modifier lists from §2.7.4 (p.52) and semantics in §§2.7.1–2.7.14 (pp.48–60).
  • default(none|present) (treated as in §2.5.16, p.40).

Enter data directive (§2.6.6, p.45)

  • if(condition) optional guard (§2.6.6, p.45).
  • async[(async-argument)] and wait[(wait-argument)] per §§2.16.1–2.16.2 (pp.99–100).
  • copyin([modifier-list:]var-list), create([modifier-list:]var-list), and attach(var-list) with data clause modifiers from §2.7.4 (p.52).

Exit data directive (§2.6.6, p.45)

  • if(condition), async[(async-argument)], wait[(wait-argument)] as above.
  • copyout([modifier-list:]var-list), delete(var-list), detach(var-list) with modifiers from §2.7.4 (p.52) and clause semantics in §§2.7.9–2.7.14 (pp.56–60).
  • finalize — forces dynamic reference counters to zero (§2.6.6, p.46).

Host_data construct (§2.8, p.62)

  • use_device(var-list) — maps host pointers to device addresses (§2.8.1, p.63).
  • if(condition) and if_present clauses (§§2.8.2–2.8.3, p.63).

Loop construct (§2.9, p.64)

  • collapse([force:]n) — loop nest collapsing with optional force qualifier (§2.9.1, p.65).
  • gang[(gang-arg-list)] — optional num:, dim:, and static: modifiers per §2.9.2 (pp.66–67).
  • worker[( [num:]int-expr )] (§2.9.3, p.68).
  • vector[( [length:]int-expr )] (§2.9.4, p.68).
  • seq, independent, and auto exclusivity rules in §§2.9.5–2.9.7 (pp.68–69).
  • tile(size-expr-list) with optional * entries (§2.9.8, p.69).
  • device_type(device-type-list) per §2.9.9 (p.70).
  • private(var-list) (§2.9.10, p.70) and reduction(operator:var-list) (§2.9.11, p.71).

Cache directive (§2.10, p.75)

  • cache([readonly:]var-list) — optional readonly modifier constrains writes (§2.10, p.75).

Combined constructs (§2.11, p.75)

  • parallel loop, serial loop, and kernels loop accept any clause allowed on both the outer construct and the loop construct; reductions imply copy semantics (§2.11, pp.75–76).

Atomic construct (§2.12, pp.77–80)

  • Optional atomic-clause of read, write, update, or capture; Fortran syntax variants follow §2.12 (pp.77–80).
  • Optional if(condition) clause (§2.12, p.77).

Declare directive (§2.13, pp.81–84)

  • Data clauses copy, copyin, copyout, create, present, deviceptr as in §2.13 (pp.82–83).
  • device_resident(var-list) (§2.13.1, p.82).
  • link(var-list) for static linkage of device allocations (§2.13.3, p.84).

Init directive (§2.14.1, p.84)

  • device_type(device-type-list) and device_num(int-expr) to select targets (§2.14.1, p.84).
  • Optional if(condition) guard (§2.14.1, p.84).

Shutdown directive (§2.14.2, p.85)

  • Same clause set as init: device_type, device_num, and optional if(condition) (§2.14.2, p.85).

Set directive (§2.14.3, p.87)

  • default_async(async-argument) — sets the default queue (§2.14.3, p.87).
  • device_num(int-expr) and device_type(device-type-list) adjust internal control variables (§2.14.3, p.87).
  • Optional if(condition) (§2.14.3, p.87).

Update directive (§2.14.4, p.88)

  • async[(async-argument)], wait[(wait-argument)], device_type(device-type-list), and if(condition) as above.
  • if_present skip modifier (§2.14.4, p.89).
  • Data movement clauses self(var-list), host(var-list), device(var-list) with semantics in §2.14.4 (pp.88–89).

Wait directive (§2.16.3, p.100; see also §2.14.5)

  • Optional wait-argument tuple [devnum:int-expr:][queues:]async-argument-list per §2.16 (p.99).
  • Optional async[(async-argument)] to queue the wait (§2.16.3, p.100).
  • Optional if(condition) (§2.16.3, p.100).

Routine directive (§2.15.1, pp.91–97)

  • Parallelism clauses gang[(dim:int-expr)], worker, vector, and seq define callable levels (§2.15.1, pp.91–93).
  • bind(name|string) for device linkage (§2.15.1, pp.93–94).
  • device_type(device-type-list) for specialization (§2.15.1, pp.94–95).
  • nohost to omit host compilation (§2.15.1, pp.94–95).

Do concurrent integration (§2.17.2, p.102)

  • When combined with loop constructs, local, local_init, shared, and default(none) locality specs map to private, firstprivate, copy, and default(none) clauses on the enclosing compute construct (§2.17.2, p.102).

Clause reference

Device-specific clause (§2.4, pp.31–33)

  • device_type(device-type-list) partitions clause lists by architecture name or *; default clauses apply when no device-specific override exists (§2.4, pp.31–33).
  • Abbreviation dtype is permitted (§2.4, p.31).
  • Device-specific clauses are limited per directive as documented in each directive section.

if clause (§§2.5.6 & 2.8.2, p.37 & p.63)

  • Compute constructs: true runs on the device; false reverts to host execution (§2.5.6, p.37).
  • Host_data: governs creation of device pointer aliases (§2.8.2, p.63).
  • Enter/exit/update data: conditional data movement (§2.6.6, p.45; §2.14.4, p.88).

self clause (§§2.5.7 & 2.14.4, pp.37 & 88)

  • On compute constructs, self[(condition)] forces host execution when true (§2.5.7, p.37).
  • On update, self(var-list) copies from device to host for uncaptured data (§2.14.4, p.88).

async clause (§2.16.1, p.99)

  • Allowed on parallel, serial, kernels, data constructs, enter/exit data, update, and wait directives (§2.16.1, p.99).
  • async-argument values: nonnegative integers or acc_async_default, acc_async_noval, acc_async_sync (§2.16, p.98).
  • Missing clause implies synchronous execution; empty argument implies acc_async_noval (§2.16.1, p.99).

wait clause (§2.16.2, p.100)

  • Accepts the wait-argument tuple defined in §2.16 (p.99).
  • Without arguments waits on all queues of the current device; with arguments delays launch until specified queues drain (§2.16.2, p.100).

num_gangs clause (§2.5.10, p.37)

  • Parallel construct: up to three integers for gang dimensions; defaults to 1 when omitted (§2.5.10, p.37).
  • Kernels construct: single argument per generated kernel (§2.5.10, p.37).
  • Implementations may cap values based on device limits (§2.5.10, p.37).

num_workers clause (§2.5.11, p.38)

  • Sets workers per gang; unspecified defaults are implementation-defined (§2.5.11, p.38).

vector_length clause (§2.5.12, p.38)

  • Sets vector lanes per worker; unspecified defaults are implementation-defined (§2.5.12, p.38).

private clause (§§2.5.13 & 2.9.10, pp.38 & 70)

  • Compute constructs: allocate private copies for gang members (§2.5.13, p.38).
  • Loop constructs: each iteration gets a private copy; allowed only where clause lists permit (§2.9.10, p.70).

firstprivate clause (§2.5.14, p.38)

  • Initializes private variables from original values at region entry (§2.5.14, p.38).

reduction clause (§§2.5.15 & 2.9.11, pp.39 & 71)

  • Supports operators +, *, max, min, bitwise ops, logical ops, and Fortran iand/ior/ieor with initialization table specified in §2.5.15 (pp.39–40).
  • Applies element-wise to arrays/subarrays; implies appropriate data clauses (§2.5.15, p.39).
  • Loop reductions follow §2.9.11 (pp.71–72).

default clause (§2.5.16, p.40)

  • default(none) requires explicit data clauses; default(present) asserts device presence (§2.5.16, p.40).

Data clause framework (§§2.7–2.7.4, pp.48–53)

  • Data specification syntax in §2.7.1 (pp.48–49).
  • Data actions (copy, create, delete, etc.) in §2.7.2 (pp.50–52).
  • Error conditions in §2.7.3 (p.52).
  • Modifier list tokens: always, alwaysin, alwaysout, capture, readonly, zero (§2.7.4, p.52).

deviceptr clause (§2.7.5, p.53)

  • Treats variables as preallocated device pointers; disallows conflicting data actions (§2.7.5, p.53).

present clause (§2.7.6, p.53)

  • Requires data to exist on the device; raises errors otherwise (§2.7.6, p.53).

copy/copyin/copyout clauses (§§2.7.7–2.7.9, pp.54–56)

  • copy performs in/out transfers; copyin is host→device; copyout is device→host (§§2.7.7–2.7.9, pp.54–56).
  • Respect modifier semantics from §2.7.4.

create clause (§§2.7.10 & 2.13.2, pp.57 & 83)

  • Allocates device storage without transfer (§2.7.10, p.57); declare directive variant described in §2.13.2 (p.83).

no_create clause (§2.7.11, p.57)

  • Asserts that data already exists on device; no allocation occurs (§2.7.11, p.57).

delete clause (§2.7.12, p.58)

  • Deallocates device storage at region exit (§2.7.12, p.58).

attach/detach clauses (§§2.7.13–2.7.14, pp.59–60)

  • Manage pointer attachments to device memory (§§2.7.13–2.7.14, pp.59–60).

use_device clause (§2.8.1, p.63)

  • Temporarily remaps host pointers to device addresses within host_data regions (§2.8.1, p.63).

if_present clause (§§2.8.3 & 2.14.4, pp.63 & 89)

  • Skips operations when data is absent on the device (§2.8.3, p.63; §2.14.4, p.89).

collapse clause (§2.9.1, p.65)

  • Optional force keyword overrides dependency analysis; requires positive iteration counts (§2.9.1, p.65).

gang clause (§2.9.2, pp.66–67)

  • gang-arg-list allows one each of num:, dim:, static: modifiers; dim is limited to 1–3 (§2.9.2, pp.66–67).

worker clause (§2.9.3, p.68)

  • Optional num: argument; interacts with compute scopes as described in §2.9.3 (p.68).

vector clause (§2.9.4, p.68)

  • Optional length: argument; selects vector mode (§2.9.4, p.68).

seq clause (§2.9.5, p.68)

  • Forces sequential execution of the associated loop (§2.9.5, p.68).

independent clause (§2.9.6, p.69)

  • Asserts absence of cross-iteration dependencies (§2.9.6, p.69).

auto clause (§2.9.7, p.69)

  • Delegates loop scheduling to implementation; interacts with routine clause inference (§2.9.7, p.69).

tile clause (§2.9.8, p.69)

  • Breaks iteration space into tile sizes; * uses runtime-determined tile length (§2.9.8, p.69).

device_type clause on loops (§2.9.9, p.70)

  • Restricts subsequent clauses to specified device types (§2.9.9, p.70).

device_resident clause (§2.13.1, pp.82–83)

  • Forces static device allocation with reference counting rules (§2.13.1, pp.82–83).
  • Creates persistent device linkages for large host data (§2.13.3, pp.83–84).

bind clause (§2.15.1, pp.93–94)

  • Sets alternate device symbol name (identifier or string) (§2.15.1, pp.93–94).

device_num and default_async clauses (§2.14.3, p.87)

  • Modify internal control variables acc-current-device-num-var and acc-default-async-var (§2.14.3, p.87).

nohost clause (§2.15.1, pp.94–95)

  • Suppresses host code generation for routines; cascades to dependent procedures (§2.15.1, pp.94–95).

finalize clause (§2.6.6, p.46)

  • Available on exit data; zeroes dynamic and attachment counters (§2.6.6, p.46).

wait-argument modifiers (§2.16, p.99)

  • devnum:int-expr: selects device; optional queues: prefix clarifies async argument list (§2.16, p.99).

async-value semantics (§2.16, p.98)

  • Maps async arguments to queue identifiers; acc_async_sync enforces synchronous completion, acc_async_noval uses default queue (§2.16, p.98).

OpenACC 3.4 Directive and Clause Restrictions

This digest summarizes rules and mandatory conditions attached to OpenACC 3.4 directives and clauses. Entries are grouped by the defining section and page references match the official specification pagination. ROUP rejects context-independent violations during parse; restrictions that depend on the surrounding program require parse_with_facts, and missing required facts are hard errors.

Compute constructs (§2.5.4, p.36)

  • Programs must not branch into or out of a compute construct.
  • Only the async, wait, num_gangs, num_workers, and vector_length clauses may follow a device_type clause on any compute construct.
  • At most one if clause may appear on a compute construct.
  • At most one default clause may appear and its value must be either none or present.
  • A reduction clause must not appear on a parallel construct whose num_gangs clause has more than one argument.

Compute construct errors (§2.5.5, p.37)

  • Errors raised by violating compute construct semantics follow §2.5.5; implementations must signal acc_error_host_only, acc_error_invalid_compute_region, acc_error_invalid_parallelism, or acc_error_invalid_matrix_shape as described in the specification when these conditions are detected.

Enter/exit data directives (§2.6.6, p.46)

  • enter data directives must include at least one of: copyin, create, or attach.
  • exit data directives must include at least one of: copyout, delete, or detach.
  • Only one if clause may appear on either directive.
  • finalize on exit data resets dynamic and attachment counters to zero for the listed variables; without it the counters are decremented normally.

Data environment (§2.6, pp.43–47)

  • Implicit data lifetime management must obey the reference counter semantics in §§2.6.3–2.6.8; structured and dynamic counters must never become negative.
  • Pointer attachments must respect the attach/detach counter pairing in §§2.6.7–2.6.8.

Host_data construct (§2.8, pp.62–63)

  • use_device lists must reference variables that are present on the device; otherwise behavior is undefined.
  • Host pointers aliased inside host_data regions must not be dereferenced on the host while mapped to device addresses.

Loop construct (§2.9, pp.64–71)

  • Only collapse, gang, worker, vector, seq, independent, auto, and tile clauses may follow a device_type clause.
  • worker and vector clause arguments must be invariant within the surrounding kernels region.
  • Loops without seq must satisfy: loop variable is integer/pointer/random-access iterator, iteration monotonicity, and constant-time trip count computation.
  • Only one of seq, independent, or auto may appear.
  • gang, worker, and vector clauses are mutually exclusive with an explicit seq clause.
  • A loop with a gang/worker/vector clause must not lexically enclose another loop with an equal or higher parallelism level unless the parent compute scope differs.
  • At most one gang clause may appear per loop construct.
  • tile and collapse must not be combined on loops associated with do concurrent.
  • Each associated loop in a tile construct (except the innermost) must contain exactly one loop or loop nest.
  • private clauses on loops must honor Fortran optional argument rules (§2.17.1, p.100).

Cache directive (§2.10, p.75)

  • References within the loop iteration must stay inside the index ranges listed in the cache directive.
  • Fortran optional arguments used in cache directives must follow §2.17.1.

Combined constructs (§2.11, p.76)

  • Combined constructs inherit all restrictions from their constituent parallel, serial, kernels, and loop components.

Atomic construct (§2.12, pp.77–81)

  • All atomic accesses to a given storage location must use the same type and type parameters.
  • The storage location designated by x must not exceed the hardware’s maximum native atomic width.
  • At most one if clause may appear on an atomic construct.

Declare directive (§2.13, pp.81–84)

  • declare must share scope with the declared variables (or enclosing function/module scope for Fortran).
  • At least one clause is required.
  • Clause arguments must be variable names or Fortran common block names; each variable may appear only once across declare clauses within a program unit.
  • Fortran assumed-size dummy arrays cannot appear; pointer arrays lose association on the device.
  • Fortran module declaration sections allow only create, copyin, device_resident, and link; C/C++ global scope allows only create, copyin, deviceptr, device_resident, and link.
  • C/C++ extern variables are limited to create, copyin, deviceptr, device_resident, and link.
  • link clauses must appear at global/module scope or reference extern/common-block entities.
  • declare regions must not contain longjmp/setjmp mismatches or uncaught C++ exceptions.
  • Fortran optional dummy arguments in data clauses must respect §2.17.1.

Init directive (§2.14.1, p.85)

  • May appear only in host code.
  • Re-initializing with different device types without shutting down is implementation-defined.
  • Initializing a device type not used by compiled accelerator regions yields undefined behavior.

Shutdown directive (§2.14.2, p.85)

  • May appear only in host code.

Set directive (§2.14.3, p.87)

  • Host-only directive.
  • default_async accepts only valid async identifiers; acc_async_noval has no effect, acc_async_sync forces synchronous execution on the default queue, and acc_async_default restores the initial queue.
  • Must include at least one of default_async, device_num, or device_type.
  • Duplicate clause kinds are forbidden on the same directive.

Update directive (§2.14.4, pp.88–90)

  • Requires at least one of self, host, or device clauses.
  • If if_present is absent, all listed variables must already be present on the device.
  • Only async and wait clauses may follow device_type.
  • At most one if clause may appear; it must evaluate to a scalar logical/integer value (Fortran vs C/C++ rules).
  • Noncontiguous subarrays are permitted; implementations may choose between multiple transfers or pack/unpack strategies but must not transfer outside the minimal containing contiguous region.
  • Struct/class and derived-type member restrictions follow §2.14.4; parent objects cannot simultaneously use subarray notation with member subarrays.
  • Fortran optional arguments in self, host, and device follow §2.17.1.
  • Directive must occupy a statement position (cannot replace the body after conditional headers or labels).

Wait directive (§2.16.3, p.100)

  • devnum values in wait-arguments must identify valid devices; invalid values trigger runtime errors (§2.16.3).
  • Queue identifiers must be valid async arguments or errors result (§2.16.3).

Routine directive (§2.15.1, pp.91–97)

  • Implicit routine directives derive from usage; implementations must propagate relevant clauses to dependent procedures and avoid infinite recursion when determining implicit attributes.
  • gang dimension argument must be an integer constant expression in {1,2,3}.
  • worker routines cannot be parents of gang routines; vector routines cannot be parents of worker or gang routines; seq routines cannot be parents of parallel routines.
  • Procedures compiled with nohost must not be called from host-only regions; enclosing procedures must also carry nohost when they call such routines.

Do concurrent integration (§2.17.2, pp.102–103)

  • When mapping Fortran do concurrent locality specs to OpenACC clauses, users must ensure host/device sharing matches the specified locality (e.g., local to private, local_init to firstprivate).

Data clauses (§§2.7–2.7.14, pp.48–60)

  • Clause arguments must reference contiguous array sections or pointer references as defined in §2.7.1.
  • Overlapping array sections in data clauses yield unspecified behavior (§2.7.1).
  • Modifier keywords (always, alwaysin, alwaysout, capture, readonly, zero) enforce the transfer behaviors in §2.7.4 and must not contradict device pointer semantics.
  • deviceptr variables cannot appear in other data clauses in the same region (§2.7.5).
  • present clauses require existing device data; absence triggers runtime errors (§2.7.6).
  • no_create forbids allocation and therefore requires prior presence (§2.7.11).
  • attach/detach must pair with pointer lifetimes and respect attachment counters (§§2.7.13–2.7.14).

Cache clause (§2.10, p.75)

  • Array references must remain within the listed cache ranges per iteration; violations are undefined.

Clause argument rules (§2.16, p.98)

  • async-argument values are limited to nonnegative integers or the special constants acc_async_default, acc_async_noval, acc_async_sync.
  • wait-argument syntax [devnum:int-expr:][queues:]async-argument-list requires valid device numbers and async identifiers.

OpenMP syntax catalogue and version policy

ROUP models standardized OpenMP syntax from version 1.0 through 6.0. The public OmpDirectiveKind and OmpClauseKind enums are the canonical semantic catalogue; directive parameters and clause payload enums describe the data attached to each kind.

For normative syntax and meaning, consult the OpenMP 6.0 specification and the earlier specification that introduced a historical form.

Supported specification policies

OpenMpConfig::new uses VersionPolicy::Any, the union of standardized syntax from every supported OpenMP version. OpenMpConfig::exact selects one of:

1.0, 1.1, 2.0, 2.5, 3.0, 3.1, 4.0, 4.5, 5.0, 5.1, 5.2, or 6.0.

An exact version is an introduction ceiling. A feature first standardized after the selected version is rejected. A feature standardized at or before the selected version remains accepted, even if the later specification deprecated, renamed, or removed it. For example, OpenMP 6.0 mode still accepts the historical master directive.

This cumulative policy is intentionally different from asking whether a spelling appears in the selected specification document. It lets tools parse maintained historical code without weakening validation of unknown or nonstandard syntax.

Canonical aliases

Specification-defined aliases are accepted at the syntax boundary and mapped to one semantic representation. Canonicalization never erases the checked source location: directive and clause spans still select the exact spelling in the original physical source.

Alias provenance is included in availability computation. An alias therefore cannot make a feature appear in a specification older than the one that first standardized that spelling.

What a catalogue entry guarantees

A public kind is not merely a recognized keyword. A successful public parse also guarantees:

  • a complete typed directive parameter and clause payload;
  • a valid host-language expression, type name, identifier, or locator tree;
  • availability under the selected OpenMP and host-language versions;
  • context-independent directive, clause, duplicate, and nesting checks; and
  • exact checked spans for directive and clause names.

Unknown keywords, implementation spellings without a standards entry, malformed payloads, and trailing input are hard errors. There is no public raw grammar result and no render-and-reparse path.

The public catalogue and introduction data are regression-tested by tests/feature_availability.rs, while payload shape and rejection behavior are covered by the strict payload and error suites.

OpenMP typed directive and clause components

ROUP represents OpenMP semantics directly. Directive parameters and clause payloads are enums and checked record types, not strings that a consumer must split or parse again.

Directive parameters

Dedicated parameter variants cover constructs such as:

  • distinct allocate, threadprivate, groupprivate, and historical declare target lists;
  • critical, flush, checked-lvalue depobj, and construct-name parameters;
  • declare mapper, declare reduction, declare simd, and declare induction declarations; and
  • a declare variant target with separate optional base and required variant function names.

Storage and historical declare target lists admit only whole qualified variable or procedure names and Fortran named common blocks. Array elements, array sections, and object members are hard errors because the OpenMP restrictions do not permit parts of variables in these lists. A present Fortran declare simd(proc-name) parameter always contains a procedure name; an empty target is not representable.

Historical C++ template-id variant names remain accepted cumulatively from OpenMP 5.0. A base-name: prefix is available for Fortran from OpenMP 5.0 and for C/C++ from OpenMP 5.2, matching the host-specific historical grammars.

declare reduction stores a typed reduction identifier, validated type names, combiner expression, and optional initializer. declare induction stores the induction identifier and its validated type-specifier list, including paired variable and step types. A malformed declaration cannot be represented by a partially populated record.

Clause payloads

Clause payload variants distinguish semantic families, including:

  • checked expressions, identifiers, type names, and locator lists;
  • scheduling, ordering, binding, mapping, dependence, and reduction kinds;
  • atomic operation and memory-order data;
  • mapper, iterator, induction, linear, and allocator records;
  • metadirective selectors with typed traits and nested directives;
  • transformation trees for apply; and
  • actual requirement clauses on requires rather than a synthesized summary string.

Lists are delimiter-aware and their elements are parsed once. A locator list accepts locator shapes only; it cannot silently retain a general expression. Nested directives retain spans into the outer physical source, including when a token crosses a line splice.

Standard aliases

Historical and alternate standardized spellings map to the same typed kind and payload. Source-facing tools can recover the written spelling by slicing the checked name span against the original input. Semantic consumers should use the canonical kind and payload instead of comparing source text.

Fortran end allocators and end dispatch, including compact spellings, have dedicated typed directive kinds introduced in OpenMP 5.2 and participate in strict opener/end pairing.

Optional C ABI representation

The optional ABI exposes the same structure through field metadata and owned child-node handles. Scalar leaves, UTF-8 leaves, lists, and nested records have distinct value kinds. Directive-specific list tags remain distinct in the ABI, and declare variant exposes base and function as separate fields. There is deliberately no operation that returns a whole rendered payload.

If a new Rust payload cannot be represented by these fields, the ABI and both adapters must be extended. Substituting a raw payload or default value is not a valid conversion.

OpenMP validation boundaries

The OpenMP specification defines syntax restrictions as well as rules that depend on an enclosing program. ROUP separates those two categories without silently skipping either one.

For the normative rules, consult the relevant directive and clause sections in the OpenMP 6.0 specification or the earlier specification that introduced historical syntax.

Checked by every parse

OpenMpParser::parse checks everything that can be decided from one directive and its configured profiles, including:

  • source-form, sentinel, continuation, UTF-8, and trailing-input validity;
  • complete directive, parameter, clause, and modifier syntax, plus the explicitly supported typed host-expression grammar;
  • feature introduction for the selected OpenMP and host-language versions;
  • directive-clause compatibility and duplicate singleton clauses;
  • structurally invalid nested directives and selectors; and
  • other context-independent restrictions represented by the validator.

Failure returns one structured Diagnostic. A parse never returns a partial AST, recovery node, warning-only substitute, or guessed default. Host-language constructs outside the documented typed expression grammar are also hard errors; ROUP does not claim to replace a complete C, C++, or Fortran frontend.

Facts supplied by an embedding compiler

Some specification rules require information outside the directive text, such as declaration placement, construct association, name resolution, or whether a host expression is constant. Use OpenMpParser::parse_with_facts when those checks apply.

Facts required by the parsed construct are mandatory. A missing fact is a hard diagnostic rather than permission to bypass the check. The compiler remains responsible for producing truthful facts from its program representation.

Stateful region validation

ContextValidator validates directive sequences whose correctness depends on previous input, including begin/end pairing and association state. Opening and closing locations use checked spans, so mismatches can report the related source location.

Contributor rule

When adding standardized syntax, record its introduction version, construct a fully typed payload, implement all context-independent restrictions, identify every required external fact, and add both positive and negative public-API tests. Do not add a permissive grammar branch while deferring malformed states to a renderer or compatibility adapter.

Line continuations and physical spans

ROUP accepts standard C/C++ line splices and Fortran directive continuations. It validates the complete logical directive before parsing and maps directive, clause, and nested-directive spans back to the original physical source.

C and C++

A C/C++ continuation is exactly a backslash immediately followed by LF or CRLF. No whitespace may occur between the backslash and the line ending. Translation removes exactly those characters and never invents a separator:

#pragma omp parallel for \
    schedule(dynamic, 4) \
    private(i, \
            j)

Source whitespace on either side of the splice remains significant. Therefore parallel\ followed immediately by for forms the single token parallelfor and is rejected; at least one actual source-space character is required to form parallel for.

A backslash followed by spaces, a bare CR, or an uncontinued physical newline with more directive text is a hard error.

Fortran free and fixed forms

Fortran continuations use a trailing &. A continuation line may repeat the sentinel for the configured dialect and may place & immediately after that sentinel. OpenMP accepts !$omp, OpenACC accepts !$acc, and both accept the standardized short !$ form, case insensitively; using one dialect’s long sentinel while parsing the other is a hard error. Only continuation syntax is removed, and any source whitespace needed to separate tokens must be present in the input.

!$omp target teams distribute &
!$omp& parallel do &
!$omp& private(i, j)

The corresponding OpenACC form uses !$acc on each line. Fixed-form input accepts the configured standard sentinel family, including !$OMP, C$OMP, and *$OMP, the matching ACC forms, and their short !$, C$, and *$ forms; leading horizontal whitespace is accepted before the sentinel. A comment may follow a valid trailing &. Missing markers, text after a purported marker, a conflicting-dialect sentinel, or another directive line reached through ordinary whitespace is an error.

Spans

Logical parsing does not discard the physical location map. For a token that crosses a C splice, its Span covers the complete physical slice, including the backslash and newline. Clause-name spans identify the exact source alias even when the semantic kind is canonicalized. Nested metadirective and construct selector directives use the same outer-source coordinate system.

See tests/openmp_line_continuations.rs, tests/openacc_line_continuations.rs, and tests/source_span_regressions.rs for executable examples.

Architecture

ROUP has one semantic parser implementation and two delivery layers.

Safe Rust parser

The workspace root package is the complete parser. It accepts an explicit dialect version policy, host-language profile, and source form, and returns a typed OpenMP or OpenACC AST. Every expression and structured clause payload is parsed before the result is returned. Unknown syntax, invalid combinations, unsupported host syntax, and trailing input are hard errors.

The root crate builds only an rlib and has #![forbid(unsafe_code)]. Its semantic enums have Rust-native layouts and no ABI discriminants. Syntax that was standardized by an older specification remains accepted by later exact version modes. Standard aliases are recognized at the parser boundary and map to one canonical semantic node.

Parsing is organized into four boundaries:

  1. The source-form lexer validates the pragma or Fortran sentinel and line continuation rules.
  2. The grammar recognizes directive and clause syntax without inventing defaults for malformed input.
  3. Semantic construction creates the typed AST and host-expression trees.
  4. Availability and context validation intersect every used feature with the configured specification and reject invalid clause, nesting, and association combinations.

Diagnostics carry stable codes and checked UTF-8 byte, line, and column spans.

Optional C ABI

crates/roup-capi is a separate workspace package. It depends on the safe Rust parser, but the parser never depends on it. The ABI uses opaque generational handles, by-value options, explicit byte buffers, and structured error handles. All foreign-pointer access is confined to one audited boundary module; every other ABI module denies unsafe code.

Directive parameters and clause payloads are exposed as typed fields. A missing typed conversion is a hard error. The repository document docs/C_ABI_ARCHITECTURE.md defines the detailed ownership and layout rules.

Compatibility adapters

compat/ompparser and compat/accparser build against the optional C ABI and construct the corresponding upstream C++ IR directly from typed queries. They do not link to Rust enum layouts and do not reinterpret canonical strings. Unsupported conversions are hard errors.

The upstream projects are pinned git submodules. A repository test requires both worktrees to match their recorded gitlinks, builds each adapter from a clean CMake directory, and registers both upstream ctest directories unchanged. At the current pins this runs all 1,534 ompparser and 918 accparser upstream tests, plus five local ABI/adapter checks. No upstream fixture or reference is rewritten and no test is filtered, disabled, or allowed to fail.

Contributing

Changes must preserve the parser’s central invariant: success means a complete, typed, validated AST. Semantic Rust types must not contain opaque text variants, guessed defaults, or ABI layout annotations.

When adding syntax:

  1. Add a parser-boundary representation and canonical typed AST shape.
  2. Add an explicit specification introduction entry and any historical alias provenance needed for exact-version parsing.
  3. Add directive/clause/context validation and a negative test for malformed input.
  4. Extend the C ABI typed fields if the node is externally visible.
  5. Extend both compatibility adapters or return a hard conversion error.

Run the deterministic repository gate before submitting a change:

./test.sh

The gate requires initialized submodules and the Rust, C, C++, Fortran, CMake, and mdBook toolchains. It does not install dependencies, move submodule refs, or skip unavailable test categories.

For a smaller Rust-only iteration:

cargo fmt --all --check
./scripts/audit_enum_safety.sh
cargo clippy --locked --workspace --all-targets -- -D warnings
cargo test --locked -p roup

FAQ

Does the Rust parser require a C toolchain?

No. cargo build -p roup builds the complete parser as safe Rust. The optional roup-capi package and C++ compatibility adapters are separate consumers.

What does an exact specification version mean?

It is an introduction ceiling. Syntax first standardized after the selected version is rejected. Standardized older syntax remains accepted even if a newer specification deprecated or removed it, which keeps maintained legacy code parseable.

Are spelling aliases preserved?

No. Standard aliases are accepted and canonicalized into one semantic AST shape. Version compatibility is computed from typed parser provenance. The checked name span can still select the exact spelling in the original source.

What happens to unsupported host-language expressions?

Parsing fails with an invalid-expression diagnostic. A successful result always contains a classified host-expression tree.

Where is unsafe Rust used?

Only the optional C ABI’s audited byte-copy boundary contains unsafe code. The root parser forbids unsafe Rust, and all other ABI modules deny it.

How are C ABI objects owned?

Parser, directive, child-node, and diagnostic objects share one server-owned generational arena. Each padding-free two-word handle identifies exactly one stored object, whose internal variant is checked before every access or release. Each successful handle is released exactly once with its matching release operation. Invalid, stale, fabricated, and wrong-kind handles are hard errors.

Why is there no clause payload string function?

Clause payloads are structured data rather than one scalar value. Consumers query typed fields and must report an unsupported conversion directly.