-
Notifications
You must be signed in to change notification settings - Fork 171
/
continuation.c
211 lines (188 loc) · 8.33 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
#include "moar.h"
static void clear_tag(MVMThreadContext *tc, void *sr_data) {
MVMContinuationTag **update = &tc->cur_frame->extra->continuation_tags;
while (*update) {
if (*update == sr_data) {
*update = (*update)->next;
MVM_free(sr_data);
return;
}
update = &((*update)->next);
}
MVM_exception_throw_adhoc(tc, "Internal error: failed to clear continuation tag");
}
void MVM_continuation_reset(MVMThreadContext *tc, MVMObject *tag,
MVMObject *code, MVMRegister *res_reg) {
/* Save the tag. */
MVMFrameExtra *e = MVM_frame_extra(tc, tc->cur_frame);
MVMContinuationTag *tag_record = MVM_malloc(sizeof(MVMContinuationTag));
tag_record->tag = tag;
tag_record->active_handlers = tc->active_handlers;
tag_record->next = e->continuation_tags;
e->continuation_tags = tag_record;
/* Were we passed code or a continuation? */
if (REPR(code)->ID == MVM_REPR_ID_MVMContinuation) {
/* Continuation; invoke it. */
MVM_continuation_invoke(tc, (MVMContinuation *)code, NULL, res_reg);
}
else {
/* Run the passed code. */
MVMCallsite *null_args_callsite = MVM_callsite_get_common(tc, MVM_CALLSITE_ID_NULL_ARGS);
code = MVM_frame_find_invokee(tc, code, NULL);
MVM_args_setup_thunk(tc, res_reg, MVM_RETURN_OBJ, null_args_callsite);
MVM_frame_special_return(tc, tc->cur_frame, clear_tag, NULL, tag_record, NULL);
STABLE(code)->invoke(tc, code, null_args_callsite, tc->cur_frame->args);
}
MVM_CHECK_CALLER_CHAIN(tc, tc->cur_frame);
}
void MVM_continuation_control(MVMThreadContext *tc, MVMint64 protect,
MVMObject *tag, MVMObject *code,
MVMRegister *res_reg) {
MVMObject *cont;
MVMCallsite *inv_arg_callsite;
/* Hunt the tag on the stack. Also toss any dynamic variable cache
* entries, as they may be invalid once the continuation is invoked (the
* Perl 6 $*THREAD is a good example of a problematic one). */
MVMFrame *root_frame = NULL;
MVMContinuationTag *tag_record = NULL;
MVMFrame *jump_frame;
MVMROOT(tc, tag, {
MVMROOT(tc, code, {
jump_frame = MVM_frame_force_to_heap(tc, tc->cur_frame);
});
});
while (jump_frame) {
MVMFrameExtra *e = jump_frame->extra;
if (e) {
e->dynlex_cache_name = NULL;
tag_record = e->continuation_tags;
while (tag_record) {
if (MVM_is_null(tc, tag) || tag_record->tag == tag)
break;
tag_record = tag_record->next;
}
if (tag_record)
break;
}
root_frame = jump_frame;
jump_frame = jump_frame->caller;
}
if (!tag_record)
MVM_exception_throw_adhoc(tc, "No matching continuation reset found");
if (!root_frame)
MVM_exception_throw_adhoc(tc, "No continuation root frame found");
/* Create continuation. */
MVMROOT(tc, code, {
MVMROOT(tc, jump_frame, {
MVMROOT(tc, root_frame, {
cont = MVM_repr_alloc_init(tc, tc->instance->boot_types.BOOTContinuation);
MVM_ASSIGN_REF(tc, &(cont->header), ((MVMContinuation *)cont)->body.top,
tc->cur_frame);
MVM_ASSIGN_REF(tc, &(cont->header), ((MVMContinuation *)cont)->body.root,
root_frame);
((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, root_frame);
});
});
});
/* Save and clear any active exception handler(s) added since reset. */
if (tc->active_handlers != tag_record->active_handlers) {
MVMActiveHandler *ah = tc->active_handlers;
while (ah) {
if (ah->next_handler == tag_record->active_handlers) {
/* 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 = jump_frame;
tc->current_frame_nr = jump_frame->sequence_nr;
*(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;
/* Clear special return handler, given we didn't just fall out of the
* reset. */
MVM_frame_clear_special_return(tc, tc->cur_frame);
/* If we're not protecting the follow-up call, remove the tag record. */
if (!protect)
clear_tag(tc, tag_record);
/* Invoke specified code, passing the continuation. We return to
* interpreter to run this, which then returns control to the
* original reset or invoke. */
code = MVM_frame_find_invokee(tc, code, NULL);
inv_arg_callsite = MVM_callsite_get_common(tc, MVM_CALLSITE_ID_INV_ARG);
MVM_args_setup_thunk(tc, tc->cur_frame->return_value, tc->cur_frame->return_type, inv_arg_callsite);
tc->cur_frame->args[0].o = cont;
STABLE(code)->invoke(tc, code, inv_arg_callsite, tc->cur_frame->args);
MVM_CHECK_CALLER_CHAIN(tc, tc->cur_frame);
}
void MVM_continuation_invoke(MVMThreadContext *tc, MVMContinuation *cont,
MVMObject *code, MVMRegister *res_reg) {
/* 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");
/* Switch caller of the root to current invoker. */
MVMROOT(tc, cont, {
MVMROOT(tc, code, {
MVM_frame_force_to_heap(tc, tc->cur_frame);
});
});
MVM_ASSIGN_REF(tc, &(cont->body.root->header), cont->body.root->caller, tc->cur_frame);
/* 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);
/* Switch to the target frame. */
tc->cur_frame = cont->body.top;
tc->current_frame_nr = cont->body.top->sequence_nr;
*(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. */
/* TODO: if we really need to support double-shot, 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 {
MVMCallsite *null_args_callsite = MVM_callsite_get_common(tc, MVM_CALLSITE_ID_NULL_ARGS);
code = MVM_frame_find_invokee(tc, code, NULL);
MVM_args_setup_thunk(tc, cont->body.res_reg, MVM_RETURN_OBJ, null_args_callsite);
STABLE(code)->invoke(tc, code, null_args_callsite, tc->cur_frame->args);
}
MVM_CHECK_CALLER_CHAIN(tc, tc->cur_frame);
}
void MVM_continuation_free_tags(MVMThreadContext *tc, MVMFrame *f) {
MVMContinuationTag *tag = f->extra->continuation_tags;
while (tag) {
MVMContinuationTag *next = tag->next;
MVM_free(tag);
tag = next;
}
f->extra->continuation_tags = NULL;
}