Skip to content

Commit b388166

Browse files
committed
Merge branch 'main' into list_return_1
2 parents c03b8e3 + cb2c4b5 commit b388166

15 files changed

Lines changed: 840 additions & 58 deletions

NEWS.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
# quickr (development version)
22

3-
- Internal utility `r2f()` print method now shows the generated `c_bridge`
3+
- Internal utility `r2f()` print method now shows the generated `c_bridge`
44
for translated subroutines.
5-
5+
66
- Added support for `nrow()`, `ncol()` and `dim()` (#21, @mikmart).
77

8+
- Added support for `runif()` with integration to R's RNG (#22, #45).
9+
810
- Added support for `while`, `repeat`, `break`, `next`.
911

1012
- Added support for `%%` and `%/%`.
@@ -22,6 +24,14 @@
2224

2325
- Fixed segfault encountered on Windows with variable sized arrays.
2426

27+
- Added workaround for cases where the compiler error message might not
28+
display correctly in RStudio.
29+
30+
- Improved error message when using case-sensitive variable names (#18, #36, #39)
31+
32+
- Added `AGENTS.md` and `scripts/setup_codex.sh` to enable the ChatGPT/Codex agent
33+
to run tests in a docker container configured without internet access.
34+
2535

2636
# quickr 0.1.0
2737

R/c-wrapper.R

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ make_c_bridge <- function(fsub, strict = TRUE, headers = TRUE) {
33

44
closure <- fsub@closure
55
scope <- fsub@scope
6+
uses_rng <- isTRUE(attr(scope, "uses_rng", TRUE))
67

78
fsub_arg_names <- fsub@signature # arg names
89
closure_arg_names <- names(formals(closure))
@@ -61,7 +62,9 @@ make_c_bridge <- function(fsub, strict = TRUE, headers = TRUE) {
6162

6263
append(c_body) <- c(
6364
"",
65+
if (uses_rng) "GetRNGstate();",
6466
glue("{fsub@name}({str_flatten_commas(fsub_call_args)});"),
67+
if (uses_rng) "PutRNGstate();",
6568
""
6669
)
6770
if (length(return_var_names) == 1L) {
@@ -93,14 +96,13 @@ make_c_bridge <- function(fsub, strict = TRUE, headers = TRUE) {
9396

9497
fsub_extern_decl <- fsub_extern_decl(fsub)
9598

96-
c_headers <- glue::trim(
97-
r"--(
98-
#define R_NO_REMAP
99-
#include <R.h>
100-
#include <Rinternals.h>
101-
102-
103-
)--"
99+
c_headers <- str_flatten_lines(
100+
"#define R_NO_REMAP",
101+
"#include <R.h>",
102+
"#include <Rinternals.h>",
103+
if (uses_rng) "#include <R_ext/Random.h>",
104+
"",
105+
""
104106
)
105107

106108
as_glue(str_flatten_lines(c(

R/quick.R

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
#' Compile an R function.
44
#'
55
#' @param fun An R function
6-
#' @param name Optional string, name to use for the function.
6+
#' @param name String, name to use for the function. This is optional in
7+
#' regular usage but required in an R package. As a convenience, arguments
8+
#' `fun` and `name` can also be supplied as positional arguments to `quick` with
9+
#' `name` in the first position.
710
#'
811
#' @details
912
#'
@@ -12,8 +15,9 @@
1215
#' The shape and mode of all function arguments must be declared. Local and
1316
#' return variables may optionally also be declared.
1417
#'
15-
#' `declare(type())` also has support for declaring size constraints, or size
16-
#' relationships between variables. Here are some examples of declare calls:
18+
#' `declare(type())` also has support for declaring size constraints, or
19+
#' size relationships between variables. Here are some examples of declare
20+
#' calls:
1721
#'
1822
#' ```r
1923
#' declare(type(x = double(NA))) # x is a 1-d double vector of any length
@@ -64,11 +68,11 @@
6468
#'
6569
#' ## Return values
6670
#'
67-
#' The shape and type of a function return value must be known at compile time.
68-
#' In most situations, this will be automatically inferred by `quick()`. However,
69-
#' if the output is dynamic, then you may need to provide a hint.
70-
#' For example, returning the result of `seq()` will fail because the output shape
71-
#' cannot be inferred.
71+
#' The shape and type of a function return value must be known at compile
72+
#' time. In most situations, this will be automatically inferred by
73+
#' `quick()`. However, if the output is dynamic, then you may need to
74+
#' provide a hint. For example, returning the result of `seq()` will fail
75+
#' because the output shape cannot be inferred.
7276
#'
7377
#' ```r
7478
#' # Will fail to compile:
@@ -82,8 +86,8 @@
8286
#' })
8387
#' ```
8488
#'
85-
#' However, if the output size can be declared as a dynamic expression using other
86-
#' values known at runtime, compilation will succeed:
89+
#' However, if the output size can be declared as a dynamic expression using
90+
#' other values known at runtime, compilation will succeed:
8791
#'
8892
#' ```r
8993
#' # Succeeds:
@@ -116,6 +120,8 @@ quick <- function(fun, name = NULL) {
116120
} else {
117121
make_unique_name(prefix = "anonymous_quick_function_")
118122
}
123+
} else if (is.function(name) && is_string(fun)) {
124+
.[name, fun] <- list(fun, name)
119125
}
120126

121127
if (nzchar(pkgname <- Sys.getenv("DEVTOOLS_LOAD"))) {
@@ -143,6 +149,12 @@ quick <- function(fun, name = NULL) {
143149

144150
pkgname <- parent.pkg()
145151
if (!is.null(pkgname) && pkgname != "quickr") {
152+
if (startsWith(name, 'anonymous_quick_function_')) {
153+
stop(
154+
'When used in an R package, you must provide a unique `name` to every `quick()` call.\n',
155+
'For example: `my_fun <- quick("my_fun", function(x) ....)'
156+
)
157+
}
146158
# we are in a package - but outside a quickr::compile_package() call.
147159
return(create_quick_closure(name, fun))
148160
}
@@ -184,10 +196,13 @@ compile <- function(fsub, build_dir = tempfile(paste0(fsub@name, "-build-"))) {
184196
stderr = TRUE
185197
)
186198
})
187-
if (!is.null(attr(result, "status"))) {
199+
if (!is.null(status <- attr(result, "status"))) {
200+
# Adjust the compiler error so RStudio console formatter doesn't mangle
201+
# the actual error message https://github.com/rstudio/rstudio/issues/16365
202+
result <- gsub("Error: ", "Compiler Error: ", result, fixed = TRUE)
188203
writeLines(result, stderr())
189-
str(attributes(result))
190-
stop("Compilation Error")
204+
cat("---\nCompiler exit status:", status, "\n", file = stderr())
205+
stop("Compilation Error", call. = FALSE)
191206
}
192207

193208
# tryCatch(dyn.unload(dll_path), error = identity)

R/r2f.R

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -944,6 +944,39 @@ r2f_handlers[["double"]] <- function(args, scope, ...) {
944944

945945
r2f_handlers[["numeric"]] <- r2f_handlers[["double"]]
946946

947+
r2f_handlers[["runif"]] <- function(args, scope, ..., hoist = NULL) {
948+
attr(scope, "uses_rng") <- TRUE
949+
950+
dims <- r2dims(args$n, scope)
951+
var <- Variable("double", dims)
952+
953+
min <- args$min %||% 0
954+
max <- args$max %||% 1
955+
default_min <- identical(min, 0) || identical(min, 0L)
956+
default_max <- identical(max, 1) || identical(max, 1L)
957+
958+
if (default_min && default_max) {
959+
get1rand <- "unif_rand()"
960+
} else if (default_min) {
961+
max <- r2f(max, scope, ..., hoist = hoist)
962+
get1rand <- glue("unif_rand() * {max}")
963+
} else {
964+
max <- r2f(max, scope, ..., hoist = hoist)
965+
min <- r2f(min, scope, ..., hoist = hoist)
966+
get1rand <- glue("({min} + (unif_rand() * ({max} - {min})))")
967+
}
968+
969+
if (passes_as_scalar(var)) {
970+
fortran <- get1rand
971+
} else {
972+
tmp_i <- scope@get_unique_var("integer") ## would be better as uint64...
973+
fortran <- glue("[({get1rand}, {tmp_i}=1, {dims[[1L]]})]")
974+
}
975+
976+
Fortran(fortran, var)
977+
}
978+
979+
947980
r2f_handlers[["character"]] <- r2f_handlers[["raw"]] <-
948981
.r2f_handler_not_implemented_yet
949982

R/subroutine.R

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,26 @@ new_fortran_subroutine <- function(name, closure, parent = emptyenv()) {
102102
append(used_iso_bindings) <- "c_ptrdiff_t"
103103
}
104104
}
105+
if (isTRUE(attr(scope@closure, "uses_rng", TRUE))) {
106+
used_iso_bindings <- union(used_iso_bindings, "c_double")
107+
}
105108
used_iso_bindings <- sort(used_iso_bindings, method = "radix")
106109

110+
uses_rng <- isTRUE(attr(scope, 'uses_rng', TRUE))
111+
if (uses_rng) {
112+
rng_interface <- glue::trim(
113+
'
114+
interface
115+
function unif_rand() bind(c, name = "unif_rand") result(u)
116+
use iso_c_binding, only: c_double
117+
real(c_double) :: u
118+
end function unif_rand
119+
end interface
120+
'
121+
)
122+
123+
manifest <- str_flatten_lines(manifest, "", rng_interface)
124+
}
107125
subroutine <- glue(
108126
"
109127
subroutine {name}({str_flatten_commas(fsub_arg_names)}) bind(c)

README.Rmd

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,12 @@ plot(timings) + bench::scale_x_bench_time(base = NULL)
364364
When called in a package, `quick()` will pre-compile the quick functions and place them in the `./src` directory.
365365
Run `devtools::load_all()` or `quickr::compile_package()` to ensure that the generated files in `./src` and `./R` are in sync with each other.
366366

367+
In a package, you must provide a function name to `quick()`. For example:
368+
369+
```r
370+
my_fun <- quick(name = "my_fun", function(x) ....)
371+
```
372+
367373
## Installation
368374

369375
You can install quickr from CRAN with:

README.md

Lines changed: 32 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,11 @@
33

44
# quickr <img src="man/figures/logo.png" align="right" height="138"/>
55

6-
<!-- <img
7-
src="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExMjBhNWt1Z3Q4ZW56cG00c2hncmtwbGJycm53M3JxYWdscjRkaDJobCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/12haGO61oFZ28w/giphy.gif"
8-
alt="An animated GIF showing two characters in a spaceship cockpit rapidly accelerating into hyperspace, with stars stretching into bright streaks, creating a sensation of rapid acceleration and motion." /> -->
6+
<!-- ![](https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExMjBhNWt1Z3Q4ZW56cG00c2hncmtwbGJycm53M3JxYWdscjRkaDJobCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/12haGO61oFZ28w/giphy.gif){alt="An animated GIF showing two characters in a spaceship cockpit rapidly accelerating into hyperspace, with stars stretching into bright streaks, creating a sensation of rapid acceleration and motion."} -->
97

108
<!-- badges: start -->
119

10+
[![R-CMD-check](https://github.com/t-kalinowski/quickr/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/t-kalinowski/quickr/actions/workflows/R-CMD-check.yaml)
1211
<!-- badges: end -->
1312

1413
The goal of quickr is to make your R code run quicker.
@@ -20,11 +19,11 @@ and dynamicism can come at the expense of speed. This package lets you
2019
trade back some of that flexibility for some speed, for the context of a
2120
single function.
2221

23-
<!-- Programming language design requires some hard decisions and trade-offs. -->
22+
<!-- Programming language design requires some hard decisions and trade-ofs. -->
2423

2524
<!-- When you want to have it all, you typically end up have two (or more!) languages. -->
2625

27-
<!-- An interpreted, dynamic language full of conveniences, and a statically‑typed, explicit, high-performance language. -->
26+
<!-- An interpreted, dynamic language full of conveniences, and a staticly-typed, explicit, high-performance language. -->
2827

2928
<!-- This is sometimes called the "Two Language Problem". -->
3029

@@ -138,9 +137,9 @@ timings
138137
#> # A tibble: 3 × 6
139138
#> expression min median `itr/sec` mem_alloc `gc/sec`
140139
#> <bch:expr> <bch:tm> <bch:tm> <dbl> <bch:byt> <dbl>
141-
#> 1 r 1.05s 1.05s 0.955 847KB 0.955
142-
#> 2 quickr 4.8ms 5.09ms 195. 782KB 3.07
143-
#> 3 c 4.73ms 5.04ms 196. 782KB 3.09
140+
#> 1 r 1.04s 1.04s 0.957 782KB 0.957
141+
#> 2 quickr 4.85ms 5.03ms 198. 782KB 3.61
142+
#> 3 c 4.88ms 5.06ms 197. 782KB 4.13
144143
plot(timings) + bench::scale_x_bench_time(base = NULL)
145144
```
146145

@@ -163,16 +162,17 @@ In the case of `convolve()`, `quick()` returns a function approximately
163162

164163
<!-- -->
165164

166-
#> [1] - : != ( [ [<- {
167-
#> [8] * / & && %/% %% ^
168-
#> [15] + < <- <= = == >
169-
#> [22] >= | || Arg Conj Fortran Im
170-
#> [29] Mod Re abs acos asin atan c
171-
#> [36] cat cbind ceiling character cos declare double
172-
#> [43] exp floor for if ifelse integer length
173-
#> [50] log log10 logical matrix max min numeric
174-
#> [57] print prod raw seq sin sqrt sum
175-
#> [64] tan which.max which.min
165+
#> [1] - : != ( [ [<- {
166+
#> [8] * / & && %/% %% ^
167+
#> [15] + < <- <= = == >
168+
#> [22] >= | || Arg Conj Fortran Im
169+
#> [29] Mod Re abs acos as.double asin atan
170+
#> [36] break c cat cbind ceiling character cos
171+
#> [43] declare dim double exp floor for if
172+
#> [50] ifelse integer length log log10 logical matrix
173+
#> [57] max min ncol next nrow numeric print
174+
#> [64] prod raw repeat runif seq sin sqrt
175+
#> [71] sum tan which.max which.min while
176176

177177
Many of these restrictions are expected to be relaxed as the project
178178
matures. However, quickr is intended primarily for high-performance
@@ -281,8 +281,8 @@ timings
281281
#> # A tibble: 2 × 6
282282
#> expression min median `itr/sec` mem_alloc `gc/sec`
283283
#> <bch:expr> <bch:tm> <bch:tm> <dbl> <bch:byt> <dbl>
284-
#> 1 slow_viterbi 143.04µs 162µs 6047. 178KB 14.5
285-
#> 2 quick_viterbi 2.43µs 2.53µs 368609. 0B 0
284+
#> 1 slow_viterbi 147.12µs 162.33µs 5957. 1.59KB 16.8
285+
#> 2 quick_viterbi 2.48µs 2.62µs 367468. 0B 0
286286
plot(timings)
287287
```
288288

@@ -360,8 +360,8 @@ summary(timings, relative = TRUE)
360360
#> # A tibble: 2 × 6
361361
#> expression min median `itr/sec` mem_alloc `gc/sec`
362362
#> <bch:expr> <dbl> <dbl> <dbl> <dbl> <dbl>
363-
#> 1 diffuse_heat 127. 122. 1 1014. Inf
364-
#> 2 quick_diffuse_heat 1 1 121. 1 NaN
363+
#> 1 diffuse_heat 131. 127. 1 1011. Inf
364+
#> 2 quick_diffuse_heat 1 1 127. 1 NaN
365365
plot(timings)
366366
```
367367

@@ -407,9 +407,9 @@ timings
407407
#> # A tibble: 3 × 6
408408
#> expression min median `itr/sec` mem_alloc `gc/sec`
409409
#> <bch:expr> <bch:tm> <bch:tm> <dbl> <bch:byt> <dbl>
410-
#> 1 r 105.4ms 109.13ms 7.42 124.31MB 22.3
411-
#> 2 rcpp 19.7ms 19.84ms 49.4 4.44MB 1.98
412-
#> 3 quickr 7ms 7.09ms 138. 781.35KB 2.00
410+
#> 1 r 114.2ms 132.75ms 5.66 124.24MB 9.44
411+
#> 2 rcpp 19.35ms 19.58ms 50.9 4.44MB 0
412+
#> 3 quickr 6.73ms 6.87ms 142. 781.35KB 1.98
413413

414414
timings$expression <- factor(names(timings$expression), rev(names(timings$expression)))
415415
plot(timings) + bench::scale_x_bench_time(base = NULL)
@@ -424,6 +424,13 @@ and place them in the `./src` directory. Run `devtools::load_all()` or
424424
`quickr::compile_package()` to ensure that the generated files in
425425
`./src` and `./R` are in sync with each other.
426426

427+
In a package, you must provide a function name to `quick()`. For
428+
example:
429+
430+
``` r
431+
my_fun <- quick(name = "my_fun", function(x) ....)
432+
```
433+
427434
## Installation
428435

429436
You can install quickr from CRAN with:
713 Bytes
Loading
2.24 KB
Loading
1.5 KB
Loading

0 commit comments

Comments
 (0)