What happens
When matrix dimensions can't be compared at compile time, the linear-algebra
paths diverge in three inconsistent ways — none of which prevents the bad
call:
1. %*% and triangular solve warn at compile time, then proceed
unchecked.
fn <- function(m, x) {
declare(type(m = double(3, 3)), type(x = double(NA)))
m %*% x
}
qfn <- quick(fn)
#> Warning: cannot verify conformability in %*% at compile time: 3L vs x__len_
fn(diag(3), as.double(1:2)) # R: Error: non-conformable arguments
qfn(diag(3), as.double(1:2))
#> [,1]
#> [1,] 1.00000e+00
#> [2,] 2.00000e+00
#> [3,] 5.30499e-315 <- dgemv read past the end of x
The warning fires once, at compile time, for a condition that is only
sometimes a problem (the caller may always pass conformable inputs), and
the generated code then feeds unchecked dimensions straight into BLAS.
Depending on which dimension disagrees, that is either a silent
out-of-bounds read (above) or a cryptic abort from R's BLAS wrapper
("BLAS/LAPACK routine 'DGEMM ' gave error code -10") — never R's
"non-conformable arguments". solve()/qr.solve() right-hand sides and
the square-matrix checks in solve(a), chol(), chol2inv() behave the
same way.
2. crossprod()/tcrossprod() hard-error at compile time.
fn <- function(x, y) {
declare(type(x = double(n, k)), type(y = double(m, j)))
crossprod(x, y)
}
quick(fn)
#> Error: cannot verify conformability in crossprod at compile time
This rejects programs that are perfectly fine at run time (the caller
passes conformable matrices; the dims just aren't statically provable) —
the opposite failure mode of problem 1 above, in the same file.
3. Two matrices declared with NA dims are treated as equal. The
static comparison uses identical() on the dim expressions, and
identical(NA, NA) is TRUE — so double(NA, NA) against
double(NA, NA) skips even the warning and goes straight to the
unchecked BLAS call.
Why it happens
check_conformable() (R/r2f-matrix-blas.R) returns an ok/unknown
verdict, but each caller picks its own policy for unknown:
warn_conformability_unknown() + proceed at the %*%/gemv/gemm sites and
in assert_conformable_dims()/assert_square_matrix(); a hard stop()
in crossprod_like() (R/r2f-matrix.R). The runtime error machinery these
checks need (emit_quickr_error_if(), quickr_set_error_msg) already
exists and is used for LAPACK info codes in the same functions.
Expected behavior
One policy, matching the elementwise operators: statically known
mismatches stay compile errors; dimensions that can't be verified
statically get a runtime guard — one scalar size() comparison
emitted before the BLAS/LAPACK call, raising the same error R raises
("non-conformable arguments in %*%") instead of reading out of bounds.
The compile-time warning is dropped (it fires on a per-call-site
condition the compiler can't decide, and the guard now decides it
per call), and crossprod/tcrossprod relax from the spurious compile
error to the same guard — strictly more programs compile, all safely.
NA dims are always treated as unverified, never as equal.
cbind/rbind keep their compile error on an unknowable common
dimension: that dimension is needed to declare the output, which no
runtime guard can conjure.
The guard must be rank-aware. vector %*% vector with unknown lengths
reaches the gemm fallthrough, so a guard hardcoded to rank-2 axes would
emit size(x, 2) on a rank-1 array — invalid Fortran, which would make a
perfectly conformable unknown-length dot product fail to build. Rank-1
operands guard by their whole size instead.
What happens
When matrix dimensions can't be compared at compile time, the linear-algebra
paths diverge in three inconsistent ways — none of which prevents the bad
call:
1.
%*%andtriangular solvewarn at compile time, then proceedunchecked.
The warning fires once, at compile time, for a condition that is only
sometimes a problem (the caller may always pass conformable inputs), and
the generated code then feeds unchecked dimensions straight into BLAS.
Depending on which dimension disagrees, that is either a silent
out-of-bounds read (above) or a cryptic abort from R's BLAS wrapper
("BLAS/LAPACK routine 'DGEMM ' gave error code -10") — never R's
"non-conformable arguments".
solve()/qr.solve()right-hand sides andthe square-matrix checks in
solve(a),chol(),chol2inv()behave thesame way.
2.
crossprod()/tcrossprod()hard-error at compile time.This rejects programs that are perfectly fine at run time (the caller
passes conformable matrices; the dims just aren't statically provable) —
the opposite failure mode of problem 1 above, in the same file.
3. Two matrices declared with
NAdims are treated as equal. Thestatic comparison uses
identical()on the dim expressions, andidentical(NA, NA)isTRUE— sodouble(NA, NA)againstdouble(NA, NA)skips even the warning and goes straight to theunchecked BLAS call.
Why it happens
check_conformable()(R/r2f-matrix-blas.R) returns anok/unknownverdict, but each caller picks its own policy for
unknown:warn_conformability_unknown()+ proceed at the%*%/gemv/gemm sites andin
assert_conformable_dims()/assert_square_matrix(); a hardstop()in
crossprod_like()(R/r2f-matrix.R). The runtime error machinery thesechecks need (
emit_quickr_error_if(),quickr_set_error_msg) alreadyexists and is used for LAPACK
infocodes in the same functions.Expected behavior
One policy, matching the elementwise operators: statically known
mismatches stay compile errors; dimensions that can't be verified
statically get a runtime guard — one scalar
size()comparisonemitted before the BLAS/LAPACK call, raising the same error R raises
("non-conformable arguments in %*%") instead of reading out of bounds.
The compile-time warning is dropped (it fires on a per-call-site
condition the compiler can't decide, and the guard now decides it
per call), and
crossprod/tcrossprodrelax from the spurious compileerror to the same guard — strictly more programs compile, all safely.
NAdims are always treated as unverified, never as equal.cbind/rbindkeep their compile error on an unknowable commondimension: that dimension is needed to declare the output, which no
runtime guard can conjure.
The guard must be rank-aware.
vector %*% vectorwith unknown lengthsreaches the gemm fallthrough, so a guard hardcoded to rank-2 axes would
emit
size(x, 2)on a rank-1 array — invalid Fortran, which would make aperfectly conformable unknown-length dot product fail to build. Rank-1
operands guard by their whole size instead.