File Coverage: lib/OpenMP/Environment/Validation.pm

# File Coverage

File: | lib/OpenMP/Environment/Validation.pm
Coverage: | 99.8%

line | stmt | bran | cond | sub | pod | time | code
--- | --- | --- | --- | --- | --- | --- | ---
1 |  |  |  |  |  |  | package OpenMP::Environment::Validation;
2 | 8 8 8 |  |  | 8 |  | 40 11 213 | use strict;
3 | 8 8 8 |  |  | 8 |  | 29 10 325 | use warnings;
4 |
5 | 8 8 8 |  |  | 8 |  | 36 11 368 | use Carp qw/croak/;
6 | 8 8 8 |  |  | 8 |  | 34 9 229 | use Exporter qw/import/;
7 | 8 8 8 |  |  | 8 |  | 298 10 38498 | use OpenMP::Environment::Constants ();
8 |
9 |  |  |  |  |  |  | our $VERSION = q{1.5.0};
10 |  |  |  |  |  |  | our @EXPORT_OK = qw/validate_value validate_assignment assert_variable assert_environment analyze_environment validation_rules/;
11 |
12 |  |  |  |  |  |  | my %UPPERCASE_FILTER = map { $_ => 1 } qw/
13 |  |  |  |  |  |  | OMP_CANCELLATION OMP_NESTED OMP_DISPLAY_AFFINITY OMP_DISPLAY_ENV
14 |  |  |  |  |  |  | OMP_TARGET_OFFLOAD OMP_WAIT_POLICY
15 |  |  |  |  |  |  | /;
16 |
17 |
18 |  |  |  |  |  |  | my %LEGACY_VALIDATED = map { $_ => 1 } qw/
19 |  |  |  |  |  |  | OMP_CANCELLATION OMP_DISPLAY_AFFINITY OMP_DISPLAY_ENV OMP_DEFAULT_DEVICE
20 |  |  |  |  |  |  | OMP_DYNAMIC OMP_MAX_ACTIVE_LEVELS OMP_MAX_TASK_PRIORITY OMP_NESTED
21 |  |  |  |  |  |  | OMP_NUM_TEAMS OMP_NUM_THREADS OMP_TARGET_OFFLOAD OMP_TEAMS_THREAD_LIMIT
22 |  |  |  |  |  |  | OMP_THREAD_LIMIT OMP_WAIT_POLICY GOMP_DEBUG
23 |  |  |  |  |  |  | /;
24 |  |  |  |  |  |  | my %VALIDATOR = (
25 |  |  |  |  |  |  | OMP_CANCELLATION => \&_validate_omp_cancellation,
26 |  |  |  |  |  |  | OMP_DISPLAY_ENV => \&_validate_omp_display_env,
27 |  |  |  |  |  |  | OMP_DEFAULT_DEVICE => \&_validate_omp_default_device,
28 |  |  |  |  |  |  | OMP_NUM_TEAMS => \&_validate_omp_num_teams,
29 |  |  |  |  |  |  | OMP_DYNAMIC => \&_validate_omp_dynamic,
30 |  |  |  |  |  |  | OMP_MAX_ACTIVE_LEVELS => \&_validate_omp_max_active_levels,
31 |  |  |  |  |  |  | OMP_MAX_TASK_PRIORITY => \&_validate_omp_max_task_priority,
32 |  |  |  |  |  |  | OMP_NESTED => \&_validate_omp_nested,
33 |  |  |  |  |  |  | OMP_NUM_THREADS => \&_validate_omp_num_threads,
34 |  |  |  |  |  |  | OMP_PROC_BIND => \&_validate_omp_proc_bind,
35 |  |  |  |  |  |  | OMP_PLACES => \&_validate_omp_places,
36 |  |  |  |  |  |  | OMP_STACKSIZE => \&_validate_omp_stacksize,
37 |  |  |  |  |  |  | OMP_SCHEDULE => \&_validate_omp_schedule,
38 |  |  |  |  |  |  | OMP_TARGET_OFFLOAD => \&_validate_omp_target_offload,
39 |  |  |  |  |  |  | OMP_THREAD_LIMIT => \&_validate_omp_thread_limit,
40 |  |  |  |  |  |  | OMP_WAIT_POLICY => \&_validate_omp_wait_policy,
41 |  |  |  |  |  |  | GOMP_CPU_AFFINITY => \&_validate_gomp_cpu_affinity,
42 |  |  |  |  |  |  | GOMP_DEBUG => \&_validate_gomp_debug,
43 |  |  |  |  |  |  | GOMP_STACKSIZE => \&_validate_gomp_stacksize,
44 |  |  |  |  |  |  | GOMP_SPINCOUNT => \&_validate_gomp_spincount,
45 |  |  |  |  |  |  | GOMP_RTEMS_THREAD_POOLS => \&_validate_gomp_rtems_thread_pools,
46 |  |  |  |  |  |  | OMP_TEAMS_THREAD_LIMIT => \&_validate_omp_teams_thread_limit,
47 |  |  |  |  |  |  | OMP_ALLOCATOR => \&_validate_omp_allocator,
48 |  |  |  |  |  |  | OMP_AFFINITY_FORMAT => \&_validate_omp_affinity_format,
49 |  |  |  |  |  |  | OMP_DISPLAY_AFFINITY => \&_validate_omp_display_affinity,
50 |  |  |  |  |  |  | );
51 |
52 |  |  |  |  |  |  | sub validation_rules {
53 |  |  |  |  |  |  | return {
54 | 9 |  |  | 9 | 1 | 18 | fields => [ OpenMP::Environment::Constants::environment_names() ],
55 |  |  |  |  |  |  | normalized => [ sort keys %UPPERCASE_FILTER ],
56 |  |  |  |  |  |  | assignment_validated => [ sort keys %LEGACY_VALIDATED ],
57 |  |  |  |  |  |  | whitespace_significant => [ q{OMP_AFFINITY_FORMAT} ],
58 |  |  |  |  |  |  | validators => { %VALIDATOR },
59 |  |  |  |  |  |  | profile => q{OpenMP 5.2 + GCC 16.2/libgomp},
60 |  |  |  |  |  |  | };
61 |  |  |  |  |  |  | }
62 |
63 |  |  |  |  |  |  | sub validate_value {
64 | 787 |  |  | 787 | 1 | 1097 | my ( $name, $value ) = @_;
65 | 787 | 100 |  |  |  | 1355 | croak q{Environment variable name is required} if not defined $name;
66 | 786 | 100 |  |  |  | 1091 | croak qq{Unsupported OpenMP/libgomp environment variable "$name"}
67 |  |  |  |  |  |  | if not OpenMP::Environment::Constants::is_environment_name($name);
68 | 785 | 100 |  |  |  | 1140 | return undef if not defined $value;
69 |
70 | 784 |  |  |  |  | 800 | my $normalized = $value;
71 |
72 |  |  |  |  |  |  | # OpenMP 5.2 Chapter 21 states that OpenMP environment-variable values are
73 |  |  |  |  |  |  | # generally case-insensitive and may contain leading/trailing whitespace.
74 |  |  |  |  |  |  | # OMP_AFFINITY_FORMAT is the explicit exception: it is case-sensitive and
75 |  |  |  |  |  |  | # leading/trailing whitespace is significant (Section 21.2.5). Preserve
76 |  |  |  |  |  |  | # that format string byte-for-byte; trim the other OMP_* values before
77 |  |  |  |  |  |  | # applying the module's established case normalization.
78 | 784 | 100 | 100 |  |  | 3101 | if ( $name =~ m/\AOMP_/ and $name ne q{OMP_AFFINITY_FORMAT} ) {
79 | 681 |  |  |  |  | 1015 | $normalized = _trim($normalized);
80 |  |  |  |  |  |  | }
81 | 784 | 100 |  |  |  | 1402 | $normalized = uc($normalized) if $UPPERCASE_FILTER{$name};
82 | 784 |  |  |  |  | 925 | my $validator = $VALIDATOR{$name};
83 | 784 |  |  |  |  | 985 | my $error = $validator->($normalized);
84 | 784 | 100 |  |  |  | 1935 | die qq{(fatal) $name="$value": $error\n\n} if defined $error;
85 | 784 |  |  |  |  | 1581 | return $normalized;
86 |  |  |  |  |  |  | }
87 |
88 |  |  |  |  |  |  | sub validate_assignment {
89 | 311 |  |  | 311 | 1 | 389 | my ( $name, $value ) = @_;
90 | 311 | 100 |  |  |  | 621 | croak q{Environment variable name is required} if not defined $name;
91 | 310 | 100 |  |  |  | 444 | croak qq{Unsupported OpenMP/libgomp environment variable "$name"}
92 |  |  |  |  |  |  | if not OpenMP::Environment::Constants::is_environment_name($name);
93 | 309 | 100 |  |  |  | 448 | return undef if not defined $value;
94 |
95 |  |  |  |  |  |  | # Assignment semantics are intentionally backward-compatible. Variables
96 |  |  |  |  |  |  | # that were historically pass-through remain pass-through; the stricter
97 |  |  |  |  |  |  | # grammar is available through assert()/assert_omp_environment and this
98 |  |  |  |  |  |  | # module's validate_value() API.
99 | 308 | 100 |  |  |  | 509 | return $value if not $LEGACY_VALIDATED{$name};
100 | 266 |  |  |  |  | 373 | return validate_value( $name, $value );
101 |  |  |  |  |  |  | }
102 |
103 |  |  |  |  |  |  | sub assert_variable {
104 | 36 |  |  | 36 | 1 | 45 | my ( $env, $name ) = @_;
105 | 36 | 100 |  |  |  | 143 | croak q{Environment hash reference is required} if ref($env) ne q{HASH};
106 | 35 | 100 |  |  |  | 49 | croak qq{Unsupported OpenMP/libgomp environment variable "$name"}
107 |  |  |  |  |  |  | if not OpenMP::Environment::Constants::is_environment_name($name);
108 |
109 | 34 | 100 |  |  |  | 70 | if ( exists $env->{$name} ) {
110 | 32 | 100 |  |  |  | 45 | if ( defined $env->{$name} ) {
111 | 31 |  |  |  |  | 46 | $env->{$name} = validate_value( $name, $env->{$name} );
112 |  |  |  |  |  |  | }
113 |  |  |  |  |  |  | }
114 |
115 | 33 |  |  |  |  | 45 | my $analysis = analyze_environment($env);
116 | 33 33 |  |  |  |  | 33 54 | foreach my $conflict ( @{ $analysis->{conflicts} } ) {
117 | 3 6 3 | 100 |  |  |  | 2 11 5 | next if not grep { $_ eq $name } @{ $conflict->{variables} };
118 | 2 |  |  |  |  | 45 | die qq{(fatal) OpenMP environment conflict: $conflict->{message}\n};
119 |  |  |  |  |  |  | }
120 | 31 |  |  |  |  | 185 | return 1;
121 |  |  |  |  |  |  | }
122 |
123 |  |  |  |  |  |  | sub assert_environment {
124 | 26 |  |  | 26 | 1 | 35 | my ($env) = @_;
125 | 26 | 100 |  |  |  | 191 | croak q{Environment hash reference is required} if ref($env) ne q{HASH};
126 |
127 | 25 |  |  |  |  | 40 | foreach my $name ( OpenMP::Environment::Constants::environment_names() ) {
128 | 338 | 100 |  |  |  | 448 | next if not exists $env->{$name};
129 | 188 | 100 |  |  |  | 237 | next if not defined $env->{$name};
130 | 187 |  |  |  |  | 214 | $env->{$name} = validate_value( $name, $env->{$name} );
131 |  |  |  |  |  |  | }
132 |
133 | 9 |  |  |  |  | 25 | my $analysis = analyze_environment($env);
134 | 9 9 | 100 |  |  |  | 10 20 | if ( @{ $analysis->{conflicts} } ) {
135 | 1 1 1 |  |  |  |  | 2 3 2 | my $message = join q{; }, map { $_->{message} } @{ $analysis->{conflicts} };
136 | 1 |  |  |  |  | 7 | die qq{(fatal) OpenMP environment conflict: $message\n};
137 |  |  |  |  |  |  | }
138 | 8 |  |  |  |  | 40 | return 1;
139 |  |  |  |  |  |  | }
140 |
141 |  |  |  |  |  |  | sub analyze_environment {
142 | 54 |  |  | 54 | 1 | 67 | my ($env) = @_;
143 | 54 | 100 |  |  |  | 157 | croak q{Environment hash reference is required} if ref($env) ne q{HASH};
144 |
145 | 53 |  |  |  |  | 47 | my @errors;
146 | 53 |  |  |  |  | 72 | foreach my $name ( OpenMP::Environment::Constants::environment_names() ) {
147 | 1325 | 100 |  |  |  | 1537 | next if not exists $env->{$name};
148 | 156 | 100 |  |  |  | 200 | next if not defined $env->{$name};
149 | 154 154 152 |  |  |  |  | 136 215 153 | my $ok = eval { validate_value( $name, $env->{$name} ); 1 };
150 | 154 | 100 |  |  |  | 223 | push @errors, { variable => $name, message => $@ } if not $ok;
151 |  |  |  |  |  |  | }
152 |
153 | 53 |  |  |  |  | 90 | my @conflicts;
154 | 53 | 100 |  |  |  | 95 | if ( _is_false( $env->{OMP_NESTED} ) ) {
155 | 9 | 100 |  |  |  | 15 | if ( defined $env->{OMP_MAX_ACTIVE_LEVELS} ) {
156 | 8 | 100 |  |  |  | 22 | if ( $env->{OMP_MAX_ACTIVE_LEVELS} =~ m/\A\d+\z/ ) {
157 | 7 | 100 |  |  |  | 12 | if ( $env->{OMP_MAX_ACTIVE_LEVELS} > 1 ) {
158 | 6 |  |  |  |  | 19 | push @conflicts, {
159 |  |  |  |  |  |  | variables => [qw/OMP_NESTED OMP_MAX_ACTIVE_LEVELS/],
160 |  |  |  |  |  |  | class => q{implementation-defined},
161 |  |  |  |  |  |  | message => q{OMP_NESTED=FALSE with OMP_MAX_ACTIVE_LEVELS greater than 1 is implementation-defined by OpenMP 5.2},
162 |  |  |  |  |  |  | };
163 |  |  |  |  |  |  | }
164 |  |  |  |  |  |  | }
165 |  |  |  |  |  |  | }
166 |  |  |  |  |  |  | }
167 |
168 | 53 |  |  |  |  | 68 | my @runtime_dependent;
169 | 53 |  |  |  |  | 88 | _runtime_note( \@runtime_dependent, $env, q{OMP_PLACES}, q{processor numbering, abstract-place mapping, and resource availability are implementation/runtime dependent} );
170 | 53 |  |  |  |  | 81 | _runtime_note( \@runtime_dependent, $env, q{OMP_DEFAULT_DEVICE}, q{the requested device number is not checked for existence} );
171 | 53 |  |  |  |  | 75 | _runtime_note( \@runtime_dependent, $env, q{OMP_STACKSIZE}, q{the requested stack size is not checked against available runtime resources} );
172 | 53 |  |  |  |  | 93 | _runtime_note( \@runtime_dependent, $env, q{OMP_ALLOCATOR}, q{memory-space and allocator availability are not probed} );
173 | 53 |  |  |  |  | 77 | _runtime_note( \@runtime_dependent, $env, q{OMP_THREAD_LIMIT}, q{the implementation-supported thread limit is not probed} );
174 | 53 |  |  |  |  | 84 | _runtime_note( \@runtime_dependent, $env, q{OMP_MAX_ACTIVE_LEVELS}, q{the implementation-supported active-level limit is not probed} );
175 | 53 |  |  |  |  | 71 | _runtime_note( \@runtime_dependent, $env, q{OMP_NUM_THREADS}, q{the implementation-supported thread count is not probed} );
176 | 53 |  |  |  |  | 72 | _runtime_note( \@runtime_dependent, $env, q{GOMP_CPU_AFFINITY}, q{CPU identifiers are syntax-checked but are not checked against the host} );
177 | 53 |  |  |  |  | 78 | _runtime_note( \@runtime_dependent, $env, q{GOMP_RTEMS_THREAD_POOLS}, q{RTEMS scheduler names and priorities are syntax-checked but not queried from RTEMS} );
178 |
179 | 53 |  |  |  |  | 45 | my @notes;
180 | 53 | 100 |  |  |  | 70 | if ( exists $env->{GOMP_CPU_AFFINITY} ) {
181 | 6 | 100 |  |  |  | 16 | if ( exists $env->{OMP_PROC_BIND} ) {
182 | 4 |  |  |  |  | 7 | push @notes, q{OMP_PROC_BIND takes precedence over GOMP_CPU_AFFINITY in GNU libgomp when both are set};
183 |  |  |  |  |  |  | }
184 |  |  |  |  |  |  | }
185 | 53 | 100 |  |  |  | 70 | if ( exists $env->{OMP_NESTED} ) {
186 | 16 | 100 |  |  |  | 22 | if ( exists $env->{OMP_MAX_ACTIVE_LEVELS} ) {
187 | 13 |  |  |  |  | 14 | my $conflicting = 0;
188 | 13 | 100 |  |  |  | 17 | if ( _is_false( $env->{OMP_NESTED} ) ) {
189 | 8 | 100 |  |  |  | 21 | if ( $env->{OMP_MAX_ACTIVE_LEVELS} =~ m/\A\d+\z/ ) {
190 | 7 | 100 |  |  |  | 14 | $conflicting = 1 if $env->{OMP_MAX_ACTIVE_LEVELS} > 1;
191 |  |  |  |  |  |  | }
192 |  |  |  |  |  |  | }
193 | 13 | 100 |  |  |  | 27 | push @notes, q{when OMP_NESTED and OMP_MAX_ACTIVE_LEVELS are both set without the conflicting FALSE/>1 combination, OMP_NESTED has no effect}
194 |  |  |  |  |  |  | if not $conflicting;
195 |  |  |  |  |  |  | }
196 |  |  |  |  |  |  | }
197 | 53 | 100 100 |  |  |  | 84 | if ( _has_multiple_items( $env->{OMP_NUM_THREADS} ) ) {
198 | 5 |  |  |  |  | 6 | push @notes, q{multi-item OMP_NUM_THREADS or OMP_PROC_BIND values participate in initialization of max-active-levels-var unless overridden by stronger nesting controls};
199 |  |  |  |  |  |  | }
200 |  |  |  |  |  |  | elsif ( _has_proc_bind_list( $env->{OMP_PROC_BIND} ) ) {
201 | 2 |  |  |  |  | 3 | push @notes, q{multi-item OMP_NUM_THREADS or OMP_PROC_BIND values participate in initialization of max-active-levels-var unless overridden by stronger nesting controls};
202 |  |  |  |  |  |  | }
203 |
204 | 53 |  |  |  |  | 69 | my $valid = 1;
205 | 53 | 100 |  |  |  | 69 | $valid = 0 if @errors;
206 | 53 | 100 |  |  |  | 62 | $valid = 0 if @conflicts;
207 |  |  |  |  |  |  | return {
208 | 53 |  |  |  |  | 159 | valid => $valid,
209 |  |  |  |  |  |  | errors => \@errors,
210 |  |  |  |  |  |  | conflicts => \@conflicts,
211 |  |  |  |  |  |  | runtime_dependent => \@runtime_dependent,
212 |  |  |  |  |  |  | notes => \@notes,
213 |  |  |  |  |  |  | };
214 |  |  |  |  |  |  | }
215 |
216 |  |  |  |  |  |  | sub _runtime_note {
217 | 477 |  |  | 477 |  | 479 | my ( $notes, $env, $name, $message ) = @_;
218 | 477 | 100 |  |  |  | 621 | return if not exists $env->{$name};
219 | 63 |  |  |  |  | 111 | push @$notes, { variable => $name, message => $message };
220 | 63 |  |  |  |  | 71 | return;
221 |  |  |  |  |  |  | }
222 |
223 |  |  |  |  |  |  | sub _is_false {
224 | 66 |  |  | 66 |  | 90 | my ($value) = @_;
225 | 66 | 100 |  |  |  | 107 | return 0 if not defined $value;
226 | 29 | 100 |  |  |  | 90 | return 1 if $value =~ m/\A(?:0|false)\z/i;
227 | 12 |  |  |  |  | 22 | return 0;
228 |  |  |  |  |  |  | }
229 |
230 |  |  |  |  |  |  | sub _has_multiple_items {
231 | 56 |  |  | 56 |  | 82 | my ($value) = @_;
232 | 56 | 100 |  |  |  | 132 | return 0 if not defined $value;
233 | 11 | 100 |  |  |  | 27 | return 1 if $value =~ /,/;
234 | 5 |  |  |  |  | 12 | return 0;
235 |  |  |  |  |  |  | }
236 |
237 |  |  |  |  |  |  | sub _has_proc_bind_list {
238 | 51 |  |  | 51 |  | 70 | my ($value) = @_;
239 | 51 | 100 |  |  |  | 90 | return 0 if not defined $value;
240 | 6 | 100 |  |  |  | 15 | return 1 if $value =~ /,/;
241 | 3 |  |  |  |  | 6 | return 0;
242 |  |  |  |  |  |  | }
243 |
244 |  |  |  |  |  |  | sub _enum {
245 | 252 |  |  | 252 |  | 449 | my ( $value, @allowed ) = @_;
246 | 252 730 |  |  |  |  | 323 1297 | my %allowed = map { uc($_) => 1 } @allowed;
247 | 252 | 100 |  |  |  | 789 | return undef if $allowed{ uc $value };
248 | 29 |  |  |  |  | 121 | return q{Expected one of: } . join( q{, }, @allowed );
249 |  |  |  |  |  |  | }
250 |
251 |  |  |  |  |  |  | sub _integer_at_least {
252 | 274 |  |  | 274 |  | 406 | my ( $value, $minimum ) = @_;
253 | 274 | 100 |  |  |  | 721 | return q{Value must be an integer} if $value !~ m/\A\d+\z/;
254 | 244 | 100 |  |  |  | 426 | return qq{Value must be an integer greater than or equal to $minimum} if $value < $minimum;
255 | 235 |  |  |  |  | 314 | return;
256 |  |  |  |  |  |  | }
257 |
258 |  |  |  |  |  |  | sub _positive_integer_list {
259 | 58 |  |  | 58 |  | 80 | my ($value) = @_;
260 | 58 | 100 |  |  |  | 258 | return if $value =~ m/\A\s*[1-9]\d*(?:\s*,\s*[1-9]\d*)*\s*\z/;
261 | 10 |  |  |  |  | 19 | return q{Value must be a comma-separated list of positive integers};
262 |  |  |  |  |  |  | }
263 |
264 |  |  |  |  |  |  | # GCC/libgomp: https://gcc.gnu.org/onlinedocs/gcc-16.2.0/libgomp/OMP_005fCANCELLATION.html
265 |  |  |  |  |  |  | # OpenMP 5.2: Section 21.2.6, OMP_CANCELLATION.
266 |  |  |  |  |  |  | # OpenMP requires TRUE/FALSE; other values make behavior implementation-defined.
267 |  |  |  |  |  |  | # libgomp implements the standard boolean values. We validate syntax only and
268 |  |  |  |  |  |  | # do not inspect whether cancellation points will actually be encountered.
269 | 41 |  |  | 41 |  | 59 | sub _validate_omp_cancellation { return _enum( shift, qw/TRUE FALSE/ ) }
270 |
271 |  |  |  |  |  |  | # GCC/libgomp: https://gcc.gnu.org/onlinedocs/gcc-16.2.0/libgomp/OMP_005fDISPLAY_005fENV.html
272 |  |  |  |  |  |  | # OpenMP 5.2: Section 21.7, OMP_DISPLAY_ENV.
273 |  |  |  |  |  |  | # OpenMP defines TRUE, FALSE, and VERBOSE. libgomp uses VERBOSE to include GNU
274 |  |  |  |  |  |  | # implementation-specific variables. We validate the portable values only.
275 | 35 |  |  | 35 |  | 54 | sub _validate_omp_display_env { return _enum( shift, qw/TRUE FALSE VERBOSE/ ) }
276 |
277 |  |  |  |  |  |  | # GCC/libgomp: https://gcc.gnu.org/onlinedocs/gcc-16.2.0/libgomp/OMP_005fDEFAULT_005fDEVICE.html
278 |  |  |  |  |  |  | # OpenMP 5.2: Section 21.2.7, OMP_DEFAULT_DEVICE.
279 |  |  |  |  |  |  | # The grammar is a non-negative integer. Device existence/availability is a
280 |  |  |  |  |  |  | # runtime property and is deliberately not checked by this portable module.
281 | 55 |  |  | 55 |  | 95 | sub _validate_omp_default_device { return _integer_at_least( shift, 0 ) }
282 |
283 |  |  |  |  |  |  | # GCC/libgomp: https://gcc.gnu.org/onlinedocs/gcc-16.2.0/libgomp/OMP_005fNUM_005fTEAMS.html
284 |  |  |  |  |  |  | # OpenMP 5.2: Section 21.6.1, OMP_NUM_TEAMS.
285 |  |  |  |  |  |  | # The value is a positive integer. Whether that many teams can be created is a
286 |  |  |  |  |  |  | # runtime/target matter and is deliberately not probed.
287 | 39 |  |  | 39 |  | 58 | sub _validate_omp_num_teams { return _integer_at_least( shift, 1 ) }
288 |
289 |  |  |  |  |  |  | # GCC/libgomp: https://gcc.gnu.org/onlinedocs/gcc-16.2.0/libgomp/OMP_005fDYNAMIC.html
290 |  |  |  |  |  |  | # OpenMP 5.2: Section 21.1.1, OMP_DYNAMIC.
291 |  |  |  |  |  |  | # OpenMP specifies TRUE/FALSE and leaves other values implementation-defined.
292 |  |  |  |  |  |  | # For compatibility with historical OpenMP::Environment behavior, 1/0 are also
293 |  |  |  |  |  |  | # accepted; the public accessor retains its established false-value-unsets rule.
294 | 31 |  |  | 31 |  | 56 | sub _validate_omp_dynamic { return _enum( shift, qw/TRUE FALSE 1 0/ ) }
295 |
296 |  |  |  |  |  |  | # GCC/libgomp: https://gcc.gnu.org/onlinedocs/gcc-16.2.0/libgomp/OMP_005fMAX_005fACTIVE_005fLEVELS.html
297 |  |  |  |  |  |  | # OpenMP 5.2: Section 21.1.4, OMP_MAX_ACTIVE_LEVELS.
298 |  |  |  |  |  |  | # OpenMP permits a non-negative integer, but GCC/libgomp documents a positive
299 |  |  |  |  |  |  | # integer and the pre-1.5.0 module rejected zero. We follow libgomp and preserve
300 |  |  |  |  |  |  | # that compatibility. The supported maximum is system/runtime dependent and is
301 |  |  |  |  |  |  | # not queried here.
302 | 55 |  |  | 55 |  | 76 | sub _validate_omp_max_active_levels { return _integer_at_least( shift, 1 ) }
303 |
304 |  |  |  |  |  |  | # GCC/libgomp: https://gcc.gnu.org/onlinedocs/gcc-16.2.0/libgomp/OMP_005fMAX_005fTASK_005fPRIORITY.html
305 |  |  |  |  |  |  | # OpenMP 5.2: Section 21.2.9, OMP_MAX_TASK_PRIORITY.
306 |  |  |  |  |  |  | # The portable grammar is a non-negative integer. Runtime priority support is
307 |  |  |  |  |  |  | # not probed.
308 | 44 |  |  | 44 |  | 62 | sub _validate_omp_max_task_priority { return _integer_at_least( shift, 0 ) }
309 |
310 |  |  |  |  |  |  | # GCC/libgomp: https://gcc.gnu.org/onlinedocs/gcc-16.2.0/libgomp/OMP_005fNESTED.html
311 |  |  |  |  |  |  | # OpenMP 5.2: Section 21.1.5, OMP_NESTED (deprecated).
312 |  |  |  |  |  |  | # TRUE/FALSE are defined; other values are implementation-defined. OpenMP also
313 |  |  |  |  |  |  | # declares FALSE together with OMP_MAX_ACTIVE_LEVELS>1 implementation-defined;
314 |  |  |  |  |  |  | # that relationship is checked by analyze_environment/assert_environment.
315 |  |  |  |  |  |  | # 1/0 remain accepted for backward compatibility with this module.
316 | 41 |  |  | 41 |  | 78 | sub _validate_omp_nested { return _enum( shift, qw/TRUE FALSE 1 0/ ) }
317 |
318 |  |  |  |  |  |  | # GCC/libgomp: https://gcc.gnu.org/onlinedocs/gcc-16.2.0/libgomp/OMP_005fNUM_005fTHREADS.html
319 |  |  |  |  |  |  | # OpenMP 5.2: Section 21.1.2, OMP_NUM_THREADS.
320 |  |  |  |  |  |  | # OpenMP permits a comma-separated list of positive integers for nested levels.
321 |  |  |  |  |  |  | # Values above runtime capability are implementation-defined and are not
322 |  |  |  |  |  |  | # system-checked. Multiple items also influence max-active-levels-var.
323 | 58 |  |  | 58 |  | 85 | sub _validate_omp_num_threads { return _positive_integer_list(shift) }
324 |
325 |  |  |  |  |  |  | # GCC/libgomp: https://gcc.gnu.org/onlinedocs/gcc-16.2.0/libgomp/OMP_005fPROC_005fBIND.html
326 |  |  |  |  |  |  | # OpenMP 5.2: Section 21.1.7, OMP_PROC_BIND.
327 |  |  |  |  |  |  | # OpenMP accepts TRUE, FALSE, or a list of PRIMARY/CLOSE/SPREAD. MASTER is
328 |  |  |  |  |  |  | # deprecated by OpenMP but libgomp explicitly continues to accept it, so this
329 |  |  |  |  |  |  | # validator accepts MASTER as a GNU/backward-compatible spelling. Placement and
330 |  |  |  |  |  |  | # the policy selected by TRUE are implementation/runtime dependent and not probed.
331 |  |  |  |  |  |  | sub _validate_omp_proc_bind {
332 | 22 |  |  | 22 |  | 39 | my ($value) = @_;
333 | 22 | 100 |  |  |  | 97 | return if $value =~ m/\A\s*(?:TRUE|FALSE)\s*\z/i;
334 | 20 | 100 |  |  |  | 110 | return if $value =~ m/\A\s*(?:PRIMARY|MASTER|CLOSE|SPREAD)(?:\s*,\s*(?:PRIMARY|MASTER|CLOSE|SPREAD))*\s*\z/i;
335 | 2 |  |  |  |  | 7 | return q{Expected TRUE, FALSE, or a comma-separated list of PRIMARY, MASTER, CLOSE, or SPREAD};
336 |  |  |  |  |  |  | }
337 |
338 |  |  |  |  |  |  | # GCC/libgomp: https://gcc.gnu.org/onlinedocs/gcc-16.2.0/libgomp/OMP_005fPLACES.html
339 |  |  |  |  |  |  | # OpenMP 5.2: Section 21.1.6, OMP_PLACES.
340 |  |  |  |  |  |  | # OpenMP defines abstract names, explicit resource lists, intervals, strides,
341 |  |  |  |  |  |  | # and exclusions, while explicitly leaving processor numbering, abstract-name
342 |  |  |  |  |  |  | # meaning, resource mapping, and additional abstract names implementation-defined.
343 |  |  |  |  |  |  | # libgomp documents THREADS/CORES/SOCKETS/LL_CACHES/NUMA_DOMAINS and the standard
344 |  |  |  |  |  |  | # explicit syntax. We validate grammar, including implementation-defined abstract
345 |  |  |  |  |  |  | # identifiers, but never verify that a referenced processor/resource exists.
346 |  |  |  |  |  |  | sub _validate_omp_places {
347 | 34 |  |  | 34 |  | 61 | my ($value) = @_;
348 | 34 |  |  |  |  | 65 | my $text = _trim($value);
349 | 34 | 100 |  |  |  | 93 | return q{OMP_PLACES must not be empty} if $text eq q{};
350 | 33 | 100 |  |  |  | 112 | return if $text =~ m/\A[A-Za-z_][A-Za-z0-9_]*(?:\(\s*[1-9]\d*\s*\))?\z/;
351 |
352 | 27 |  |  |  |  | 52 | my @items = _split_top_level( $text, q{,} );
353 | 27 | 100 |  |  |  | 59 | return q{Malformed OMP_PLACES list} if not @items;
354 | 22 |  |  |  |  | 35 | foreach my $item (@items) {
355 | 41 |  |  |  |  | 57 | my $err = _validate_place_interval($item);
356 | 41 | 100 |  |  |  | 99 | return $err if defined $err;
357 |  |  |  |  |  |  | }
358 | 16 |  |  |  |  | 29 | return;
359 |  |  |  |  |  |  | }
360 |
361 |  |  |  |  |  |  | # GCC/libgomp: https://gcc.gnu.org/onlinedocs/gcc-16.2.0/libgomp/OMP_005fSTACKSIZE.html
362 |  |  |  |  |  |  | # OpenMP 5.2: Section 21.2.2, OMP_STACKSIZE.
363 |  |  |  |  |  |  | # OpenMP specifies a positive integer with optional B/K/M/G unit; an unsupported
364 |  |  |  |  |  |  | # size is implementation-defined. libgomp uses kilobytes when no unit is given.
365 |  |  |  |  |  |  | # We validate syntax but do not test whether the runtime can allocate the size.
366 |  |  |  |  |  |  | sub _validate_omp_stacksize {
367 | 18 |  |  | 18 |  | 25 | my ($value) = @_;
368 | 18 | 100 |  |  |  | 62 | return if $value =~ m/\A\s*[1-9]\d*\s*(?:[BKMG])?\s*\z/i;
369 | 2 |  |  |  |  | 3 | return q{Expected a positive integer optionally followed by B, K, M, or G};
370 |  |  |  |  |  |  | }
371 |
372 |  |  |  |  |  |  | # GCC/libgomp: https://gcc.gnu.org/onlinedocs/gcc-16.2.0/libgomp/OMP_005fSCHEDULE.html
373 |  |  |  |  |  |  | # OpenMP 5.2: Section 21.2.1, OMP_SCHEDULE.
374 |  |  |  |  |  |  | # OpenMP defines [MONOTONIC|NONMONOTONIC:]STATIC|DYNAMIC|GUIDED|AUTO[,chunk],
375 |  |  |  |  |  |  | # with a positive chunk. The GCC/libgomp environment-variable page still
376 |  |  |  |  |  |  | # documents the older type[,chunk] presentation and cites OpenMP 4.5. We accept
377 |  |  |  |  |  |  | # the complete OpenMP 5.2 grammar; this is a deliberate standard-facing
378 |  |  |  |  |  |  | # allowance, not a claim that libgomp's current prose explicitly documents the
379 |  |  |  |  |  |  | # modifier syntax.
380 |  |  |  |  |  |  | sub _validate_omp_schedule {
381 | 28 |  |  | 28 |  | 33 | my ($value) = @_;
382 | 28 | 100 |  |  |  | 123 | return if $value =~ m/\A\s*(?:(?:MONOTONIC|NONMONOTONIC)\s*:\s*)?(?:STATIC|DYNAMIC|GUIDED|AUTO)(?:\s*,\s*[1-9]\d*)?\s*\z/i;
383 | 5 |  |  |  |  | 7 | return q{Expected [MONOTONIC|NONMONOTONIC:]STATIC|DYNAMIC|GUIDED|AUTO optionally followed by a positive chunk};
384 |  |  |  |  |  |  | }
385 |
386 |  |  |  |  |  |  | # GCC/libgomp: https://gcc.gnu.org/onlinedocs/gcc-16.2.0/libgomp/OMP_005fTARGET_005fOFFLOAD.html
387 |  |  |  |  |  |  | # OpenMP 5.2: Section 21.2.8, OMP_TARGET_OFFLOAD.
388 |  |  |  |  |  |  | # OpenMP defines MANDATORY, DISABLED, and DEFAULT; OpenMP 5.2 specifically
389 |  |  |  |  |  |  | # leaves support of DISABLED implementation-defined. GCC/libgomp explicitly
390 |  |  |  |  |  |  | # implements all three spellings and documents host execution for DISABLED.
391 |  |  |  |  |  |  | # We validate the token but do not probe target devices or offload plugins.
392 | 23 |  |  | 23 |  | 35 | sub _validate_omp_target_offload { return _enum( shift, qw/MANDATORY DISABLED DEFAULT/ ) }
393 |
394 |  |  |  |  |  |  | # GCC/libgomp: https://gcc.gnu.org/onlinedocs/gcc-16.2.0/libgomp/OMP_005fTHREAD_005fLIMIT.html
395 |  |  |  |  |  |  | # OpenMP 5.2: Section 21.1.3, OMP_THREAD_LIMIT.
396 |  |  |  |  |  |  | # A positive integer is required; exceeding implementation capability is
397 |  |  |  |  |  |  | # implementation-defined and deliberately not system-checked.
398 | 40 |  |  | 40 |  | 56 | sub _validate_omp_thread_limit { return _integer_at_least( shift, 1 ) }
399 |
400 |  |  |  |  |  |  | # GCC/libgomp: https://gcc.gnu.org/onlinedocs/gcc-16.2.0/libgomp/OMP_005fWAIT_005fPOLICY.html
401 |  |  |  |  |  |  | # OpenMP 5.2: Section 21.2.3, OMP_WAIT_POLICY.
402 |  |  |  |  |  |  | # ACTIVE/PASSIVE are portable values, but the detailed waiting behavior is
403 |  |  |  |  |  |  | # explicitly implementation-defined. We validate the token, not timing behavior.
404 | 20 |  |  | 20 |  | 28 | sub _validate_omp_wait_policy { return _enum( shift, qw/ACTIVE PASSIVE/ ) }
405 |
406 |  |  |  |  |  |  | # GCC/libgomp: https://gcc.gnu.org/onlinedocs/gcc-16.2.0/libgomp/GOMP_005fCPU_005fAFFINITY.html
407 |  |  |  |  |  |  | # OpenMP 5.2: no GOMP_CPU_AFFINITY variable; this is a GNU extension related to
408 |  |  |  |  |  |  | # OMP_PLACES/OMP_PROC_BIND. libgomp accepts CPU numbers, M-N ranges, and M-N:S
409 |  |  |  |  |  |  | # strides separated by spaces or commas; OMP_PROC_BIND takes precedence.
410 |  |  |  |  |  |  | # We validate this GNU grammar but do not verify CPU identifiers on the host.
411 |  |  |  |  |  |  | sub _validate_gomp_cpu_affinity {
412 | 16 |  |  | 16 |  | 23 | my ($value) = @_;
413 | 16 |  |  |  |  | 35 | my $entry = qr/\d+(?:-\d+(?::[1-9]\d*)?)?/;
414 | 16 | 100 |  |  |  | 674 | return if $value =~ m/\A\s*$entry(?:\s*(?:,|\s)\s*$entry)*\s*\z/;
415 | 2 |  |  |  |  | 6 | return q{Expected CPU numbers, M-N ranges, or M-N:S ranges separated by spaces or commas};
416 |  |  |  |  |  |  | }
417 |
418 |  |  |  |  |  |  | # GCC/libgomp: https://gcc.gnu.org/onlinedocs/gcc-16.2.0/libgomp/GOMP_005fDEBUG.html
419 |  |  |  |  |  |  | # OpenMP 5.2: no GOMP_DEBUG variable; it is a GNU extension.
420 |  |  |  |  |  |  | # libgomp documents 0/1. We validate only those GNU-defined values.
421 | 19 |  |  | 19 |  | 29 | sub _validate_gomp_debug { return _enum( shift, qw/0 1/ ) }
422 |
423 |  |  |  |  |  |  | # GCC/libgomp: https://gcc.gnu.org/onlinedocs/gcc-16.2.0/libgomp/GOMP_005fSTACKSIZE.html
424 |  |  |  |  |  |  | # OpenMP 5.2: no GOMP_STACKSIZE variable; OMP_STACKSIZE is the standard analogue.
425 |  |  |  |  |  |  | # libgomp documents GOMP_STACKSIZE as a numeric kilobyte value with no unit
426 |  |  |  |  |  |  | # suffix. Older OpenMP::Environment releases passed arbitrary values through, and
427 |  |  |  |  |  |  | # the unchanged regression suite asserts an OMP_STACKSIZE-style suffixed value.
428 |  |  |  |  |  |  | # To preserve that public behavior, both assignment and assertion accept B/K/M/G
429 |  |  |  |  |  |  | # suffixes as an explicit OpenMP::Environment compatibility extension. This is
430 |  |  |  |  |  |  | # NOT presented as documented libgomp syntax; callers wanting the GNU-native form
431 |  |  |  |  |  |  | # should use an unsuffixed positive integer (kilobytes).
432 |  |  |  |  |  |  | sub _validate_gomp_stacksize {
433 | 12 |  |  | 12 |  | 17 | my ($value) = @_;
434 | 12 | 100 |  |  |  | 57 | return if $value =~ m/\A\s*[1-9]\d*\s*(?:[BKMG])?\s*\z/i;
435 | 1 |  |  |  |  | 2 | return q{Expected a positive integer, with an optional B, K, M, or G OpenMP::Environment compatibility suffix};
436 |  |  |  |  |  |  | }
437 |
438 |  |  |  |  |  |  | # GCC/libgomp: https://gcc.gnu.org/onlinedocs/gcc-16.2.0/libgomp/GOMP_005fSPINCOUNT.html
439 |  |  |  |  |  |  | # OpenMP 5.2: no GOMP_SPINCOUNT variable; it is a GNU extension.
440 |  |  |  |  |  |  | # libgomp accepts INFINITE/INFINITY or a non-negative integer optionally suffixed
441 |  |  |  |  |  |  | # by k/M/G/T multipliers. The number of CPUs can alter libgomp's effective spin
442 |  |  |  |  |  |  | # behavior, but this portable validator checks syntax only.
443 |  |  |  |  |  |  | sub _validate_gomp_spincount {
444 | 20 |  |  | 20 |  | 30 | my ($value) = @_;
445 | 20 | 100 |  |  |  | 51 | return if $value =~ m/\A\s*(?:INFINITE|INFINITY)\s*\z/i;
446 | 11 | 100 |  |  |  | 45 | return if $value =~ m/\A\s*\d+(?:k|M|G|T)?\s*\z/;
447 | 3 |  |  |  |  | 16 | return q{Expected INFINITE, INFINITY, or a non-negative integer optionally suffixed by k, M, G, or T};
448 |  |  |  |  |  |  | }
449 |
450 |  |  |  |  |  |  | # GCC/libgomp: https://gcc.gnu.org/onlinedocs/gcc-16.2.0/libgomp/GOMP_005fRTEMS_005fTHREAD_005fPOOLS.html
451 |  |  |  |  |  |  | # OpenMP 5.2: no GOMP_RTEMS_THREAD_POOLS variable; it is a GNU/RTEMS extension.
452 |  |  |  |  |  |  | # libgomp documents colon-separated count[$priority]@scheduler configurations.
453 |  |  |  |  |  |  | # We validate that grammar only; scheduler names, RTEMS presence, and acceptable
454 |  |  |  |  |  |  | # pthread priorities are intentionally not queried for portability.
455 |  |  |  |  |  |  | sub _validate_gomp_rtems_thread_pools {
456 | 13 |  |  | 13 |  | 17 | my ($value) = @_;
457 | 13 |  |  |  |  | 23 | my $config = qr/[1-9]\d*(?:\$\d+)?\@[A-Za-z_][A-Za-z0-9_.-]*/;
458 | 13 | 100 |  |  |  | 470 | return if $value =~ m/\A\s*$config(?:\s*:\s*$config)*\s*\z/;
459 | 2 |  |  |  |  | 5 | return q{Expected colon-separated count[$priority]@scheduler configurations};
460 |  |  |  |  |  |  | }
461 |
462 |  |  |  |  |  |  | # GCC/libgomp: https://gcc.gnu.org/onlinedocs/gcc-16.2.0/libgomp/OMP_005fTEAMS_005fTHREAD_005fLIMIT.html
463 |  |  |  |  |  |  | # OpenMP 5.2: Section 21.6.2, OMP_TEAMS_THREAD_LIMIT.
464 |  |  |  |  |  |  | # A positive integer is portable. Target/runtime capability is not probed.
465 | 39 |  |  | 39 |  | 56 | sub _validate_omp_teams_thread_limit { return _integer_at_least( shift, 1 ) }
466 |
467 |  |  |  |  |  |  | # GCC/libgomp: https://gcc.gnu.org/onlinedocs/gcc-16.2.0/libgomp/OMP_005fALLOCATOR.html
468 |  |  |  |  |  |  | # OpenMP 5.2: Section 21.5.1 plus Sections 6.1/6.2 (memory spaces/allocators).
469 |  |  |  |  |  |  | # OpenMP permits a predefined allocator, a predefined memory space, or a memory
470 |  |  |  |  |  |  | # space plus allocator traits. Several memory-space mappings and pool-size
471 |  |  |  |  |  |  | # defaults are implementation-defined. libgomp additionally documents its GNU
472 |  |  |  |  |  |  | # handling/mappings for implementation-defined allocators and GNU ompx_* names.
473 |  |  |  |  |  |  | # OpenMP permits fb_data and allocator_fb. libgomp also lists allocator_fb as an
474 |  |  |  |  |  |  | # allowed fallback token, but explicitly declares fb_data unsupported in the
475 |  |  |  |  |  |  | # OMP_ALLOCATOR environment string because fb_data requires an allocator handle.
476 |  |  |  |  |  |  | # We therefore accept fallback=allocator_fb as documented by libgomp, reject an
477 |  |  |  |  |  |  | # explicit fb_data trait, and do not infer whether allocator_fb can provide a
478 |  |  |  |  |  |  | # useful fallback without fb_data. We also do not test whether a memory space or
479 |  |  |  |  |  |  | # allocator can actually be instantiated on the current runtime.
480 |  |  |  |  |  |  | sub _validate_omp_allocator {
481 | 36 |  |  | 36 |  | 41 | my ($value) = @_;
482 | 36 |  |  |  |  | 47 | my $text = lc _trim($value);
483 | 36 360 |  |  |  |  | 56 438 | my %allocator = map { $_ => 1 } qw/
484 |  |  |  |  |  |  | omp_default_mem_alloc omp_large_cap_mem_alloc omp_const_mem_alloc
485 |  |  |  |  |  |  | omp_high_bw_mem_alloc omp_low_lat_mem_alloc omp_cgroup_mem_alloc
486 |  |  |  |  |  |  | omp_pteam_mem_alloc omp_thread_mem_alloc ompx_gnu_pinned_mem_alloc
487 |  |  |  |  |  |  | ompx_gnu_managed_mem_alloc
488 |  |  |  |  |  |  | /;
489 | 36 216 |  |  |  |  | 59 254 | my %space = map { $_ => 1 } qw/
490 |  |  |  |  |  |  | omp_default_mem_space omp_large_cap_mem_space omp_const_mem_space
491 |  |  |  |  |  |  | omp_high_bw_mem_space omp_low_lat_mem_space ompx_gnu_managed_mem_space
492 |  |  |  |  |  |  | /;
493 |
494 | 36 | 100 |  |  |  | 91 | return if $allocator{$text};
495 | 25 | 100 |  |  |  | 40 | return if $space{$text};
496 |
497 | 23 |  |  |  |  | 85 | my ( $memspace, $traits ) = $text =~ m/\A([^:]+):(.*)\z/;
498 | 23 | 100 |  |  |  | 43 | return q{Expected a predefined OpenMP allocator or memory space, optionally followed by allocator traits}
499 |  |  |  |  |  |  | if not defined $memspace;
500 |  |  |  |  |  |  | return q{Expected a predefined OpenMP allocator or memory space, optionally followed by allocator traits}
501 | 22 | 100 |  |  |  | 37 | if not $space{$memspace};
502 | 21 | 100 |  |  |  | 24 | return q{Allocator trait list must not be empty} if not length _trim($traits);
503 |
504 | 20 |  |  |  |  | 31 | my %seen;
505 |  |  |  |  |  |  | my %parsed;
506 | 20 |  |  |  |  | 57 | foreach my $pair ( split /\s*,\s*/, $traits ) {
507 | 29 |  |  |  |  | 104 | my ( $name, $trait_value ) = $pair =~ m/\A\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*([^\s,]+)\s*\z/;
508 | 29 | 100 |  |  |  | 47 | return q{Malformed allocator trait; expected name=value} if not defined $name;
509 | 28 |  |  |  |  | 32 | $name = lc $name;
510 | 28 | 100 |  |  |  | 54 | return qq{Duplicate allocator trait "$name"} if $seen{$name}++;
511 | 27 |  |  |  |  | 37 | my $err = _validate_allocator_trait( $name, $trait_value, \%allocator );
512 | 27 | 100 |  |  |  | 67 | return $err if defined $err;
513 | 18 |  |  |  |  | 26 | $parsed{$name} = $trait_value;
514 |  |  |  |  |  |  | }
515 |
516 | 9 | 100 |  |  |  | 16 | if ( defined $parsed{fb_data} ) {
517 | 1 |  |  |  |  | 4 | return q{fb_data is permitted by OpenMP but unsupported by GNU libgomp in OMP_ALLOCATOR};
518 |  |  |  |  |  |  | }
519 | 8 |  |  |  |  | 26 | return;
520 |  |  |  |  |  |  | }
521 |
522 |  |  |  |  |  |  | # GCC/libgomp: https://gcc.gnu.org/onlinedocs/gcc-16.2.0/libgomp/OMP_005fAFFINITY_005fFORMAT.html
523 |  |  |  |  |  |  | # OpenMP 5.2: Section 21.2.5, OMP_AFFINITY_FORMAT.
524 |  |  |  |  |  |  | # OpenMP defines percent field syntax and explicitly permits additional
525 |  |  |  |  |  |  | # implementation-defined field types. A width is a positive decimal integer and
526 |  |  |  |  |  |  | # may appear bare (%4L), right-justified (%.4L), or zero-padded for numeric fields
527 |  |  |  |  |  |  | # (%0.4L). libgomp documents the standard short and long field names and the same
528 |  |  |  |  |  |  | # width forms. OMP_AFFINITY_FORMAT is case-sensitive and its leading/trailing
529 |  |  |  |  |  |  | # whitespace is significant, so validate_value deliberately does not trim it.
530 |  |  |  |  |  |  | # Unknown alphabetic short/long fields are accepted as possible implementation-
531 |  |  |  |  |  |  | # defined extensions. We validate syntax only and do not reject the standard's
532 |  |  |  |  |  |  | # explicitly unspecified combinations such as zero-padding a nonnumeric field.
533 |  |  |  |  |  |  | sub _validate_omp_affinity_format {
534 | 23 |  |  | 23 |  | 27 | my ($value) = @_;
535 | 23 |  |  |  |  | 26 | my $i = 0;
536 | 23 |  |  |  |  | 41 | while ( $i < length $value ) {
537 | 36 |  |  |  |  | 53 | my $pos = index( $value, q{%}, $i );
538 | 36 | 100 |  |  |  | 49 | return if $pos < 0;
539 | 34 | 100 |  |  |  | 46 | return q{Trailing percent sign in affinity format} if $pos == length($value) - 1;
540 | 33 |  |  |  |  | 34 | $i = $pos + 1;
541 | 33 | 100 |  |  |  | 58 | if ( substr( $value, $i, 1 ) eq q{%} ) {
542 | 1 |  |  |  |  | 1 | $i++;
543 | 1 |  |  |  |  | 2 | next;
544 |  |  |  |  |  |  | }
545 | 32 |  |  |  |  | 41 | my $tail = substr( $value, $i );
546 | 32 |  |  |  |  | 59 | my $width = qr/(?:[1-9]\d*|(?:0\.|\.)[1-9]\d*)?/;
547 | 32 | 100 |  |  |  | 348 | if ( $tail =~ m/\A$width([A-Za-z])/ ) {
548 | 25 |  |  |  |  | 41 | $i += length $&;
549 | 25 |  |  |  |  | 62 | next;
550 |  |  |  |  |  |  | }
551 | 7 | 100 |  |  |  | 117 | if ( $tail =~ m/\A$width\{[A-Za-z_][A-Za-z0-9_]*\}/ ) {
552 | 3 |  |  |  |  | 7 | $i += length $&;
553 | 3 |  |  |  |  | 10 | next;
554 |  |  |  |  |  |  | }
555 | 4 |  |  |  |  | 11 | return q{Malformed affinity field; expected %% or %[width]field or %[width]{field_name}};
556 |  |  |  |  |  |  | }
557 | 16 |  |  |  |  | 23 | return;
558 |  |  |  |  |  |  | }
559 |
560 |  |  |  |  |  |  | # GCC/libgomp: https://gcc.gnu.org/onlinedocs/gcc-16.2.0/libgomp/OMP_005fDISPLAY_005fAFFINITY.html
561 |  |  |  |  |  |  | # OpenMP 5.2: Section 21.2.4, OMP_DISPLAY_AFFINITY.
562 |  |  |  |  |  |  | # TRUE/FALSE are defined; all other values have implementation-defined display
563 |  |  |  |  |  |  | # behavior. libgomp implements the standard boolean forms. No affinity topology
564 |  |  |  |  |  |  | # is queried by this validator.
565 | 22 |  |  | 22 |  | 44 | sub _validate_omp_display_affinity { return _enum( shift, qw/TRUE FALSE/ ) }
566 |
567 |  |  |  |  |  |  | sub _validate_allocator_trait {
568 | 27 |  |  | 27 |  | 33 | my ( $name, $value, $allocators ) = @_;
569 | 27 | 100 |  |  |  | 38 | if ( $name eq q{sync_hint} ) {
570 | 2 |  |  |  |  | 3 | return _enum( $value, qw/contended uncontended serialized private/ );
571 |  |  |  |  |  |  | }
572 | 25 | 100 |  |  |  | 37 | if ( $name eq q{alignment} ) {
573 | 4 | 100 |  |  |  | 13 | return q{alignment must be a positive power of two}
574 |  |  |  |  |  |  | if $value !~ m/\A[1-9]\d*\z/;
575 | 3 | 100 |  |  |  | 8 | return q{alignment must be a positive power of two}
576 |  |  |  |  |  |  | if ( $value & ( $value - 1 ) ) != 0;
577 | 2 |  |  |  |  | 3 | return;
578 |  |  |  |  |  |  | }
579 | 21 | 100 |  |  |  | 28 | if ( $name eq q{access} ) {
580 | 2 |  |  |  |  | 3 | return _enum( $value, qw/all cgroup pteam thread/ );
581 |  |  |  |  |  |  | }
582 | 19 | 100 |  |  |  | 22 | if ( $name eq q{pool_size} ) {
583 | 2 |  |  |  |  | 2 | return _integer_at_least( $value, 1 );
584 |  |  |  |  |  |  | }
585 | 17 | 100 |  |  |  | 17 | if ( $name eq q{fallback} ) {
586 | 2 |  |  |  |  | 4 | return _enum( $value, qw/default_mem_fb null_fb abort_fb allocator_fb/ );
587 |  |  |  |  |  |  | }
588 | 15 | 100 |  |  |  | 17 | if ( $name eq q{fb_data} ) {
589 | 2 | 100 |  |  |  | 6 | return undef if $allocators->{ lc $value };
590 | 1 |  |  |  |  | 2 | return q{fb_data must name a predefined allocator};
591 |  |  |  |  |  |  | }
592 | 13 | 100 |  |  |  | 15 | if ( $name eq q{pinned} ) {
593 | 6 |  |  |  |  | 8 | return _enum( $value, qw/true false/ );
594 |  |  |  |  |  |  | }
595 | 7 | 100 |  |  |  | 26 | if ( $name eq q{partition} ) {
596 | 6 |  |  |  |  | 9 | return _enum( $value, qw/environment nearest blocked interleaved/ );
597 |  |  |  |  |  |  | }
598 | 1 |  |  |  |  | 2 | return qq{Unknown allocator trait "$name"};
599 |  |  |  |  |  |  | }
600 |
601 |  |  |  |  |  |  | sub _trim {
602 | 1054 |  |  | 1054 |  | 1324 | my ($value) = @_;
603 | 1054 |  |  |  |  | 1774 | $value =~ s/\A\s+//;
604 | 1054 |  |  |  |  | 1472 | $value =~ s/\s+\z//;
605 | 1054 |  |  |  |  | 1785 | return $value;
606 |  |  |  |  |  |  | }
607 |
608 |  |  |  |  |  |  | sub _split_top_level {
609 | 62 |  |  | 62 |  | 147 | my ( $text, $separator ) = @_;
610 | 62 |  |  |  |  | 75 | my @parts;
611 | 62 |  |  |  |  | 79 | my $part = q{};
612 | 62 |  |  |  |  | 89 | my ( $brace, $paren ) = ( 0, 0 );
613 | 62 |  |  |  |  | 181 | foreach my $char ( split //, $text ) {
614 | 472 | 100 |  |  |  | 658 | $brace++ if $char eq q[{];
615 | 472 | 100 |  |  |  | 608 | $brace-- if $char eq q[}];
616 | 472 | 100 |  |  |  | 597 | $paren++ if $char eq q{(};
617 | 472 | 100 |  |  |  | 598 | $paren-- if $char eq q{)};
618 | 472 | 100 |  |  |  | 592 | return () if $brace < 0;
619 | 471 | 100 |  |  |  | 599 | return () if $paren < 0;
620 | 470 | 100 |  |  |  | 615 | if ( $char eq $separator ) {
621 | 120 | 100 |  |  |  | 161 | if ( not $brace ) {
622 | 71 | 100 |  |  |  | 89 | if ( not $paren ) {
623 | 68 |  |  |  |  | 82 | push @parts, _trim($part);
624 | 68 |  |  |  |  | 172 | $part = q{};
625 | 68 |  |  |  |  | 103 | next;
626 |  |  |  |  |  |  | }
627 |  |  |  |  |  |  | }
628 |  |  |  |  |  |  | }
629 | 402 |  |  |  |  | 476 | $part .= $char;
630 |  |  |  |  |  |  | }
631 | 60 | 100 |  |  |  | 131 | return () if $brace;
632 | 59 | 100 |  |  |  | 101 | return () if $paren;
633 | 58 |  |  |  |  | 84 | push @parts, _trim($part);
634 | 58 125 | 100 |  |  |  | 93 219 | return () if grep { $_ eq q{} } @parts;
635 | 55 |  |  |  |  | 146 | return @parts;
636 |  |  |  |  |  |  | }
637 |
638 |  |  |  |  |  |  | sub _validate_place_interval {
639 | 42 |  |  | 42 |  | 61 | my ($item) = @_;
640 | 42 |  |  |  |  | 55 | my $text = _trim($item);
641 | 42 |  |  |  |  | 60 | $text =~ s/\A!\s*//;
642 |
643 | 42 |  |  |  |  | 82 | my ( $base, $suffix );
644 | 42 | 100 100 |  |  |  | 121 | if ( substr( $text, 0, 1 ) eq q[{] ) {
645 | 36 |  |  |  |  | 54 | my $close = _matching_brace($text);
646 | 36 | 100 |  |  |  | 60 | return q{Unbalanced braces in OMP_PLACES} if $close < 0;
647 | 35 |  |  |  |  | 55 | $base = substr( $text, 0, $close + 1 );
648 | 35 |  |  |  |  | 54 | $suffix = substr( $text, $close + 1 );
649 | 35 |  |  |  |  | 54 | my $inside = substr( $base, 1, length($base) - 2 );
650 | 35 |  |  |  |  | 50 | my @resources = _split_top_level( $inside, q{,} );
651 | 35 | 100 |  |  |  | 60 | return q{Empty resource list in OMP_PLACES} if not @resources;
652 | 33 |  |  |  |  | 72 | foreach my $resource (@resources) {
653 | 78 |  |  |  |  | 100 | my $err = _validate_resource_interval($resource);
654 | 78 | 100 |  |  |  | 176 | return $err if defined $err;
655 |  |  |  |  |  |  | }
656 |  |  |  |  |  |  | }
657 |  |  |  |  |  |  | elsif ( $text =~ m/\A(\d+)(.*)\z/s ) {
658 | 4 |  |  |  |  | 14 | $base = $1;
659 | 4 |  |  |  |  | 8 | $suffix = $2;
660 |  |  |  |  |  |  | }
661 |  |  |  |  |  |  | else {
662 | 2 |  |  |  |  | 4 | return q{Expected an explicit place/resource or an abstract place name};
663 |  |  |  |  |  |  | }
664 |
665 | 36 | 100 |  |  |  | 51 | return if _trim($suffix) eq q{};
666 | 5 | 100 |  |  |  | 35 | return q{Malformed place interval; expected :length or :length:stride}
667 |  |  |  |  |  |  | if $suffix !~ m/\A\s*:\s*[1-9]\d*(?:\s*:\s*[+-]?\d+)?\s*\z/;
668 | 4 |  |  |  |  | 12 | return;
669 |  |  |  |  |  |  | }
670 |
671 |  |  |  |  |  |  | sub _validate_resource_interval {
672 | 78 |  |  | 78 |  | 102 | my ($resource) = @_;
673 | 78 |  |  |  |  | 87 | my $text = _trim($resource);
674 | 78 |  |  |  |  | 98 | $text =~ s/\A!\s*//;
675 | 78 | 100 |  |  |  | 258 | return if $text =~ m/\A\d+(?:\s*:\s*[1-9]\d*(?:\s*:\s*[+-]?\d+)?)?\z/;
676 | 1 |  |  |  |  | 3 | return q{Malformed OMP_PLACES resource; expected resource, resource:length, or resource:length:stride};
677 |  |  |  |  |  |  | }
678 |
679 |  |  |  |  |  |  | sub _matching_brace {
680 | 37 |  |  | 37 |  | 54 | my ($text) = @_;
681 | 37 |  |  |  |  | 43 | my $depth = 0;
682 | 37 |  |  |  |  | 81 | for my $i ( 0 .. length($text) - 1 ) {
683 | 223 |  |  |  |  | 271 | my $char = substr( $text, $i, 1 );
684 | 223 | 100 |  |  |  | 305 | $depth++ if $char eq q[{];
685 | 223 | 100 |  |  |  | 317 | if ( $char eq q[}] ) {
686 | 35 |  |  |  |  | 39 | $depth--;
687 | 35 | 50 |  |  |  | 78 | return $i if $depth == 0;
688 |  |  |  |  |  |  | }
689 |  |  |  |  |  |  | }
690 | 2 |  |  |  |  | 3 | return -1;
691 |  |  |  |  |  |  | }
692 |
693 |  |  |  |  |  |  | 1;
694 |