File Coverage

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

linestmtbrancondsubpodtimecode
1package OpenMP::Environment::Validation;
2
8
8
8
40
11
213
use strict;
3
8
8
8
29
10
325
use warnings;
4
5
8
8
8
36
11
368
use Carp qw/croak/;
6
8
8
8
34
9
229
use Exporter qw/import/;
7
8
8
8
298
10
38498
use OpenMP::Environment::Constants ();
8
9our $VERSION = q{1.5.0};
10our @EXPORT_OK = qw/validate_value validate_assignment assert_variable assert_environment analyze_environment validation_rules/;
11
12my %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
18my %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/;
24my %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
52sub validation_rules {
53    return {
54
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
63sub validate_value {
64
787
1
1097
    my ( $name, $value ) = @_;
65
787
1355
    croak q{Environment variable name is required} if not defined $name;
66
786
1091
    croak qq{Unsupported OpenMP/libgomp environment variable "$name"}
67      if not OpenMP::Environment::Constants::is_environment_name($name);
68
785
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
3101
    if ( $name =~ m/\AOMP_/ and $name ne q{OMP_AFFINITY_FORMAT} ) {
79
681
1015
        $normalized = _trim($normalized);
80    }
81
784
1402
    $normalized = uc($normalized) if $UPPERCASE_FILTER{$name};
82
784
925
    my $validator = $VALIDATOR{$name};
83
784
985
    my $error = $validator->($normalized);
84
784
1935
    die qq{(fatal) $name="$value": $error\n\n} if defined $error;
85
784
1581
    return $normalized;
86}
87
88sub validate_assignment {
89
311
1
389
    my ( $name, $value ) = @_;
90
311
621
    croak q{Environment variable name is required} if not defined $name;
91
310
444
    croak qq{Unsupported OpenMP/libgomp environment variable "$name"}
92      if not OpenMP::Environment::Constants::is_environment_name($name);
93
309
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
509
    return $value if not $LEGACY_VALIDATED{$name};
100
266
373
    return validate_value( $name, $value );
101}
102
103sub assert_variable {
104
36
1
45
    my ( $env, $name ) = @_;
105
36
143
    croak q{Environment hash reference is required} if ref($env) ne q{HASH};
106
35
49
    croak qq{Unsupported OpenMP/libgomp environment variable "$name"}
107      if not OpenMP::Environment::Constants::is_environment_name($name);
108
109
34
70
    if ( exists $env->{$name} ) {
110
32
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
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
123sub assert_environment {
124
26
1
35
    my ($env) = @_;
125
26
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
448
        next if not exists $env->{$name};
129
188
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
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
141sub analyze_environment {
142
54
1
67
    my ($env) = @_;
143
54
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
1537
        next if not exists $env->{$name};
148
156
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
223
        push @errors, { variable => $name, message => $@ } if not $ok;
151    }
152
153
53
90
    my @conflicts;
154
53
95
    if ( _is_false( $env->{OMP_NESTED} ) ) {
155
9
15
        if ( defined $env->{OMP_MAX_ACTIVE_LEVELS} ) {
156
8
22
            if ( $env->{OMP_MAX_ACTIVE_LEVELS} =~ m/\A\d+\z/ ) {
157
7
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
70
    if ( exists $env->{GOMP_CPU_AFFINITY} ) {
181
6
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
70
    if ( exists $env->{OMP_NESTED} ) {
186
16
22
        if ( exists $env->{OMP_MAX_ACTIVE_LEVELS} ) {
187
13
14
            my $conflicting = 0;
188
13
17
            if ( _is_false( $env->{OMP_NESTED} ) ) {
189
8
21
                if ( $env->{OMP_MAX_ACTIVE_LEVELS} =~ m/\A\d+\z/ ) {
190
7
14
                    $conflicting = 1 if $env->{OMP_MAX_ACTIVE_LEVELS} > 1;
191                }
192            }
193
13
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
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
69
    $valid = 0 if @errors;
206
53
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
216sub _runtime_note {
217
477
479
    my ( $notes, $env, $name, $message ) = @_;
218
477
621
    return if not exists $env->{$name};
219
63
111
    push @$notes, { variable => $name, message => $message };
220
63
71
    return;
221}
222
223sub _is_false {
224
66
90
    my ($value) = @_;
225
66
107
    return 0 if not defined $value;
226
29
90
    return 1 if $value =~ m/\A(?:0|false)\z/i;
227
12
22
    return 0;
228}
229
230sub _has_multiple_items {
231
56
82
    my ($value) = @_;
232
56
132
    return 0 if not defined $value;
233
11
27
    return 1 if $value =~ /,/;
234
5
12
    return 0;
235}
236
237sub _has_proc_bind_list {
238
51
70
    my ($value) = @_;
239
51
90
    return 0 if not defined $value;
240
6
15
    return 1 if $value =~ /,/;
241
3
6
    return 0;
242}
243
244sub _enum {
245
252
449
    my ( $value, @allowed ) = @_;
246
252
730
323
1297
    my %allowed = map { uc($_) => 1 } @allowed;
247
252
789
    return undef if $allowed{ uc $value };
248
29
121
    return q{Expected one of: } . join( q{, }, @allowed );
249}
250
251sub _integer_at_least {
252
274
406
    my ( $value, $minimum ) = @_;
253
274
721
    return q{Value must be an integer} if $value !~ m/\A\d+\z/;
254
244
426
    return qq{Value must be an integer greater than or equal to $minimum} if $value < $minimum;
255
235
314
    return;
256}
257
258sub _positive_integer_list {
259
58
80
    my ($value) = @_;
260
58
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
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
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
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
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
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
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
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
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
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.
331sub _validate_omp_proc_bind {
332
22
39
    my ($value) = @_;
333
22
97
    return if $value =~ m/\A\s*(?:TRUE|FALSE)\s*\z/i;
334
20
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.
346sub _validate_omp_places {
347
34
61
    my ($value) = @_;
348
34
65
    my $text = _trim($value);
349
34
93
    return q{OMP_PLACES must not be empty} if $text eq q{};
350
33
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
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
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.
366sub _validate_omp_stacksize {
367
18
25
    my ($value) = @_;
368
18
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.
380sub _validate_omp_schedule {
381
28
33
    my ($value) = @_;
382
28
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
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
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
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.
411sub _validate_gomp_cpu_affinity {
412
16
23
    my ($value) = @_;
413
16
35
    my $entry = qr/\d+(?:-\d+(?::[1-9]\d*)?)?/;
414
16
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
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).
432sub _validate_gomp_stacksize {
433
12
17
    my ($value) = @_;
434
12
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.
443sub _validate_gomp_spincount {
444
20
30
    my ($value) = @_;
445
20
51
    return if $value =~ m/\A\s*(?:INFINITE|INFINITY)\s*\z/i;
446
11
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.
455sub _validate_gomp_rtems_thread_pools {
456
13
17
    my ($value) = @_;
457
13
23
    my $config = qr/[1-9]\d*(?:\$\d+)?\@[A-Za-z_][A-Za-z0-9_.-]*/;
458
13
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
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.
480sub _validate_omp_allocator {
481
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
91
    return if $allocator{$text};
495
25
40
    return if $space{$text};
496
497
23
85
    my ( $memspace, $traits ) = $text =~ m/\A([^:]+):(.*)\z/;
498
23
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
37
      if not $space{$memspace};
502
21
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
47
        return q{Malformed allocator trait; expected name=value} if not defined $name;
509
28
32
        $name = lc $name;
510
28
54
        return qq{Duplicate allocator trait "$name"} if $seen{$name}++;
511
27
37
        my $err = _validate_allocator_trait( $name, $trait_value, \%allocator );
512
27
67
        return $err if defined $err;
513
18
26
        $parsed{$name} = $trait_value;
514    }
515
516
9
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.
533sub _validate_omp_affinity_format {
534
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
49
        return if $pos < 0;
539
34
46
        return q{Trailing percent sign in affinity format} if $pos == length($value) - 1;
540
33
34
        $i = $pos + 1;
541
33
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
348
        if ( $tail =~ m/\A$width([A-Za-z])/ ) {
548
25
41
            $i += length $&;
549
25
62
            next;
550        }
551
7
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
44
sub _validate_omp_display_affinity { return _enum( shift, qw/TRUE FALSE/ ) }
566
567sub _validate_allocator_trait {
568
27
33
    my ( $name, $value, $allocators ) = @_;
569
27
38
    if ( $name eq q{sync_hint} ) {
570
2
3
        return _enum( $value, qw/contended uncontended serialized private/ );
571    }
572
25
37
    if ( $name eq q{alignment} ) {
573
4
13
        return q{alignment must be a positive power of two}
574          if $value !~ m/\A[1-9]\d*\z/;
575
3
8
        return q{alignment must be a positive power of two}
576          if ( $value & ( $value - 1 ) ) != 0;
577
2
3
        return;
578    }
579
21
28
    if ( $name eq q{access} ) {
580
2
3
        return _enum( $value, qw/all cgroup pteam thread/ );
581    }
582
19
22
    if ( $name eq q{pool_size} ) {
583
2
2
        return _integer_at_least( $value, 1 );
584    }
585
17
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
17
    if ( $name eq q{fb_data} ) {
589
2
6
        return undef if $allocators->{ lc $value };
590
1
2
        return q{fb_data must name a predefined allocator};
591    }
592
13
15
    if ( $name eq q{pinned} ) {
593
6
8
        return _enum( $value, qw/true false/ );
594    }
595
7
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
601sub _trim {
602
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
608sub _split_top_level {
609
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
658
        $brace++ if $char eq q[{];
615
472
608
        $brace-- if $char eq q[}];
616
472
597
        $paren++ if $char eq q{(};
617
472
598
        $paren-- if $char eq q{)};
618
472
592
        return () if $brace < 0;
619
471
599
        return () if $paren < 0;
620
470
615
        if ( $char eq $separator ) {
621
120
161
            if ( not $brace ) {
622
71
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
131
    return () if $brace;
632
59
101
    return () if $paren;
633
58
84
    push @parts, _trim($part);
634
58
125
93
219
    return () if grep { $_ eq q{} } @parts;
635
55
146
    return @parts;
636}
637
638sub _validate_place_interval {
639
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
121
    if ( substr( $text, 0, 1 ) eq q[{] ) {
645
36
54
        my $close = _matching_brace($text);
646
36
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
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
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
51
    return if _trim($suffix) eq q{};
666
5
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
671sub _validate_resource_interval {
672
78
102
    my ($resource) = @_;
673
78
87
    my $text = _trim($resource);
674
78
98
    $text =~ s/\A!\s*//;
675
78
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
679sub _matching_brace {
680
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
305
        $depth++ if $char eq q[{];
685
223
317
        if ( $char eq q[}] ) {
686
35
39
            $depth--;
687
35
78
            return $i if $depth == 0;
688        }
689    }
690
2
3
    return -1;
691}
692
6931;
694