Skip to content

Commit 9ac2245

Browse files
committed
Extract the shared conformability helpers
Three duplications across the elementwise operators, ifelse() and the BLAS/LAPACK lowerings collapse into one helper each, all in r2f-operators-helpers.R: - guard_conformable_dims() becomes the single guard emitter for the conformability policy -- a statically known mismatch is a compile error, dims that cannot be compared statically get a statement-level runtime guard, provably equal dims need nothing. It moves out of r2f-matrix-blas.R (with guard_dim_f) and absorbs both private copies: emit_elementwise_size_guard() and ifelse()'s ifelse_axis_verdict() plus its inline .or. guard. - check_conformable() was dims_match() written in list form; both call sites (bind_common_dim, solve routing) now say so, and the weaker helper's contract is spelled out next to it. - real_floor_expr() carries the real-domain floor spelling shared by floor() and double %/%, so the aint/merge trick lives in one place. Behavior-neutral: zero snapshot churn and a full QUICKR_FULL_GRID=1 pass at 14207 assertions.
1 parent 4385c15 commit 9ac2245

6 files changed

Lines changed: 133 additions & 224 deletions

File tree

R/r2f-arithmetic.R

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -108,16 +108,13 @@ r2f_handlers[["%/%"]] <- function(args, scope, ..., hoist = NULL) {
108108
"int(floor(real({left}, kind=c_double) / real({right}, kind=c_double)), kind=c_int)"
109109
),
110110
double = {
111-
# Fortran FLOOR() returns an integer, so a large double quotient
112-
# (e.g. 1e20 %/% 3) would silently overflow. Stay in the real domain
113-
# as the floor() handler does; the quotient is spliced three times,
114-
# so hoist it to evaluate once.
111+
# The quotient is spliced three times by real_floor_expr(), so
112+
# hoist it to evaluate once.
115113
q <- hoist_unless_name(
116114
Fortran(glue("({left} / {right})"), out_val),
117115
hoist
118116
)
119-
aint <- glue("aint({q})")
120-
glue("({aint} - merge(1.0_c_double, 0.0_c_double, ({q} < {aint})))")
117+
real_floor_expr(q)
121118
},
122119
stop("%/% only implemented for numeric types")
123120
)

R/r2f-conditionals.R

Lines changed: 14 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -8,67 +8,34 @@ ifelse_branch_shape_msg <- paste0(
88
"R-style recycling is not supported"
99
)
1010

11-
# Three-valued conformability verdict for one axis of an ifelse() branch
12-
# against `test`: ok+known (no guard), not-ok+known (compile error), or
13-
# unknown (runtime guard). NA dims are always unknown: two unknown lengths
14-
# are not the same quantity.
15-
ifelse_axis_verdict <- function(test_dim, branch_dim) {
16-
if (is_wholenumber(test_dim) && is_wholenumber(branch_dim)) {
17-
return(list(
18-
ok = identical(as.integer(test_dim), as.integer(branch_dim)),
19-
unknown = FALSE
20-
))
21-
}
22-
if (!is_scalar_na(test_dim) && !is_scalar_na(branch_dim)) {
23-
test_norm <- fortranize_expr_symbols(test_dim)
24-
branch_norm <- fortranize_expr_symbols(branch_dim)
25-
if (identical(test_norm, branch_norm)) {
26-
return(list(ok = TRUE, unknown = FALSE))
27-
}
28-
}
29-
list(ok = TRUE, unknown = TRUE)
30-
}
31-
3211
# Enforce the shape contract for one ifelse() branch: scalars broadcast
3312
# natively; a non-scalar branch must match `test`'s shape, because
3413
# merge() requires conformable arguments and a runtime mismatch would
35-
# read past the shorter branch. Statically unequal dims are a compile
36-
# error; symbolic dims get a statement-level runtime size guard, emitted into
37-
# `hoist` -- always a live hoist context, since r2f() substitutes a fresh one
38-
# before dispatching to any handler.
14+
# read past the shorter branch. Per axis, guard_conformable_dims()
15+
# applies the framework policy: statically unequal dims are a compile
16+
# error; symbolic dims get a statement-level runtime size guard, emitted
17+
# into `hoist` -- always a live hoist context, since r2f() substitutes a
18+
# fresh one before dispatching to any handler.
3919
check_ifelse_branch_shape <- function(branch, mask, hoist, scope) {
4020
if (passes_as_scalar(branch@value)) {
4121
return(invisible())
4222
}
4323
if (branch@value@rank != mask@value@rank) {
4424
stop(ifelse_branch_shape_msg, call. = FALSE)
4525
}
46-
unknown_axes <- integer()
4726
for (axis in seq_len(mask@value@rank)) {
48-
verdict <- ifelse_axis_verdict(
27+
guard_conformable_dims(
28+
dim_or_one(branch, axis),
4929
dim_or_one(mask, axis),
50-
dim_or_one(branch, axis)
30+
ifelse_branch_shape_msg,
31+
hoist,
32+
scope,
33+
left = branch,
34+
right = mask,
35+
left_axis = axis,
36+
right_axis = axis
5137
)
52-
if (!verdict$ok) {
53-
stop(ifelse_branch_shape_msg, call. = FALSE)
54-
}
55-
if (verdict$unknown) {
56-
unknown_axes <- c(unknown_axes, axis)
57-
}
5838
}
59-
if (!length(unknown_axes)) {
60-
return(invisible())
61-
}
62-
# size() is an inquiry, so applying it to operand expression text does
63-
# not evaluate the operands.
64-
condition <- str_flatten(
65-
map_chr(
66-
unknown_axes,
67-
function(axis) glue("size({branch}, {axis}) /= size({mask}, {axis})")
68-
),
69-
" .or. "
70-
)
71-
emit_quickr_error_if(condition, ifelse_branch_shape_msg, hoist, scope)
7239
invisible()
7340
}
7441

R/r2f-math.R

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -55,15 +55,9 @@ r2f_handlers[["floor"]] <- function(args, scope, ..., hoist = NULL) {
5555
}
5656
out_val <- Variable("double", arg@value@dims)
5757

58-
# Avoid Fortran FLOOR() overflow (it returns an integer) by staying in the
59-
# real domain:
60-
# - aint(x) truncates toward 0 (real result)
61-
# - adjust by -1 where trunc differs from floor (negative non-integers)
62-
aint <- glue("aint({arg})")
63-
Fortran(
64-
glue("({aint} - merge(1.0_c_double, 0.0_c_double, ({arg} < {aint})))"),
65-
out_val
66-
)
58+
# Avoid Fortran FLOOR() overflow (it returns an integer) by staying in
59+
# the real domain; real_floor_expr() shares the spelling with `%/%`.
60+
Fortran(real_floor_expr(arg), out_val)
6761
}
6862

6963
r2f_handlers[["ceiling"]] <- function(args, scope, ..., hoist = NULL) {

R/r2f-matrix-blas.R

Lines changed: 1 addition & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -61,54 +61,6 @@ assert_rhs_rank <- function(
6161
invisible(TRUE)
6262
}
6363

64-
# Render one side of a dim-comparison guard: a literal dim as the literal,
65-
# anything else as the operand's actual extent. size() is an inquiry, so
66-
# applying it to operand expression text does not evaluate the operand.
67-
guard_dim_f <- function(dim, operand, axis = NULL) {
68-
if (is_wholenumber(dim)) {
69-
return(as.character(as.integer(dim)))
70-
}
71-
if (is.null(axis)) {
72-
glue("size({operand})")
73-
} else {
74-
glue("size({operand}, {axis})")
75-
}
76-
}
77-
78-
# The one conformability policy for BLAS/LAPACK lowerings: a statically
79-
# known mismatch is a compile error; dims that cannot be compared
80-
# statically get a statement-level runtime guard emitted before the BLAS
81-
# call; provably equal dims need nothing. Never warn-and-proceed. `axis`
82-
# NULL compares the operand's whole size (rank-1 operands).
83-
guard_conformable_dims <- function(
84-
left_dim,
85-
right_dim,
86-
message,
87-
hoist,
88-
scope,
89-
left,
90-
right,
91-
left_axis = NULL,
92-
right_axis = NULL
93-
) {
94-
stopifnot(is_string(message))
95-
conform <- check_elementwise_lengths(left_dim, right_dim)
96-
if (!conform$ok) {
97-
stop(message, call. = FALSE)
98-
}
99-
if (conform$unknown) {
100-
emit_quickr_error_if(
101-
glue(
102-
"{guard_dim_f(left_dim, left, left_axis)} /= {guard_dim_f(right_dim, right, right_axis)}"
103-
),
104-
message,
105-
hoist,
106-
scope
107-
)
108-
}
109-
invisible(TRUE)
110-
}
111-
11264
# Return the R symbol name if operand is a bare symbol; otherwise NULL.
11365
symbol_name_or_null <- function(x) {
11466
stopifnot(inherits(x, Fortran))
@@ -199,18 +151,6 @@ effective_dims <- function(dims, trans) {
199151
}
200152
}
201153

202-
# Return conformability status (ok/unknown) without side-effects.
203-
check_conformable <- function(left, right) {
204-
if (is_wholenumber(left) && is_wholenumber(right)) {
205-
ok <- identical(as.integer(left), as.integer(right))
206-
return(list(ok = ok, unknown = FALSE))
207-
}
208-
if (identical(left, right)) {
209-
return(list(ok = TRUE, unknown = FALSE))
210-
}
211-
list(ok = TRUE, unknown = TRUE)
212-
}
213-
214154
# Enforce that `dims` describe a square matrix: a known mismatch is a
215155
# compile error; unverifiable dims get a runtime guard on the operand's
216156
# actual extents.
@@ -709,8 +649,7 @@ lapack_solve <- function(
709649
# test-matrix-lapack.R. Squareness is a routing decision here, not a
710650
# correctness guard: unknown squareness routes to dgels, which solves
711651
# square systems exactly too.
712-
square <- check_conformable(m, n)
713-
if (square$ok && !square$unknown && !identical(context, "qr.solve")) {
652+
if (dims_match(m, n) && !identical(context, "qr.solve")) {
714653
A_work <- hoist$declare_tmp(mode = "double", dims = list(m, m))
715654
hoist$emit(glue("{A_work@name} = {A_name}"))
716655

R/r2f-matrix.R

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -288,17 +288,10 @@ bind_common_dim <- function(dim_list, scalar_flags, context, label) {
288288
}
289289
if (length(non_scalar) > 1L) {
290290
for (idx in non_scalar[-1L]) {
291-
conform <- check_conformable(target, dim_list[[idx]])
292-
if (!conform$ok) {
293-
stop(
294-
context,
295-
" requires inputs with a common ",
296-
label,
297-
" count",
298-
call. = FALSE
299-
)
300-
}
301-
if (conform$unknown) {
291+
# A dim that is not provably equal to the common one is an error
292+
# either way: the declaration needs the dim, so "unknown" cannot be
293+
# deferred to a runtime guard here.
294+
if (!dims_match(target, dim_list[[idx]])) {
302295
stop(
303296
context,
304297
" requires inputs with a common ",

0 commit comments

Comments
 (0)