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
// value is already done, otherwise records the state, registers
8
// the continuation through the builder and `leave`s out of
9
// MoveNext. The entry-dispatch placeholder at the top of MoveNext
10
// routes re-entry back to `cold_resume_label`, where state is
11
// cleared and execution falls through to `hot_resume_label` to
12
// call `GetResult` and leave T on the stack (or stack-effect 0
13
// for a void result).
14
//
15
// The three awaiter-pattern calls arrive as ordinary call values:
16
// `get_awaiter` over the awaited operand, and `is_completed` /
17
// `get_result` over a load of the awaiter field, so the receiver
18
// conventions (address of a struct, dispatch on a class) are the
19
// ones every other call uses.
20
//
21
// ghūl uses CLASS-based state machines (matches the generator
22
// shape) — `ldarg.0` is the SM instance and `ldarga.s 0` is the
23
// byref needed for `ref TStateMachine`. C# Release uses STRUCT-
24
// based for performance; the CLASS path costs one alloc per
25
// async call.
26
class AWAIT_SUSPEND: Value, TypeTyped is
27
type: Type
28
29
get_awaiter: Value
30
is_completed: Value
31
get_result: Value
32
awaiter_type: Type
33
34
// The awaiter implements `ICriticalNotifyCompletion`, so the
35
// continuation is registered with `AwaitUnsafeOnCompleted`
36
// rather than `AwaitOnCompleted`.
37
is_critical: bool
38
39
state_number: int
40
cold_resume_label: IR.LABEL
41
hot_resume_label: IR.LABEL
42
end_of_movenext_label: IR.LABEL
43
state_machine_type: Type
44
state_field: Semantic.Symbols.Field
45
awaiter_field: Semantic.Symbols.Field
46
builder_field: Semantic.Symbols.Field
47
48
init(
49
type: Type,
50
get_awaiter: Value,
51
is_completed: Value,
52
get_result: Value,
53
awaiter_type: Type,
54
is_critical: bool,
55
state_number: int,
56
cold_resume_label: IR.LABEL,
57
hot_resume_label: IR.LABEL,
58
end_of_movenext_label: IR.LABEL,
59
state_machine_type: Type,
60
state_field: Semantic.Symbols.Field,
61
awaiter_field: Semantic.Symbols.Field,
62
builder_field: Semantic.Symbols.Field
63
) is
64
super.init()
65
66
self.type = type
67
self.get_awaiter = get_awaiter
68
self.is_completed = is_completed
69
self.get_result = get_result
70
self.awaiter_type = awaiter_type
71
self.is_critical = is_critical
72
self.state_number = state_number
73
self.cold_resume_label = cold_resume_label
74
self.hot_resume_label = hot_resume_label
75
self.end_of_movenext_label = end_of_movenext_label
76
self.state_machine_type = state_machine_type
77
self.state_field = state_field
78
self.awaiter_field = awaiter_field
79
self.builder_field = builder_field
80
si
81
82
// The CLR local MoveNext caches the state in. Written by the
83
// same name the entry sequence declared it under, so the two
84
// name one slot.
85
_STATE_LOCAL: string static => ".async_state"
86
87
gen(context: IR.CONTEXT) is
88
let body = context.current_srm_body_emitter!
89
_gen_srm(context, body)
90
si
91
92
_gen_srm(context: IR.CONTEXT, body: IR.Emitter.SRM_METHOD_BODY_EMITTER) is
93
let state = context.resolve_field_target(state_field)
94
let awaiter = context.resolve_field_target(awaiter_field)
95
let builder = context.resolve_field_target(builder_field)
96
97
// Capture the awaiter into the frame.
98
body.ldarg(0)
99
gen(get_awaiter, context)
100
body.stfld(awaiter)
101
102
// Already completed: skip registering a continuation.
103
gen(is_completed, context)
104
body.branch(System.Reflection.Metadata.ILOpCode.BRTRUE, hot_resume_label)
105
106
_stamp_state_srm(context, body, state_number, state)
107
108
body.ldarg(0)
109
110
// The builder goes by address for a struct - the shape the
111
// BCL builders use - and as the object a class builder's
112
// methods receive as `this`.
113
if builder_field.type!.is_value_type then
114
body.ldflda(builder)
115
else
116
body.ldfld(builder)
117
fi
118
119
body.ldarg(0)
120
body.ldflda(awaiter)
121
122
// The state machine goes by reference for a builder whose
123
// member takes it so, and as the interface object for one
124
// that takes it by value.
125
if context.builder_awaits_sm_by_reference(builder_field.type!, is_critical) then
126
body.ldarga(0)
127
else
128
body.ldarg(0)
129
fi
130
131
body.call(
132
context.resolve_await_on_completed(
133
builder_field.type!, awaiter_type, state_machine_type, is_critical))
134
body.branch(System.Reflection.Metadata.ILOpCode.LEAVE, end_of_movenext_label)
135
136
body.mark_label(cold_resume_label)
137
138
_stamp_state_srm(context, body, -1, state)
139
140
body.mark_label(hot_resume_label)
141
gen(get_result, context)
142
si
143
144
// Write a state value to both the cached CLR local and the
145
// frame's state field, so a `leave` out of a protected region
146
// fires finally bodies that read the local and see the right
147
// sentinel.
148
_stamp_state_srm(
149
context: IR.CONTEXT,
150
body: IR.Emitter.SRM_METHOD_BODY_EMITTER,
151
value: int,
152
state: System.Reflection.Metadata.EntityHandle
153
) is
154
body.ldarg(0)
155
body.ldc_i4(value)
156
body.op(System.Reflection.Metadata.ILOpCode.DUP)
157
body.stloc(_STATE_LOCAL)
158
body.stfld(state)
159
si
160
161
to_string() -> string =>
162
"await-suspend:[{type}](state={state_number})"
163
si
164
si