Skip to content

Commit b6824c5

Browse files
committed
fix: guard dynamic empty BLAS outputs
1 parent bf0daf1 commit b6824c5

2 files changed

Lines changed: 97 additions & 15 deletions

File tree

R/r2f-matrix-blas.R

Lines changed: 64 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -191,19 +191,38 @@ assert_square_matrix <- function(dims, operand, context, hoist, scope) {
191191
# ---- BLAS emitters ----
192192

193193
# Generated function results cannot currently represent zero-sized arrays.
194-
# Reject a statically known zero output before emitting a BLAS call with an
195-
# invalid leading dimension. A zero contracted dimension remains supported
196-
# when every output extent is nonzero.
197-
assert_nonempty_blas_output <- function(dims, context) {
198-
stopifnot(is.list(dims), length(dims) > 0L, is_string(context))
199-
has_zero_extent <- any(vapply(
200-
dims,
201-
function(dim) is_wholenumber(dim) && as.integer(dim) == 0L,
202-
logical(1)
203-
))
204-
if (has_zero_extent) {
205-
stop(context, " zero-sized outputs are not supported", call. = FALSE)
194+
# Reject a known zero output during translation and guard unknown output
195+
# extents at runtime before emitting a BLAS call with an invalid leading
196+
# dimension. A zero contracted dimension remains supported when every output
197+
# extent is nonzero.
198+
assert_nonempty_blas_output <- function(
199+
dim,
200+
operand,
201+
axis,
202+
context,
203+
hoist,
204+
scope
205+
) {
206+
stopifnot(
207+
inherits(operand, Fortran),
208+
is.numeric(axis),
209+
length(axis) == 1L,
210+
is_string(context)
211+
)
212+
message <- paste0(context, " zero-sized outputs are not supported")
213+
if (is_wholenumber(dim)) {
214+
if (as.integer(dim) == 0L) {
215+
stop(message, call. = FALSE)
216+
}
217+
return(invisible(TRUE))
206218
}
219+
220+
emit_quickr_error_if(
221+
glue("{guard_dim_f(dim, operand, axis)} == 0_c_ptrdiff_t"),
222+
message,
223+
hoist,
224+
scope
225+
)
207226
invisible(TRUE)
208227
}
209228

@@ -344,7 +363,22 @@ gemm <- function(
344363
context = "gemm"
345364
) {
346365
assert_hoist_env(hoist)
347-
assert_nonempty_blas_output(list(m, n), context)
366+
assert_nonempty_blas_output(
367+
m,
368+
left,
369+
if (opA == "N") 1L else 2L,
370+
context,
371+
hoist,
372+
scope
373+
)
374+
assert_nonempty_blas_output(
375+
n,
376+
right,
377+
if (opB == "N") 2L else 1L,
378+
context,
379+
hoist,
380+
scope
381+
)
348382
A_name <- ensure_blas_operand_name(left, hoist)
349383
B_name <- ensure_blas_operand_name(right, hoist)
350384

@@ -391,7 +425,15 @@ gemv <- function(
391425
context = "gemv"
392426
) {
393427
assert_hoist_env(hoist)
394-
assert_nonempty_blas_output(out_dims, context)
428+
output_dim <- if (transA == "N") m else n
429+
assert_nonempty_blas_output(
430+
output_dim,
431+
A,
432+
if (transA == "N") 1L else 2L,
433+
context,
434+
hoist,
435+
scope
436+
)
395437
A_name <- ensure_blas_operand_name(A, hoist)
396438
x_name <- ensure_blas_operand_name(x, hoist)
397439

@@ -495,7 +537,14 @@ syrk <- function(
495537
k <- x_dims$cols
496538
}
497539
lda <- x_dims$rows
498-
assert_nonempty_blas_output(list(n, n), context)
540+
assert_nonempty_blas_output(
541+
n,
542+
X,
543+
if (trans == "T") 2L else 1L,
544+
context,
545+
hoist,
546+
scope
547+
)
499548
X_name <- ensure_blas_operand_name(X, hoist)
500549

501550
# Output is symmetric n x n matrix

tests/testthat/test-blas-guards.R

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,39 @@ test_that("matrix BLAS rejects known zero-sized outputs", {
190190
expect_error(quick(cross_mat), "zero-sized outputs are not supported")
191191
})
192192

193+
test_that("matrix BLAS guards unknown output extents at runtime", {
194+
matrix_matrix <- function(a, b) {
195+
declare(type(a = double(NA, 2)), type(b = double(2, 3)))
196+
a %*% b
197+
}
198+
matrix_vector <- function(a, x) {
199+
declare(type(a = double(NA, 2)), type(x = double(2)))
200+
a %*% x
201+
}
202+
cross_mat <- function(x) {
203+
declare(type(x = double(2, NA)))
204+
crossprod(x)
205+
}
206+
tcross_mat <- function(x) {
207+
declare(type(x = double(NA, 2)))
208+
tcrossprod(x)
209+
}
210+
211+
q_matrix_matrix <- expect_no_warning(quick(matrix_matrix))
212+
q_matrix_vector <- expect_no_warning(quick(matrix_vector))
213+
q_cross_mat <- expect_no_warning(quick(cross_mat))
214+
q_tcross_mat <- expect_no_warning(quick(tcross_mat))
215+
message <- "zero-sized outputs are not supported"
216+
217+
expect_error(
218+
q_matrix_matrix(matrix(double(), 0, 2), matrix(double(), 2, 3)),
219+
message
220+
)
221+
expect_error(q_matrix_vector(matrix(double(), 0, 2), double(2)), message)
222+
expect_error(q_cross_mat(matrix(double(), 2, 0)), message)
223+
expect_error(q_tcross_mat(matrix(double(), 0, 2)), message)
224+
})
225+
193226
test_that("NA dims are never treated as equal", {
194227
fn <- function(a, b) {
195228
declare(type(a = double(NA, NA)), type(b = double(NA, NA)))

0 commit comments

Comments
 (0)