You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
[5/15] Validate subscripts and make in-loop errors cancel OpenMP loops (#141)
* Move emit_quickr_error_if() to error-handling.R
The statement-level runtime-guard emitter is generic error machinery,
but it lived in the BLAS translation file, forcing any other handler
that wants a guard into an odd dependency. Move it next to the rest of
the quickr error plumbing, unchanged.
* Validate subscripts: reject exclusion, guard index ranges
R's negative subscript means exclusion and its zero subscript is
dropped -- both produce value-dependent result shapes that quickr's
static-shape model cannot represent -- but the `[` handler forwarded
them verbatim into Fortran, where x(-1) and x(0) are silent
out-of-bounds reads. Similarly, x[a:b] lowers to the section
a:b:sign(1, b-a) with claimed length abs(b-a)+1, which is only correct
when both bounds are >= 1: x[1:n] with n = 0 read x(0) and returned 2
values where R returns 1.
Compile errors where the problem is visible statically:
- any negative or zero subscript value, and the syntactic form x[-i]
(unary minus is unambiguously exclusion in R)
- literal range bounds < 1
- seq() in subscript position with a non-literal `by`: the result
length divides by the step and is evaluated in the generated C bridge
before any Fortran guard could run, so a zero step would be an
unguardable division-by-zero crash there
Runtime guards (one scalar check per statement, via
emit_quickr_error_if) where the values are only known at run time:
- x[a:b] bounds not provably >= 1, raising "index ranges in x[a:b]
must have bounds >= 1"; literal-bound halves of the check are
constant-folded away, and descending ranges x[n:1] still work
- x[seq(a, b, by)] with literal step but symbolic bounds, raising
"wrong sign in 'by' argument" as R does
Also fixes seq_like_length_expr() silently dropping a non-literal `by`
when from/to are literal: seq(1L, 9L, by = k) claimed length 9
regardless of k, mis-sizing constructor results (now sized by the
step) and subscript sections (now rejected, above).
x[seq_len(n)] / x[seq_along(y)] need no guard: their worst case is a
legal zero-length section, matching R's x[integer(0)]. For-loop ranges
are untouched (do i = 1, 0 already runs zero times).
Behavior change to note: R's x[1:0] returns x[1] (the 0 is dropped);
quickr now errors at runtime instead of reading out of bounds.
* Enable OpenMP cancellation so in-loop errors exit early
Error paths inside parallel loops emit `!$omp cancel do`, but per the
OpenMP spec cancel constructs are no-ops unless the cancel-var ICV is
true, which requires OMP_CANCELLATION=true in the environment when the
OpenMP runtime first initializes. Nothing set it, so an error raised
inside a parallel loop recorded its message correctly (first-wins
critical section) but every remaining iteration still ran -- wasted
work, and statements after a failed in-loop check kept executing in
that iteration's thread.
Set OMP_CANCELLATION=true in .onLoad when unset (a pre-set value is
respected). Caveats documented in ?declare: no effect if another
package already initialized the OpenMP runtime, so early exit is
best-effort -- error messages are always correct either way.
* Validate assignment subscripts and literal bounds against known extents
Two gaps in subscript validation (both compile-and-return-garbage, found
in external review):
- The write side never validated at all: x[-1L] <- 9 and x[0L] <- 1
compiled into silent out-of-bounds Fortran writes, bypassing the
exclusion/zero rejection the read-side `[` handler already had.
compile_subset_designator() now runs the same checks, covering [<-,
[<<-, and closure host writes.
- Literal subscripts were never checked against a statically-known
extent: x[4L] and x[2:4] on a declared double(3) compiled and read
garbage, where R pads with NA (and grows the vector on writes) --
neither representable in quickr's static-shape model. When the base's
extent along an axis is a literal, out-of-range literal values, `:`
endpoints, and c() elements are now compile errors. A single subscript
on a rank>1 base (R's linear indexing) checks against the product of
the dims. Symbolic subscripts and symbolic extents are untouched, per
the documented bounds contract.
Range lower-bound validation (>= 1, including the runtime guard for
symbolic endpoints) stays in check_subscript_range_bounds(); the new
extent check only adds the upper side for literals.
* Extract check_subscript_exprs() shared by read and write subscripts
The read-side `[` handler and the write-side compile_subset_designator()
carried the same six-line validation loop; the invariant that both sides
validate identically is now pinned by a single helper instead of a
comment asking to keep two copies in sync.
Review finding (fable-final-review.md #3); no behavior change.
* Validate coerced subscripts and symbolic seq steps
* Accept signed literal seq steps
* Accept valid singleton and double-negated subscripts
* Fix hoisting in unbraced for-loop bodies
Braced loop bodies give each statement its own hoist target, but a single-expression for-loop body inherited the target of the enclosing statement. Body-local setup could therefore be emitted before the loop, where it executed only once and could reference an uninitialized loop variable.
Give both index- and value-iteration bodies a fresh hoist target. Iterable setup remains outside the loop, while guards and temporaries required by the body are emitted inside it.
* Drop runtime guards for dynamic subscript bounds
Dynamic range guards added work to hot loops while checking only lower bounds. Upper bounds and symbolic scalar and vector subscripts remained unchecked, so the partial guard did not provide a coherent safety contract.
Keep zero-cost compile-time validation for unsupported or statically invalid subscripts, and keep runtime validation required for seq() step semantics. Dynamically computed array bounds remain the caller's responsibility.
---------
Co-authored-by: Tomasz Kalinowski <kalinowskit@gmail.com>
0 commit comments