/
continuation.c
220 lines (195 loc) · 9.63 KB
/
continuation.c
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
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
#include "moar.h"
void MVM_continuation_reset(MVMThreadContext *tc, MVMObject *tag,
MVMObject *code, MVMRegister *res_reg) {
/* Continuations always have their base in a stack region, so we can easily
* slice it off at the continuation control point. There are three cases we
* might have here.
* 1. A reset with code. In this case, we will make a new stack region and
* put the tag in it. Common.
* 2. A reset with a continuation that is unprotected. In this case, we do
* not need to make a new segment, we'll just steal the tag slot in the
* continuation we're invoking. Also common.
* 3. A reset with a continuation that is protected. Presumably unusual.
* We'll make a new stack region with our tag in it. */
/* Were we passed code or a continuation? */
if (REPR(code)->ID == MVM_REPR_ID_MVMContinuation) {
/* Continuation; invoke it. */
MVMContinuation *cont = (MVMContinuation *)code;
if (cont->body.protected_tag) {
MVM_callstack_new_continuation_region(tc, tag);
MVM_continuation_invoke(tc, (MVMContinuation *)code, NULL, res_reg, NULL);
}
else {
MVM_continuation_invoke(tc, (MVMContinuation *)code, NULL, res_reg, tag);
}
}
else if (MVM_code_iscode(tc, code)) {
/* Run the passed code. */
MVM_callstack_new_continuation_region(tc, tag);
MVMCallStackArgsFromC *args_record = MVM_callstack_allocate_args_from_c(tc,
MVM_callsite_get_common(tc, MVM_CALLSITE_ID_ZERO_ARITY));
MVM_frame_dispatch_from_c(tc, (MVMCode *)code, args_record, res_reg, MVM_RETURN_OBJ);
}
else {
MVM_exception_throw_adhoc(tc,
"continuationreset requires a continuation or a code handle");
}
MVM_CHECK_CALLER_CHAIN(tc, tc->cur_frame);
}
void MVM_continuation_control(MVMThreadContext *tc, MVMint64 protect,
MVMObject *tag, MVMObject *code,
MVMRegister *res_reg) {
if (!MVM_code_iscode(tc, code))
MVM_exception_throw_adhoc(tc,
"continuationcontrol requires a code handle");
/* Allocate the continuation (done here so that we don't have any more
* allocation while we're slicing the stack frames off). */
MVM_jit_code_trampoline(tc);
MVMObject *cont;
MVMROOT2(tc, tag, code, {
cont = MVM_repr_alloc_init(tc, tc->instance->boot_types.BOOTContinuation);
});
/* Find the tag and slice the required regions off the callstack. */
MVMActiveHandler *active_handler_at_reset;
MVMCallStackRecord *orig_top = tc->stack_top;
MVMCallStackRegion *taken_region = MVM_callstack_continuation_slice(tc, tag,
&active_handler_at_reset);
if (!taken_region)
MVM_exception_throw_adhoc(tc, "No matching continuation reset found");
/* Clear the caller of the first frame in the taken region. */
MVMFrame *first_frame = MVM_callstack_first_frame_from_region(tc, taken_region);
first_frame->caller = NULL;
/* Set up the continuation. */
((MVMContinuation *)cont)->body.stack_top = orig_top;
((MVMContinuation *)cont)->body.first_region = taken_region;
((MVMContinuation *)cont)->body.addr = *tc->interp_cur_op;
((MVMContinuation *)cont)->body.res_reg = res_reg;
if (tc->instance->profiling)
((MVMContinuation *)cont)->body.prof_cont =
MVM_profile_log_continuation_control(tc, first_frame);
/* Save and clear any active exception handler(s) added since reset. */
if (tc->active_handlers != active_handler_at_reset) {
MVMActiveHandler *ah = tc->active_handlers;
while (ah) {
if (ah->next_handler == active_handler_at_reset) {
/* Found the handler at the point of reset. Slice off the more
* recent ones. */
((MVMContinuation *)cont)->body.active_handlers = tc->active_handlers;
tc->active_handlers = ah->next_handler;
ah->next_handler = NULL;
break;
}
ah = ah->next_handler;
}
}
/* Move back to the frame with the reset in it. */
tc->cur_frame = MVM_callstack_current_frame(tc);
*(tc->interp_cur_op) = tc->cur_frame->return_address;
*(tc->interp_bytecode_start) = MVM_frame_effective_bytecode(tc->cur_frame);
*(tc->interp_reg_base) = tc->cur_frame->work;
*(tc->interp_cu) = tc->cur_frame->static_info->body.cu;
/* If we're protecting the tag, we need to re-instate it again, as reset
* would, and also remember it in the continuation. */
if (protect) {
MVM_callstack_new_continuation_region(tc, tag);
MVM_ASSIGN_REF(tc, &(cont->header), ((MVMContinuation *)cont)->body.protected_tag,
tag);
}
/* Invoke specified code, passing the continuation. We return to
* interpreter to run this, which then returns control to the
* original reset or invoke. */
MVMCallStackArgsFromC *args_record = MVM_callstack_allocate_args_from_c(tc,
MVM_callsite_get_common(tc, MVM_CALLSITE_ID_OBJ));
args_record->args.source[0].o = cont;
MVM_frame_dispatch_from_c(tc, (MVMCode *)code, args_record, tc->cur_frame->return_value,
tc->cur_frame->return_type);
MVM_CHECK_CALLER_CHAIN(tc, tc->cur_frame);
}
void MVM_continuation_invoke(MVMThreadContext *tc, MVMContinuation *cont,
MVMObject *code, MVMRegister *res_reg,
MVMObject *insert_tag) {
/* First of all do a repr id check */
if (REPR(cont)->ID != MVM_REPR_ID_MVMContinuation)
MVM_exception_throw_adhoc(tc, "continuationinvoke expects an MVMContinuation");
if (!(MVM_is_null(tc, code) || MVM_code_iscode(tc, code)))
MVM_exception_throw_adhoc(tc, "continuationinvoke requires a code handle");
/* Ensure we are the only invoker of the continuation. */
if (!MVM_trycas(&(cont->body.invoked), 0, 1))
MVM_exception_throw_adhoc(tc, "This continuation has already been invoked");
/* Walk the frames we are going to put atop of the stack, and see if any
* are heap frames. Also clear their dynamic tag. */
MVMCallStackIterator iter;
MVMFrame *bottom_frame = NULL;
MVMuint32 have_heap_frame = 0;
MVM_callstack_iter_frame_init(tc, &iter, cont->body.stack_top);
while (MVM_callstack_iter_move_next(tc, &iter)) {
MVMFrame *cur_frame = MVM_callstack_iter_current_frame(tc, &iter);
if (cur_frame->extra)
cur_frame->extra->dynlex_cache_name = NULL;
if (!MVM_FRAME_IS_ON_CALLSTACK(tc, cur_frame))
have_heap_frame = 1;
bottom_frame = cur_frame;
}
if (!bottom_frame)
MVM_exception_throw_adhoc(tc, "Corrupt continuation: failed to find bottom frame");
/* Force current frames to heap if there are heap frames in the continuation,
* to maintain the no heap -> stack invariant. */
if (have_heap_frame) {
MVMROOT3(tc, cont, code, bottom_frame, {
MVM_frame_force_to_heap(tc, tc->cur_frame);
});
}
/* Switch caller of the root to current invoker. */
if (MVM_FRAME_IS_ON_CALLSTACK(tc, tc->cur_frame)) {
bottom_frame->caller = tc->cur_frame;
}
else {
MVM_ASSIGN_REF(tc, &(bottom_frame->header), bottom_frame->caller, tc->cur_frame);
}
/* Splice the call stack region(s) from the continuation onto the top
* of our callstack, optionally replacing the continuation tag record.
* Then detach it from the wrapper object. */
MVM_callstack_continuation_append(tc, cont->body.first_region,
cont->body.stack_top,
cont->body.protected_tag ? cont->body.protected_tag : insert_tag);
cont->body.first_region = NULL;
cont->body.stack_top = NULL;
/* Set up current frame to receive result. */
tc->cur_frame->return_value = res_reg;
tc->cur_frame->return_type = MVM_RETURN_OBJ;
tc->cur_frame->return_address = *(tc->interp_cur_op);
MVM_jit_code_trampoline(tc);
/* Sync up current frame to the target frame and the interpreter. */
tc->cur_frame = MVM_callstack_current_frame(tc);
*(tc->interp_cur_op) = cont->body.addr;
*(tc->interp_bytecode_start) = MVM_frame_effective_bytecode(tc->cur_frame);
*(tc->interp_reg_base) = tc->cur_frame->work;
*(tc->interp_cu) = tc->cur_frame->static_info->body.cu;
/* Put saved active handlers list in place. */
/* If we ever need to support multi-shot, continuations this needs a re-visit.
* As it is, Rakudo's gather/take only needs single-invoke continuations,
* so we'll punt on the issue for now. */
if (cont->body.active_handlers) {
MVMActiveHandler *ah = cont->body.active_handlers;
while (ah->next_handler)
ah = ah->next_handler;
ah->next_handler = tc->active_handlers;
tc->active_handlers = cont->body.active_handlers;
cont->body.active_handlers = NULL;
}
/* If we're profiling, get it back in sync. */
if (cont->body.prof_cont && tc->instance->profiling)
MVM_profile_log_continuation_invoke(tc, cont->body.prof_cont);
/* Provided we have it, invoke the specified code, putting its result in
* the specified result register. Otherwise, put a NULL there. */
if (MVM_is_null(tc, code)) {
cont->body.res_reg->o = tc->instance->VMNull;
}
else {
MVMCallStackArgsFromC *args_record = MVM_callstack_allocate_args_from_c(tc,
MVM_callsite_get_common(tc, MVM_CALLSITE_ID_ZERO_ARITY));
MVM_frame_dispatch_from_c(tc, (MVMCode *)code, args_record, cont->body.res_reg,
MVM_RETURN_OBJ);
}
MVM_CHECK_CALLER_CHAIN(tc, tc->cur_frame);
}