Appearance
| 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 |