Skip to content

Commit 702cbd6

Browse files
committed
Merge conformability fixes into short-circuit lowering
2 parents b3caad2 + f4d4749 commit 702cbd6

16 files changed

Lines changed: 422 additions & 357 deletions

R/classes.R

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,14 @@ Variable := new_class(
339339
# storage (0/1) rather than Fortran LOGICAL.
340340
logical_as_int = prop_bool(default = FALSE),
341341

342+
# Fortran kind for integer variables. User-facing R integers remain c_int;
343+
# pointer-sized compiler locals opt into c_ptrdiff_t explicitly.
344+
integer_kind = prop_enum(
345+
c("c_int", "c_ptrdiff_t"),
346+
default = "c_int",
347+
exact = TRUE
348+
),
349+
342350
# TRUE when the variable is available via host association and should not
343351
# be redeclared in the local scope.
344352
host_associated = prop_bool(default = FALSE),
@@ -350,7 +358,13 @@ Variable := new_class(
350358

351359
validator = function(self) {
352360
if (isTRUE(self@logical_as_int) && !identical(self@mode, "logical")) {
353-
"`logical_as_int` can only be TRUE when `mode` is 'logical'"
361+
return("`logical_as_int` can only be TRUE when `mode` is 'logical'")
362+
}
363+
if (
364+
!identical(self@integer_kind, "c_int") &&
365+
!identical(self@mode, "integer")
366+
) {
367+
"`integer_kind` can only differ from 'c_int' when `mode` is 'integer'"
354368
}
355369
}
356370
)

R/manifest.R

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,11 @@ var_storage_bytes <- function(var) {
7373
switch(
7474
var@mode,
7575
double = 8,
76-
integer = 4,
76+
integer = if (identical(var@integer_kind, "c_ptrdiff_t")) {
77+
.Machine$sizeof.pointer
78+
} else {
79+
4
80+
},
7781
complex = 16,
7882
logical = 4,
7983
raw = 1,
@@ -196,7 +200,7 @@ iso_c_binding_symbols <- function(
196200
switch(
197201
var@mode,
198202
double = "c_double",
199-
integer = "c_int",
203+
integer = var@integer_kind,
200204
complex = "c_double_complex",
201205
logical = if (isTRUE(logical_is_c_int(var))) "c_int",
202206
raw = "c_int8_t",
@@ -262,7 +266,7 @@ emit_decl_line <- function(
262266
type <- switch(
263267
var@mode,
264268
double = "real(c_double)",
265-
integer = "integer(c_int)",
269+
integer = glue("integer({var@integer_kind})"),
266270
complex = "complex(c_double_complex)",
267271
logical = if (logical_as_int(var)) "integer(c_int)" else "logical",
268272
raw = "integer(c_int8_t)",
@@ -371,7 +375,7 @@ r2f.scope <- function(scope, include_errors = FALSE) {
371375
type <- switch(
372376
var@mode,
373377
double = "real(c_double)",
374-
integer = "integer(c_int)",
378+
integer = glue("integer({var@integer_kind})"),
375379
complex = "complex(c_double_complex)",
376380
logical = if (logical_as_int(var)) "integer(c_int)" else "logical",
377381
raw = "integer(c_int8_t)",

R/r2f-constructors.R

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,14 @@
88
# double(k), numeric(k). These lower to a single scalar literal carrying
99
# array dims, so splicing contexts must spread them explicitly.
1010
# Used by: c(), array()
11-
is_fill_constructor_call <- function(e) {
12-
is.call(e) &&
13-
is.symbol(e[[1L]]) &&
14-
as.character(e[[1L]]) %in% c("logical", "integer", "double", "numeric")
11+
is_fill_constructor_call <- function(e, scope) {
12+
if (!is.call(e) || !is.symbol(e[[1L]])) {
13+
return(FALSE)
14+
}
15+
name <- as.character(e[[1L]])
16+
name %in%
17+
c("logical", "integer", "double", "numeric") &&
18+
(is.null(scope) || !inherits(scope[[name]], LocalClosure))
1519
}
1620

1721
# Name of the call one frame above the current handler ("" at top level).
@@ -44,7 +48,7 @@ r2f_handlers[["c"]] <- function(args, scope = NULL, ...) {
4448
mode <- promoted$mode
4549
# Fill constructors are one scalar literal claiming length k; spread them
4650
# as implied-dos so the emitted element count matches the claimed length.
47-
fill_idx <- which(map_lgl(args, is_fill_constructor_call))
51+
fill_idx <- which(map_lgl(args, is_fill_constructor_call, scope = scope))
4852
if (length(fill_idx)) {
4953
spread_var <- NULL
5054
for (j in fill_idx) {
@@ -60,9 +64,16 @@ r2f_handlers[["c"]] <- function(args, scope = NULL, ...) {
6064
call. = FALSE
6165
)
6266
}
63-
spread_var <- spread_var %||% scope_unique_var(scope, "integer")
67+
spread_var <- spread_var %||%
68+
scope_unique_var(
69+
scope,
70+
"integer",
71+
integer_kind = "c_ptrdiff_t"
72+
)
6473
ff[[j]] <- Fortran(
65-
glue("({ff[[j]]}, {spread_var}=1, int({len_f}))"),
74+
glue(
75+
"({ff[[j]]}, {spread_var}=1_c_ptrdiff_t, int({len_f}, kind=c_ptrdiff_t))"
76+
),
6677
ff[[j]]@value
6778
)
6879
}
@@ -383,7 +394,7 @@ r2f_handlers[["array"]] <- function(args, scope = NULL, ..., hoist = NULL) {
383394
}
384395
shape <- glue("int([{dims_f}])")
385396

386-
is_fill_constructor <- is_fill_constructor_call(args$data)
397+
is_fill_constructor <- is_fill_constructor_call(args$data, scope)
387398

388399
axis_terms <- vapply(
389400
target_dims,

R/r2f-matrix-blas.R

Lines changed: 138 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
6584
symbol_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.
174230
assert_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

Comments
 (0)