@@ -61,6 +61,25 @@ assert_rhs_rank <- function(
6161 invisible (TRUE )
6262}
6363
64+ # BLAS/LAPACK dimensions use equality semantics: equal zero contracted
65+ # dimensions are conformable and can still produce a non-empty result.
66+ check_blas_dims <- function (left , right ) {
67+ if (is_wholenumber(left ) && is_wholenumber(right )) {
68+ return (list (
69+ ok = identical(as.integer(left ), as.integer(right )),
70+ unknown = FALSE
71+ ))
72+ }
73+ if (! is_scalar_na(left ) && ! is_scalar_na(right )) {
74+ left_norm <- fortranize_expr_symbols(left )
75+ right_norm <- fortranize_expr_symbols(right )
76+ if (identical(left_norm , right_norm )) {
77+ return (list (ok = TRUE , unknown = FALSE ))
78+ }
79+ }
80+ list (ok = TRUE , unknown = TRUE )
81+ }
82+
6483# Return the R symbol name if operand is a bare symbol; otherwise NULL.
6584symbol_name_or_null <- function (x ) {
6685 stopifnot(inherits(x , Fortran ))
@@ -164,12 +183,49 @@ assert_square_matrix <- function(dims, operand, context, hoist, scope) {
164183 left = operand ,
165184 right = operand ,
166185 left_axis = 1L ,
167- right_axis = 2L
186+ right_axis = 2L ,
187+ checker = check_blas_dims
168188 )
169189}
170190
171191# ---- BLAS emitters ----
172192
193+ # Generated function results cannot currently represent zero-sized arrays.
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 ))
218+ }
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+ )
226+ invisible (TRUE )
227+ }
228+
173229# Check that destination dimensions match expected output dimensions.
174230assert_dest_dims_compatible <- function (dest , expected_dims , context ) {
175231 if (is.null(dest ) || is.null(expected_dims )) {
@@ -260,6 +316,32 @@ blas_int <- function(x) {
260316 glue(" int({x_str}, kind=c_int)" )
261317}
262318
319+ # Emit a BLAS call for positive contractions and fill the result with zero
320+ # without calling BLAS when the contracted dimension is zero.
321+ emit_blas_contraction <- function (call , output , contracted_dim , hoist ) {
322+ stopifnot(is_string(call ), is_string(output ))
323+ assert_hoist_env(hoist )
324+
325+ if (is_wholenumber(contracted_dim )) {
326+ if (as.integer(contracted_dim ) == 0L ) {
327+ hoist $ emit(glue(" {output} = 0.0_c_double" ))
328+ } else {
329+ hoist $ emit(call )
330+ }
331+ return (invisible (TRUE ))
332+ }
333+
334+ hoist $ emit(glue(
335+ "
336+ if ({blas_int(contracted_dim)} == 0_c_int) then
337+ {output} = 0.0_c_double
338+ else
339+ {call}
340+ end if"
341+ ))
342+ invisible (TRUE )
343+ }
344+
263345# Centralized GEMM emission with optional destination
264346# gemm: centralized BLAS GEMM emission.
265347# - 'hoist' is required and provided by r2f(); handlers thread it through so
@@ -281,6 +363,22 @@ gemm <- function(
281363 context = " gemm"
282364) {
283365 assert_hoist_env(hoist )
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+ )
284382 A_name <- ensure_blas_operand_name(left , hoist )
285383 B_name <- ensure_blas_operand_name(right , hoist )
286384
@@ -292,18 +390,20 @@ gemm <- function(
292390 context = context
293391 )
294392 ) {
295- hoist $ emit( glue(
393+ blas_call <- glue(
296394 " call dgemm('{opA}','{opB}', {blas_int(m)}, {blas_int(n)}, {blas_int(k)}, 1.0_c_double, {A_name}, {blas_int(lda)}, {B_name}, {blas_int(ldb)}, 0.0_c_double, {dest@name}, {blas_int(ldc_expr)})"
297- ))
395+ )
396+ emit_blas_contraction(blas_call , dest @ name , k , hoist )
298397 out <- Fortran(dest @ name , dest )
299398 out @ writes_to_dest <- TRUE
300399 return (out )
301400 }
302401
303402 output_var <- hoist $ declare_tmp(mode = " double" , dims = list (m , n ))
304- hoist $ emit( glue(
403+ blas_call <- glue(
305404 " call dgemm('{opA}','{opB}', {blas_int(m)}, {blas_int(n)}, {blas_int(k)}, 1.0_c_double, {A_name}, {blas_int(lda)}, {B_name}, {blas_int(ldb)}, 0.0_c_double, {output_var@name}, {blas_int(ldc_expr)})"
306- ))
405+ )
406+ emit_blas_contraction(blas_call , output_var @ name , k , hoist )
307407 Fortran(output_var @ name , output_var )
308408}
309409
@@ -325,6 +425,15 @@ gemv <- function(
325425 context = " gemv"
326426) {
327427 assert_hoist_env(hoist )
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+ )
328437 A_name <- ensure_blas_operand_name(A , hoist )
329438 x_name <- ensure_blas_operand_name(x , hoist )
330439
@@ -337,18 +446,22 @@ gemv <- function(
337446 )
338447 ) {
339448 # Assign output to output destination
340- hoist $ emit( glue(
449+ blas_call <- glue(
341450 " call dgemv('{transA}', {blas_int(m)}, {blas_int(n)}, 1.0_c_double, {A_name}, {blas_int(lda)}, {x_name}, 1_c_int, 0.0_c_double, {dest@name}, 1_c_int)"
342- ))
451+ )
452+ contracted_dim <- if (transA == " N" ) n else m
453+ emit_blas_contraction(blas_call , dest @ name , contracted_dim , hoist )
343454 out <- Fortran(dest @ name , dest )
344455 out @ writes_to_dest <- TRUE
345456 return (out )
346457 }
347458 # Else assign to a temporary variable
348459 output_var <- hoist $ declare_tmp(mode = " double" , dims = out_dims )
349- hoist $ emit( glue(
460+ blas_call <- glue(
350461 " call dgemv('{transA}', {blas_int(m)}, {blas_int(n)}, 1.0_c_double, {A_name}, {blas_int(lda)}, {x_name}, 1_c_int, 0.0_c_double, {output_var@name}, 1_c_int)"
351- ))
462+ )
463+ contracted_dim <- if (transA == " N" ) n else m
464+ emit_blas_contraction(blas_call , output_var @ name , contracted_dim , hoist )
352465 Fortran(output_var @ name , output_var )
353466}
354467
@@ -412,8 +525,6 @@ syrk <- function(
412525 context = " syrk"
413526) {
414527 assert_hoist_env(hoist )
415- X_name <- ensure_blas_operand_name(X , hoist )
416-
417528 x_dims <- matrix_dims(X )
418529
419530 # For trans = "T": C = t(X) %*% X, so C is k x k where k = ncol(X)
@@ -426,6 +537,15 @@ syrk <- function(
426537 k <- x_dims $ cols
427538 }
428539 lda <- x_dims $ rows
540+ assert_nonempty_blas_output(
541+ n ,
542+ X ,
543+ if (trans == " T" ) 2L else 1L ,
544+ context ,
545+ hoist ,
546+ scope
547+ )
548+ X_name <- ensure_blas_operand_name(X , hoist )
429549
430550 # Output is symmetric n x n matrix
431551 writes_to_dest <- FALSE
@@ -448,9 +568,10 @@ syrk <- function(
448568 out_name <- out_var @ name
449569 }
450570
451- hoist $ emit( glue(
571+ blas_call <- glue(
452572 " call dsyrk('U', '{trans}', {blas_int(n)}, {blas_int(k)}, 1.0_c_double, {X_name}, {blas_int(lda)}, 0.0_c_double, {out_name}, {blas_int(n)})"
453- ))
573+ )
574+ emit_blas_contraction(blas_call , out_name , k , hoist )
454575 symmetrize_upper_to_lower(out_name , n , hoist = hoist )
455576
456577 out <- Fortran(out_name , out_var )
@@ -547,7 +668,8 @@ triangular_solve <- function(
547668 left = A ,
548669 right = B ,
549670 left_axis = 1L ,
550- right_axis = if (b_rank == 1L ) NULL else 1L
671+ right_axis = if (b_rank == 1L ) NULL else 1L ,
672+ checker = check_blas_dims
551673 )
552674
553675 A_name <- ensure_blas_operand_name(A , hoist )
@@ -635,7 +757,8 @@ lapack_solve <- function(
635757 left = A ,
636758 right = B ,
637759 left_axis = 1L ,
638- right_axis = if (b_rank == 1L ) NULL else 1L
760+ right_axis = if (b_rank == 1L ) NULL else 1L ,
761+ checker = check_blas_dims
639762 )
640763
641764 A_name <- ensure_blas_operand_name(A , hoist )
0 commit comments