Skip to content
← Back

src/ir/values/await_suspend.ghul

1
namespace IR.Values is
2
use TypeTyped = Semantic.Types.Typed;
3
use Semantic.Types.Type;
4
5
// Per-`await` IL emission inside MoveNext. Stashes the awaiter
6
// to a frame field, IsCompleted-short-circuits when the awaited
7
// task is already done, otherwise records the state, calls
8
// `AwaitUnsafeOnCompleted` and `leave`s out of MoveNext. The
9
// entry-dispatch placeholder at the top of MoveNext routes
10
// re-entry back to `cold_resume_label`, where state is cleared
11
// and execution falls through to `hot_resume_label` to call
12
// `GetResult` and leave T on the stack (or stack-effect 0 for
13
// void-async).
14
//
15
// ghūl uses CLASS-based state machines (matches the generator
16
// shape) — `ldarg.0` is the SM instance and `ldarga.s 0` is the
17
// byref needed for `ref TStateMachine`. C# Release uses STRUCT-
18
// based for performance; the CLASS path costs one alloc per
19
// async call.
20
class AWAIT_SUSPEND: Value, TypeTyped is
21
type: Type;
22
23
awaited: Value;
24
awaited_type: Type;
25
awaiter_type: Type;
26
state_number: int;
27
cold_resume_label: IR.LABEL;
28
hot_resume_label: IR.LABEL;
29
end_of_movenext_label: IR.LABEL;
30
state_machine_type: Type;
31
state_field: Semantic.Symbols.Field;
32
awaiter_field: Semantic.Symbols.Field;
33
builder_field: Semantic.Symbols.Field;
34
35
// Open-class-T form — same purpose as in ASYNC_METHOD_LAUNCH.
36
unspecialized_awaiter_type: Type?;
37
38
init(
39
type: Type,
40
awaited: Value,
41
awaited_type: Type,
42
awaiter_type: Type,
43
unspecialized_awaiter_type: Type,
44
state_number: int,
45
cold_resume_label: IR.LABEL,
46
hot_resume_label: IR.LABEL,
47
end_of_movenext_label: IR.LABEL,
48
state_machine_type: Type,
49
state_field: Semantic.Symbols.Field,
50
awaiter_field: Semantic.Symbols.Field,
51
builder_field: Semantic.Symbols.Field
52
) is
53
super.init();
54
55
self.type = type;
56
self.awaited = awaited;
57
self.awaited_type = awaited_type;
58
self.awaiter_type = awaiter_type;
59
self.unspecialized_awaiter_type = unspecialized_awaiter_type;
60
self.state_number = state_number;
61
self.cold_resume_label = cold_resume_label;
62
self.hot_resume_label = hot_resume_label;
63
self.end_of_movenext_label = end_of_movenext_label;
64
self.state_machine_type = state_machine_type;
65
self.state_field = state_field;
66
self.awaiter_field = awaiter_field;
67
self.builder_field = builder_field;
68
si
69
70
gen(context: IR.CONTEXT) is
71
let awaiter_il = awaiter_type.get_il_type();
72
let awaited_il = awaited_type.get_il_type();
73
let sm_il = state_machine_type.get_il_type();
74
let builder_ref = builder_field.get_il_reference();
75
let awaiter_ref = awaiter_field.get_il_reference();
76
let state_ref = state_field.get_il_reference();
77
78
// Open-class-T form of the awaiter — needed for the
79
// GetAwaiter / get_IsCompleted / GetResult methodref
80
// signatures (CLR fails the lookup if the signature
81
// uses the substituted concrete T).
82
let open_awaiter_il =
83
if unspecialized_awaiter_type? then
84
unspecialized_awaiter_type.get_il_type();
85
else
86
awaiter_il;
87
fi;
88
89
// Cold-resume entry: the per-region dispatch (emitted at
90
// the top of each `.try` body OR at MoveNext top for the
91
// outer region) branches to {cold_resume_label} when
92
// V_state matches this await's state. Both the `beq` and
93
// this label live in the same protected region, so no
94
// branch crosses a `.try` boundary (ECMA-335 forbids
95
// that — the original centralized-dispatch-from-outside
96
// was the bug this restructure fixes).
97
98
// Hot path: capture the awaiter into the frame.
99
context.write_line("ldarg.0");
100
gen(awaited, context);
101
context.write_line("callvirt instance {open_awaiter_il} {awaited_il}::'GetAwaiter'()");
102
context.write_line("stfld {awaiter_ref}");
103
104
// Hot-resume short-circuit: task already completed
105
// synchronously — fall straight through to GetResult
106
// without registering a continuation.
107
context.write_line("ldarg.0");
108
context.write_line("ldflda {awaiter_ref}");
109
context.write_line("call instance bool {awaiter_il}::'get_IsCompleted'()");
110
context.write_line("brtrue {hot_resume_label}");
111
112
// Cold suspension: mark state, register the continuation,
113
// leave MoveNext. The builder re-enters via MoveNext; the
114
// per-region dispatch (at the top of the enclosing `.try`
115
// body or at MoveNext top) routes back to
116
// {cold_resume_label}. State writes use `dup; stloc;
117
// stfld` so the CLR local stays in sync — finally bodies
118
// read the local to decide whether to skip dispose while
119
// suspending.
120
let builder_il = builder_field.type!.get_il_type();
121
_emit_state_stamp(context, "ldc.i4 {state_number}", state_ref);
122
123
context.write_line("ldarg.0");
124
context.write_line("ldflda {builder_ref}");
125
context.write_line("ldarg.0");
126
context.write_line("ldflda {awaiter_ref}");
127
context.write_line("ldarga.s 0");
128
context.write_line(
129
"call instance void {builder_il}::'AwaitUnsafeOnCompleted'<{awaiter_il}, {sm_il}>(!!0&, !!1&)"
130
);
131
context.write_line("leave {end_of_movenext_label}");
132
133
// Cold-resume block — entered via the inline guard above
134
// when resuming from a suspend. Clear state to -1 (and
135
// the local) so finally bodies on later `leave`s see the
136
// "not suspending" sentinel and run normally.
137
context.write_line("{cold_resume_label}:");
138
_emit_state_stamp(context, "ldc.i4.m1", state_ref);
139
140
// Hot path — fall through from cold-resume, or jumped to
141
// by the IsCompleted=true short-circuit above. The
142
// methodref's return type must be `!0` (open class-T of
143
// the awaiter struct), not the substituted concrete T.
144
context.write_line("{hot_resume_label}:");
145
context.write_line("ldarg.0");
146
context.write_line("ldflda {awaiter_ref}");
147
148
if !type.is_void then
149
context.write_line("call instance !0 {awaiter_il}::'GetResult'()");
150
else
151
context.write_line("call instance void {awaiter_il}::'GetResult'()");
152
fi
153
si
154
155
// Emit `ldarg.0; <value_load_il>; dup; stloc '.async_state';
156
// stfld <state_ref>` — write a state value to BOTH the CLR
157
// local cached at MoveNext entry AND the state-machine
158
// frame's state field, so subsequent `leave`s out of `.try`
159
// blocks fire finally bodies that read the local and see the
160
// correct sentinel. Used on suspend (value = `ldc.i4 N`) and
161
// on cold-resume cleanup (value = `ldc.i4.m1`).
162
_emit_state_stamp(context: IR.CONTEXT, value_load_il: string, state_ref: string) is
163
context.write_line("ldarg.0");
164
context.write_line(value_load_il);
165
context.write_line("dup");
166
context.write_line("stloc '.async_state'");
167
context.write_line("stfld {state_ref}");
168
si
169
170
to_string() -> string =>
171
"await-suspend:[{type}](state={state_number})";
172
si
173
si