Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .Rbuildignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@
^scripts$
^AGENTS.md$
^CRAN-SUBMISSION$
^doc$
3 changes: 3 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@

The plan is to add more functions in the future (#77 @mns-nordicals)

- Added support for `cbind()` and `rbind()` for rank-0/1/2 inputs, with scalar
recycling only and strict length checks for non-scalar inputs.

- On macOS, quickr will use LLVM flang (`flang-new`) for compilation when
available (e.g. `brew install flang`). This is optional and can be disabled
with `options(quickr.prefer_flang = FALSE)`.
Expand Down
2 changes: 0 additions & 2 deletions R/classes.R
Original file line number Diff line number Diff line change
Expand Up @@ -389,8 +389,6 @@ FortranSubroutine := new_class(
)
)

`%error%` <- function(x, y) tryCatch(x, error = function(e) y)

try_prop <- function(object, name) S7::prop(object, name) %error% NULL

emit <- function(..., sep = "", end = "\n") cat(..., end, sep = sep)
Expand Down
158 changes: 127 additions & 31 deletions R/compiler.R
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,73 @@ quickr_flang_path <- function(which = Sys.which) {
""
}

quickr_flang_available <- function(
which = Sys.which,
system2 = base::system2
) {
flang <- quickr_flang_path(which = which)
if (!nzchar(flang)) {
return(list(path = "", available = FALSE))
}
probe <- tryCatch(
system2(flang, "--version", stdout = TRUE, stderr = TRUE),
error = function(e) structure(character(), status = 1L)
)
if (!is.null(attr(probe, "status"))) {
return(list(path = flang, available = FALSE))
}
list(path = flang, available = TRUE)
}

quickr_flang_state <- local({
state <- new.env(parent = emptyenv())
state$auto_disabled <- FALSE
state$fallback_warned <- FALSE
state
})

quickr_flang_auto_disabled <- function(state = quickr_flang_state) {
isTRUE(state$auto_disabled)
}

quickr_disable_flang_auto <- function(state = quickr_flang_state) {
state$auto_disabled <- TRUE
invisible(state$auto_disabled)
}

quickr_warn_flang_fallback_once <- function(state = quickr_flang_state) {
if (isTRUE(state$fallback_warned)) {
return(invisible(TRUE))
}
warning(
paste(
"flang compilation failed; falling back to gfortran and disabling",
"automatic flang preference for this session."
),
call. = FALSE
)
state$fallback_warned <- TRUE
invisible(TRUE)
}

quickr_compiler_warning_state <- local({
state <- new.env(parent = emptyenv())
state$warned <- FALSE
state
})

quickr_warn_compiler_failure_once <- function(
message,
state = quickr_compiler_warning_state
) {
if (isTRUE(state$warned)) {
return(invisible(TRUE))
}
warning(message, call. = FALSE)
state$warned <- TRUE
invisible(TRUE)
}

quickr_flang_runtime_flags <- local({
cache <- NULL

Expand Down Expand Up @@ -50,37 +117,55 @@ quickr_flang_runtime_flags <- local({
}
})

quickr_env_is_true <- function(name) {
val <- Sys.getenv(name, unset = "")
if (!nzchar(val)) {
return(FALSE)
quickr_fortran_compiler_option <- function(
opt = getOption("quickr.fortran_compiler")
) {
if (is.null(opt)) {
return(NULL)
}
if (!is_string(opt)) {
stop(
"`options(quickr.fortran_compiler)` must be a single string.",
call. = FALSE
)
}
opt <- tolower(trimws(opt))
if (!nzchar(opt) || opt %in% c("auto", "default", "system")) {
return(NULL)
}
if (opt %in% c("flang", "flang-new")) {
return("flang")
}
tolower(val) %in% c("1", "true", "t", "yes", "y", "on")
if (opt %in% c("gfortran", "gnu")) {
return("gfortran")
}
stop(
"`options(quickr.fortran_compiler)` must be one of ",
"\"flang\", \"gfortran\", or \"auto\".",
call. = FALSE
)
}

quickr_prefer_flang <- function(
sysname = Sys.info()[["sysname"]],
which = Sys.which
which = Sys.which,
system2 = base::system2
) {
opt <- getOption("quickr.prefer_flang")
if (isFALSE(opt)) {
return(FALSE)
}
if (quickr_env_is_true("QUICKR_PREFER_FLANG")) {
compiler_opt <- quickr_fortran_compiler_option()
if (identical(compiler_opt, "flang")) {
return(TRUE)
}
if (isTRUE(getOption("quickr.prefer_flang_force"))) {
return(TRUE)
if (identical(compiler_opt, "gfortran")) {
return(FALSE)
}
if (interactive() && isTRUE(opt)) {
return(TRUE)
if (quickr_flang_auto_disabled()) {
return(FALSE)
}

# Best-effort: on macOS, prefer flang if it is available.
if (
isTRUE(getOption("quickr.prefer_flang_auto", TRUE)) && sysname == "Darwin"
) {
return(nzchar(quickr_flang_path(which = which)))
if (sysname == "Darwin") {
info <- quickr_flang_available(which = which, system2 = system2)
return(isTRUE(info$available))
}

FALSE
Expand All @@ -89,9 +174,7 @@ quickr_prefer_flang <- function(
quickr_fcompiler_env <- function(
build_dir,
which = Sys.which,
prefer_flang = quickr_prefer_flang(which = which),
prefer_flang_force = isTRUE(getOption("quickr.prefer_flang_force")) ||
quickr_env_is_true("QUICKR_PREFER_FLANG"),
system2 = base::system2,
write_lines = writeLines,
sysname = Sys.info()[["sysname"]],
use_openmp = FALSE,
Expand All @@ -101,17 +184,32 @@ quickr_fcompiler_env <- function(

use_openmp <- isTRUE(use_openmp)
link_flags <- link_flags[nzchar(link_flags)]
compiler_opt <- quickr_fortran_compiler_option()
explicit_request <- identical(compiler_opt, "flang")

flang <- ""
flang_runtime <- character()
use_flang <- isTRUE(prefer_flang)
use_flang <- isTRUE(quickr_prefer_flang(
sysname = sysname,
which = which,
system2 = system2
))
if (use_flang) {
flang <- quickr_flang_path(which = which)
if (!nzchar(flang)) {
flang_info <- quickr_flang_available(which = which, system2 = system2)
flang <- flang_info$path
if (!isTRUE(flang_info$available)) {
if (isTRUE(explicit_request)) {
stop(
"quickr was configured to use flang, but flang was not available or could not be executed.\n",
"Ensure flang is on your PATH and that `flang --version` succeeds, or switch compilers with:\n",
" options(quickr.fortran_compiler = \"gfortran\")\n"
)
}
use_flang <- FALSE
flang <- ""
}
}
if (use_openmp && use_flang && !isTRUE(prefer_flang_force)) {
if (use_openmp && use_flang && !isTRUE(explicit_request)) {
use_flang <- FALSE
flang <- ""
}
Expand All @@ -122,15 +220,13 @@ quickr_fcompiler_env <- function(
character()
}
if (sysname == "Darwin" && !length(flang_runtime)) {
if (isTRUE(prefer_flang_force)) {
if (isTRUE(explicit_request)) {
stop(
"quickr was configured to use flang (",
flang,
") but could not locate the flang runtime library (libflang_rt.runtime.dylib) to link against.\n",
"Either reinstall flang so the runtime is available, or disable flang selection with:\n",
" options(quickr.prefer_flang = FALSE)\n",
"or:\n",
" Sys.setenv(QUICKR_PREFER_FLANG = 0)\n"
"Either reinstall flang so the runtime is available, or switch compilers with:\n",
" options(quickr.fortran_compiler = \"gfortran\")\n"
)
}
use_flang <- FALSE
Expand Down
10 changes: 0 additions & 10 deletions R/preprocess-lang.R
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,3 @@ whole_doubles_to_ints <- function(x) {
}
walker(x)
}

substitute_unique_case_insensitive_symbols <- function(x) {
# TODO: would be nice to fix case-insenstive name clashes
# with automatic substitutions. Would be a little involved since
# substitute will not replace tag names in a call, e.g.,
# declare(type(<NAME> = ...)), NAME would need to be manually replaced.
stopifnot(is.function(x))
nms <- unique(c(all.names(body(x), names(formals(x)))))
stop("not yet implemented")
}
25 changes: 17 additions & 8 deletions R/quick.R
Original file line number Diff line number Diff line change
Expand Up @@ -108,18 +108,21 @@
#' quickr compiles via `R CMD SHLIB` and will normally use the same toolchain
#' that R was built/configured with.
#'
#' On macOS, quickr will speculatively prefer LLVM flang when it is available on
#' `PATH` (falling back to R's default toolchain if compilation fails).
#' quickr only uses LLVM flang when it is explicitly requested or, on macOS,
#' when flang is available on `PATH` (and `flang --version` succeeds). If flang
#' is requested but unavailable, compilation errors. If flang compilation
#' fails, quickr retries with the default toolchain; on success it emits a
#' one-time warning and disables automatic flang preference for the rest of the
#' session.
#'
#' In interactive use, you can explicitly control this with:
#'
#' ```r
#' options(quickr.prefer_flang = TRUE)
#' options(quickr.fortran_compiler = "flang")
#' ```
#'
#' To disable the macOS auto-preference, set `options(quickr.prefer_flang_auto = FALSE)`
#' (or set `options(quickr.prefer_flang = FALSE)` to opt out entirely).
#' In non-interactive scripts, set `Sys.setenv(QUICKR_PREFER_FLANG = "1")`.
#' To disable the macOS auto-preference, set
#' `options(quickr.fortran_compiler = "gfortran")`.
#'
#' @returns A quicker R function.
#' @export
Expand Down Expand Up @@ -252,6 +255,8 @@ compile <- function(fsub, build_dir = tempfile(paste0(fsub@name, "-build-"))) {
)
if (is.null(attr(result2, "status"))) {
result <- result2
quickr_disable_flang_auto()
quickr_warn_flang_fallback_once()
} else {
# Prefer to show the flang attempt first, then the fallback attempt.
result <- c(
Expand All @@ -271,8 +276,12 @@ compile <- function(fsub, build_dir = tempfile(paste0(fsub@name, "-build-"))) {
# Adjust the compiler error so RStudio console formatter doesn't mangle
# the actual error message https://github.com/rstudio/rstudio/issues/16365
result <- gsub("Error: ", "Compiler Error: ", result, fixed = TRUE)
writeLines(result, stderr())
cat("---\nCompiler exit status:", status, "\n", file = stderr())
quickr_warn_compiler_failure_once(
paste(
c(result, "---", sprintf("Compiler exit status: %s", status)),
collapse = "\n"
)
)
if (use_openmp) {
openmp_abort(
paste(
Expand Down
26 changes: 26 additions & 0 deletions R/r2f-aaa-registry.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Handler registry and registration helpers.

r2f_handlers <- new.env(parent = emptyenv())

## ??? export as S7::convert() methods?
register_r2f_handler <- function(
name,
fun,
dest_supported = NULL,
dest_infer = NULL,
match_fun = TRUE
) {
if (!is.null(dest_supported)) {
attr(fun, "dest_supported") <- dest_supported
}
if (!is.null(dest_infer)) {
attr(fun, "dest_infer") <- dest_infer
}
if (!is.null(match_fun) && !isTRUE(match_fun)) {
attr(fun, "match.fun") <- match_fun
}
for (nm in name) {
r2f_handlers[[nm]] <- fun
}
invisible(fun)
}
Loading