Skip to content

Commit fa7ada1

Browse files
authored
Merge pull request t-kalinowski#86 from t-kalinowski/error-handling
Add error handling plumbing
2 parents f6654f4 + 16bcb7a commit fa7ada1

23 files changed

Lines changed: 1135 additions & 49 deletions

NEWS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
- Added OpenMP parallelization via `declare(parallel())`/`declare(omp())` for
1818
`for` loops and `sapply()` calls.
1919

20+
- Added support for throwing errors with `stop()`. (#86)
21+
2022
- Added support for `abs()` in size expressions used by `declare(type(...))`
2123
(e.g. `declare(type(x = integer(abs(end - start) + 1L)))`).
2224

R/c-wrapper.R

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ make_c_bridge <- function(fsub, strict = TRUE, headers = TRUE) {
44
closure <- fsub@closure
55
scope <- fsub@scope
66
uses_rng <- isTRUE(attr(scope, "uses_rng", TRUE))
7+
uses_errors <- isTRUE(attr(scope, "uses_errors", TRUE))
78

89
fsub_arg_names <- fsub@signature # arg names
910
closure_arg_names <- names(formals(closure)) %||% character()
@@ -52,8 +53,22 @@ make_c_bridge <- function(fsub, strict = TRUE, headers = TRUE) {
5253
}
5354
}
5455

56+
if (uses_errors) {
57+
append(c_body) <- c(
58+
"",
59+
glue("char {quickr_error_msg_name()}[{quickr_error_msg_len()}];"),
60+
glue("{quickr_error_msg_name()}[0] = '\\0';"),
61+
""
62+
)
63+
}
64+
5565
fsub_call_args <- fsub_arg_names |>
56-
lapply(\(nm) paste0(nm, if (!is_size_name(nm)) "__")) |>
66+
lapply(\(nm) {
67+
if (is_quickr_error_msg(nm)) {
68+
return(nm)
69+
}
70+
paste0(nm, if (!is_size_name(nm)) "__")
71+
}) |>
5772
unlist()
5873

5974
if (length(fsub_call_args) > 3) {
@@ -65,6 +80,13 @@ make_c_bridge <- function(fsub, strict = TRUE, headers = TRUE) {
6580
if (uses_rng) "GetRNGstate();",
6681
glue("{fsub@name}({str_flatten_commas(fsub_call_args)});"),
6782
if (uses_rng) "PutRNGstate();",
83+
if (uses_errors) glue("if ({quickr_error_msg_name()}[0] != '\\0') {{"),
84+
if (uses_errors) {
85+
indent(glue(
86+
"Rf_error(\"%s\", {quickr_error_msg_name()});"
87+
))
88+
},
89+
if (uses_errors) "}",
6890
""
6991
)
7092
# Determine if the closure returns a list call or a single symbol
@@ -552,6 +574,9 @@ fsub_extern_decl <- function(fsub) {
552574
scope <- fsub@scope
553575

554576
fsub_c_sig <- map_chr(fsub_arg_names, function(name) {
577+
if (is_quickr_error_msg(name)) {
578+
return(glue("char* {name}"))
579+
}
555580
if (is_size_name(name)) {
556581
type <- if (name |> endsWith("__len_")) {
557582
"R_xlen_t"

R/error-handling.R

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
quickr_error_msg_name <- function() "quickr_err_msg"
2+
3+
quickr_error_msg_len <- function() 256L
4+
5+
quickr_error_setter_name <- function() "quickr_set_error_msg"
6+
7+
quickr_error_arg_names <- function() {
8+
c(quickr_error_msg_name())
9+
}
10+
11+
is_quickr_error_msg <- function(name) {
12+
identical(name, quickr_error_msg_name())
13+
}
14+
15+
scope_root_for_errors <- function(scope) {
16+
if (!inherits(scope, "quickr_scope")) {
17+
return(scope)
18+
}
19+
while (
20+
!identical(attr(scope, "kind", exact = TRUE), "subroutine") &&
21+
inherits(parent.env(scope), "quickr_scope")
22+
) {
23+
scope <- parent.env(scope)
24+
}
25+
scope
26+
}
27+
28+
mark_scope_uses_errors <- function(scope) {
29+
root <- scope_root_for_errors(scope)
30+
if (inherits(root, "quickr_scope")) {
31+
attr(root, "uses_errors") <- TRUE
32+
}
33+
invisible(TRUE)
34+
}
35+
36+
scope_uses_errors <- function(scope) {
37+
root <- scope_root_for_errors(scope)
38+
isTRUE(attr(root, "uses_errors", TRUE))
39+
}
40+
41+
fortran_string_literal <- function(x) {
42+
stopifnot(is_string(x))
43+
escaped <- gsub("\r\n|\r|\n", "\\\\n", x)
44+
escaped <- gsub("\"", "\"\"", escaped, fixed = TRUE)
45+
paste0("\"", escaped, "\"")
46+
}
47+
48+
check_quickr_error_message_continuable <- function(msg) {
49+
stopifnot(is_string(msg))
50+
if (grepl("[ \t]", msg)) {
51+
return(invisible(TRUE))
52+
}
53+
msg_literal <- fortran_string_literal(msg)
54+
line_len <- nchar(glue(
55+
"&{quickr_error_setter_name()}( {msg_literal} )"
56+
))
57+
if (line_len > 132L) {
58+
stop(
59+
"Error message is too long to fit in a single Fortran line without spaces.",
60+
" Add spaces to allow line continuations.",
61+
call. = FALSE
62+
)
63+
}
64+
invisible(TRUE)
65+
}
66+
67+
quickr_error_manifest_lines <- function() {
68+
msg_name <- quickr_error_msg_name()
69+
len_val <- quickr_error_msg_len()
70+
71+
glue("character(kind=c_char), intent(inout) :: {msg_name}({len_val})")
72+
}
73+
74+
quickr_error_helper_fortran <- function(openmp = FALSE) {
75+
msg_name <- quickr_error_msg_name()
76+
setter <- quickr_error_setter_name()
77+
len_val <- quickr_error_msg_len()
78+
79+
glue::trim(str_flatten_lines(
80+
glue("subroutine {setter}(msg)"),
81+
" character(len=*), intent(in) :: msg",
82+
" integer :: i",
83+
" integer :: n",
84+
if (isTRUE(openmp)) " !$omp critical (quickr_error)",
85+
glue(" if ({msg_name}(1) == c_null_char) then"),
86+
glue(" n = min(len(msg), {len_val} - 1)"),
87+
glue(" {msg_name}(1:n) = [(msg(i:i), i = 1, n)]"),
88+
glue(" {msg_name}(n + 1) = c_null_char"),
89+
" end if",
90+
if (isTRUE(openmp)) " !$omp end critical (quickr_error)",
91+
glue("end subroutine {setter}")
92+
))
93+
}
94+
95+
quickr_error_fortran_lines <- function(message = NULL, scope = NULL) {
96+
msg <- message %||% "quickr error"
97+
stopifnot(is_string(msg))
98+
if (!nzchar(msg)) {
99+
msg <- "quickr error"
100+
}
101+
check_quickr_error_message_continuable(msg)
102+
msg_literal <- fortran_string_literal(msg)
103+
lines <- glue("call {quickr_error_setter_name()}({msg_literal})")
104+
if (isTRUE(scope_in_openmp(scope))) {
105+
lines <- c(lines, "!$omp cancel do")
106+
} else {
107+
lines <- c(lines, "return")
108+
}
109+
lines
110+
}
111+
112+
quickr_error_return_if_set <- function(
113+
scope,
114+
openmp_depth = scope_openmp_depth(scope)
115+
) {
116+
if (!isTRUE(scope_uses_errors(scope))) {
117+
return("")
118+
}
119+
if (is.null(openmp_depth)) {
120+
openmp_depth <- 0L
121+
}
122+
openmp_depth <- max(as.integer(openmp_depth), 0L)
123+
if (openmp_depth > 0L) {
124+
return(str_flatten_lines(
125+
glue("if ({quickr_error_msg_name()}(1) /= c_null_char) then"),
126+
" !$omp cancel do",
127+
"end if"
128+
))
129+
}
130+
glue("if ({quickr_error_msg_name()}(1) /= c_null_char) return")
131+
}

R/manifest.R

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,8 @@ iso_c_binding_symbols <- function(
105105
vars,
106106
body_code = "",
107107
logical_is_c_int = logical_as_int,
108-
uses_rng = FALSE
108+
uses_rng = FALSE,
109+
include_errors = FALSE
109110
) {
110111
stopifnot(is.list(vars), is_string(body_code), is.function(logical_is_c_int))
111112

@@ -152,6 +153,13 @@ iso_c_binding_symbols <- function(
152153
used_iso_bindings <- union(used_iso_bindings, "c_double")
153154
}
154155

156+
if (isTRUE(include_errors)) {
157+
used_iso_bindings <- union(
158+
used_iso_bindings,
159+
c("c_char", "c_null_char")
160+
)
161+
}
162+
155163
used_iso_bindings |>
156164
compact() |>
157165
unique() |>
@@ -256,7 +264,7 @@ emit_block <- function(decls, stmts) {
256264
))
257265
}
258266

259-
r2f.scope <- function(scope) {
267+
r2f.scope <- function(scope, include_errors = FALSE) {
260268
vars <- scope_vars(scope)
261269
vars <- lapply(vars, function(var) {
262270
intent_in <- var@name %in% names(formals(scope@closure))
@@ -331,6 +339,7 @@ r2f.scope <- function(scope) {
331339

332340
manifest <- compact(list(
333341
sizes = sizes,
342+
error = if (isTRUE(include_errors)) quickr_error_manifest_lines(),
334343
args = vars[non_local_var_names],
335344
locals = vars[setdiff(names(vars), non_local_var_names)]
336345
))
@@ -346,7 +355,8 @@ r2f.scope <- function(scope) {
346355
# # method="radix" for locale-independent stable order.
347356
signature <- unique(c(
348357
non_local_var_names,
349-
sort(size_names, method = "radix")
358+
sort(size_names, method = "radix"),
359+
if (isTRUE(include_errors)) quickr_error_arg_names()
350360
))
351361
attr(manifest, "signature") <- signature
352362

R/parallel.R

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,44 @@ mark_openmp_used <- function(scope) {
3131
invisible(root)
3232
}
3333

34+
scope_openmp_depth <- function(scope) {
35+
if (!inherits(scope, "quickr_scope")) {
36+
return(0L)
37+
}
38+
depth <- attr(scope, "openmp_depth", exact = TRUE)
39+
if (is.null(depth)) {
40+
0L
41+
} else {
42+
as.integer(depth)
43+
}
44+
}
45+
46+
scope_in_openmp <- function(scope) {
47+
scope_openmp_depth(scope) > 0L
48+
}
49+
50+
enter_openmp_scope <- function(scope) {
51+
if (!inherits(scope, "quickr_scope")) {
52+
return(NULL)
53+
}
54+
previous_depth <- attr(scope, "openmp_depth", exact = TRUE)
55+
depth <- scope_openmp_depth(scope)
56+
attr(scope, "openmp_depth") <- depth + 1L
57+
previous_depth
58+
}
59+
60+
exit_openmp_scope <- function(scope, previous_depth) {
61+
if (!inherits(scope, "quickr_scope")) {
62+
return(invisible(NULL))
63+
}
64+
if (is.null(previous_depth)) {
65+
attr(scope, "openmp_depth") <- NULL
66+
} else {
67+
attr(scope, "openmp_depth") <- as.integer(previous_depth)
68+
}
69+
invisible(TRUE)
70+
}
71+
3472
openmp_abort <- function(message, class = "quickr_openmp_error") {
3573
stop(
3674
structure(

R/quick.R

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -447,7 +447,9 @@ check_all_var_names_valid <- function(fun) {
447447
"c_ptrdiff_t",
448448

449449
# clashes with C bridge symbols
450-
"int" #, "double",
450+
"int", #, "double",
451+
quickr_error_msg_name(),
452+
quickr_error_setter_name()
451453

452454
# ??? (clashes with R symbols?)
453455
# "double", "integer"

R/r2f-closures.R

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -467,9 +467,16 @@ compile_closure_call <- function(
467467

468468
call_args <- unname(args_f)
469469
if (length(call_args)) {
470-
return(Fortran(glue("call {proc$name}({str_flatten_commas(call_args)})")))
470+
call_stmt <- glue("call {proc$name}({str_flatten_commas(call_args)})")
471+
return(Fortran(str_flatten_lines(
472+
call_stmt,
473+
quickr_error_return_if_set(scope)
474+
)))
471475
}
472-
return(Fortran(glue("call {proc$name}()")))
476+
return(Fortran(str_flatten_lines(
477+
glue("call {proc$name}()"),
478+
quickr_error_return_if_set(scope)
479+
)))
473480
}
474481

475482
proc <- compile_local_closure_proc(
@@ -484,9 +491,16 @@ compile_closure_call <- function(
484491
if (!needs_value && is.null(proc$res)) {
485492
call_args <- unname(args_f)
486493
if (length(call_args)) {
487-
return(Fortran(glue("call {proc$name}({str_flatten_commas(call_args)})")))
494+
call_stmt <- glue("call {proc$name}({str_flatten_commas(call_args)})")
495+
return(Fortran(str_flatten_lines(
496+
call_stmt,
497+
quickr_error_return_if_set(scope)
498+
)))
488499
}
489-
return(Fortran(glue("call {proc$name}()")))
500+
return(Fortran(str_flatten_lines(
501+
glue("call {proc$name}()"),
502+
quickr_error_return_if_set(scope)
503+
)))
490504
}
491505

492506
res_var <- proc$res_var
@@ -497,6 +511,7 @@ compile_closure_call <- function(
497511
tmp <- hoist$declare_tmp(mode = res_var@mode, dims = res_var@dims)
498512
call_args <- c(unname(args_f), tmp@name)
499513
call_stmt <- glue("call {proc$name}({str_flatten_commas(call_args)})")
514+
call_stmt <- str_flatten_lines(call_stmt, quickr_error_return_if_set(scope))
500515

501516
if (needs_value) {
502517
hoist$emit(call_stmt)
@@ -621,6 +636,7 @@ compile_closure_call_assignment <- function(
621636
Fortran(glue(
622637
"
623638
call {proc$name}({str_flatten_commas(call_args)})
639+
{quickr_error_return_if_set(scope)}
624640
{str_flatten_lines(post)}
625641
"
626642
))
@@ -863,6 +879,22 @@ compile_sapply_assignment <- function(
863879
if (!is.null(parallel)) {
864880
mark_openmp_used(scope)
865881
}
882+
error_check_inner <- if (is.null(parallel)) {
883+
quickr_error_return_if_set(scope)
884+
} else {
885+
quickr_error_return_if_set(
886+
scope,
887+
openmp_depth = scope_openmp_depth(scope) + 1L
888+
)
889+
}
890+
error_check_after <- if (!is.null(parallel)) {
891+
quickr_error_return_if_set(
892+
scope,
893+
openmp_depth = scope_openmp_depth(scope)
894+
)
895+
} else {
896+
""
897+
}
866898
loop_header <- glue("do {idx@name} = 1_c_int, {last_i}")
867899
prefix <- str_flatten_lines(
868900
if (!index_iterable) iterable_tmp_assign else NULL,
@@ -872,8 +904,10 @@ compile_sapply_assignment <- function(
872904
"
873905
{prefix}
874906
call {proc_name}({call_args})
907+
{error_check_inner}
875908
end do
876909
{str_flatten_lines(directives$suffix)}
910+
{error_check_after}
877911
{str_flatten_lines(post_stmts)}
878912
"
879913
))

0 commit comments

Comments
 (0)