-
Notifications
You must be signed in to change notification settings - Fork 292
Expand file tree
/
Copy pathcleancall.c
More file actions
165 lines (126 loc) · 4.11 KB
/
cleancall.c
File metadata and controls
165 lines (126 loc) · 4.11 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
#define R_NO_REMAP
#include <Rinternals.h>
#include "cleancall.h"
#if (defined(R_VERSION) && R_VERSION < R_Version(3, 4, 0))
SEXP R_MakeExternalPtrFn(DL_FUNC p, SEXP tag, SEXP prot) {
fn_ptr ptr;
ptr.fn = p;
return R_MakeExternalPtr(ptr.p, tag, prot);
}
DL_FUNC R_ExternalPtrAddrFn(SEXP s) {
fn_ptr ptr;
ptr.p = R_ExternalPtrAddr(s);
return ptr.fn;
}
#endif
// The R API does not have a setter for function pointers
SEXP cleancall_MakeExternalPtrFn(DL_FUNC p, SEXP tag, SEXP prot) {
fn_ptr tmp;
tmp.fn = p;
return R_MakeExternalPtr(tmp.p, tag, prot);
}
void cleancall_SetExternalPtrAddrFn(SEXP s, DL_FUNC p) {
fn_ptr ptr;
ptr.fn = p;
R_SetExternalPtrAddr(s, ptr.p);
}
// Initialised at load time with the `.Call` primitive
SEXP cleancall_fns_dot_call = NULL;
static SEXP callbacks = NULL;
void cleancall_init(void) {
cleancall_fns_dot_call = Rf_findVar(Rf_install(".Call"), R_BaseEnv);
callbacks = R_NilValue;
}
struct eval_args {
SEXP call;
SEXP env;
};
static SEXP eval_wrap(void* data) {
struct eval_args* args = (struct eval_args*) data;
return Rf_eval(args->call, args->env);
}
SEXP cleancall_call(SEXP args, SEXP env) {
SEXP call = PROTECT(Rf_lcons(cleancall_fns_dot_call, args));
struct eval_args data = { call, env };
SEXP out = r_with_cleanup_context(&eval_wrap, &data);
UNPROTECT(1);
return out;
}
// Preallocate a callback
static void push_callback(SEXP stack) {
SEXP top = CDR(stack);
SEXP early_handler = PROTECT(Rf_allocVector(LGLSXP, 1));
SEXP fn_extptr = PROTECT(cleancall_MakeExternalPtrFn(NULL, R_NilValue,
R_NilValue));
SEXP data_extptr = PROTECT(R_MakeExternalPtr(NULL, early_handler,
R_NilValue));
SEXP cb = Rf_cons(Rf_cons(fn_extptr, data_extptr), top);
SETCDR(stack, cb);
UNPROTECT(3);
}
struct data_wrapper {
SEXP (*fn)(void* data);
void *data;
SEXP callbacks;
int success;
};
static void call_exits(void* data) {
// Remove protecting node. Don't remove the preallocated callback on
// the top as it might contain a handler when something went wrong.
SEXP top = CDR(callbacks);
// Restore old stack
struct data_wrapper* state = data;
callbacks = (SEXP) state->callbacks;
// Handlers should not jump
while (top != R_NilValue) {
SEXP cb = CAR(top);
top = CDR(top);
void (*fn)(void*) = (void (*)(void*)) R_ExternalPtrAddrFn(CAR(cb));
void *data = (void*) R_ExternalPtrAddr(CDR(cb));
int early_handler = LOGICAL(R_ExternalPtrTag(CDR(cb)))[0];
// Check for empty pointer in preallocated callbacks
if (fn) {
if (!early_handler || !state->success) fn(data);
}
}
}
static SEXP with_cleanup_context_wrap(void *data) {
struct data_wrapper* cdata = data;
SEXP ret = cdata->fn(cdata->data);
cdata->success = 1;
return ret;
}
SEXP r_with_cleanup_context(SEXP (*fn)(void* data), void* data) {
// Preallocate new stack before changing `callbacks` to avoid
// leaving the global variable in a bad state if alloc fails
SEXP new = PROTECT(Rf_cons(R_NilValue, R_NilValue));
push_callback(new);
SEXP old = callbacks;
callbacks = new;
struct data_wrapper state = { fn, data, old, 0 };
SEXP out = R_ExecWithCleanup(with_cleanup_context_wrap, &state,
&call_exits, &state);
UNPROTECT(1);
return out;
}
static void call_save_handler(void (*fn)(void *data), void* data,
int early) {
if (Rf_isNull(callbacks)) {
fn(data);
Rf_error("Internal error: Exit handler pushed outside "
"of an exit context");
}
SEXP cb = CADR(callbacks);
// Update pointers
cleancall_SetExternalPtrAddrFn(CAR(cb), (DL_FUNC) fn);
R_SetExternalPtrAddr(CDR(cb), data);
LOGICAL(R_ExternalPtrTag(CDR(cb)))[0] = early;
// Preallocate the next callback in case the allocator jumps
push_callback(callbacks);
}
void r_call_on_exit(void (*fn)(void* data), void* data) {
call_save_handler(fn, data, /* early = */ 0);
}
void r_call_on_early_exit(void (*fn)(void* data), void* data) {
call_save_handler(fn, data, /* early = */ 1);
}