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 (rlibonly)roup-capi: an optional C ABI (rlib,staticlib, andcdylib)
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:
- Query the field count.
- Query
RoupFieldInfofor each field. - Dispatch on
value_kind. - Read boolean leaves with
*_field_bool, closed numeric tags with*_field_u32, and UTF-8 leaves with the paired length/copy operations. - Acquire
NODEorNODE_LISTvalues 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 resultsroup::ast: canonical typed directive and clause nodesroup::host: typed C, C++, and Fortran expression syntaxroup::ir: shared typed clause components such as expressions, variables, locators, modifiers, and reduction operatorsroup::version: specification policies, host profiles, and source formsroup::diagnosticandroup::source: stable hard errors and checked spansroup::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,pcreateand theirpresent_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,attacheach 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, andvector_lengthare forbidden (§2.5.2, p.34). Other clause semantics match the sections cited above.
Kernels construct (§2.5.3, p.35)
async[(async-argument)]andwait[(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)andvector_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)]andwait[(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,attachwith 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)]andwait[(wait-argument)]per §§2.16.1–2.16.2 (pp.99–100).copyin([modifier-list:]var-list),create([modifier-list:]var-list), andattach(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)andif_presentclauses (§§2.8.2–2.8.3, p.63).
Loop construct (§2.9, p.64)
collapse([force:]n)— loop nest collapsing with optionalforcequalifier (§2.9.1, p.65).gang[(gang-arg-list)]— optionalnum:,dim:, andstatic: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, andautoexclusivity 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) andreduction(operator:var-list)(§2.9.11, p.71).
Cache directive (§2.10, p.75)
cache([readonly:]var-list)— optionalreadonlymodifier constrains writes (§2.10, p.75).
Combined constructs (§2.11, p.75)
parallel loop,serial loop, andkernels loopaccept any clause allowed on both the outer construct and the loop construct; reductions implycopysemantics (§2.11, pp.75–76).
Atomic construct (§2.12, pp.77–80)
- Optional
atomic-clauseofread,write,update, orcapture; 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,deviceptras 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)anddevice_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 optionalif(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)anddevice_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), andif(condition)as above.if_presentskip 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-argumenttuple[devnum:int-expr:][queues:]async-argument-listper §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, andseqdefine 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).nohostto 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, anddefault(none)locality specs map toprivate,firstprivate,copy, anddefault(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
dtypeis 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-argumentvalues: nonnegative integers oracc_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-argumenttuple 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 Fortraniand/ior/ieorwith 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)
copyperforms in/out transfers;copyinis host→device;copyoutis 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
forcekeyword overrides dependency analysis; requires positive iteration counts (§2.9.1, p.65).
gang clause (§2.9.2, pp.66–67)
gang-arg-listallows one each ofnum:,dim:,static:modifiers;dimis 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).
link clause (§2.13.3, p.84)
- 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-varandacc-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; optionalqueues:prefix clarifies async argument list (§2.16, p.99).
async-value semantics (§2.16, p.98)
- Maps async arguments to queue identifiers;
acc_async_syncenforces synchronous completion,acc_async_novaluses 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, andvector_lengthclauses may follow adevice_typeclause on any compute construct. - At most one
ifclause may appear on a compute construct. - At most one
defaultclause may appear and its value must be eithernoneorpresent. - A
reductionclause must not appear on aparallelconstruct whosenum_gangsclause 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, oracc_error_invalid_matrix_shapeas described in the specification when these conditions are detected.
Enter/exit data directives (§2.6.6, p.46)
enter datadirectives must include at least one of:copyin,create, orattach.exit datadirectives must include at least one of:copyout,delete, ordetach.- Only one
ifclause may appear on either directive. finalizeonexit dataresets 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_devicelists must reference variables that are present on the device; otherwise behavior is undefined.- Host pointers aliased inside
host_dataregions 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, andtileclauses may follow adevice_typeclause. workerandvectorclause arguments must be invariant within the surroundingkernelsregion.- Loops without
seqmust satisfy: loop variable is integer/pointer/random-access iterator, iteration monotonicity, and constant-time trip count computation. - Only one of
seq,independent, orautomay appear. gang,worker, andvectorclauses are mutually exclusive with an explicitseqclause.- 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
gangclause may appear per loop construct. tileandcollapsemust not be combined on loops associated withdo concurrent.- Each associated loop in a
tileconstruct (except the innermost) must contain exactly one loop or loop nest. privateclauses 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
cachedirective. - Fortran optional arguments used in
cachedirectives must follow §2.17.1.
Combined constructs (§2.11, p.76)
- Combined constructs inherit all restrictions from their constituent
parallel,serial,kernels, andloopcomponents.
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
xmust not exceed the hardware’s maximum native atomic width. - At most one
ifclause may appear on an atomic construct.
Declare directive (§2.13, pp.81–84)
declaremust 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
declareclauses 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, andlink; C/C++ global scope allows onlycreate,copyin,deviceptr,device_resident, andlink. - C/C++ extern variables are limited to
create,copyin,deviceptr,device_resident, andlink. linkclauses must appear at global/module scope or reference extern/common-block entities.declareregions must not containlongjmp/setjmpmismatches 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_asyncaccepts only valid async identifiers;acc_async_novalhas no effect,acc_async_syncforces synchronous execution on the default queue, andacc_async_defaultrestores the initial queue.- Must include at least one of
default_async,device_num, ordevice_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, ordeviceclauses. - If
if_presentis absent, all listed variables must already be present on the device. - Only
asyncandwaitclauses may followdevice_type. - At most one
ifclause 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, anddevicefollow §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)
devnumvalues 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.
gangdimension argument must be an integer constant expression in {1,2,3}.workerroutines cannot be parents of gang routines;vectorroutines cannot be parents of worker or gang routines;seqroutines cannot be parents of parallel routines.- Procedures compiled with
nohostmust not be called from host-only regions; enclosing procedures must also carrynohostwhen they call such routines.
Do concurrent integration (§2.17.2, pp.102–103)
- When mapping Fortran
do concurrentlocality specs to OpenACC clauses, users must ensure host/device sharing matches the specified locality (e.g.,localtoprivate,local_inittofirstprivate).
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. deviceptrvariables cannot appear in other data clauses in the same region (§2.7.5).presentclauses require existing device data; absence triggers runtime errors (§2.7.6).no_createforbids allocation and therefore requires prior presence (§2.7.11).attach/detachmust 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-argumentvalues are limited to nonnegative integers or the special constantsacc_async_default,acc_async_noval,acc_async_sync.wait-argumentsyntax[devnum:int-expr:][queues:]async-argument-listrequires 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 historicaldeclare targetlists; critical,flush, checked-lvaluedepobj, and construct-name parameters;declare mapper,declare reduction,declare simd, anddeclare inductiondeclarations; and- a
declare varianttarget 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
requiresrather 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:
- The source-form lexer validates the pragma or Fortran sentinel and line continuation rules.
- The grammar recognizes directive and clause syntax without inventing defaults for malformed input.
- Semantic construction creates the typed AST and host-expression trees.
- 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:
- Add a parser-boundary representation and canonical typed AST shape.
- Add an explicit specification introduction entry and any historical alias provenance needed for exact-version parsing.
- Add directive/clause/context validation and a negative test for malformed input.
- Extend the C ABI typed fields if the node is externally visible.
- 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.