Skip to content

[11/15] && and || compile elementwise and never short-circuit #132

Description

@mns-nordicals

What happens

R's &&/|| are scalar control operators: operands must be length 1,
and the right operand is evaluated only when the left side does not
decide. quickr compiles them exactly like &/|:

fn <- function(x, y) {
  declare(type(x = logical(3)), type(y = logical(3)))
  x && y
}
c(TRUE, TRUE, FALSE) && c(TRUE, FALSE, TRUE)
#> Error in ... && ... : 'length = 3' in coercion to 'logical(1)'
quick(fn)(c(TRUE, TRUE, FALSE), c(TRUE, FALSE, TRUE))
#> [1]  TRUE FALSE FALSE     # an answer where R errors

And with both sides always evaluated, the canonical guarded-access
idiom is unsafe — .and. gives Fortran license to evaluate x(i) even
when i > n, an out-of-bounds read R's semantics rule out:

while (i <= n && x[i] > 0) i <- i + 1L

Why it happens

R/r2f-logical.R registers && on the same handler as &, which emits
.and. (a marked TODO). Fortran's .and./.or. are elementwise and
leave operand evaluation order unspecified — there is no short-circuit
guarantee to inherit.

Expected behavior

Match R: non-scalar operands are an error (compile-time, since the
answer never changes at run time), and the right operand's evaluation —
including any side effects or potential errors — happens only inside a
conditional on the left operand's value. while conditions need the
same care: anything the condition's translation hoists must re-run
every iteration, not once before the loop.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions