ROUP
Rust-based OpenMP & OpenACC Parser
Safe, fast, and comprehensive directive parsing
What is ROUP?
ROUP is an experimental parser for OpenMP and OpenACC directives, written in safe Rust with C, C++, and Fortran bindings. Parse pragmas like #pragma omp parallel for or !$acc parallel into structured data that your tools can analyze, transform, and process.
⚠️ Experimental Status: ROUP is under active development and not yet production-ready. APIs may change, and some OpenMP features are still being implemented. Use in research and experimental projects.
Perfect for:
- 🔧 Compiler research - Experiment with OpenMP parsing in compilers
- 🔍 Analysis prototypes - Build experimental linters and analyzers
- 🎓 Researchers - Study parallelization patterns and test new ideas
- 📚 Educators - Teaching tool for parallel programming concepts
Why ROUP?
🚀 Fast & Lightweight
- Zero-copy lexer and hand-written parsers
- Standalone library (no LLVM/ANTLR dependencies)
- Compatibility shims for ompparser and accparser
🛡️ Safe & Reliable
- Memory safety guaranteed except for the narrow FFI boundary
- Extensive automated tests plus OpenMP_VV/OpenACCV-V validation and compat ctests
- NULL-safe C API with defensive checks
📚 Comprehensive Directive Support
- OpenMP 3.0–6.0 directives, clauses, combined forms, and metadirectives
- OpenACC 3.4 directives, clauses, aliases, and end-paired constructs
- Canonical keyword handling and clause alias preservation
- Interactive parser debugger (
roup_debug) for OpenMP/OpenACC (C and Fortran sentinels)
🔌 Multi-Language APIs
| Language | API Style | Memory Management | Status |
|---|---|---|---|
| Rust | Native | Automatic (ownership) | ✅ |
| C | Pointer-based | Manual (malloc/free pattern) | ✅ |
| C++ | RAII wrappers | Automatic (destructors) | ✅ |
| Fortran | C interop | Manual (via iso_c_binding) | ✅ (via C API) |
Quick Example
Parse in 3 Lines (Rust)
use roup::parser::openmp;
let parser = openmp::parser();
let (_, directive) = parser.parse("#pragma omp parallel for num_threads(4)").unwrap();
// Access directive information
println!("Directive: {}", directive.name); // Output: Directive: parallel for
println!("Found {} clauses", directive.clauses.len()); // Output: Found 1 clauses
// Iterate through clauses
for clause in &directive.clauses {
println!(" Clause: {}", clause.name); // Output: Clause: num_threads
}
Parse in C
OmpDirective* dir = roup_parse("#pragma omp parallel for num_threads(4)");
printf("Clauses: %d\n", roup_directive_clause_count(dir));
roup_directive_free(dir);
Parse in C++ (with RAII)
roup::Directive dir("#pragma omp parallel for num_threads(4)");
std::cout << "Clauses: " << dir.clause_count() << "\n";
// Automatic cleanup!
Parse Fortran
! Free-form Fortran
directive_ptr = roup_parse_with_language("!$OMP PARALLEL PRIVATE(A)", &
ROUP_LANG_FORTRAN_FREE)
Feature Highlights
🎯 Comprehensive Coverage
Parallel Constructs - 20+ directives
parallel- Basic parallel regionsparallel for- Combined parallel + worksharingparallel sections- Parallel sectionsparallel master- Parallel master threadparallel loop- OpenMP 5.0+ parallel loop- And more…
Work-Sharing - 10+ directives
for/do- Loop worksharingsections/section- Code sectionssingle- Execute onceworkshare- Fortran worksharingloop- Generic loop construct
Tasking - 15+ directives
task- Explicit taskstaskloop- Loop-based taskstaskgroup- Task synchronizationtaskwait- Wait for taskstaskyield- Yield to other tasks- Dependency clauses:
depend,priority,detach
Device Offloading - 25+ directives
target- Offload to devicetarget data- Device data managementtarget enter/exit data- Data transfertarget update- Synchronize datateams- Multiple thread teamsdistribute- Distribute iterations
SIMD - 10+ directives
simd- SIMD loopsdeclare simd- Vectorizable functionsdistribute simd- Combined distribute + SIMD- Various alignment and vectorization clauses
Advanced (OpenMP 5.0+)
metadirective- Context-sensitive directivesdeclare variant- Function variantsloop- Generic loop constructscan- Prefix scan operationsassume- Compiler assumptions
🔍 Rich Clause Support
92+ clause types including:
| Category | Clauses |
|---|---|
| Data Sharing | private, shared, firstprivate, lastprivate |
| Reductions | reduction(+:x), reduction(min:y), custom operators |
| Scheduling | schedule(static), schedule(dynamic,100), collapse(3) |
| Control | if(condition), num_threads(8), proc_bind(close) |
| Device | map(to:x), device(2), defaultmap(tofrom:scalar) |
| Dependencies | depend(in:x), depend(out:y), depend(inout:z) |
Complete clause reference → | Edge cases | Handled (fuzzing tested) | Likely has bugs | | Spec compliance | Verified | Uncertain |
Verdict: Unless you have very specific needs, use ROUP.
Safety Guarantees
ROUP prioritizes safety without compromising usability:
Memory Safety
- ✅ No buffer overflows - Rust prevents at compile time
- ✅ No use-after-free - Ownership system enforces
- ✅ No double-free - Checked at FFI boundary
- ✅ No memory leaks - RAII and destructors
- ✅ No data races - Thread-safe parsing
API Safety
Rust API:
- 100% memory-safe by construction
- Impossible to trigger undefined behavior
C API:
- NULL checks before all pointer operations
- Returns safe defaults on error (-1, NULL)
- Validates UTF-8 encoding
- Documents all safety contracts
Getting Started
Choose your language:
Community
- GitHub: ouankou/roup
- Issues: Bug reports
- Discussions: Questions & ideas
- Contributing: How to contribute
License
ROUP is open source under the MIT License.
Copyright © 2024-2025 Anjia Wang
Next Steps
- 📖 Read the Getting Started guide
- 🦀 Try the Rust tutorial
- 🔧 Try the C tutorial
- 📚 Browse the API reference
- 🏗️ Learn the architecture
- ❓ Check the FAQ
Why ROUP?
For Compiler Developers
- Drop-in OpenMP/OpenACC parser component
- Well-tested, battle-hardened parsing logic
- Easy FFI integration from any language
For Tool Builders
- Analyze OpenMP code without a full compiler
- Build linters, formatters, and code analyzers
- Extract parallelization patterns from codebases
For Researchers
- Study directive usage patterns
- Prototype new directive extensions
- Educational tool for learning parallel programming
Quick Example
Rust
use roup::parser::openmp;
let parser = openmp::parser();
let input = "#pragma omp parallel for num_threads(4) private(i)";
match parser.parse(input) {
Ok((_, directive)) => {
println!("Directive: {:?}", directive.kind);
println!("Clauses: {}", directive.clauses.len());
}
Err(e) => eprintln!("Parse error: {:?}", e),
}
C
#include <stdio.h>
// Forward declarations
typedef struct OmpDirective OmpDirective;
extern OmpDirective* roup_parse(const char* input);
extern int32_t roup_directive_clause_count(const OmpDirective* dir);
extern void roup_directive_free(OmpDirective* dir);
int main() {
OmpDirective* dir = roup_parse("#pragma omp parallel for num_threads(4)");
if (dir) {
printf("Clauses: %d\n", roup_directive_clause_count(dir));
roup_directive_free(dir);
}
return 0;
}
C++
#include <iostream>
#include <memory>
struct OmpDirective;
extern "C" {
OmpDirective* roup_parse(const char* input);
int32_t roup_directive_clause_count(const OmpDirective* dir);
void roup_directive_free(OmpDirective* dir);
}
// RAII wrapper
class Directive {
OmpDirective* ptr_;
public:
explicit Directive(const char* input) : ptr_(roup_parse(input)) {}
~Directive() { if (ptr_) roup_directive_free(ptr_); }
bool valid() const { return ptr_ != nullptr; }
int clause_count() const {
return ptr_ ? roup_directive_clause_count(ptr_) : 0;
}
};
int main() {
Directive dir("#pragma omp parallel for num_threads(4)");
if (dir.valid()) {
std::cout << "Clauses: " << dir.clause_count() << "\n";
}
return 0;
}
Architecture
ROUP uses a clean, modular architecture:
┌─────────────────────────────────────────┐
│ Application Layer │
│ (Your compiler/tool/analyzer) │
└─────────────────┬───────────────────────┘
│
┌───────────┼───────────┐
│ │ │
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Rust API│ │ C API │ │ C++ API │
│ │ │ │ │ (RAII) │
└─────────┘ └─────────┘ └─────────┘
│ │ │
└───────────┼───────────┘
│
▼
┌────────────────┐
│ Core Parser │
│ (nom-based) │
└────────────────┘
│
┌───────────┼───────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Lexer │ │Directive│ │ Clause │
│ │ │ Parser │ │ Parser │
└─────────┘ └─────────┘ └─────────┘
Key Design Principles:
- Safe by default - Rust’s ownership system prevents memory errors
- Zero-copy parsing - Uses string slices, not allocations
- Minimal unsafe - FFI boundary only, well-documented
- Extensible - Easy to add new directives and clauses
OpenMP Support
ROUP currently supports OpenMP 5.0+ with comprehensive coverage:
Supported Directives (15+)
parallel- Parallel regionsfor- Worksharing loopssections,single- Worksharing constructstask,taskwait,taskgroup- Taskingtarget,teams,distribute- Device offloadingbarrier,critical,atomic- Synchronizationmetadirective- Dynamic selection- And more…
Supported Clauses (50+)
- Data sharing:
private,shared,firstprivate,lastprivate - Parallelism control:
num_threads,if,proc_bind - Worksharing:
schedule,collapse,nowait - Reductions:
reductionwith 10+ operators (+, *, min, max, etc.) - Device:
map,device,defaultmap - Dependencies:
depend,in,out,inout - And more…
See the OpenMP Support Matrix for the complete list.
Safety Guarantees
All unsafe code is isolated to the FFI boundary (src/c_api.rs), documented with safety requirements, and NULL-checked before dereferencing.
Getting Started
Want to experiment with ROUP? Check out our tutorials:
- C++ Tutorial - Build an experimental application with C++17
- Rust API Docs - Complete API reference
Or jump straight to the code:
License
ROUP is open source under the MIT License.
Copyright © 2024-2025 Anjia Wang
Getting Started
This guide shows how to compile ROUP, add the crate to a Rust project, and link the C/C++ bindings.
Prerequisites
- Rust 1.88 or newer (
rustupis recommended). - A C/C++ toolchain (clang or GCC) when using the FFI bindings.
- Optional: a Fortran compiler for the example programs in
examples/fortran/.
Clone and build the project:
git clone https://github.com/ouankou/roup.git
cd roup
cargo build --release
The build produces libroup.so on Linux, libroup.dylib on macOS, and
roup.dll on Windows inside target/release/.
Rust quick start
Add ROUP to your Cargo.toml:
[dependencies]
roup = "0.7"
Example program:
use roup::parser::openmp;
use roup::lexer::Language;
fn main() {
let input = "#pragma omp parallel for num_threads(4)";
let parser = openmp::parser().with_language(Language::C);
match parser.parse(input) {
Ok((_, directive)) => {
println!("directive: {}", directive.name);
println!("clauses: {}", directive.clauses.len());
}
Err(err) => eprintln!("parse error: {err:?}"),
}
}
Run it with cargo run.
C quick start
Write a small program that calls the C API:
#include <stdint.h>
#include <stdio.h>
struct OmpDirective;
struct OmpDirective* roup_parse(const char* input);
int32_t roup_directive_clause_count(const struct OmpDirective* dir);
void roup_directive_free(struct OmpDirective* dir);
int main(void) {
struct OmpDirective* directive = roup_parse("#pragma omp parallel num_threads(4)");
if (!directive) {
fputs("parse failed\n", stderr);
return 1;
}
printf("clause count: %d\n", roup_directive_clause_count(directive));
roup_directive_free(directive);
return 0;
}
Compile against the release build of ROUP:
cargo build --release
clang example.c \
-L./target/release \
-lroup -lpthread -ldl -lm \
-Wl,-rpath,./target/release \
-o example
./example
macOS users can replace the rpath with
-Wl,-rpath,@executable_path/../target/release.
C++ quick start
The C API can be wrapped with RAII helpers:
#include <cstdint>
#include <iostream>
extern "C" {
struct OmpDirective;
OmpDirective* roup_parse(const char* input);
int32_t roup_directive_clause_count(const OmpDirective* dir);
void roup_directive_free(OmpDirective* dir);
}
class Directive {
public:
explicit Directive(const char* input) : ptr_(roup_parse(input)) {}
~Directive() { if (ptr_) roup_directive_free(ptr_); }
Directive(const Directive&) = delete;
Directive& operator=(const Directive&) = delete;
Directive(Directive&& other) noexcept : ptr_(other.ptr_) { other.ptr_ = nullptr; }
bool valid() const { return ptr_ != nullptr; }
int32_t clause_count() const { return ptr_ ? roup_directive_clause_count(ptr_) : 0; }
private:
OmpDirective* ptr_;
};
int main() {
Directive directive("#pragma omp parallel for num_threads(4)");
if (!directive.valid()) {
std::cerr << "parse failed\n";
return 1;
}
std::cout << "clauses: " << directive.clause_count() << "\n";
}
Compile with clang++ or g++ in the same way as the C example.
Next steps
- Explore the complete examples in
examples/(C, C++, Fortran). - Read the Rust tutorial for more detailed use cases.
- Consult the Testing guide before contributing changes.
- Try the interactive debugger:
cargo run --release --bin roup_debug '#pragma omp parallel' -- --non-interactive.
Building Guide
This guide covers building ROUP and integrating it into your projects across different languages and platforms.
Quick Start
# Clone the repository
git clone https://github.com/ouankou/roup.git
cd roup
# Build the library (release mode, optimized)
cargo build --release
# Run tests to verify
cargo test
# Build is complete! Library at: target/release/libroup.{a,so,dylib,dll}
Next steps:
- Rust users: Add ROUP as a dependency (see below)
- C users: Link against
libroup.a(see C Tutorial) - C++ users: Use RAII wrappers (see C++ Tutorial)
Rust Integration
Option 1: Using crates.io (Recommended)
Add to your Cargo.toml:
[dependencies]
roup = "0.7"
Then use in your code:
use roup::parser::openmp;
use roup::lexer::Language;
fn main() {
let parser = openmp::parser().with_language(Language::C);
let (_, directive) = parser.parse("#pragma omp parallel for").unwrap();
println!("Parsed directive: {}", directive.name);
}
Option 2: Using Git Dependency
[dependencies]
roup = { git = "https://github.com/ouankou/roup.git" }
Option 3: Local Development
[dependencies]
roup = { path = "../roup" }
See Rust Tutorial for complete usage examples and roup_debug for interactive inspection.
C Integration
Prerequisites
- Rust toolchain (to build libroup)
- C compiler: GCC, Clang, or MSVC
- Build tools: Make or CMake (optional)
Step 1: Build the ROUP Library
cd /path/to/roup
cargo build --release
This creates:
- Linux:
target/release/libroup.{a,so} - macOS:
target/release/libroup.{a,dylib} - Windows:
target/release/roup.{lib,dll}
Step 2: Create Header File
Create roup_ffi.h with function declarations (see C Tutorial for complete header). The generated constants header lives at src/roup_constants.h after a build.
Step 3: Compile Your C Program
Using GCC/Clang (Linux/macOS)
# Static linking
gcc -o myapp main.c \
-I/path/to/roup_ffi.h \
-L/path/to/roup/target/release \
-lroup \
-lpthread -ldl -lm
# Dynamic linking with rpath
gcc -o myapp main.c \
-I/path/to/roup_ffi.h \
-L/path/to/roup/target/release \
-lroup \
-Wl,-rpath,/path/to/roup/target/release \
-lpthread -ldl -lm
Using CMake
cmake_minimum_required(VERSION 3.10)
project(MyApp C)
# Add ROUP library
add_library(roup STATIC IMPORTED)
set_target_properties(roup PROPERTIES
IMPORTED_LOCATION "/path/to/roup/target/release/libroup.a"
)
# Create executable
add_executable(myapp main.c)
target_include_directories(myapp PRIVATE "/path/to/roup_ffi.h")
target_link_libraries(myapp roup pthread dl m)
Using MSVC (Windows)
REM Build ROUP library first
cargo build --release
REM Compile C program
cl.exe main.c /I"C:\path\to\roup" ^
/link "C:\path\to\roup\target\release\roup.lib" ^
ws2_32.lib userenv.lib
Step 4: Run Your Program
# If using static linking
./myapp
# If using dynamic linking without rpath
LD_LIBRARY_PATH=/path/to/roup/target/release ./myapp
# Windows
set PATH=C:\path\to\roup\target\release;%PATH%
myapp.exe
Complete Example
See examples/c/tutorial_basic.c for a full working example with build instructions.
C++ Integration
C++ programs use the same C API with optional RAII wrappers for automatic memory management.
Step 1: Build ROUP Library
cargo build --release
Step 2: Create RAII Wrappers
See C++ Tutorial - Step 2 for complete wrapper code.
Step 3: Compile with C++17
# Using g++
g++ -o myapp main.cpp \
-I/path/to/roup_ffi.h \
-I/path/to/roup_wrapper.hpp \
-L/path/to/roup/target/release \
-lroup \
-std=c++17 \
-lpthread -ldl -lm
# Using Clang++
clang++ -o myapp main.cpp \
-I/path/to/roup_ffi.h \
-I/path/to/roup_wrapper.hpp \
-L/path/to/roup/target/release \
-lroup \
-std=c++17 \
-lpthread -ldl -lm
CMake for C++
cmake_minimum_required(VERSION 3.10)
project(MyApp CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_library(roup STATIC IMPORTED)
set_target_properties(roup PROPERTIES
IMPORTED_LOCATION "/path/to/roup/target/release/libroup.a"
)
add_executable(myapp main.cpp)
target_include_directories(myapp PRIVATE
"/path/to/roup_ffi.h"
"/path/to/roup_wrapper.hpp"
)
target_link_libraries(myapp roup pthread dl m)
Platform-Specific Notes
Linux
Ubuntu/Debian
# Install build tools
sudo apt-get update
sudo apt-get install build-essential curl git
# Install Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Build ROUP
cargo build --release
Fedora/RHEL
# Install build tools
sudo dnf install gcc git
# Install Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Build ROUP
cargo build --release
Library location: target/release/libroup.{a,so}
macOS
# Install Xcode Command Line Tools
xcode-select --install
# Install Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Build ROUP
cargo build --release
Library location: target/release/libroup.{a,dylib}
Note: On macOS, the dynamic library extension is .dylib, not .so.
Windows
Using Rust with MSVC
# Install Rust (uses MSVC toolchain by default on Windows)
# Download from: https://rustup.rs/
# Install Visual Studio Build Tools
# Download from: https://visualstudio.microsoft.com/downloads/
# Build ROUP
cargo build --release
Library location: target\release\roup.{lib,dll}
Using Rust with GNU (MinGW)
# Install MSYS2 from https://www.msys2.org/
# Then in MSYS2 terminal:
# Install MinGW toolchain
pacman -S mingw-w64-x86_64-gcc mingw-w64-x86_64-rust
# Build ROUP
cargo build --release
WSL (Recommended for C/C++)
Windows Subsystem for Linux provides a full Linux environment:
# In WSL (Ubuntu)
sudo apt-get install build-essential
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
cargo build --release
Build Configurations
Debug Build (Development)
cargo build
# Output: target/debug/libroup.{a,so}
# Features: Debug symbols, assertions, slower but better errors
Release Build (Production)
cargo build --release
# Output: target/release/libroup.{a,so}
# Features: Optimized, no debug symbols, faster execution
Release with Debug Info
cargo build --release --config profile.release.debug=true
# Output: target/release/libroup.{a,so}
# Features: Optimized but with debug symbols for profiling
Custom Features
# Build with all features
cargo build --all-features
# Build with specific feature
cargo build --features "serde"
# Build without default features
cargo build --no-default-features
Testing
Run All Tests
# Run all tests
cargo test
# Run with output
cargo test -- --nocapture
# Run specific test
cargo test test_parallel_directive
# Run tests for specific module
cargo test parser::
Run Examples
Examples live under examples/ for C, C++, and Fortran. Build them with the provided Makefiles in those directories.
Troubleshooting
“linker cc not found”
Problem: No C compiler installed.
Solution:
# Linux
sudo apt-get install build-essential
# macOS
xcode-select --install
# Windows
# Install Visual Studio Build Tools
“cannot find -lroup”
Problem: Linker can’t find the ROUP library.
Solution:
# Verify library exists
ls -lh target/release/libroup.*
# Rebuild if needed
cargo build --release
# Check linker path
gcc ... -L$(pwd)/target/release -lroup
“error while loading shared libraries: libroup.so”
Problem: Runtime can’t find dynamic library.
Solution 1 - rpath:
gcc ... -Wl,-rpath,/path/to/roup/target/release
Solution 2 - LD_LIBRARY_PATH:
export LD_LIBRARY_PATH=/path/to/roup/target/release:$LD_LIBRARY_PATH
./myapp
Solution 3 - Install system-wide:
sudo cp target/release/libroup.so /usr/local/lib/
sudo ldconfig
Rust Version Too Old
Problem: Compilation fails with version error.
Solution:
# Update Rust toolchain
rustup update stable
# Verify version
rustc --version
# Ensure your Rust version is at least 1.88.0
Windows: “VCRUNTIME140.dll missing”
Problem: Missing Visual C++ runtime.
Solution: Download and install Visual C++ Redistributable
Build Performance Tips
Faster Incremental Builds
# Use sccache for caching
cargo install sccache
export RUSTC_WRAPPER=sccache
# Use faster linker (Linux)
sudo apt-get install lld
export RUSTFLAGS="-C link-arg=-fuse-ld=lld"
Parallel Builds
# Use all CPU cores (default)
cargo build -j $(nproc)
# Limit parallel jobs
cargo build -j 4
Reduce Binary Size
# Add to Cargo.toml
[profile.release]
opt-level = "z" # Optimize for size
lto = true # Link-time optimization
codegen-units = 1 # Better optimization
strip = true # Remove debug symbols
Cross-Compilation
Linux to Windows
# Install target
rustup target add x86_64-pc-windows-gnu
# Install MinGW
sudo apt-get install mingw-w64
# Build
cargo build --release --target x86_64-pc-windows-gnu
macOS to Linux
# Install target
rustup target add x86_64-unknown-linux-gnu
# Build (requires cross-compilation setup)
cargo build --release --target x86_64-unknown-linux-gnu
For more complex cross-compilation, consider cross:
cargo install cross
cross build --release --target x86_64-unknown-linux-gnu
IDE Setup
Visual Studio Code
Recommended extensions:
- rust-analyzer - Language server
- CodeLLDB - Debugger
- crates - Dependency management
CLion / IntelliJ IDEA
Install the Rust plugin from JetBrains marketplace.
Vim/Neovim
Use rust-analyzer with your LSP client (coc.nvim, nvim-lspconfig, etc.)
Next Steps
After building successfully:
- Rust developers: See Rust Tutorial
- C developers: See C Tutorial
- C++ developers: See C++ Tutorial
- API Reference: See API Reference
Getting Help
- Build issues: Check GitHub Issues
- Questions: See FAQ
- Examples: Browse
examples/directory - Documentation: Read
docs/directory
Rust Tutorial
This tutorial shows the current Rust entry points for parsing and inspecting OpenMP and OpenACC directives.
Basic Parsing
use roup::parser::openmp;
use roup::lexer::Language;
fn main() {
let parser = openmp::parser().with_language(Language::C);
let (_, directive) = parser
.parse("#pragma omp parallel for num_threads(4)")
.expect("parse");
println!("name: {}", directive.name);
println!("clauses: {}", directive.clauses.len());
}
OpenACC works the same way:
use roup::parser::openacc;
use roup::lexer::Language;
let parser = openacc::parser().with_language(Language::FortranFree);
let (_, directive) = parser.parse("!$acc parallel async(1)").unwrap();
println!("name: {}", directive.name);
Language controls sentinel parsing (C, FortranFree, FortranFixed). openmp::parse_omp_directive and openacc::parse_acc_directive provide convenience wrappers if you do not need to customise the parser.
Error Handling
use roup::parser::openmp;
fn parse_or_report(input: &str) {
let parser = openmp::parser();
match parser.parse(input) {
Ok((rest, dir)) if rest.trim().is_empty() => {
println!("parsed {}", dir.name);
}
Ok((rest, _)) => eprintln!("trailing tokens: {rest:?}"),
Err(e) => eprintln!("parse error: {e:?}"),
}
}
Working with Clauses
use roup::parser::openmp;
let parser = openmp::parser();
let (_, directive) = parser.parse("#pragma omp parallel private(x) reduction(+:sum)").unwrap();
for clause in &directive.clauses {
match clause.name.as_ref() {
"private" => println!("private({:?})", clause.kind),
"reduction" => println!("reduction({:?})", clause.kind),
other => println!("clause: {other}"),
}
}
clause.kind preserves the original clause text (Parenthesized or Bare).
Building AST/IR
Use Parser::parse_ast to build the IR and normalise clauses:
use roup::ast::ClauseNormalizationMode;
use roup::ir::ParserConfig;
use roup::parser::openmp;
let parser = openmp::parser();
let ir = parser
.parse_ast(
"#pragma omp parallel for private(i,j)",
ClauseNormalizationMode::ParserParity,
&ParserConfig::default(),
)
.unwrap();
println!("{}", ir.to_string());
Translation API
Translate between C/C++ and Fortran OpenMP pragmas:
use roup::ir::translate::{translate_c_to_fortran, translate_fortran_to_c};
let f = translate_c_to_fortran("#pragma omp parallel for private(i)")?;
assert_eq!(f, "!$omp parallel do private(i)");
let c = translate_fortran_to_c("!$omp target teams distribute")?;
assert_eq!(c, "#pragma omp target teams distribute");
Debugger
roup_debug (built with the crate) provides interactive and batch step tracing:
cargo run --release --bin roup_debug '#pragma omp parallel' -- --non-interactive
It supports OpenMP and OpenACC pragmas in C and Fortran forms.
C Tutorial
This tutorial demonstrates how to use ROUP’s minimal unsafe pointer-based C API for parsing OpenMP directives. The API uses direct C pointers following standard malloc/free patterns familiar to C programmers.
API Design: Direct pointers (
*mut OmpDirective,*mut OmpClause) with manual memory management. No global state, no handles.Source:
src/c_api.rs- minimal unsafe code confined to the FFI boundary
Prerequisites
Before starting, ensure you have:
- C compiler (GCC, Clang, or MSVC)
- ROUP library compiled (see Building Guide)
- Basic understanding of malloc/free patterns
Example code: See examples/c/tutorial_basic.c for a complete working example.
Step 1: Setup and Compilation
Project Structure
my-project/
├── src/
│ └── main.c
├── include/
│ └── roup_ffi.h # Forward declarations
└── libroup.a # Built from cargo build
Forward Declarations
Create include/roup_ffi.h with the C API declarations:
#ifndef ROUP_FFI_H
#define ROUP_FFI_H
#include <stdint.h>
// Opaque types (defined in Rust)
typedef struct OmpDirective OmpDirective;
typedef struct OmpClause OmpClause;
typedef struct OmpClauseIterator OmpClauseIterator;
typedef struct OmpStringList OmpStringList;
// Lifecycle functions
extern OmpDirective* roup_parse(const char* input);
extern void roup_directive_free(OmpDirective* directive);
extern void roup_clause_free(OmpClause* clause);
// Directive queries
extern int32_t roup_directive_kind(const OmpDirective* directive);
extern int32_t roup_directive_clause_count(const OmpDirective* directive);
extern OmpClauseIterator* roup_directive_clauses_iter(const OmpDirective* directive);
// Iterator functions
extern int32_t roup_clause_iterator_next(OmpClauseIterator* iter, OmpClause** out);
extern void roup_clause_iterator_free(OmpClauseIterator* iter);
// Clause queries
extern int32_t roup_clause_kind(const OmpClause* clause);
extern int32_t roup_clause_schedule_kind(const OmpClause* clause);
extern int32_t roup_clause_reduction_operator(const OmpClause* clause);
extern int32_t roup_clause_default_data_sharing(const OmpClause* clause);
// Variable lists
extern OmpStringList* roup_clause_variables(const OmpClause* clause);
extern int32_t roup_string_list_len(const OmpStringList* list);
extern const char* roup_string_list_get(const OmpStringList* list, int32_t index);
extern void roup_string_list_free(OmpStringList* list);
#endif // ROUP_FFI_H
Compilation
Option 1: Using GCC/Clang
# Build ROUP library
cargo build --release
# Compile C program
gcc -o my_app src/main.c \
-I include \
-L target/release \
-lroup \
-lpthread -ldl -lm
Option 2: Using CMake
cmake_minimum_required(VERSION 3.10)
project(roup_example C)
add_executable(my_app src/main.c)
target_include_directories(my_app PRIVATE include)
target_link_libraries(my_app ${CMAKE_SOURCE_DIR}/target/release/libroup.a pthread dl m)
Step 2: Parse a Simple Directive
Let’s start with the most basic operation: parsing a simple directive.
#include <stdio.h>
#include "roup_ffi.h"
int main(void) {
const char* input = "#pragma omp parallel";
// Parse the directive
OmpDirective* dir = roup_parse(input);
// Check for errors (NULL = parse failed)
if (!dir) {
fprintf(stderr, "Parse failed!\n");
return 1;
}
printf("✅ Parse succeeded!\n");
// IMPORTANT: Free the directive
roup_directive_free(dir);
return 0;
}
Key Points:
roup_parse()returns a pointer orNULLon error- Always check for
NULLbefore using the directive - Always call
roup_directive_free()to prevent memory leaks
Step 3: Query Directive Properties
After parsing, you can query the directive’s properties:
#include <stdio.h>
#include "roup_ffi.h"
int main(void) {
const char* input = "#pragma omp parallel for num_threads(4)";
OmpDirective* dir = roup_parse(input);
if (!dir) {
return 1;
}
// Query directive properties
int32_t kind = roup_directive_kind(dir);
int32_t clause_count = roup_directive_clause_count(dir);
printf("Directive kind: %d\n", kind);
printf("Clause count: %d\n", clause_count);
roup_directive_free(dir);
return 0;
}
Output:
Directive kind: 28
Clause count: 1
Note: Directive kind is an integer from the parser’s internal registry. For practical use, you typically care more about the clauses than the exact directive kind. The kind value comes from the order in which directives were registered in
src/parser/openmp.rs- these internal IDs are not part of the stable API and may change between versions.
Step 4: Iterate Through Clauses
To access individual clauses, use the iterator pattern:
#include <stdio.h>
#include "roup_ffi.h"
const char* clause_name(int32_t kind) {
switch(kind) {
case 0: return "num_threads";
case 1: return "if";
case 2: return "private";
case 3: return "shared";
case 6: return "reduction";
case 7: return "schedule";
case 10: return "nowait";
default: return "unknown";
}
}
int main(void) {
const char* input = "#pragma omp parallel num_threads(8) default(shared) nowait";
OmpDirective* dir = roup_parse(input);
if (!dir) return 1;
// Create iterator
OmpClauseIterator* iter = roup_directive_clauses_iter(dir);
if (!iter) {
roup_directive_free(dir);
return 1;
}
// Iterate through clauses
printf("Clauses:\n");
OmpClause* clause;
while (roup_clause_iterator_next(iter, &clause)) {
int32_t kind = roup_clause_kind(clause);
printf(" - %s (kind=%d)\n", clause_name(kind), kind);
}
// Cleanup
roup_clause_iterator_free(iter);
roup_directive_free(dir);
return 0;
}
Output:
Clauses:
- num_threads (kind=0)
- default (kind=11)
- nowait (kind=10)
Key Points:
roup_clause_iterator_next()returns1if clause available,0when done- Write the clause pointer to
outparameter - Always free the iterator with
roup_clause_iterator_free()
Step 5: Query Clause-Specific Data
Different clause types have different data. Use type-specific query functions:
Schedule Clause
OmpClause* clause = /* ... get clause ... */;
if (roup_clause_kind(clause) == 7) { // SCHEDULE
int32_t sched = roup_clause_schedule_kind(clause);
const char* names[] = {"static", "dynamic", "guided", "auto", "runtime"};
printf("Schedule: %s\n", names[sched]);
}
Reduction Clause
if (roup_clause_kind(clause) == 6) { // REDUCTION
int32_t op = roup_clause_reduction_operator(clause);
const char* ops[] = {"+", "-", "*", "&", "|", "^", "&&", "||", "min", "max"};
printf("Reduction operator: %s\n", ops[op]);
}
Default Clause
if (roup_clause_kind(clause) == 11) { // DEFAULT
int32_t def = roup_clause_default_data_sharing(clause);
printf("Default: %s\n", def == 0 ? "shared" : "none");
}
Complete Example
#include <stdio.h>
#include "roup_ffi.h"
int main(void) {
const char* input = "#pragma omp parallel for schedule(static, 10) reduction(+:sum)";
OmpDirective* dir = roup_parse(input);
if (!dir) return 1;
OmpClauseIterator* iter = roup_directive_clauses_iter(dir);
if (!iter) {
roup_directive_free(dir);
return 1;
}
OmpClause* clause;
while (roup_clause_iterator_next(iter, &clause)) {
int32_t kind = roup_clause_kind(clause);
if (kind == 7) { // SCHEDULE
int32_t sched = roup_clause_schedule_kind(clause);
const char* names[] = {"static", "dynamic", "guided", "auto", "runtime"};
printf("Schedule: %s\n", names[sched]);
}
else if (kind == 6) { // REDUCTION
int32_t op = roup_clause_reduction_operator(clause);
const char* ops[] = {"+", "-", "*", "&", "|", "^", "&&", "||", "min", "max"};
printf("Reduction: %s\n", ops[op]);
}
}
roup_clause_iterator_free(iter);
roup_directive_free(dir);
return 0;
}
Output:
Schedule: static
Reduction: +
Step 6: Access Variable Lists
Clauses like private(x, y, z) contain lists of variables:
#include <stdio.h>
#include "roup_ffi.h"
int main(void) {
const char* input = "#pragma omp parallel private(x, y, z) shared(a, b)";
OmpDirective* dir = roup_parse(input);
if (!dir) return 1;
OmpClauseIterator* iter = roup_directive_clauses_iter(dir);
if (!iter) {
roup_directive_free(dir);
return 1;
}
OmpClause* clause;
while (roup_clause_iterator_next(iter, &clause)) {
int32_t kind = roup_clause_kind(clause);
// Get variable list
OmpStringList* vars = roup_clause_variables(clause);
if (vars) {
int32_t len = roup_string_list_len(vars);
const char* kind_name = (kind == 2) ? "private" : "shared";
printf("%s variables: ", kind_name);
for (int32_t i = 0; i < len; i++) {
const char* var = roup_string_list_get(vars, i);
printf("%s%s", var, (i < len - 1) ? ", " : "");
}
printf("\n");
roup_string_list_free(vars);
}
}
roup_clause_iterator_free(iter);
roup_directive_free(dir);
return 0;
}
Output:
private variables: x, y, z
shared variables: a, b
Key Points:
roup_clause_variables()returns aOmpStringList*orNULL- Use
roup_string_list_len()to get the count - Use
roup_string_list_get(list, index)to access individual strings - Always call
roup_string_list_free()when done
Step 7: Error Handling
Robust error handling is crucial. The API returns NULL on failure:
#include <stdio.h>
#include "roup_ffi.h"
int main(void) {
// Test 1: Invalid syntax
const char* invalid = "#pragma omp INVALID_DIRECTIVE";
OmpDirective* dir1 = roup_parse(invalid);
if (!dir1) {
printf("✓ Invalid syntax correctly rejected\n");
}
// Test 2: NULL input
OmpDirective* dir2 = roup_parse(NULL);
if (!dir2) {
printf("✓ NULL input correctly rejected\n");
}
// Test 3: Empty string
OmpDirective* dir3 = roup_parse("");
if (!dir3) {
printf("✓ Empty string correctly rejected\n");
}
// Test 4: Querying NULL
int32_t kind = roup_directive_kind(NULL);
printf("roup_directive_kind(NULL) = %d\n", kind); // Returns -1
return 0;
}
Error Handling Guidelines:
- Always check
roup_parse()return value forNULL - Check
roup_directive_clauses_iter()forNULL - Query functions return
-1or safe defaults forNULLinputs - Free resources even in error paths (if allocated)
Step 8: Complete Example
Here’s a complete program that demonstrates all concepts:
#include <stdio.h>
#include <stdlib.h>
#include "roup_ffi.h"
void print_clause_details(OmpClause* clause) {
int32_t kind = roup_clause_kind(clause);
switch(kind) {
case 0:
printf(" - num_threads\n");
break;
case 2: {
printf(" - private(");
OmpStringList* vars = roup_clause_variables(clause);
if (vars) {
int32_t len = roup_string_list_len(vars);
for (int32_t i = 0; i < len; i++) {
printf("%s%s", roup_string_list_get(vars, i),
(i < len - 1) ? ", " : "");
}
roup_string_list_free(vars);
}
printf(")\n");
break;
}
case 6: {
printf(" - reduction(");
int32_t op = roup_clause_reduction_operator(clause);
const char* ops[] = {"+", "-", "*", "&", "|", "^", "&&", "||", "min", "max"};
if (op >= 0 && op < 10) {
printf("%s", ops[op]);
}
printf(":...)\n");
break;
}
case 7: {
printf(" - schedule(");
int32_t sched = roup_clause_schedule_kind(clause);
const char* names[] = {"static", "dynamic", "guided", "auto", "runtime"};
if (sched >= 0 && sched < 5) {
printf("%s", names[sched]);
}
printf(")\n");
break;
}
case 10:
printf(" - nowait\n");
break;
case 11:
printf(" - default(%s)\n",
roup_clause_default_data_sharing(clause) == 0 ? "shared" : "none");
break;
default:
printf(" - unknown (kind=%d)\n", kind);
break;
}
}
int main(void) {
const char* test_cases[] = {
"#pragma omp parallel",
"#pragma omp parallel for num_threads(4) private(i, j)",
"#pragma omp parallel for schedule(static, 100) reduction(+:sum)",
"#pragma omp task default(shared) nowait",
NULL
};
for (int i = 0; test_cases[i] != NULL; i++) {
printf("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
printf("Input: %s\n", test_cases[i]);
printf("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
OmpDirective* dir = roup_parse(test_cases[i]);
if (!dir) {
printf("❌ Parse failed!\n");
continue;
}
int32_t clause_count = roup_directive_clause_count(dir);
printf("Clauses: %d\n", clause_count);
if (clause_count > 0) {
OmpClauseIterator* iter = roup_directive_clauses_iter(dir);
if (iter) {
OmpClause* clause;
while (roup_clause_iterator_next(iter, &clause)) {
print_clause_details(clause);
}
roup_clause_iterator_free(iter);
}
}
roup_directive_free(dir);
}
printf("\n✅ All tests completed!\n\n");
return 0;
}
Clause Kind Reference
The C API supports 12 common clause types:
| Kind | Clause | Has Variables | Has Specific Data |
|---|---|---|---|
| 0 | num_threads | No | Value (int) |
| 1 | if | No | Condition (string) |
| 2 | private | Yes | Variable list |
| 3 | shared | Yes | Variable list |
| 4 | firstprivate | Yes | Variable list |
| 5 | lastprivate | Yes | Variable list |
| 6 | reduction | Yes | Operator + variables |
| 7 | schedule | No | Schedule kind + chunk |
| 8 | collapse | No | Depth (int) |
| 9 | ordered | No | - |
| 10 | nowait | No | - |
| 11 | default | No | Sharing kind |
| -1 | Unknown | - | - |
Schedule Kinds (for clause kind 7):
- 0 =
static - 1 =
dynamic - 2 =
guided - 3 =
auto - 4 =
runtime
Reduction Operators (for clause kind 6):
- 0 =
+, 1 =-, 2 =* - 3 =
&, 4 =|, 5 =^ - 6 =
&&, 7 =|| - 8 =
min, 9 =max
Default Kinds (for clause kind 11):
- 0 =
shared - 1 =
none - 2 =
private - 3 =
firstprivate
Memory Management Checklist
✅ DO:
- Call
roup_directive_free()for every successfulroup_parse() - Call
roup_clause_iterator_free()for everyroup_directive_clauses_iter() - Call
roup_string_list_free()for everyroup_clause_variables() - Check for
NULLreturns before using pointers
❌ DON’T:
- Call
roup_clause_free()on clauses from iterators (owned by directive) - Access freed pointers (use-after-free)
- Forget to free in error paths
- Assume parse always succeeds
Performance Tips
- Reuse parsed directives - Don’t reparse the same string repeatedly
- Minimize FFI crossings - Batch operations when possible
- Avoid unnecessary iteration - If you only need clause count, don’t iterate
- Use local variables - Cache query results instead of calling repeatedly
Example (inefficient):
// BAD: Queries kind multiple times
for (int i = 0; i < count; i++) {
if (roup_clause_kind(clause) == 2) {
process_private(roup_clause_kind(clause));
}
}
Example (efficient):
// GOOD: Cache the kind
int32_t kind = roup_clause_kind(clause);
if (kind == 2) {
process_private(kind);
}
Next Steps
Now that you understand the C API basics:
- Build the example - Compile and run
examples/c/tutorial_basic.c - Explore directives - See OpenMP Support for all 120+ directives
- Advanced usage - Check API Reference for complete function details
- C++ wrappers - Read C++ Tutorial for RAII wrappers
Full Example Code: examples/c/tutorial_basic.c (433 lines with detailed comments)
Troubleshooting
Linker Errors
Problem: undefined reference to roup_parse
Solution: Link against the ROUP static library:
gcc ... -L target/release -lroup -lpthread -ldl -lm
Parse Always Returns NULL
Problem: All parses fail, even valid input
Solution:
- Check that the library was built correctly (
cargo build --release) - Verify the input string is valid OpenMP syntax
- Ensure the string is null-terminated
- Try the examples first to verify the library works
Memory Leaks
Problem: Valgrind reports memory leaks
Solution:
- Ensure every
roup_parse()has a matchingroup_directive_free() - Ensure every
roup_directive_clauses_iter()has a matchingroup_clause_iterator_free() - Ensure every
roup_clause_variables()has a matchingroup_string_list_free()
Segmentation Fault
Problem: Program crashes with segfault
Solution:
- Check for
NULLbefore dereferencing pointers - Don’t access freed pointers
- Don’t call
roup_clause_free()on clauses from iterators
Questions? Check the FAQ or open an issue on GitHub.
C++ Tutorial: Building a Real Application
This tutorial shows how to integrate ROUP into a real C++ application using modern C++17 features.
We’ll build an OpenMP pragma analyzer - a tool that reads C/C++ source files, extracts OpenMP directives, and reports statistics.
What You’ll Build
A command-line tool that:
- Reads source files line-by-line
- Detects OpenMP pragmas
- Parses them using ROUP
- Reports directive types and clause counts
- Provides summary statistics
Example output:
$ ./omp_analyzer mycode.c
Found 5 OpenMP directives:
Line 10: parallel (3 clauses)
Line 25: for (2 clauses)
Line 42: parallel for (4 clauses)
Line 68: task (1 clause)
Line 95: barrier (0 clauses)
Summary:
Total directives: 5
Total clauses: 10
Most common: parallel (2 occurrences)
Prerequisites
- C++ Compiler: clang++ or g++ with C++17 support
- ROUP Library: Built and installed (see below)
- System: Linux, macOS, or Windows with WSL
Building ROUP
git clone https://github.com/ouankou/roup.git
cd roup
cargo build --release
# Library is now at: target/release/libroup.so (Linux)
# or: target/release/libroup.dylib (macOS)
Step 1: Understanding the ROUP C API
ROUP exports a minimal C API with 16 functions. Here are the key ones for our tool:
Lifecycle Functions
// Parse an OpenMP directive string
OmpDirective* roup_parse(const char* input);
// Free the parsed directive
void roup_directive_free(OmpDirective* directive);
Query Functions
// Get directive type (0=parallel, 1=for, 4=task, etc.)
int32_t roup_directive_kind(const OmpDirective* directive);
// Get number of clauses
int32_t roup_directive_clause_count(const OmpDirective* directive);
// Create iterator for clauses
OmpClauseIterator* roup_directive_clauses_iter(const OmpDirective* directive);
// Get next clause (returns 1 if available, 0 if done)
int32_t roup_clause_iterator_next(OmpClauseIterator* iter, OmpClause** out);
// Get clause type (0=num_threads, 2=private, 6=reduction, etc.)
int32_t roup_clause_kind(const OmpClause* clause);
// Free iterator
void roup_clause_iterator_free(OmpClauseIterator* iter);
Step 2: Create RAII Wrappers (Modern C++)
Instead of manual memory management, let’s use RAII (Resource Acquisition Is Initialization) to automatically clean up resources.
Create roup_wrapper.hpp:
#pragma once
#include <cstdint>
#include <memory>
#include <string>
#include <vector>
#include <optional>
// Forward declarations of opaque C types
struct OmpDirective;
struct OmpClause;
struct OmpClauseIterator;
// C API declarations
extern "C" {
OmpDirective* roup_parse(const char* input);
void roup_directive_free(OmpDirective* directive);
int32_t roup_directive_kind(const OmpDirective* directive);
int32_t roup_directive_clause_count(const OmpDirective* directive);
OmpClauseIterator* roup_directive_clauses_iter(const OmpDirective* directive);
int32_t roup_clause_iterator_next(OmpClauseIterator* iter, OmpClause** out);
void roup_clause_iterator_free(OmpClauseIterator* iter);
int32_t roup_clause_kind(const OmpClause* clause);
}
namespace roup {
// RAII wrapper for OmpDirective
class Directive {
private:
OmpDirective* ptr_;
public:
// Constructor: parse directive
explicit Directive(const std::string& input)
: ptr_(roup_parse(input.c_str())) {}
// Destructor: automatic cleanup
~Directive() {
if (ptr_) {
roup_directive_free(ptr_);
}
}
// Delete copy (move-only type)
Directive(const Directive&) = delete;
Directive& operator=(const Directive&) = delete;
// Move constructor
Directive(Directive&& other) noexcept : ptr_(other.ptr_) {
other.ptr_ = nullptr;
}
// Move assignment
Directive& operator=(Directive&& other) noexcept {
if (this != &other) {
if (ptr_) roup_directive_free(ptr_);
ptr_ = other.ptr_;
other.ptr_ = nullptr;
}
return *this;
}
// Check if parse succeeded
bool valid() const { return ptr_ != nullptr; }
explicit operator bool() const { return valid(); }
// Get directive kind
int32_t kind() const {
return ptr_ ? roup_directive_kind(ptr_) : -1;
}
// Get clause count
int32_t clause_count() const {
return ptr_ ? roup_directive_clause_count(ptr_) : 0;
}
// Get raw pointer (for advanced usage)
OmpDirective* get() const { return ptr_; }
};
// RAII wrapper for OmpClauseIterator
class ClauseIterator {
private:
OmpClauseIterator* iter_;
public:
explicit ClauseIterator(const Directive& directive)
: iter_(directive.valid() ? roup_directive_clauses_iter(directive.get()) : nullptr) {}
~ClauseIterator() {
if (iter_) {
roup_clause_iterator_free(iter_);
}
}
// Delete copy
ClauseIterator(const ClauseIterator&) = delete;
ClauseIterator& operator=(const ClauseIterator&) = delete;
// Get next clause kind (returns std::optional)
std::optional<int32_t> next() {
if (!iter_) return std::nullopt;
OmpClause* clause = nullptr;
if (roup_clause_iterator_next(iter_, &clause) == 1) {
return roup_clause_kind(clause);
// Note: Don't free clause - owned by directive
}
return std::nullopt;
}
};
// Helper: Convert directive kind to name
inline const char* directive_kind_name(int32_t kind) {
switch (kind) {
case 0: return "parallel";
case 1: return "for";
case 2: return "sections";
case 3: return "single";
case 4: return "task";
case 5: return "master";
case 6: return "critical";
case 7: return "barrier";
case 8: return "taskwait";
case 9: return "taskgroup";
case 10: return "atomic";
case 11: return "flush";
case 12: return "ordered";
case 13: return "target";
case 14: return "teams";
case 15: return "distribute";
case 16: return "metadirective";
default: return "unknown";
}
}
// Helper: Convert clause kind to name
inline const char* clause_kind_name(int32_t kind) {
switch (kind) {
case 0: return "num_threads";
case 1: return "if";
case 2: return "private";
case 3: return "shared";
case 4: return "firstprivate";
case 5: return "lastprivate";
case 6: return "reduction";
case 7: return "schedule";
case 8: return "collapse";
case 9: return "ordered";
case 10: return "nowait";
case 11: return "default";
default: return "unknown";
}
}
} // namespace roup
Key RAII benefits:
- ✅ Automatic cleanup - No need to call
_free()functions - ✅ Exception safe - Resources freed even if exceptions thrown
- ✅ Move semantics - Efficient transfer of ownership
- ✅ Modern C++ - Uses
std::optional, deleted copy constructors
Step 3: Build the Analyzer Tool
Create omp_analyzer.cpp:
#include "roup_wrapper.hpp"
#include <iostream>
#include <fstream>
#include <string>
#include <map>
#include <vector>
#include <algorithm>
struct DirectiveInfo {
int line_number;
std::string directive_name;
int clause_count;
std::vector<std::string> clause_names;
};
class OMPAnalyzer {
private:
std::vector<DirectiveInfo> directives_;
std::map<std::string, int> directive_counts_;
public:
// Analyze a single line for OpenMP pragmas
void analyze_line(const std::string& line, int line_number) {
// Check if line contains OpenMP pragma
if (line.find("#pragma omp") == std::string::npos) {
return;
}
// Parse the directive
roup::Directive directive(line);
if (!directive) {
std::cerr << "Warning: Failed to parse line " << line_number
<< ": " << line << std::endl;
return;
}
// Extract directive info
DirectiveInfo info;
info.line_number = line_number;
info.directive_name = roup::directive_kind_name(directive.kind());
info.clause_count = directive.clause_count();
// Extract clause names
roup::ClauseIterator iter(directive);
while (auto clause_kind = iter.next()) {
info.clause_names.push_back(roup::clause_kind_name(*clause_kind));
}
directives_.push_back(info);
directive_counts_[info.directive_name]++;
}
// Analyze entire file
bool analyze_file(const std::string& filename) {
std::ifstream file(filename);
if (!file) {
std::cerr << "Error: Cannot open file: " << filename << std::endl;
return false;
}
std::string line;
int line_number = 0;
while (std::getline(file, line)) {
line_number++;
analyze_line(line, line_number);
}
return true;
}
// Print detailed report
void print_report() const {
if (directives_.empty()) {
std::cout << "No OpenMP directives found." << std::endl;
return;
}
std::cout << "\nFound " << directives_.size()
<< " OpenMP directive(s):\n" << std::endl;
for (const auto& info : directives_) {
std::cout << " Line " << info.line_number << ": "
<< info.directive_name << " ("
<< info.clause_count << " clause"
<< (info.clause_count != 1 ? "s" : "") << ")";
if (!info.clause_names.empty()) {
std::cout << " [";
for (size_t i = 0; i < info.clause_names.size(); ++i) {
if (i > 0) std::cout << ", ";
std::cout << info.clause_names[i];
}
std::cout << "]";
}
std::cout << std::endl;
}
print_summary();
}
// Print summary statistics
void print_summary() const {
int total_clauses = 0;
for (const auto& info : directives_) {
total_clauses += info.clause_count;
}
std::cout << "\nSummary:" << std::endl;
std::cout << " Total directives: " << directives_.size() << std::endl;
std::cout << " Total clauses: " << total_clauses << std::endl;
// Find most common directive
auto max_elem = std::max_element(
directive_counts_.begin(),
directive_counts_.end(),
[](const auto& a, const auto& b) { return a.second < b.second; }
);
if (max_elem != directive_counts_.end()) {
std::cout << " Most common: " << max_elem->first
<< " (" << max_elem->second << " occurrence"
<< (max_elem->second != 1 ? "s" : "") << ")" << std::endl;
}
// Print directive type breakdown
if (directive_counts_.size() > 1) {
std::cout << "\nDirective breakdown:" << std::endl;
for (const auto& [name, count] : directive_counts_) {
std::cout << " " << name << ": " << count << std::endl;
}
}
}
};
int main(int argc, char* argv[]) {
if (argc < 2) {
std::cerr << "Usage: " << argv[0] << " <source-file>" << std::endl;
std::cerr << "Example: " << argv[0] << " mycode.c" << std::endl;
return 1;
}
OMPAnalyzer analyzer;
if (!analyzer.analyze_file(argv[1])) {
return 1;
}
analyzer.print_report();
return 0;
}
```text
---
## Step 4: Build and Run
### Compilation
```bash
# Build ROUP library first
cd roup
cargo build --release
# Build the analyzer
clang++ -std=c++17 omp_analyzer.cpp \
-L./target/release -lroup \
-Wl,-rpath,./target/release \
-o omp_analyzer
# Or with g++:
g++ -std=c++17 omp_analyzer.cpp \
-L./target/release -lroup \
-Wl,-rpath,./target/release \
-o omp_analyzer
Test with Sample File
Create test.c:
#include <stdio.h>
int main() {
int n = 1000;
int sum = 0;
#pragma omp parallel for reduction(+:sum) num_threads(4)
for (int i = 0; i < n; i++) {
sum += i;
}
#pragma omp parallel
{
#pragma omp single
{
printf("Hello from thread\\n");
}
}
#pragma omp task depend(in: sum)
printf("Sum: %d\\n", sum);
#pragma omp barrier
return 0;
}
Run the Analyzer
$ ./omp_analyzer test.c
Found 5 OpenMP directive(s):
Line 7: for (3 clauses) [reduction, num_threads]
Line 11: parallel (0 clauses)
Line 13: single (0 clauses)
Line 19: task (1 clause) [depend]
Line 22: barrier (0 clauses)
Summary:
Total directives: 5
Total clauses: 4
Most common: parallel (1 occurrence)
Directive breakdown:
barrier: 1
for: 1
parallel: 1
single: 1
task: 1
Step 5: Advanced Features
5.1 Extract Variable Names from Clauses
Some clauses (like private, shared) contain variable lists. To access them:
// In C API (add to roup_wrapper.hpp):
extern "C" {
OmpStringList* roup_clause_variables(const OmpClause* clause);
int32_t roup_string_list_len(const OmpStringList* list);
const char* roup_string_list_get(const OmpStringList* list, int32_t index);
void roup_string_list_free(OmpStringList* list);
}
// RAII wrapper for string list
class StringList {
private:
OmpStringList* list_;
public:
explicit StringList(OmpStringList* list) : list_(list) {}
~StringList() {
if (list_) roup_string_list_free(list_);
}
int32_t size() const {
return list_ ? roup_string_list_len(list_) : 0;
}
std::string get(int32_t index) const {
if (!list_ || index >= size()) return "";
return roup_string_list_get(list_, index);
}
std::vector<std::string> to_vector() const {
std::vector<std::string> result;
for (int32_t i = 0; i < size(); ++i) {
result.push_back(get(i));
}
return result;
}
};
```text
### 5.2 Handle Parse Errors Gracefully
```cpp
std::optional<DirectiveInfo> parse_directive(const std::string& line) {
roup::Directive directive(line);
if (!directive) {
return std::nullopt; // Parse failed
}
DirectiveInfo info;
info.directive_name = roup::directive_kind_name(directive.kind());
info.clause_count = directive.clause_count();
return info;
}
5.3 Process Multiple Files
void analyze_project(const std::vector<std::string>& files) {
OMPAnalyzer combined;
for (const auto& file : files) {
OMPAnalyzer file_analyzer;
if (file_analyzer.analyze_file(file)) {
std::cout << "\n=== " << file << " ===" << std::endl;
file_analyzer.print_report();
}
}
}
Real-World Use Cases
1. OpenMP Linter
Check for common mistakes:
parallel forwithoutprivateon loop variablereductionwith unsupported operators- Missing
nowaitopportunities
2. Code Modernization Tool
- Convert OpenMP 3.x → 5.x syntax
- Suggest modern alternatives (e.g.,
taskloopinstead of manual tasks)
3. Performance Analyzer
- Count parallelization opportunities
- Identify nested parallel regions (potential over-subscription)
- Find synchronization hotspots
4. IDE Integration
- Syntax highlighting for OpenMP pragmas
- Auto-completion for clause names
- Quick documentation lookup
Performance Considerations
ROUP is fast:
- Parsing a typical directive: ~1-5 microseconds
- Zero-copy design: Uses string slices, not allocations
- Suitable for real-time IDE integration
Benchmark (on typical code):
File size: 10,000 lines
OpenMP directives: 500
Total parse time: ~2.5 milliseconds
Throughput: ~200,000 directives/second
Troubleshooting
Issue: undefined reference to roup_parse
Solution: Make sure library path is correct:
clang++ ... -L./target/release -lroup -Wl,-rpath,./target/release
Issue: error while loading shared libraries: libroup.so
Solution: Set LD_LIBRARY_PATH (Linux) or DYLD_LIBRARY_PATH (macOS):
export LD_LIBRARY_PATH=./target/release:$LD_LIBRARY_PATH
Issue: Parse failures on valid pragmas
Cause: ROUP currently supports C/C++ syntax (#pragma omp), not Fortran (!$omp).
Solution: Ensure input starts with #pragma omp.
Next Steps
- Explore the Rust API - See API Reference
- Check out more examples - GitHub repository
- Contribute - Report issues or submit PRs!
Complete Example Code
All code from this tutorial is available at:
examples/cpp/roup_wrapper.hpp- RAII wrappersexamples/cpp/omp_analyzer.cpp- Full analyzer tool
Clone and try:
git clone https://github.com/ouankou/roup.git
cd roup/examples/cpp
make # Builds all examples
./omp_analyzer ../../tests/data/sample.c
Happy parsing! 🚀
Fortran Tutorial
This tutorial demonstrates how to use ROUP to parse OpenMP directives in Fortran code.
🚧 Experimental Status
Fortran support in ROUP is currently experimental. Features may change as development progresses.
Introduction
ROUP supports parsing OpenMP directives from Fortran source code in both free-form and fixed-form formats. This enables Fortran developers to:
- Parse OpenMP directives from Fortran source
- Validate directive syntax
- Extract clause information
- Build tools for Fortran+OpenMP code analysis
Fortran Directive Formats
Free-Form (Modern Fortran)
Free-form Fortran uses the !$OMP sentinel (case-insensitive):
!$OMP PARALLEL PRIVATE(A, B) NUM_THREADS(4)
!$OMP END PARALLEL
!$omp parallel do private(i) reduction(+:sum)
!$OMP END PARALLEL DO
Fixed-Form (Fortran 77)
Fixed-form Fortran uses !$OMP or C$OMP sentinels in columns 1-6:
C$OMP PARALLEL PRIVATE(X, Y)
!$OMP DO SCHEDULE(STATIC, 10)
C$OMP END DO
C$OMP END PARALLEL
Fortran-Specific Directives
ROUP supports Fortran-specific directive syntax, including the Fortran DO construct which is equivalent to C/C++ FOR:
DO Directive (Fortran)
!$OMP DO PRIVATE(I) SCHEDULE(STATIC)
do i = 1, n
! Loop body
end do
!$OMP END DO
PARALLEL DO (Fortran)
!$OMP PARALLEL DO PRIVATE(I) REDUCTION(+:SUM)
do i = 1, n
sum = sum + a(i)
end do
!$OMP END PARALLEL DO
Note: ROUP recognizes both Fortran syntax (DO) and C syntax (FOR) for compatibility:
!$OMP DO→ Fortran-specific (preferred for Fortran code)!$OMP FOR→ C/C++ syntax (also works in Fortran mode)!$OMP PARALLEL DO→ Fortran-specific!$OMP PARALLEL FOR→ C/C++ syntax (also works in Fortran mode)
Language Constants
ROUP provides language format constants for Fortran parsing:
#define ROUP_LANG_C 0 // C/C++ (#pragma omp)
#define ROUP_LANG_FORTRAN_FREE 1 // Fortran free-form (!$OMP)
#define ROUP_LANG_FORTRAN_FIXED 2 // Fortran fixed-form (!$OMP or C$OMP)
Using the Rust API
Parsing Fortran Directives
use roup::lexer::Language;
use roup::parser::openmp;
// Create parser with Fortran free-form support
let parser = openmp::parser().with_language(Language::FortranFree);
// Parse a Fortran OpenMP directive
let input = "!$OMP PARALLEL PRIVATE(A, B) NUM_THREADS(4)";
let (rest, directive) = parser.parse(input).expect("parsing should succeed");
println!("Directive: {}", directive.name);
println!("Clauses: {}", directive.clauses.len());
Supported Language Formats
use roup::lexer::Language;
// C/C++ format (default)
let c_parser = openmp::parser().with_language(Language::C);
// Fortran free-form
let fortran_free = openmp::parser().with_language(Language::FortranFree);
// Fortran fixed-form
let fortran_fixed = openmp::parser().with_language(Language::FortranFixed);
Using the C API from Fortran
Setting Up Fortran-C Interoperability
Create an interface module for ROUP C API:
module roup_interface
use iso_c_binding
implicit none
! Language constants
integer(c_int), parameter :: ROUP_LANG_FORTRAN_FREE = 1
integer(c_int), parameter :: ROUP_LANG_FORTRAN_FIXED = 2
interface
! Parse with language specification
function roup_parse_with_language(input, language) &
bind(C, name="roup_parse_with_language")
use iso_c_binding
type(c_ptr), value :: input
integer(c_int), value :: language
type(c_ptr) :: roup_parse_with_language
end function roup_parse_with_language
! Free directive
subroutine roup_directive_free(directive) &
bind(C, name="roup_directive_free")
use iso_c_binding
type(c_ptr), value :: directive
end subroutine roup_directive_free
! Get directive name
function roup_directive_name(directive) &
bind(C, name="roup_directive_name")
use iso_c_binding
type(c_ptr), value :: directive
type(c_ptr) :: roup_directive_name
end function roup_directive_name
! Get clause count
function roup_directive_clause_count(directive) &
bind(C, name="roup_directive_clause_count")
use iso_c_binding
type(c_ptr), value :: directive
integer(c_size_t) :: roup_directive_clause_count
end function roup_directive_clause_count
end interface
end module roup_interface
Parsing Fortran Directives from Fortran
program parse_example
use iso_c_binding
use roup_interface
implicit none
type(c_ptr) :: directive_ptr, name_ptr
character(len=100) :: input = "!$OMP PARALLEL PRIVATE(X)"
character(kind=c_char), dimension(:), allocatable :: c_input
integer :: i, n
! Convert Fortran string to C string
n = len_trim(input)
allocate(c_input(n+1))
do i = 1, n
c_input(i) = input(i:i)
end do
c_input(n+1) = c_null_char
! Parse directive
directive_ptr = roup_parse_with_language(c_loc(c_input), &
ROUP_LANG_FORTRAN_FREE)
if (c_associated(directive_ptr)) then
! Get directive information
name_ptr = roup_directive_name(directive_ptr)
! ... process name_ptr ...
! Clean up
call roup_directive_free(directive_ptr)
else
print *, "Parse error"
end if
deallocate(c_input)
end program parse_example
Case Insensitivity
Fortran is case-insensitive, and ROUP respects this:
! All of these are equivalent
!$OMP PARALLEL PRIVATE(X)
!$omp parallel private(x)
!$Omp Parallel Private(X)
ROUP normalizes Fortran identifiers to lowercase internally while preserving the original case in the parsed output.
Common Fortran OpenMP Constructs
Parallel Regions
!$OMP PARALLEL PRIVATE(TID) SHARED(N)
! Parallel code
!$OMP END PARALLEL
Work-Sharing Constructs
In Fortran, use DO instead of C’s for:
!$OMP DO PRIVATE(I) SCHEDULE(STATIC, 10)
do i = 1, n
! Loop body
end do
!$OMP END DO
Or combined:
!$OMP PARALLEL DO PRIVATE(I,J) REDUCTION(+:SUM)
do i = 1, n
sum = sum + array(i)
end do
!$OMP END PARALLEL DO
Array Sections
Fortran array sections use different syntax than C:
!$OMP PARALLEL PRIVATE(A(1:N), B(:,1:M))
! Work with array sections
!$OMP END PARALLEL
Common Blocks
Fortran’s THREADPRIVATE can apply to common blocks:
COMMON /MYDATA/ X, Y, Z
!$OMP THREADPRIVATE(/MYDATA/)
Examples
See the examples/fortran/ directory for complete working examples:
- basic_parse.f90: Simple Fortran directive examples
- tutorial_basic.f90: Full C API integration tutorial
Building Fortran Programs with ROUP
Using gfortran
# Compile Fortran code
gfortran -c my_program.f90
# Link with ROUP library
gcc my_program.o -L/path/to/roup/target/release -lroup -o my_program
# Run (set LD_LIBRARY_PATH)
LD_LIBRARY_PATH=/path/to/roup/target/release ./my_program
Makefile Example
FC = gfortran
ROUP_LIB = -L../../target/release -lroup -Wl,-rpath,../../target/release
my_program: my_program.f90
$(FC) -o $@ $< $(ROUP_LIB)
Known Limitations
⚠️ Current limitations in experimental Fortran support:
-
Continuation Syntax Requirements: Multi-line directives must use standard continuation markers
- Fortran: Terminate continued lines with
&and optionally repeat the sentinel on the next line - C/C++: Place a trailing
\at the end of each continued line - See Line Continuations for canonical examples across languages
- Fortran: Terminate continued lines with
-
End Directives:
!$OMP END PARALLELand similar end directives may not parse correctly -
Array Sections: Complex array section syntax may have issues
-
Fixed-Form Column Rules: Strict column 1-6 sentinel placement not enforced
-
Fortran-Specific Directives: Some Fortran-only directives (e.g.,
WORKSHARE) may not be registered
Troubleshooting
Parse Errors
If parsing fails:
- Check sentinel format: Use
!$OMPfor free-form or!$OMP/C$OMPfor fixed-form - Verify case: While case-insensitive, ensure proper formatting
- Check whitespace: Ensure proper spacing after sentinel
- Use correct language mode: Specify
ROUP_LANG_FORTRAN_FREEorROUP_LANG_FORTRAN_FIXED
Directive Not Found
Some directives may not be in the registry. Check:
- Is the directive name correct? (Fortran supports both
DOandFORsyntax) - Is it a composite directive? (Use
PARALLEL DOnotPARALLEL+DO)
API Reference
See:
- C Tutorial - C API documentation
- API Reference - Complete API listing
- Architecture - Parser internals
Contributing
Fortran support is under active development. Contributions welcome:
- Test with real Fortran+OpenMP code
- Report parsing issues
- Add more Fortran-specific test cases
- Improve documentation
Further Reading
- OpenMP 5.2 Specification - Official OpenMP standard
- Fortran OpenMP Documentation - GCC Fortran OpenMP guide
- ISO C Binding - Fortran-C interop guide
ompparser Compatibility Layer
ROUP provides a drop-in compatibility layer for projects using ompparser, allowing you to switch to ROUP’s faster, safer Rust-based parser without changing your code.
What is it?
A compatibility layer that provides:
- Same API as ompparser - no code changes needed
- Drop-in replacement via
libompparser.so - ROUP backend - expected-to-be faster, safer parsing in Rust
- Reuses ompparser methods - toString(), generateDOT(), etc. (zero duplication)
Quick Start
One-Command Build
cd compat/ompparser
./build.sh
The script will:
- Check prerequisites (git, cmake, gcc, cargo)
- Initialize ompparser submodule
- Build ROUP core library
- Build
libompparser.so(size varies by build configuration) - Run the upstream ompparser ctest suite
Manual Build
# 1. Initialize ompparser submodule
git submodule update --init --recursive
# 2. Build ROUP core
cd /path/to/roup
cargo build --release
# 3. Build compatibility layer
cd compat/ompparser
mkdir -p build && cd build
cmake ..
make
# 4. Run tests
ctest --output-on-failure
Usage
Drop-in Replacement
Install system-wide and use exactly like original ompparser:
# Install
cd compat/ompparser/build
sudo make install
sudo ldconfig
# Use (unchanged!)
g++ mycompiler.cpp -lompparser -o mycompiler
Code Example
Your existing ompparser code works without changes:
#include <OpenMPIR.h>
#include <iostream>
int main() {
// Parse OpenMP directive
OpenMPDirective* dir = parseOpenMP("omp parallel num_threads(4)", nullptr);
if (dir) {
// Use ompparser methods (all work!)
std::cout << "Kind: " << dir->getKind() << std::endl;
std::cout << "String: " << dir->toString() << std::endl;
// Access clauses
auto* clauses = dir->getAllClauses();
std::cout << "Clauses: " << clauses->size() << std::endl;
delete dir;
}
return 0;
}
CMake Integration
Option 1: pkg-config
find_package(PkgConfig REQUIRED)
pkg_check_modules(OMPPARSER REQUIRED ompparser)
target_link_libraries(your_app ${OMPPARSER_LIBRARIES})
target_include_directories(your_app PRIVATE ${OMPPARSER_INCLUDE_DIRS})
Option 2: Direct linking
target_link_libraries(your_app
${PATH_TO_ROUP}/compat/ompparser/build/libompparser.so
)
What’s Included
libompparser.so
Single self-contained library with:
- ROUP parser (statically embedded) - Rust-based, safe parsing
- ompparser methods - toString, generateDOT, etc.
- Compatibility wrapper - Seamless integration layer
- Self-contained - No libroup.so dependency (system libs via libc)
Comprehensive Testing
The shim is validated via the ompparser ctest suite bundled in the submodule. It covers directive kinds (including combined/end-paired forms), clause parameters, unparsing, and language modes.
Architecture
Your Application (OpenMP directives to parse)
↓
compat_impl.cpp (~190 lines) - Minimal wrapper
↓
ROUP C API (roup_parse, roup_directive_kind, etc.)
↓
ROUP Rust Parser (safe parser core)
↓
Returns: OpenMPDirective with ompparser methods
Key Design:
- Reuses 90% of ompparser code (no duplication)
- Git submodule approach - automatic ompparser upgrades
- Minimal unsafe code (~60 lines, 0.9%), all at FFI boundary
Known Limitations
The shim uses the generated ROUP_OMPD_*/ROUP_OMPC_* constants from src/roup_constants.h and preserves the canonical keyword spellings from ROUP. Legacy ompparser enum values are mapped internally.
Documentation
Complete documentation in compat/ompparser/:
- README.md - Complete compatibility layer guide with build instructions and examples
For detailed ROUP API documentation, see API Reference.
Requirements
- Rust toolchain (for ROUP core)
- CMake 3.10+
- C++11 compiler (gcc/clang)
- Git (for submodule management)
CI/CD
The compatibility layer is tested automatically via GitHub Actions (.github/workflows/build.yml):
- Tests ROUP core (always)
- Tests compat layer (if submodule initialized)
- Verifies library builds successfully
- Validates drop-in functionality
- Checks constants synchronization (checksum validation)
FAQ
Q: Do I need to change my code?
A: No! It’s a drop-in replacement with the same API.
Q: What if I don’t need compat layer?
A: ROUP works perfectly standalone. The compat layer is optional.
Q: How do I get ompparser upgrades?
A: git submodule update --remote pulls latest ompparser automatically.
Q: What about performance?
A: ROUP is expected to be faster than original ompparser due to Rust optimizations.
Q: Is it stable?
A: ⚠️ Experimental stage - thoroughly tested (46 tests) but under active development.
Support
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Email: See Contributing Guide
accparser Compatibility Layer
ROUP provides a drop-in compatibility layer for projects using accparser, allowing you to switch to ROUP’s faster, safer Rust-based parser without changing your code.
What is it?
A compatibility layer that provides:
- Same API as accparser - no code changes needed
- Drop-in replacement via
libaccparser.so - Zero ANTLR4 dependency - no need to install antlr4 runtime or toolchain
- ROUP backend - faster, safer parsing in Rust
- Reuses accparser methods - toString(), generateDOT(), etc. (zero duplication)
Quick Start
One-Command Build
cd compat/accparser
./build.sh
The script will:
- Check prerequisites (git, cmake, gcc, cargo)
- Initialize accparser submodule
- Build ROUP core library
- Build
libaccparser.so - Run the full upstream accparser ctest suite (900+ cases)
Manual Build
# 1. Initialize accparser submodule
git submodule update --init --recursive
# 2. Build ROUP core
cd /path/to/roup
cargo build --release
# 3. Build compatibility layer
cd compat/accparser
mkdir -p build && cd build
cmake ..
make
# 4. Run tests
ctest --output-on-failure
Usage
Drop-in Replacement
Install system-wide and use exactly like original accparser:
# Install
cd compat/accparser/build
sudo make install
sudo ldconfig
# Use (unchanged!)
g++ mycompiler.cpp -laccparser -o mycompiler
Code Example
Your existing accparser code works without changes:
#include <OpenACCIR.h>
#include <iostream>
int main() {
// Set language mode
setLang(ACC_Lang_C);
// Parse OpenACC directive
OpenACCDirective* dir = parseOpenACC("acc parallel num_gangs(4)", nullptr);
if (dir) {
// Use accparser methods (all work!)
std::cout << "Kind: " << dir->getKind() << std::endl;
std::cout << "String: " << dir->toString() << std::endl;
// Access clauses
auto* clauses = dir->getAllClauses();
std::cout << "Clauses: " << clauses->size() << std::endl;
delete dir;
}
return 0;
}
CMake Integration
Option 1: pkg-config
find_package(PkgConfig REQUIRED)
pkg_check_modules(ACCPARSER REQUIRED accparser)
target_link_libraries(your_app ${ACCPARSER_LIBRARIES})
target_include_directories(your_app PRIVATE ${ACCPARSER_INCLUDE_DIRS})
Option 2: Direct linking
target_link_libraries(your_app
${PATH_TO_ROUP}/compat/accparser/build/libaccparser.so
)
What’s Included
libaccparser.so
Single self-contained library with:
- ROUP parser (statically embedded) - Rust-based, safe parsing
- accparser methods - toString, etc.
- Compatibility wrapper - Seamless integration layer
- Self-contained - No libroup.so dependency
Comprehensive Testing
The bundled ctest run exercises the upstream accparser test matrix (directives, clauses, aliases, unparsing) against the ROUP-backed shim. It also validates end-paired directives, wait/cache data, and alias spellings under the ROUP_ACC_* namespace.
Architecture
Your Application (OpenACC directives to parse)
↓
compat_impl.cpp (~220 lines) - Minimal wrapper
↓
ROUP C API (acc_parse, acc_directive_kind, etc.)
↓
ROUP Rust Parser (safe parser core)
↓
Returns: OpenACCDirective with accparser methods
Key Design:
- Reuses 90% of accparser code (no duplication)
- Git submodule approach - automatic accparser upgrades
- No ANTLR4 dependency - cleaner build process
- Minimal unsafe code, all at FFI boundary
Supported Features
Directives (17+)
- Compute:
parallel,kernels,serial,loop - Data:
data,enter data,exit data,host_data - Synchronization:
wait,atomic - Other:
declare,routine,init,shutdown,set,update,end
Clauses (45+)
- Compute:
num_gangs,num_workers,vector_length,async,wait - Data:
copy,copyin,copyout,create,delete,present - Loop:
gang,worker,vector,seq,independent,collapse,tile - Other:
private,firstprivate,reduction,if,default
See compat/accparser/src/roup_constants.h (and the auto-generated
src/roup_constants.h) for the complete list of generated macros. Note that
generated OpenACC macros now use the ROUP_ACC_* prefix (for example
ROUP_ACCD_parallel and ROUP_ACCC_async). The legacy
ACC_* aliases are not emitted by the generator.
Known Limitations
The shim inherits ROUP’s canonical token choices. Generated OpenACC macros use the ROUP_ACC_* prefix (for example ROUP_ACCD_parallel); legacy ACC_* names are intentionally not emitted.
Documentation
Complete documentation in compat/accparser/:
- README.md - Complete compatibility layer guide with build instructions and examples
For detailed ROUP API documentation, see API Reference.
Requirements
- Rust toolchain (for ROUP core)
- CMake 3.10+
- C++11 compiler (gcc/clang)
- Git (for submodule management)
- NO antlr4 required!
CI/CD
The compatibility layer is tested automatically via GitHub Actions (.github/workflows/ci.yml):
- Tests ROUP core (always)
- Tests compat layer (if submodule initialized)
- Verifies library builds successfully
- Validates drop-in functionality
- Checks constants synchronization (checksum validation)
FAQ
Q: Do I need to change my code? A: No! It’s a drop-in replacement with the same API.
Q: What about ANTLR4 dependency? A: ROUP completely eliminates the ANTLR4 dependency. You only need Rust, CMake, and a C++ compiler.
Q: What if I don’t need compat layer? A: ROUP works perfectly standalone. The compat layer is optional.
Q: How do I get accparser upgrades?
A: git submodule update --remote pulls latest accparser automatically.
Q: What about performance? A: ROUP’s hand-written parser is 2-5x faster than ANTLR-generated parsers for typical OpenACC pragmas.
Q: Is it stable? A: Thoroughly tested (38 tests) and ready for use. All tests passing.
Support
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Email: See Contributing Guide
API Reference
This page points to the primary surfaces for Rust and C/C++/Fortran users. Full Rustdoc is generated as part of the build.
Rust
- Rustdoc:
cargo doc --no-depsgenerates the full API docs. - Parsing:
parser::openmp::parser()andparser::openacc::parser()return aParserthat accepts C or Fortran sentinels viawith_language(...). Convenience helpersparse_omp_directive/parse_acc_directiveare also available. - AST/IR:
Parser::parse_ast(...)converts parsed directives into the IR layer for semantic queries and unparsing. - Translation:
ir::translate::{translate_c_to_fortran, translate_fortran_to_c}(and their_irvariants) map OpenMP directives between languages. - Debugger:
roup_debugbinary wrapsdebugger::DebugSessionfor interactive and batch stepping.
Quick example:
use roup::parser::openmp;
use roup::lexer::Language;
let parser = openmp::parser().with_language(Language::C);
let (_, directive) = parser.parse("#pragma omp parallel for").unwrap();
println!("name: {}", directive.name);
C / OpenACC APIs
src/roup_constants.h is generated at build time and contains:
- Directive/clause macro constants (
ROUP_OMPD_*,ROUP_OMPC_*,ROUP_ACCD_*,ROUP_ACCC_*) - Language codes:
ROUP_LANG_C = 0,ROUP_LANG_FORTRAN_FREE = 1,ROUP_LANG_FORTRAN_FIXED = 2 - Unknown sentinel:
-1
Key function families (OpenMP):
roup_parse,roup_parse_with_language- Iterators:
roup_directive_clauses_iter,roup_clause_iterator_next,*_free - Queries:
roup_directive_kind,roup_directive_clause_count,roup_clause_kind, schedule/reduction/default queries, variable lists - Translation helpers:
roup_convert_language,roup_string_free
Key function families (OpenACC):
acc_parse,acc_parse_with_language,acc_directive_free- Clause queries:
acc_clause_kind,acc_clause_expressions_count,acc_clause_expression_at,acc_clause_modifier,acc_clause_operator,acc_clause_original_keyword - Directive extras: cache and wait accessors, routine name, end-paired mapping
Error handling:
- Functions returning pointers (e.g.,
*_parse,*_iter, string accessors) returnNULLon error and must be checked before use. - Functions returning integers (e.g., kind lookups) return
-1for invalid inputs or unknown kinds.
C++ RAII
See the C++ tutorial for RAII wrappers that call the C API and auto-free resources.
OpenMP support
ROUP registers every directive and clause keyword from the OpenMP 6.0 specification. The canonical listings live in the generated reference chapters:
Quick facts
| Feature | Details |
|---|---|
| OpenMP versions | 3.0 – 6.0 |
| Directive/Clause coverage | Complete registry mirroring the spec |
| Languages | C, C++, Fortran |
| Automated tests | Keyword coverage, round-trips, IR/display parity, and VV suites |
Unknown directives and clauses are rejected so unsupported constructs fail fast.
The tests/openmp_keyword_coverage.rs integration test keeps the registry in
sync with the specification, and additional round-trip tests validate parsing,
IR conversion, and display.
OpenACC support
ROUP implements the complete OpenACC 3.4 directive and clause vocabulary. The reference chapters below catalogue every keyword and restriction with section and page references back to the official specification PDF.
Quick facts
| Feature | Details |
|---|---|
| OpenACC version | 3.4 |
| Directive keywords | 24 (including space/underscore aliases) |
| Clause keywords | 49 |
| Languages | C, C++, Fortran (free & fixed form) |
| Automated tests | Full directive and clause round-trip coverage |
Unknown directives and clauses are rejected so unsupported constructs fail fast.
The tests/openacc_roundtrip.rs suite keeps the registry in sync with the
specification and exercises round-trip formatting for every keyword, and the C
API mappings are validated to guarantee consistent clause kind values.
OpenACC 3.4 Directives and Clauses
This comprehensive reference catalogue documents all OpenACC 3.4 keywords from the OpenACC Application Programming Interface Version 3.4 specification.
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
- 24 Directives/Constructs - All compute, data, loop, synchronization, declaration, and runtime directives (including space/underscore alias forms)
- 50+ Clauses - All clause keywords
- 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 registers each alias alongside the canonical clause name so original source spellings are retained during parsing.
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 every OpenACC 3.4 directive with its allowed clauses and enumerates the clause-level modifiers and arguments. Section numbers and page references point back to the canonical OpenACC 3.4 PDF so that every entry can be validated directly against the specification. Use this document together with the directive/clauses index and the restrictions digest to obtain a complete, single-source view of the standard.
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 enumerates every rule, restriction, and mandatory condition attached to OpenACC 3.4 directives and clauses. Entries are grouped by the section that defines the constraint. Page references match the official specification pagination so each item can be cross-checked word-for-word.
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 6.0 Directives and Clauses
This catalogue is derived directly from the ROUP parser’s keyword registries and
shows exactly which OpenMP 6.0 directive and clause tokens are recognised. The
source of truth is OpenMpDirective::ALL and OpenMpClause::ALL in
src/parser/openmp.rs. Every entry listed
below is exercised by the automated support-matrix tests, so the tables always
reflect parser reality.
For the normative meaning of each keyword, consult the OpenMP Application Programming Interface Version 6.0 specification.
Directive keywords (127 total)
allocate | distribute parallel loop simd | parallel master taskloop | target teams distribute parallel for simd |
allocators | distribute simd | parallel master taskloop simd | target teams distribute parallel loop |
assume | do | parallel sections | target teams distribute parallel loop simd |
assumes | do simd | requires | target teams distribute simd |
atomic | end declare target | reverse | target teams loop |
atomic capture | error | scan | target teams loop simd |
atomic compare capture | flush | scope | target update |
atomic read | for | section | task |
atomic update | for simd | sections | task iteration |
atomic write | fuse | simd | taskgraph |
barrier | groupprivate | single | taskgroup |
begin assumes | interchange | split | taskloop |
begin declare target | interop | stripe | taskloop simd |
begin declare variant | loop | target | taskwait |
begin metadirective | masked | target data | taskyield |
cancel | masked taskloop | target enter data | teams |
cancellation point | masked taskloop simd | target exit data | teams distribute |
critical | master | target loop | teams distribute parallel do |
declare induction | metadirective | target loop simd | teams distribute parallel do simd |
declare mapper | nothing | target parallel | teams distribute parallel for |
declare reduction | ordered | target parallel do | teams distribute parallel for simd |
declare simd | parallel | target parallel do simd | teams distribute parallel loop |
declare target | parallel do | target parallel for | teams distribute parallel loop simd |
declare variant | parallel do simd | target parallel for simd | teams distribute simd |
depobj | parallel for | target parallel loop | teams loop |
dispatch | parallel for simd | target parallel loop simd | teams loop simd |
distribute | parallel loop | target simd | threadprivate |
distribute parallel do | parallel loop simd | target teams | tile |
distribute parallel do simd | parallel masked | target teams distribute | unroll |
distribute parallel for | parallel masked taskloop | target teams distribute parallel do | workdistribute |
distribute parallel for simd | parallel masked taskloop simd | target teams distribute parallel do simd | workshare |
distribute parallel loop | parallel master | target teams distribute parallel for |
Clause keywords (132 total)
absent | doacross | looprange | reproducible |
acq_rel | dynamic_allocators | map | reverse |
acquire | enter | match | reverse_offload |
adjust_args | exclusive | memscope | safelen |
affinity | fail | mergeable | safesync |
align | filter | message | schedule |
aligned | final | no_openmp | self_maps |
allocate | firstprivate | no_openmp_constructs | seq_cst |
allocator | from | no_openmp_routines | severity |
append_args | full | no_parallelism | shared |
apply | grainsize | nocontext | simd |
at | graph_id | nogroup | simdlen |
atomic_default_mem_order | graph_reset | nontemporal | sizes |
bind | has_device_addr | notinbranch | task_reduction |
capture | hint | novariants | thread_limit |
collapse | holds | nowait | threads |
collector | if | num_tasks | threadset |
combiner | in_reduction | num_teams | tile |
compare | inbranch | num_threads | to |
contains | inclusive | order | transparent |
copyin | indirect | ordered | unified_address |
copyprivate | induction | otherwise | unified_shared_memory |
counts | inductor | partial | uniform |
default | init | permutation | unroll |
defaultmap | init_complete | priority | untied |
depend | initializer | private | update |
destroy | interop | proc_bind | use |
detach | is_device_ptr | public | use_device_addr |
device | label | read | use_device_ptr |
device_resident | lastprivate | reduction | uses_allocators |
device_safesync | linear | relaxed | weak |
device_type | link | release | when |
dist_schedule | local | replayable | write |
Keeping the list in sync
To regenerate the tables after changing src/parser/openmp.rs, run the helper
script below and replace the output in this document:
python - <<'PY'
import math, pathlib, re
text = pathlib.Path('src/parser/openmp.rs').read_text()
block = re.search(r"openmp_directives!\s*{(.*?)}\s*\n\npub fn clause_registry", text, re.S).group(1)
directives = sorted({re.search(r'"([^"]+)"', line).group(1)
for line in block.splitlines() if '"' in line})
clauses_block = re.search(r"openmp_clauses!\s*{(.*?)}\s*\n\nmacro_rules!", text, re.S).group(1)
clauses = sorted({re.search(r'name: "([^"]+)"', line).group(1)
for line in clauses_block.splitlines() if 'name:' in line})
def make_table(items, columns=4):
rows = math.ceil(len(items) / columns)
table = ['| ' + ' | '.join([''] * columns) + ' |', '| ' + ' | '.join(['---'] * columns) + ' |']
for r in range(rows):
row = []
for c in range(columns):
idx = c * rows + r
row.append(f"`{items[idx]}`" if idx < len(items) else '')
table.append('| ' + ' | '.join(row) + ' |')
return '\n'.join(table)
print(make_table(directives))
print('\n')
print(make_table(clauses))
PY
Keeping this document machine-derived guarantees it matches the parser at all times.
OpenMP 6.0 Directive–Clause Components
This reference summarises how the ROUP parser tokenises OpenMP 6.0 clause
keywords. Rather than attempting to restate the entire specification, the
information below mirrors the parser’s ClauseRule
data so you can quickly see which textual forms are accepted. Combined
constructs (for example parallel for or target teams distribute parallel for simd) are already part of the directive keyword registry listed in the
directive catalogue.
Note: The OpenMP specification defines which directives may use a given clause and any semantic restrictions. ROUP currently enforces keyword syntax and delegates the normative rules to higher layers. Consult the OpenMP 6.0 specification for the full directive–clause compatibility matrix.
Clause syntax summary
| Clause | Parser rule | Accepted forms |
|---|---|---|
absent | Parenthesized | absent(...) |
acq_rel | Bare | #pragma omp parallel acq_rel |
acquire | Bare | #pragma omp parallel acquire |
adjust_args | Parenthesized | adjust_args(...) |
affinity | Parenthesized | affinity(...) |
align | Parenthesized | align(...) |
aligned | Parenthesized | aligned(...) |
allocate | Parenthesized | allocate(...) |
allocator | Parenthesized | allocator(...) |
append_args | Parenthesized | append_args(...) |
apply | Parenthesized | apply(...) |
at | Parenthesized | at(...) |
atomic_default_mem_order | Parenthesized | atomic_default_mem_order(...) |
bind | Parenthesized | bind(...) |
capture | Flexible | capture or capture(...) |
collapse | Parenthesized | collapse(...) |
collector | Parenthesized | collector(...) |
combiner | Parenthesized | combiner(...) |
compare | Flexible | compare or compare(...) |
contains | Parenthesized | contains(...) |
copyin | Parenthesized | copyin(...) |
copyprivate | Parenthesized | copyprivate(...) |
counts | Parenthesized | counts(...) |
default | Parenthesized | default(...) |
defaultmap | Parenthesized | defaultmap(...) |
depend | Parenthesized | depend(...) |
destroy | Flexible | destroy or destroy(...) |
detach | Parenthesized | detach(...) |
device | Parenthesized | device(...) |
device_resident | Parenthesized | device_resident(...) |
device_safesync | Flexible | device_safesync or device_safesync(...) |
device_type | Parenthesized | device_type(...) |
dist_schedule | Parenthesized | dist_schedule(...) |
doacross | Parenthesized | doacross(...) |
dynamic_allocators | Bare | #pragma omp parallel dynamic_allocators |
enter | Parenthesized | enter(...) |
exclusive | Bare | #pragma omp parallel exclusive |
fail | Flexible | fail or fail(...) |
filter | Parenthesized | filter(...) |
final | Parenthesized | final(...) |
firstprivate | Parenthesized | firstprivate(...) |
from | Parenthesized | from(...) |
full | Flexible | full or full(...) |
grainsize | Parenthesized | grainsize(...) |
graph_id | Parenthesized | graph_id(...) |
graph_reset | Parenthesized | graph_reset(...) |
has_device_addr | Parenthesized | has_device_addr(...) |
hint | Parenthesized | hint(...) |
holds | Parenthesized | holds(...) |
if | Parenthesized | if(...) |
in_reduction | Parenthesized | in_reduction(...) |
inbranch | Bare | #pragma omp parallel inbranch |
inclusive | Bare | #pragma omp parallel inclusive |
indirect | Flexible | indirect or indirect(...) |
induction | Parenthesized | induction(...) |
inductor | Parenthesized | inductor(...) |
init | Parenthesized | init(...) |
init_complete | Flexible | init_complete or init_complete(...) |
initializer | Parenthesized | initializer(...) |
interop | Parenthesized | interop(...) |
is_device_ptr | Parenthesized | is_device_ptr(...) |
label | Parenthesized | label(...) |
lastprivate | Parenthesized | lastprivate(...) |
linear | Parenthesized | linear(...) |
link | Parenthesized | link(...) |
local | Parenthesized | local(...) |
looprange | Parenthesized | looprange(...) |
map | Parenthesized | map(...) |
match | Parenthesized | match(...) |
memscope | Parenthesized | memscope(...) |
mergeable | Bare | #pragma omp parallel mergeable |
message | Parenthesized | message(...) |
no_openmp | Flexible | no_openmp or no_openmp(...) |
no_openmp_constructs | Flexible | no_openmp_constructs or no_openmp_constructs(...) |
no_openmp_routines | Flexible | no_openmp_routines or no_openmp_routines(...) |
no_parallelism | Flexible | no_parallelism or no_parallelism(...) |
nocontext | Parenthesized | nocontext(...) |
nogroup | Bare | #pragma omp parallel nogroup |
nontemporal | Parenthesized | nontemporal(...) |
notinbranch | Bare | #pragma omp parallel notinbranch |
novariants | Flexible | novariants or novariants(...) |
nowait | Bare | #pragma omp parallel nowait |
num_tasks | Parenthesized | num_tasks(...) |
num_teams | Parenthesized | num_teams(...) |
num_threads | Parenthesized | num_threads(...) |
order | Parenthesized | order(...) |
ordered | Flexible | ordered or ordered(...) |
otherwise | Parenthesized | otherwise(...) |
partial | Flexible | partial or partial(...) |
permutation | Parenthesized | permutation(...) |
priority | Parenthesized | priority(...) |
private | Parenthesized | private(...) |
proc_bind | Parenthesized | proc_bind(...) |
public | Flexible | public or public(...) |
read | Flexible | read or read(...) |
reduction | Parenthesized | reduction(...) |
relaxed | Bare | #pragma omp parallel relaxed |
release | Bare | #pragma omp parallel release |
replayable | Flexible | replayable or replayable(...) |
reproducible | Bare | #pragma omp parallel reproducible |
reverse | Flexible | reverse or reverse(...) |
reverse_offload | Bare | #pragma omp parallel reverse_offload |
safelen | Parenthesized | safelen(...) |
safesync | Bare | #pragma omp parallel safesync |
schedule | Parenthesized | schedule(...) |
self_maps | Bare | #pragma omp parallel self_maps |
seq_cst | Bare | #pragma omp parallel seq_cst |
severity | Parenthesized | severity(...) |
shared | Parenthesized | shared(...) |
simd | Bare | #pragma omp parallel simd |
simdlen | Parenthesized | simdlen(...) |
sizes | Parenthesized | sizes(...) |
task_reduction | Parenthesized | task_reduction(...) |
thread_limit | Parenthesized | thread_limit(...) |
threads | Bare | #pragma omp parallel threads |
threadset | Parenthesized | threadset(...) |
tile | Parenthesized | tile(...) |
to | Parenthesized | to(...) |
transparent | Flexible | transparent or transparent(...) |
unified_address | Flexible | unified_address or unified_address(...) |
unified_shared_memory | Flexible | unified_shared_memory or unified_shared_memory(...) |
uniform | Parenthesized | uniform(...) |
unroll | Flexible | unroll or unroll(...) |
untied | Bare | #pragma omp parallel untied |
update | Flexible | update or update(...) |
use | Parenthesized | use(...) |
use_device_addr | Parenthesized | use_device_addr(...) |
use_device_ptr | Parenthesized | use_device_ptr(...) |
uses_allocators | Parenthesized | uses_allocators(...) |
weak | Flexible | weak or weak(...) |
when | Parenthesized | when(...) |
write | Flexible | write or write(...) |
Updating this index
The table can be regenerated with the following helper if new clauses are added or the parser changes a clause rule:
python - <<'PY'
import pathlib, re
text = pathlib.Path('src/parser/openmp.rs').read_text()
block = re.search(r"openmp_clauses!\s*{(.*?)}\s*\n\nmacro_rules!", text, re.S).group(1)
clauses = []
for entry in block.split('},'):
name_match = re.search(r'name: "([^"]+)"', entry)
rule_match = re.search(r'rule: ClauseRule::([A-Za-z_]+)', entry)
if name_match and rule_match:
clauses.append((name_match.group(1), rule_match.group(1)))
for name, rule in sorted(clauses):
if rule == 'Bare':
forms = f"`#pragma omp parallel {name}`"
elif rule == 'Parenthesized':
forms = f"`{name}(...)`"
else:
forms = f"`{name}` or `{name}(...)`"
print(f"| `{name}` | {rule} | {forms} |")
PY
Copy the generated rows into the table above to keep this document aligned with
OpenMpClause::ALL.
OpenMP 6.0 Restrictions
The OpenMP specification attaches normative “Restrictions” text to almost every directive and clause. Rather than duplicating that material (which quickly falls out of sync), this guide explains how to locate and verify the restrictions relevant to any construct while working on ROUP.
Where to look in the specification
- Directive sections – Each directive definition in Chapters 5–17 of the
OpenMP 6.0 specification
ends with a Restrictions subsection. The text typically begins with
“Restrictions to the
<name>directive are as follows”. - Clause sections – Clause descriptions follow the same pattern, usually
starting with “Restrictions to the
<name>clause”. - Tables and modifiers – Map-type modifiers, dependence types, and other structured lists include restrictions inline with the tables that define the allowed values.
- Language-specific notes – When the requirements differ between C/C++ and Fortran, the spec labels each bullet accordingly. Keep both variants in mind when adding parser validation or documentation.
Practical workflow for ROUP contributors
- Locate the canonical section using the directive and clause catalogues in
this repository. Both
openmp60-directives-clauses.mdandopenmp60-directive-clause-components.mdlink directly to the parser keywords. - Read the specification subsection for the construct you are working on and note any “must”, “shall”, or “must not” statements. These are the normative requirements that need to be respected by higher-level tooling.
- Capture parser limitations in code comments or documentation if ROUP does not yet enforce a particular rule. This keeps behaviour transparent for users of the library and the ompparser compatibility layer.
- Add tests where feasible. Many restrictions can be unit- or integration-tested (for example, rejecting a clause form that is not permitted). When runtime enforcement is out of scope, reference the relevant specification section in the documentation so readers know where the gap is.
Keeping restriction notes accurate
- Do not duplicate specification prose verbatim; link to the relevant section instead. This avoids stale text and respects the licence of the official document.
- Record deviations whenever ROUP intentionally accepts syntax that the specification would forbid. Document the reasoning in the relevant module or test to make review easier.
- Use citations (for example,
§7.5.4) when summarising restrictions in release notes, tutorials, or design documents. It gives downstream users a precise pointer back to the standard.
By following this workflow the repository remains aligned with the official standard while still clearly communicating the parser’s current behaviour.
Line Continuations
⚠️ Experimental: ROUP now understands multi-line OpenMP directives across C, C++, and Fortran. This guide shows how to format continuations so the parser and ompparser compatibility layer recognize them reliably.
When to use continuations
OpenMP pragmas often grow long once multiple clauses are attached. Rather than forcing everything onto a single line, you can split directives while keeping source files readable. ROUP stitches the continued lines together during lexing, so downstream APIs still observe canonical single-line directive strings.
Continuations are supported in two situations:
- C / C++ pragmas that end a line with a trailing backslash (
\). - Fortran sentinels (
!$OMP,C$OMP,*$OMP) that use the standard ampersand (&) continuation syntax.
ROUP preserves clause order and trims whitespace that was introduced only to align indentation.
C / C++ example
#pragma omp parallel for \
schedule(dynamic, 4) \
private(i, \
j)
ROUP merges this directive into #pragma omp parallel for schedule(dynamic, 4) private(i, j). Clause arguments keep their
original spacing. Comments (/* */ or //) may appear between continued lines and are ignored during merging.
Tips for C / C++
- The backslash must be the final character on the line (aside from trailing spaces or tabs).
- Windows line endings (
\r\n) are handled automatically. - Keep at least one space between the directive name and the first clause on subsequent lines.
Fortran free-form example
!$omp target teams distribute &
!$omp parallel do &
!$omp& private(i, j)
The parser removes the continuation markers and produces !$omp target teams distribute parallel do private(i, j).
Fortran fixed-form example
C$OMP DO &
!$OMP& SCHEDULE(DYNAMIC) &
!$OMP PRIVATE(I) SHARED(A)
Column prefixes (!, C, or *) are respected. ROUP normalizes the directive to do schedule(DYNAMIC) private(I) shared(A).
Tips for Fortran continuations
- Terminate every continued line with
&. ROUP tolerates trailing comments (e.g.,& ! explanation) and skips them automatically. - You may repeat the sentinel on continuation lines (
!$OMP&), or start the next line with only&. Both forms are accepted. - Blank continuation lines are ignored as long as they contain only whitespace.
- Clause bodies can span multiple lines; nested continuations inside parentheses are collapsed to a single line in the parsed clause value.
Troubleshooting
- Missing continuation marker: If a line break appears without
&(Fortran) or\(C/C++), the parser treats the next line as a separate statement and reports an error or unexpected directive name. - Custom formatting macros: Preprocessors that insert trailing spaces after
\may break continuations. Ensure the backslash is the final printable character. - Compatibility layer: The ompparser shim mirrors the same behavior. The comprehensive tests in
compat/ompparser/tests/comprehensive_test.cppinclude multi-line inputs for both languages.
For more examples, refer to the automated tests in tests/openmp_line_continuations.rs and the parser unit tests in
src/parser/mod.rs.
Architecture
This chapter explains how ROUP is organised internally and where to look when modifying the parser or the public bindings.
High level view
source text
│
▼
lexer (`src/lexer.rs`)
│ ─ token stream with language specific helpers
▼
parser (`src/parser/`)
│ ─ builds an intermediate representation (IR)
▼
IR (`src/ir/`)
│ ├─ used directly by Rust callers
│ └─ converted into the C API data structures
▼
C bindings (`src/c_api.rs`)
The lexer normalises whitespace, line continuations, sentinel comments, and language specific keywords before the parser consumes the token stream. The parser modules mirror the OpenMP structure: directives, clauses, helper enumerations, and validation passes. Rust callers typically work with the IR structures directly, while C and C++ consumers receive stable C structs exposed through the FFI layer.
Unsafe code boundaries
The vast majority of the project uses safe Rust. The only unsafe blocks live
inside src/c_api.rs where pointers cross the FFI boundary. Each function
performs explicit null checks and documents its expectations. When modifying or
adding FFI functions, keep the following rules in mind:
- Convert raw pointers to Rust types as late as possible and convert back only when returning values to the caller.
- Maintain ownership invariants: the caller is responsible for freeing values returned by constructors and must not free borrowed data.
- Update the generated C constants header whenever the exported structs or enums change.
Generated constants and headers
The build script (build.rs) parses portions of src/c_api.rs using syn to
produce src/roup_constants.h (also emitted to OUT_DIR). This keeps the OpenMP
and OpenACC directive/clause tables in sync with the Rust implementation.
Compatibility layers
compat/ompparser/mirrors the original ompparser API, forwarding calls through the ROUP C API and converting the resulting structures back into the expected C++ types. The CMake tests in the ompparser submodule validate parity.compat/accparser/provides the same drop-in experience for accparser with ROUP_ACC_* constants and the upstream ctest suite.
Testing
Integration tests live under tests/ and cover keyword registration, parser
round-trips, language specific behaviour, and helper utilities. Running
cargo test executes the Rust suites, while test.sh orchestrates the full
project matrix including compatibility and documentation builds.
Contributing Guide
Thank you for your interest in contributing to ROUP! This guide will help you get started.
Quick Links
- Source Code: github.com/ouankou/roup
- Issue Tracker: GitHub Issues
- Discussions: GitHub Discussions
- Documentation: roup.ouankou.com
Ways to Contribute
1. Report Bugs
Found a bug? Please open an issue with:
- Clear title: What’s wrong?
- Input: The OpenMP directive that caused the issue
- Expected: What should happen?
- Actual: What actually happened?
- Environment: OS, Rust version, ROUP version
Example:
Title: Parser fails on `collapse` clause with variable
Input: #pragma omp for collapse(n)
Expected: Parse successfully
Actual: Parse error: "Expected integer literal"
Environment: Ubuntu 22.04, Rust 1.75, ROUP 0.1.0
```text
### 2. Suggest Features
Have an idea? [Start a discussion](https://github.com/ouankou/roup/discussions) or open an issue with:
- **Use case**: Why is this needed?
- **Proposed API**: How would it work?
- **Alternatives**: Other ways to solve the problem?
### 3. Improve Documentation
Documentation improvements are always welcome:
- Fix typos or unclear explanations
- Add examples
- Improve error messages
- Translate documentation
- Write tutorials or blog posts
See [Documentation Updates](#documentation-updates) below.
### 4. Submit Code
Ready to code? See [Development Setup](#development-setup) and [Pull Request Process](#pull-request-process).
---
## Development Setup
### Prerequisites
- **Rust 1.88+** - [Install Rust](https://rustup.rs/)
- **Git** - Version control
- **mdBook** (optional) - For documentation: `cargo install mdbook`
### Clone and Build
```bash
# Clone repository
git clone https://github.com/ouankou/roup.git
cd roup
# Build library
cargo build
# Run tests
cargo test
# Build documentation
cargo doc --no-deps --open
```text
### Development Tools
**Recommended VS Code Extensions:**
- rust-analyzer
- Even Better TOML
- Error Lens
**Recommended CLI Tools:**
```bash
# Code formatter
rustup component add rustfmt
# Linter
rustup component add clippy
# Documentation builder
cargo install mdbook
```text
---
## Code Quality Standards
### Rust Code
#### 1. Use Safe Rust
**Rule**: Unsafe code is permitted ONLY at the FFI boundary in `src/c_api.rs`.
```rust
// ✅ GOOD: Safe Rust in parser
pub fn parse(input: &str) -> Result<DirectiveIR, ParseError> {
// All safe code
}
// ❌ BAD: Unsafe in parser
pub fn parse(input: &str) -> Result<DirectiveIR, ParseError> {
unsafe { // ← Not allowed outside c_api.rs!
// ...
}
}
```text
#### 2. Format Your Code
```bash
# Format all code
cargo fmt
# Check formatting (CI uses this)
cargo fmt -- --check
```text
#### 3. Pass Clippy
```bash
# Run linter
cargo clippy
# CI requires no warnings
cargo clippy -- -D warnings
```text
#### 4. Write Tests
Every new feature or bug fix should include tests.
```rust
#[test]
fn test_new_feature() {
let result = parse("#pragma omp my_new_directive");
assert!(result.is_ok());
// More assertions...
}
```text
#### 5. Document Public APIs
```rust
/// Parse an OpenMP directive from a string.
///
/// # Arguments
///
/// * `input` - The OpenMP directive text
///
/// # Returns
///
/// * `Ok(DirectiveIR)` - Parsed directive
/// * `Err(ParseError)` - Parse failure with location
///
/// # Examples
///
/// ```
/// use roup::parser::parse;
///
/// let directive = parse("#pragma omp parallel").unwrap();
/// assert_eq!(directive.clauses.len(), 0);
/// ```
pub fn parse(input: &str) -> Result<DirectiveIR, ParseError> {
// ...
}
```text
### C API Code
If modifying `src/c_api.rs`:
- **Minimize unsafe blocks**: Only what's absolutely necessary
- **NULL checks**: Before every pointer dereference
- **Document safety contracts**: Explain caller obligations
- **Test thoroughly**: Including NULL inputs and edge cases
```rust
/// # Safety
///
/// Caller must ensure:
/// - `input` points to a valid null-terminated C string
/// - The string remains valid for the duration of this call
/// - The string is valid UTF-8
#[no_mangle]
pub extern "C" fn roup_parse(input: *const c_char) -> *mut OmpDirective {
if input.is_null() {
return std::ptr::null_mut();
}
// ... minimal unsafe code ...
}
```text
---
## Testing Guidelines
### Running Tests
```bash
# All tests
cargo test
# Specific test
cargo test test_parallel_directive
# Tests with output
cargo test -- --nocapture
# Tests in specific module
cargo test parser::
```text
### Test Categories
#### Unit Tests
Test individual functions in isolation.
```rust
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tokenize_identifier() {
let tokens = tokenize("parallel");
assert_eq!(tokens.len(), 1);
assert_eq!(tokens[0], Token::Identifier("parallel"));
}
}
```text
#### Integration Tests
Test complete parsing workflows in `tests/`.
```rust
// tests/openmp_parallel.rs
#[test]
fn test_parallel_with_clauses() {
let input = "#pragma omp parallel num_threads(4) private(x)";
let result = roup::parser::parse(input);
assert!(result.is_ok());
let directive = result.unwrap();
assert_eq!(directive.clauses.len(), 2);
}
```text
#### FFI Tests
Test C API safety and correctness.
```rust
#[test]
fn test_null_safety() {
let dir = roup_parse(std::ptr::null());
assert!(dir.is_null());
}
```text
### Test Coverage
Aim for:
- **90%+ coverage** for parser code
- **100% coverage** for FFI boundary code
- **All error paths** tested
---
## Documentation Updates
### mdBook Website
The main documentation is in `docs/book/src/`:
```text
docs/book/src/
├── SUMMARY.md # Navigation (table of contents)
├── intro.md # Homepage
├── getting-started.md # Quick start guide
├── rust-tutorial.md # Rust API tutorial
├── c-tutorial.md # C API tutorial
├── cpp-tutorial.md # C++ API tutorial
├── building.md # Build instructions
├── api-reference.md # API reference
├── architecture.md # Internal design
├── openmp-support.md # OpenMP support matrix
├── contributing.md # This file
└── faq.md # Frequently asked questions
```text
#### Building Documentation
```bash
# Build website
cd docs/book
mdbook build
# Serve locally (with live reload)
mdbook serve --open
# View at http://localhost:3000
```text
#### Adding New Pages
1. Create `.md` file in `docs/book/src/`
2. Add to `SUMMARY.md`:
```markdown
- [My New Page](./my-new-page.md)
- Build and verify:
mdbook build
Rustdoc
API documentation is generated from source code:
# Generate API docs
cargo doc --no-deps --open
# With private items (for development)
cargo doc --no-deps --document-private-items --open
```text
### README.md
**IMPORTANT**: After any change, check that `README.md` stays in sync:
- API changes → Update README examples
- Feature changes → Update README feature list
- Build changes → Update README installation instructions
The README should match website content in `docs/book/src/`.
---
## Pull Request Process
### 1. Fork and Branch
```bash
# Fork on GitHub, then clone your fork
git clone https://github.com/YOUR_USERNAME/roup.git
cd roup
# Create feature branch
git checkout -b feature/my-awesome-feature
```text
### 2. Make Changes
- Write code
- Write tests
- Update documentation
- Format code: `cargo fmt`
- Run tests: `cargo test`
- Check lints: `cargo clippy`
### 3. Commit
Use clear, descriptive commit messages:
```bash
git commit -m "feat: add support for OpenMP 6.0 loop directive"
git commit -m "fix: handle null pointers in roup_parse"
git commit -m "docs: add examples for metadirective"
git commit -m "test: add tests for error recovery"
```text
**Commit Message Format:**
- `feat:` - New feature
- `fix:` - Bug fix
- `docs:` - Documentation only
- `test:` - Tests only
- `refactor:` - Code refactoring
- `perf:` - Performance improvement
- `chore:` - Maintenance tasks
### 4. Pre-PR Checklist
Before opening a PR, ensure:
- [ ] `cargo fmt -- --check` passes (no formatting issues)
- [ ] `cargo build` passes (no compilation warnings)
- [ ] `cargo clippy` passes (no linter warnings)
- [ ] `cargo test` passes (all tests green)
- [ ] `cargo doc --no-deps` passes (no rustdoc warnings)
- [ ] `mdbook build docs/book` passes (if docs changed)
- [ ] README.md is in sync with changes
- [ ] New features have tests
- [ ] New features have documentation
### 5. Push and Open PR
```bash
# Push to your fork
git push origin feature/my-awesome-feature
# Open PR on GitHub
# Go to https://github.com/ouankou/roup and click "New Pull Request"
```text
### 6. PR Description
Include:
**What**: What does this PR do?
**Why**: Why is this change needed?
**How**: How does it work?
**Testing**: How was it tested?
**Example:**
```markdown
## What
Adds support for the OpenMP 6.0 `loop` directive.
## Why
OpenMP 6.0 introduced a new `loop` directive as a more generic alternative to `for`.
## How
- Added `Loop` variant to `DirectiveKind` enum
- Added parsing logic in `directive.rs`
- Updated OpenMP support matrix
## Testing
- Added 15 new test cases covering various `loop` directive forms
- All existing tests still pass
- Manually tested with real-world code
```text
### 7. Code Review
Maintainers will review your PR and may:
- Request changes
- Ask questions
- Suggest improvements
**Be patient and responsive!** Code review is a collaborative process.
### 8. Merge
Once approved, maintainers will merge your PR. Congratulations! 🎉
---
## OpenMP Specification Compliance
When adding support for new OpenMP features:
### 1. Consult Official Specs
- **OpenMP 6.0**: [Latest specification](https://www.openmp.org/specifications/)
- **Archive**: [Older versions](https://www.openmp.org/specifications/)
### 2. Check Syntax Carefully
OpenMP syntax can be subtle. Double-check:
- Required vs optional clauses
- Clause argument types
- Directive applicability (C/C++ vs Fortran)
- Version introduced
### 3. Update Support Matrix
After adding a directive/clause, update `docs/book/src/openmp-support.md`:
```markdown
| Directive | OpenMP Version | Status | Notes |
|-----------|----------------|--------|-------|
| `loop` | 5.0 | ✅ Supported | New in OpenMP 5.0 |
```text
### 4. Add Examples
Include examples in documentation showing correct usage.
---
## Performance Considerations
### Benchmarking
If your change affects performance:
```bash
# Run benchmarks (if available)
cargo bench
# Profile with flamegraph
cargo install flamegraph
cargo flamegraph --bin roup
```text
### Performance Guidelines
- Avoid unnecessary allocations
- Prefer zero-copy when possible
- Use `&str` instead of `String` where appropriate
- Benchmark before/after for significant changes
---
## Security
### Reporting Security Issues
Please report security vulnerabilities by opening a GitHub issue at:
[https://github.com/ouankou/roup/issues](https://github.com/ouankou/roup/issues)
Include:
- Description of vulnerability
- Steps to reproduce
- Potential impact
- Suggested fix (if any)
### Security Best Practices
When writing code:
- Validate all inputs
- Check for integer overflow
- Avoid buffer overruns
- Be careful with unsafe code
- Use safe defaults
---
## Release Process
(For maintainers)
### Version Numbering
ROUP follows [Semantic Versioning](https://semver.org/):
- **MAJOR**: Breaking API changes
- **MINOR**: New features (backward compatible)
- **PATCH**: Bug fixes (backward compatible)
### Release Checklist
1. Update version in `Cargo.toml`
2. Update `CHANGELOG.md`
3. Run full test suite
4. Build documentation
5. Create git tag: `git tag v0.2.0`
6. Push tag: `git push origin v0.2.0`
7. Publish to crates.io: `cargo publish`
8. Create GitHub release with notes
---
## Getting Help
### Stuck?
- **Documentation**: Read [roup.ouankou.com](https://roup.ouankou.com)
- **Discussions**: Ask on [GitHub Discussions](https://github.com/ouankou/roup/discussions)
- **Issues**: Search [existing issues](https://github.com/ouankou/roup/issues)
- **Examples**: Check `examples/` directory
### Communication Guidelines
- Be respectful and professional
- Provide context for questions
- Include minimal reproducible examples
- Search before asking (avoid duplicates)
---
## Code of Conduct
### Our Standards
- **Be respectful**: Treat everyone with respect
- **Be constructive**: Provide helpful feedback
- **Be patient**: Remember that everyone is learning
- **Be inclusive**: Welcome newcomers
### Unacceptable Behavior
- Harassment or discrimination
- Trolling or insulting comments
- Personal attacks
- Publishing others' private information
### Reporting
Report unacceptable behavior to: [conduct@ouankou.com](mailto:conduct@ouankou.com)
---
## Recognition
Contributors will be:
- Listed in `CONTRIBUTORS.md`
- Mentioned in release notes
- Credited in commit messages
Significant contributions may result in:
- Maintainer status
- Commit access
- Decision-making authority
---
## License
By contributing, you agree that your contributions will be licensed under the same license as the project (see `LICENSE` file).
---
## Questions?
Still have questions? Open a [discussion](https://github.com/ouankou/roup/discussions) or reach out to the maintainers.
**Thank you for contributing to ROUP!** 🚀
Frequently Asked Questions
General
What is ROUP?
ROUP is a Rust library that parses OpenMP and OpenACC directives and exposes the result to Rust, C, C++, and Fortran consumers. It focuses on analysing and transforming existing pragma-based code rather than compiling it.
Is ROUP production ready?
Not yet. The project is actively developed and APIs may change between releases. Treat the current builds as experimental and review the release notes for the latest status updates.
Which versions are supported?
- OpenMP: Up to 6.0. Integration tests exercise the keyword registry, loop-transform directives, metadirectives, and device constructs.
- OpenACC: 3.4 with full directive/clause coverage and aliases. Unsupported constructs fail with descriptive parse errors instead of being silently accepted.
Installation
How do I install ROUP for Rust?
Add the crate to your Cargo.toml:
[dependencies]
roup = "0.7"
How do I use ROUP from C or C++?
Build the library with cargo build --release and link against the generated
artefact (libroup.so, libroup.dylib, or roup.dll). The generated constants
header lives at src/roup_constants.h. See the C/C++ tutorials for function prototypes and RAII helpers.
What toolchains are required?
- Rust 1.88 or newer (matches the MSRV in
Cargo.toml). - A C/C++ compiler when using the FFI bindings.
- Optional: Fortran compiler for the Fortran examples.
Usage
How do I parse a directive?
use roup::parser::openmp;
let parser = openmp::parser();
let (_, directive) = parser.parse("#pragma omp parallel for").expect("parse");
println!("kind: {}", directive.name);
How do I walk clauses from C?
OmpClauseIterator* it = roup_directive_clauses_iter(dir);
OmpClause* clause = NULL;
while (roup_clause_iterator_next(it, &clause)) {
printf("clause kind: %d\n", roup_clause_kind(clause));
}
roup_clause_iterator_free(it);
Testing and contributing
Which tests should I run before sending a change?
At minimum run cargo test. For thorough coverage run ./test.sh and
./test_rust_versions.sh (see TESTING.md for details). These scripts build
examples, execute the ompparser and accparser compatibility tests, and verify the
documentation.