mt-lang 0.3.24 → 0.3.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. checksums.yaml +4 -4
  2. data/lib/milk_tea/base.rb +1 -1
  3. data/lib/milk_tea/core/bindings/attribute_binding.rb +51 -49
  4. data/lib/milk_tea/core/bindings/function_binding.rb +3 -1
  5. data/lib/milk_tea/core/bindings/module_binding.rb +42 -40
  6. data/lib/milk_tea/core/bindings/value_binding.rb +16 -14
  7. data/lib/milk_tea/core/bindings.rb +4 -9
  8. data/lib/milk_tea/core/c_backend/control_flow_emission.rb +11 -11
  9. data/lib/milk_tea/core/c_backend/expressions.rb +11 -11
  10. data/lib/milk_tea/core/c_backend/feature_detection.rb +15 -19
  11. data/lib/milk_tea/core/c_backend/format_helpers.rb +1 -1
  12. data/lib/milk_tea/core/c_backend/reinterpret.rb +10 -10
  13. data/lib/milk_tea/core/c_backend/runtime_helpers.rb +1 -1
  14. data/lib/milk_tea/core/c_backend/statements.rb +20 -20
  15. data/lib/milk_tea/core/c_backend/type_declaration.rb +4 -4
  16. data/lib/milk_tea/core/c_backend/type_system.rb +1 -1
  17. data/lib/milk_tea/core/c_backend.rb +3 -3
  18. data/lib/milk_tea/core/compile_time.rb +41 -70
  19. data/lib/milk_tea/core/control_flow/builder.rb +3 -3
  20. data/lib/milk_tea/core/control_flow/constant_propagation.rb +1 -1
  21. data/lib/milk_tea/core/control_flow/graph.rb +1 -1
  22. data/lib/milk_tea/core/control_flow/termination.rb +1 -1
  23. data/lib/milk_tea/core/cst.rb +16 -0
  24. data/lib/milk_tea/core/flow_scope.rb +13 -0
  25. data/lib/milk_tea/core/lexer/character_classes.rb +2 -2
  26. data/lib/milk_tea/core/lexer/format_strings.rb +5 -5
  27. data/lib/milk_tea/core/lexer/heredocs.rb +6 -6
  28. data/lib/milk_tea/core/lexer/indentation.rb +4 -4
  29. data/lib/milk_tea/core/lexer/numbers.rb +1 -1
  30. data/lib/milk_tea/core/lexer/strings.rb +4 -4
  31. data/lib/milk_tea/core/lexer/symbols.rb +3 -3
  32. data/lib/milk_tea/core/lexer/trivia.rb +4 -4
  33. data/lib/milk_tea/core/lexer.rb +17 -17
  34. data/lib/milk_tea/core/lowering/async/analysis.rb +210 -208
  35. data/lib/milk_tea/core/lowering/async/async_lowering.rb +1406 -0
  36. data/lib/milk_tea/core/lowering/async/frame_builder.rb +615 -613
  37. data/lib/milk_tea/core/lowering/async/normalization.rb +410 -408
  38. data/lib/milk_tea/core/lowering/block.rb +982 -980
  39. data/lib/milk_tea/core/lowering/calls.rb +1360 -1358
  40. data/lib/milk_tea/core/lowering/declarations.rb +175 -173
  41. data/lib/milk_tea/core/lowering/dyn.rb +171 -169
  42. data/lib/milk_tea/core/lowering/events.rb +946 -944
  43. data/lib/milk_tea/core/lowering/expressions.rb +1514 -1512
  44. data/lib/milk_tea/core/lowering/foreign_cstr.rb +180 -178
  45. data/lib/milk_tea/core/lowering/functions.rb +186 -184
  46. data/lib/milk_tea/core/lowering/loops.rb +1004 -1002
  47. data/lib/milk_tea/core/lowering/lowering_context.rb +1 -1
  48. data/lib/milk_tea/core/lowering/proc.rb +355 -353
  49. data/lib/milk_tea/core/lowering/resolve.rb +2127 -2125
  50. data/lib/milk_tea/core/lowering/scans.rb +162 -168
  51. data/lib/milk_tea/core/lowering/str_buffer.rb +104 -102
  52. data/lib/milk_tea/core/lowering/utils.rb +965 -963
  53. data/lib/milk_tea/core/lowering.rb +22 -22
  54. data/lib/milk_tea/core/module_binder.rb +1 -1
  55. data/lib/milk_tea/core/module_loader.rb +2 -2
  56. data/lib/milk_tea/core/parser/attributes.rb +1 -1
  57. data/lib/milk_tea/core/parser/blocks.rb +2 -2
  58. data/lib/milk_tea/core/parser/declarations.rb +15 -15
  59. data/lib/milk_tea/core/parser/expressions.rb +20 -20
  60. data/lib/milk_tea/core/parser/recovery.rb +1 -1
  61. data/lib/milk_tea/core/parser/statements.rb +3 -3
  62. data/lib/milk_tea/core/parser/types.rb +4 -4
  63. data/lib/milk_tea/core/parser.rb +39 -21
  64. data/lib/milk_tea/core/pretty_printer/ast_formatter.rb +6 -6
  65. data/lib/milk_tea/core/pretty_printer/ir_formatter.rb +5 -5
  66. data/lib/milk_tea/core/semantic_analyzer/analysis_context.rb +2 -2
  67. data/lib/milk_tea/core/semantic_analyzer/calls.rb +1 -1
  68. data/lib/milk_tea/core/semantic_analyzer/expressions.rb +4 -4
  69. data/lib/milk_tea/core/semantic_analyzer/flow_refinement.rb +37 -37
  70. data/lib/milk_tea/core/semantic_analyzer/foreign_functions.rb +4 -4
  71. data/lib/milk_tea/core/semantic_analyzer/function_binding.rb +4 -4
  72. data/lib/milk_tea/core/semantic_analyzer/generics.rb +4 -4
  73. data/lib/milk_tea/core/semantic_analyzer/name_resolution.rb +7 -7
  74. data/lib/milk_tea/core/semantic_analyzer/nullability.rb +59 -59
  75. data/lib/milk_tea/core/semantic_analyzer/statements.rb +3 -3
  76. data/lib/milk_tea/core/semantic_analyzer/top_level.rb +1 -1
  77. data/lib/milk_tea/core/semantic_analyzer/type_declaration.rb +12 -12
  78. data/lib/milk_tea/core/types/layout.rb +3 -1
  79. data/lib/milk_tea/core/types/visitor.rb +351 -349
  80. data/lib/milk_tea/core.rb +0 -2
  81. data/lib/milk_tea/tooling/linter/flow_rules.rb +3 -3
  82. metadata +4 -5
  83. data/lib/milk_tea/core/cst_builder.rb +0 -19
  84. data/lib/milk_tea/core/lowering/async/lowering.rb +0 -1404
  85. data/lib/milk_tea/core/token_stream.rb +0 -30
@@ -0,0 +1,1406 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MilkTea
4
+ module Lowering
5
+ module Async
6
+ def lower_contained_task_release_statements(value_expr, type)
7
+ return [] unless contains_task_type?(type)
8
+
9
+ void_type = @ctx.types.fetch("void")
10
+
11
+ case type
12
+ when Types::Task
13
+ task_frame_expr = IR::Member.new(receiver: value_expr, member: "frame", type: type.field("frame"))
14
+ release_call = IR::ExpressionStmt.new(
15
+ expression: IR::Call.new(
16
+ callee: IR::Member.new(receiver: value_expr, member: "release", type: type.field("release")),
17
+ arguments: [task_frame_expr],
18
+ type: void_type,
19
+ ),
20
+ )
21
+ [IR::IfStmt.new(condition: task_frame_expr, then_body: [release_call], else_body: nil)]
22
+
23
+ when Types::Struct, Types::StructInstance, Types::Union, Types::GenericStructDefinition, Types::VariantArmPayload
24
+ statements = []
25
+ type.fields.each do |field_name, field_type|
26
+ next unless contains_task_type?(field_type)
27
+
28
+ field_expr = IR::Member.new(receiver: value_expr, member: field_name, type: field_type)
29
+ statements.concat(lower_contained_task_release_statements(field_expr, field_type))
30
+ end
31
+ statements
32
+
33
+ when Types::VariantInstance
34
+ if type.definition.name == "Option" && type.arguments.any? { |a| contains_task_type?(a) }
35
+ lower_opt_task_value_release_statements(value_expr, type.arguments.first)
36
+ else
37
+ []
38
+ end
39
+
40
+ when Types::Variant, Types::GenericVariantDefinition
41
+ statements = []
42
+ type.arms.each do |arm_name, arm_fields|
43
+ arm_fields.each do |field_name, field_type|
44
+ next unless contains_task_type?(field_type)
45
+ data_expr = IR::Member.new(receiver: value_expr, member: "data", type: nil)
46
+ arm_expr = IR::Member.new(receiver: data_expr, member: arm_name, type: nil)
47
+ field_expr = IR::Member.new(receiver: arm_expr, member: field_name, type: field_type)
48
+ statements.concat(lower_contained_task_release_statements(field_expr, field_type))
49
+ end
50
+ end
51
+ statements
52
+
53
+ when Types::GenericInstance
54
+ if type.name == "Option" && type.arguments.any? { |a| contains_task_type?(a) }
55
+ lower_opt_task_value_release_statements(value_expr, type.arguments.first)
56
+ else
57
+ []
58
+ end
59
+
60
+ when Types::Nullable
61
+ lower_contained_task_release_statements(value_expr, type.base)
62
+
63
+ else
64
+ []
65
+ end
66
+ end
67
+
68
+ def lower_opt_task_value_release_statements(value_expr, task_type)
69
+ int_type = @ctx.types.fetch("int")
70
+ kind_expr = IR::Member.new(receiver: value_expr, member: "kind", type: int_type)
71
+ some_check = IR::Binary.new(
72
+ operator: "==",
73
+ left: kind_expr,
74
+ right: IR::IntegerLiteral.new(value: 0, type: int_type),
75
+ type: @ctx.types.fetch("bool"),
76
+ )
77
+ data_expr = IR::Member.new(receiver: value_expr, member: "data", type: nil)
78
+ some_payload_expr = IR::Member.new(receiver: data_expr, member: "some", type: nil)
79
+ task_value_expr = IR::Member.new(receiver: some_payload_expr, member: "value", type: task_type)
80
+ release_body = lower_contained_task_release_statements(task_value_expr, task_type)
81
+ none_assignment = IR::Assignment.new(
82
+ target: kind_expr,
83
+ operator: "=",
84
+ value: IR::IntegerLiteral.new(value: 1, type: int_type),
85
+ )
86
+ then_body = release_body + [none_assignment]
87
+ [IR::IfStmt.new(condition: some_check, then_body:, else_body: nil)]
88
+ end
89
+
90
+ def lower_async_local_decl_statement(statement, field_info:, env:, frame_expr:, raw_frame_expr:, resume_linkage_name:, async_info:, active_defers: [], loop_flow: nil)
91
+ lowered = []
92
+ type = field_info[:type]
93
+ storage_type = field_info[:storage_type]
94
+ target = async_frame_field_expression(frame_expr, field_info[:field_name], storage_type)
95
+ prepared_setup = []
96
+ prepared_value = statement.value
97
+
98
+ if statement.value
99
+ cleanup_start = (env[:prepared_expression_cleanups] ||= []).length
100
+ prepared_setup, prepared_value = prepare_expression_for_inline_lowering(
101
+ statement.value,
102
+ env:,
103
+ expected_type: storage_type,
104
+ allow_root_statement_foreign: true,
105
+ )
106
+ cleanup_count = (env[:prepared_expression_cleanups] || []).length - cleanup_start
107
+ if cleanup_count.positive?
108
+ async_info[:format_str_fields][field_info[:field_name]] = storage_type
109
+ env[:prepared_expression_cleanups].slice!(cleanup_start, cleanup_count)
110
+ end
111
+ lowered.concat(prepared_setup)
112
+ end
113
+
114
+ if prepared_value && (foreign_call = foreign_call_info(prepared_value, env))
115
+ setup, value, call_type, release_assignments, cleanup_statements = lower_foreign_call_components(
116
+ foreign_call,
117
+ env:,
118
+ expected_type: storage_type,
119
+ statement_position: false,
120
+ )
121
+ lowered.concat(setup)
122
+ raise LoweringError.new("foreign call used to initialize #{statement.name} must return a value", line: 0, column: 0, path: @ctx.current_analysis_path) if call_type == @ctx.types.fetch("void")
123
+ raise LoweringError.new("consuming foreign calls must return void", line: 0, column: 0, path: @ctx.current_analysis_path) unless release_assignments.empty?
124
+
125
+ lowered << IR::Assignment.new(target:, operator: "=", value:)
126
+ lowered.concat(cleanup_statements)
127
+ else
128
+ value = if prepared_value
129
+ lower_contextual_expression(
130
+ prepared_value,
131
+ env:,
132
+ expected_type: storage_type,
133
+ contextual_int_to_float: statement.type && contextual_int_to_float_target?(type),
134
+ )
135
+ else
136
+ IR::ZeroInit.new(type: storage_type)
137
+ end
138
+ lowered << IR::Assignment.new(target:, operator: "=", value:)
139
+ end
140
+
141
+ if statement.else_body
142
+ else_env = duplicate_env(env)
143
+ if statement.else_binding
144
+ current_actual_scope(else_env[:scopes])[statement.else_binding.name] = local_binding(
145
+ type: let_else_error_type(storage_type),
146
+ storage_type:,
147
+ linkage_name: async_frame_field_c_name(field_info[:field_name]),
148
+ mutable: false,
149
+ pointer: false,
150
+ projection: :result_failure_error,
151
+ )
152
+ end
153
+ else_body = lower_async_body(
154
+ statement.else_body,
155
+ env: else_env,
156
+ frame_expr:,
157
+ raw_frame_expr:,
158
+ resume_linkage_name:,
159
+ async_info:,
160
+ active_defers:,
161
+ loop_flow:,
162
+ )
163
+ lowered << IR::IfStmt.new(
164
+ condition: let_else_failure_condition(target, storage_type),
165
+ then_body: else_body,
166
+ else_body: nil,
167
+ )
168
+ end
169
+
170
+ lowered
171
+ end
172
+ # Lowers a list of statements that contain no `await` anywhere, but live
173
+ # inside an async resume function. Return statements are lowered as async
174
+ # completions. All other control flow is lowered recursively.
175
+ def statements_contain_await?(statements, async_info)
176
+ statements.any? do |s|
177
+ case s
178
+ when AST::LocalDecl
179
+ async_info[:await_fields].key?(s.value&.object_id) || async_expression_contains_await?(s.value) || (s.else_body && statements_contain_await?(s.else_body, async_info))
180
+ when AST::Assignment
181
+ async_info[:await_fields].key?(s.value&.object_id) || async_expression_contains_await?(s.target) || async_expression_contains_await?(s.value)
182
+ when AST::ExpressionStmt
183
+ async_info[:await_fields].key?(s.expression&.object_id) || async_expression_contains_await?(s.expression)
184
+ when AST::ReturnStmt
185
+ async_info[:await_fields].key?(s.value&.object_id) || async_expression_contains_await?(s.value)
186
+ when AST::IfStmt
187
+ s.branches.any? { |b| statements_contain_await?(b.body, async_info) } ||
188
+ (s.else_body && statements_contain_await?(s.else_body, async_info))
189
+ when AST::WhileStmt
190
+ statements_contain_await?(s.body, async_info)
191
+ when AST::ForStmt
192
+ statements_contain_await?(s.body, async_info)
193
+ when AST::MatchStmt
194
+ s.arms.any? { |arm| statements_contain_await?(arm.body, async_info) }
195
+ when AST::UnsafeStmt
196
+ statements_contain_await?(s.body, async_info)
197
+ when AST::DeferStmt
198
+ statements_contain_await?(s.body, async_info)
199
+ else
200
+ false
201
+ end
202
+ end
203
+ end
204
+
205
+ def lower_async_body(body, env:, frame_expr:, raw_frame_expr:, async_info:, resume_linkage_name: nil, active_defers: [], loop_flow: nil)
206
+ if statements_contain_await?(body, async_info)
207
+ lower_async_cf_statements(body, env:, frame_expr:, raw_frame_expr:, resume_linkage_name:, async_info:, active_defers:, loop_flow:)
208
+ else
209
+ lower_async_non_await_statements(body, env:, frame_expr:, raw_frame_expr:, async_info:, active_defers:, loop_flow:)
210
+ end
211
+ end
212
+
213
+ def build_async_while_with_condition_setup(condition_setup, condition, body, break_label)
214
+ if condition_setup.empty?
215
+ stmts = [IR::WhileStmt.new(condition:, body:)]
216
+ stmts << IR::LabelStmt.new(name: break_label) if contains_label_target?(body, break_label)
217
+ return stmts
218
+ end
219
+
220
+ loop_body = [
221
+ *condition_setup,
222
+ IR::IfStmt.new(
223
+ condition: IR::Unary.new(operator: "not", operand: condition, type: @ctx.types.fetch("bool")),
224
+ then_body: [loop_exit_statement(loop_exit_break(break_label), local_defers: [], outer_defers: [])],
225
+ else_body: nil,
226
+ ),
227
+ *body,
228
+ ]
229
+ stmts = [IR::WhileStmt.new(condition: IR::BooleanLiteral.new(value: true, type: @ctx.types.fetch("bool")), body: loop_body)]
230
+ stmts << IR::LabelStmt.new(name: break_label) if contains_label_target?(loop_body, break_label)
231
+ stmts
232
+ end
233
+
234
+ # Lower a list of statements that MAY contain await expressions inside nested control flow.
235
+ # CPS-via-goto: labels placed inside if/while/match bodies, reachable from top-level switch dispatch.
236
+ def lower_async_cf_statements(statements, env:, frame_expr:, raw_frame_expr:, resume_linkage_name:, async_info:, active_defers: [], loop_flow: nil)
237
+ lowered = []
238
+ local_defers = []
239
+ env[:return_context] = async_return_context(
240
+ return_type: async_info[:result_type],
241
+ active_defers:,
242
+ local_defers:,
243
+ frame_expr:,
244
+ raw_frame_expr:,
245
+ async_info:,
246
+ )
247
+
248
+ statements.each do |statement|
249
+ case statement
250
+ when AST::LocalDecl
251
+ field_info = async_info[:local_fields].fetch(async_local_decl_field_key(statement))
252
+ await_info = async_info[:await_fields][statement.value&.object_id]
253
+ if await_info
254
+ lowered.concat(lower_async_await_statement(statement, field_info:, await_info:, env:, frame_expr:, raw_frame_expr:, resume_linkage_name:, async_info:, active_defers: active_defers + local_defers, loop_flow: nested_loop_flow(loop_flow, local_defers)))
255
+ else
256
+ lowered.concat(lower_async_local_decl_statement(statement, field_info:, env:, frame_expr:, raw_frame_expr:, resume_linkage_name:, async_info:, active_defers: active_defers + local_defers, loop_flow: nested_loop_flow(loop_flow, local_defers)))
257
+ end
258
+ async_bind_local!(env, statement.name, field_info) if bind_let_else_local?(statement)
259
+ when AST::Assignment
260
+ await_info = async_info[:await_fields][statement.value&.object_id]
261
+ if await_info
262
+ lowered.concat(lower_async_await_statement(statement, await_info:, env:, frame_expr:, raw_frame_expr:, resume_linkage_name:, async_info:))
263
+ else
264
+ lowered.concat(lower_async_assignment_statement(statement, env:))
265
+ end
266
+ when AST::ExpressionStmt
267
+ await_info = async_info[:await_fields][statement.expression&.object_id]
268
+ if await_info
269
+ lowered.concat(lower_async_await_statement(statement, await_info:, env:, frame_expr:, raw_frame_expr:, resume_linkage_name:, async_info:))
270
+ else
271
+ lowered.concat(lower_async_expression_statement(statement, env:))
272
+ end
273
+ when AST::ReturnStmt
274
+ cleanup = lower_async_cleanup_entries(local_defers, active_defers, frame_expr:, raw_frame_expr:, async_info:)
275
+ await_info = async_info[:await_fields][statement.value&.object_id]
276
+ if await_info
277
+ lowered.concat(lower_async_await_statement(statement, await_info:, env:, frame_expr:, raw_frame_expr:, resume_linkage_name:, async_info:, cleanup:))
278
+ else
279
+ lowered.concat(lower_async_return_statement(statement, env:, frame_expr:, raw_frame_expr:, async_info:, cleanup:))
280
+ end
281
+ when AST::IfStmt
282
+ lowered.concat(lower_async_cf_if_stmt(statement, env:, frame_expr:, raw_frame_expr:, resume_linkage_name:, async_info:, active_defers: active_defers + local_defers, loop_flow: nested_loop_flow(loop_flow, local_defers)))
283
+ when AST::WhileStmt
284
+ lowered.concat(lower_async_cf_while_stmt(statement, env:, frame_expr:, raw_frame_expr:, resume_linkage_name:, async_info:, active_defers: active_defers + local_defers, loop_flow: nested_loop_flow(loop_flow, local_defers)))
285
+ when AST::ForStmt
286
+ lowered.concat(lower_async_cf_for_stmt(statement, env:, frame_expr:, raw_frame_expr:, resume_linkage_name:, async_info:, active_defers: active_defers + local_defers))
287
+ when AST::MatchStmt
288
+ lowered.concat(lower_async_cf_match_stmt(statement, env:, frame_expr:, raw_frame_expr:, resume_linkage_name:, async_info:, active_defers: active_defers + local_defers, loop_flow: nested_loop_flow(loop_flow, local_defers)))
289
+ when AST::UnsafeStmt
290
+ lowered.concat(lower_async_cf_statements(statement.body, env:, frame_expr:, raw_frame_expr:, resume_linkage_name:, async_info:, active_defers: active_defers + local_defers, loop_flow: nested_loop_flow(loop_flow, local_defers)))
291
+ when AST::DeferStmt
292
+ local_defers << lower_async_defer_cleanup(statement, env:, async_info:)
293
+ when AST::PassStmt
294
+ nil
295
+ when AST::BreakStmt
296
+ if loop_flow
297
+ lowered.concat(lower_async_loop_exit(loop_flow[:break_target], local_defers, loop_flow[:break_defers], frame_expr:, raw_frame_expr:, async_info:))
298
+ else
299
+ lowered << IR::BreakStmt.new
300
+ end
301
+ when AST::ContinueStmt
302
+ if loop_flow
303
+ lowered.concat(lower_async_loop_exit(loop_flow[:continue_target], local_defers, loop_flow[:continue_defers], frame_expr:, raw_frame_expr:, async_info:))
304
+ else
305
+ lowered << IR::ContinueStmt.new
306
+ end
307
+ when AST::StaticAssert
308
+ lowered.concat(lower_static_assert(statement))
309
+ else
310
+ raise LoweringError.new("unsupported async cf statement #{statement.class.name}", line: 0, column: 0, path: @ctx.current_analysis_path)
311
+ end
312
+ end
313
+
314
+ unless cfg_block_always_terminates?(statements)
315
+ lowered.concat(lower_async_cleanup_entries(local_defers, [], frame_expr:, raw_frame_expr:, async_info:))
316
+ end
317
+ lowered
318
+ end
319
+
320
+ def lower_async_cf_if_stmt(statement, env:, frame_expr:, raw_frame_expr:, resume_linkage_name:, async_info:, active_defers:, loop_flow:)
321
+ branch_entries = statement.branches.map do |branch|
322
+ condition_setup, prepared_cond = prepare_expression_for_inline_lowering(branch.condition, env:)
323
+ condition = lower_contextual_expression(prepared_cond, env:, expected_type: @ctx.types.fetch("bool"))
324
+ body = lower_async_body(branch.body, env:, frame_expr:, raw_frame_expr:, resume_linkage_name:, async_info:, active_defers:, loop_flow:)
325
+ { condition_setup:, condition:, body: }
326
+ end
327
+
328
+ else_body = if statement.else_body
329
+ lower_async_body(statement.else_body, env:, frame_expr:, raw_frame_expr:, resume_linkage_name:, async_info:, active_defers:, loop_flow:)
330
+ end
331
+
332
+ nested_else = else_body
333
+ branch_entries.reverse_each do |entry|
334
+ nested_else = [
335
+ *entry[:condition_setup],
336
+ IR::IfStmt.new(condition: entry[:condition], then_body: entry[:body], else_body: nested_else),
337
+ ]
338
+ end
339
+ nested_else || []
340
+ end
341
+
342
+ def lower_async_cf_while_stmt(statement, env:, frame_expr:, raw_frame_expr:, resume_linkage_name:, async_info:, active_defers:, loop_flow:)
343
+ continue_label = fresh_c_temp_name(env, "loop_continue")
344
+ break_label = fresh_c_temp_name(env, "loop_break")
345
+ condition_setup, prepared_cond = prepare_expression_for_inline_lowering(statement.condition, env:)
346
+ condition = lower_contextual_expression(prepared_cond, env:, expected_type: @ctx.types.fetch("bool"))
347
+ inner_loop_flow = loop_flow(break_target: loop_exit_break(break_label), continue_target: loop_exit_continue(continue_label))
348
+ body = lower_async_body(statement.body, env: duplicate_env(env), frame_expr:, raw_frame_expr:, resume_linkage_name:, async_info:, active_defers:, loop_flow: inner_loop_flow)
349
+ body << IR::LabelStmt.new(name: continue_label) if contains_label_target?(body, continue_label)
350
+
351
+ build_async_while_with_condition_setup(condition_setup, condition, body, break_label)
352
+ end
353
+
354
+ def lower_async_cf_for_stmt(statement, env:, frame_expr:, raw_frame_expr:, resume_linkage_name:, async_info:, active_defers:)
355
+ return lower_async_cf_parallel_collection_for_stmt(statement, env:, frame_expr:, raw_frame_expr:, resume_linkage_name:, async_info:, active_defers:) if statement.parallel?
356
+
357
+ if range_iterable?(statement.iterable)
358
+ lower_async_cf_range_for_stmt(statement, env:, frame_expr:, raw_frame_expr:, resume_linkage_name:, async_info:, active_defers:)
359
+ else
360
+ lower_async_cf_collection_for_stmt(statement, env:, frame_expr:, raw_frame_expr:, resume_linkage_name:, async_info:, active_defers:)
361
+ end
362
+ end
363
+
364
+ def lower_range_call(iterable, env:)
365
+ loop_type = infer_range_loop_type(iterable, env:)
366
+ start_expr_ast = range_start_of(iterable)
367
+ stop_expr_ast = range_end_of(iterable)
368
+ start_ir = lower_expression(start_expr_ast, env:, expected_type: loop_type)
369
+ stop_ir = lower_expression(stop_expr_ast, env:, expected_type: loop_type)
370
+ [start_ir, stop_ir, false]
371
+ end
372
+
373
+ def lower_async_cf_range_for_stmt(statement, env:, frame_expr:, raw_frame_expr:, resume_linkage_name:, async_info:, active_defers:)
374
+ loop_var_name = statement.name
375
+ loop_var_type = infer_range_loop_type(statement.iterable, env:)
376
+ loop_var_field = async_info[:local_fields].fetch(loop_var_name)
377
+ loop_var_expr = async_frame_field_expression(frame_expr, loop_var_field[:field_name], loop_var_type)
378
+ continue_label = fresh_c_temp_name(env, "loop_continue")
379
+ break_label = fresh_c_temp_name(env, "loop_break")
380
+
381
+ start_expr, stop_expr, inclusive = lower_range_call(statement.iterable, env:)
382
+
383
+ # Store stop value in frame too so it survives suspension
384
+ stop_field_name = "#{loop_var_field[:field_name]}_stop"
385
+ async_info[:local_fields][stop_field_name] ||= { field_name: stop_field_name, type: loop_var_type, mutable: true }
386
+ stop_field_expr = async_frame_field_expression(frame_expr, stop_field_name, loop_var_type)
387
+
388
+ inner_env = duplicate_env(env)
389
+ inner_env[:scopes].last[loop_var_name] = local_binding(
390
+ type: loop_var_type,
391
+ linkage_name: async_frame_field_c_name(loop_var_field[:field_name]),
392
+ mutable: true, pointer: false
393
+ )
394
+ inner_loop_flow = loop_flow(break_target: loop_exit_break(break_label), continue_target: loop_exit_continue(continue_label))
395
+
396
+ body = lower_async_body(statement.body, env: inner_env, frame_expr:, raw_frame_expr:, resume_linkage_name:, async_info:, active_defers:, loop_flow: inner_loop_flow)
397
+ body << IR::LabelStmt.new(name: continue_label) if contains_label_target?(body, continue_label)
398
+
399
+ cmp_op = inclusive ? "<=" : "<"
400
+ stmts = [
401
+ IR::Assignment.new(target: loop_var_expr, operator: "=", value: start_expr),
402
+ IR::Assignment.new(target: stop_field_expr, operator: "=", value: stop_expr),
403
+ IR::WhileStmt.new(
404
+ condition: IR::Binary.new(operator: cmp_op, left: loop_var_expr, right: stop_field_expr, type: @ctx.types.fetch("bool")),
405
+ body: body + [IR::Assignment.new(target: loop_var_expr, operator: "+=", value: IR::IntegerLiteral.new(value: 1, type: loop_var_type))],
406
+ ),
407
+ ]
408
+ stmts << IR::LabelStmt.new(name: break_label) if contains_label_target?(body, break_label)
409
+ stmts
410
+ end
411
+
412
+ def lower_async_cf_collection_for_stmt(statement, env:, frame_expr:, raw_frame_expr:, resume_linkage_name:, async_info:, active_defers:)
413
+ iterable_type = infer_expression_type(statement.iterable, env:)
414
+ element_type = collection_loop_type(iterable_type)
415
+ raise LoweringError.new("for loop expects start..stop, array[T, N], or span[T], got #{iterable_type}", line: 0, column: 0, path: @ctx.current_analysis_path) unless element_type
416
+
417
+ iterable_setup, prepared_iterable = prepare_expression_for_inline_lowering(statement.iterable, env:, expected_type: iterable_type)
418
+ continue_label = fresh_c_temp_name(env, "loop_continue")
419
+ break_label = fresh_c_temp_name(env, "loop_break")
420
+ iterable_field = async_info[:local_fields].fetch(async_collection_iterable_field_key(statement))
421
+ index_field = async_info[:local_fields].fetch(async_collection_index_field_key(statement))
422
+ iterable_ref = async_frame_field_expression(frame_expr, iterable_field[:field_name], iterable_type)
423
+ index_ref = async_frame_field_expression(frame_expr, index_field[:field_name], @ctx.types.fetch("ptr_uint"))
424
+
425
+ # Loop variable stored in frame so it survives suspension
426
+ loop_var_field = async_info[:local_fields].fetch(statement.name)
427
+ loop_var_expr = async_frame_field_expression(frame_expr, loop_var_field[:field_name], element_type)
428
+
429
+ item_value = if array_type?(iterable_type)
430
+ IR::Index.new(receiver: iterable_ref, index: index_ref, type: element_type)
431
+ else
432
+ data_ref = IR::Member.new(receiver: iterable_ref, member: "data", type: pointer_to(element_type))
433
+ IR::Index.new(receiver: data_ref, index: index_ref, type: element_type)
434
+ end
435
+ stop_value = if array_type?(iterable_type)
436
+ IR::IntegerLiteral.new(value: array_length(iterable_type), type: @ctx.types.fetch("ptr_uint"))
437
+ else
438
+ IR::Member.new(receiver: iterable_ref, member: "len", type: @ctx.types.fetch("ptr_uint"))
439
+ end
440
+
441
+ inner_env = duplicate_env(env)
442
+ inner_env[:scopes].last[statement.name] = local_binding(
443
+ type: element_type, linkage_name: async_frame_field_c_name(loop_var_field[:field_name]), mutable: true, pointer: false
444
+ )
445
+ inner_loop_flow = loop_flow(break_target: loop_exit_break(break_label), continue_target: loop_exit_continue(continue_label))
446
+
447
+ assign_item = IR::Assignment.new(target: loop_var_expr, operator: "=", value: item_value)
448
+ body_stmts = lower_async_body(statement.body, env: inner_env, frame_expr:, raw_frame_expr:, resume_linkage_name:, async_info:, active_defers:, loop_flow: inner_loop_flow)
449
+ body_stmts << IR::LabelStmt.new(name: continue_label) if contains_label_target?(body_stmts, continue_label)
450
+
451
+ stmts = [
452
+ *iterable_setup,
453
+ IR::Assignment.new(target: iterable_ref, operator: "=", value: lower_expression(prepared_iterable, env:, expected_type: iterable_type)),
454
+ IR::Assignment.new(target: index_ref, operator: "=", value: IR::IntegerLiteral.new(value: 0, type: @ctx.types.fetch("ptr_uint"))),
455
+ IR::WhileStmt.new(
456
+ condition: IR::Binary.new(operator: "<", left: index_ref, right: stop_value, type: @ctx.types.fetch("bool")),
457
+ body: [assign_item] + body_stmts + [
458
+ IR::Assignment.new(target: index_ref, operator: "+=", value: IR::IntegerLiteral.new(value: 1, type: @ctx.types.fetch("ptr_uint"))),
459
+ ],
460
+ ),
461
+ ]
462
+ stmts << IR::LabelStmt.new(name: break_label) if contains_label_target?(body_stmts, break_label)
463
+ stmts
464
+ end
465
+
466
+ def lower_async_cf_parallel_collection_for_stmt(statement, env:, frame_expr:, raw_frame_expr:, resume_linkage_name:, async_info:, active_defers:)
467
+ infos = statement.bindings.each_with_index.map do |binding, index|
468
+ iterable = statement.iterables[index]
469
+ iterable_type = infer_expression_type(iterable, env:)
470
+ element_type = collection_loop_type(iterable_type)
471
+ raise LoweringError.new("parallel for loops expect arrays or spans for each iterable, got #{iterable_type}", line: 0, column: 0, path: @ctx.current_analysis_path) unless element_type
472
+
473
+ {
474
+ binding:,
475
+ iterable:,
476
+ iterable_type:,
477
+ element_type:,
478
+ binding_type: collection_loop_binding_type(iterable_type, element_type) || element_type,
479
+ iterable_field: async_info[:local_fields].fetch(async_collection_iterable_field_key(statement, index)),
480
+ }
481
+ end
482
+
483
+ iterable_entries = infos.map do |info|
484
+ setup, prepared_iterable = prepare_expression_for_inline_lowering(info[:iterable], env:, expected_type: info[:iterable_type])
485
+ info.merge(setup:, prepared_iterable:)
486
+ end
487
+
488
+ continue_label = fresh_c_temp_name(env, "loop_continue")
489
+ break_label = fresh_c_temp_name(env, "loop_break")
490
+ index_field = async_info[:local_fields].fetch(async_collection_index_field_key(statement))
491
+ index_ref = async_frame_field_expression(frame_expr, index_field[:field_name], @ctx.types.fetch("ptr_uint"))
492
+ iterable_refs = iterable_entries.map do |entry|
493
+ async_frame_field_expression(frame_expr, entry[:iterable_field][:field_name], entry[:iterable_type])
494
+ end
495
+ stop_value = collection_loop_stop_value(iterable_refs.first, iterable_entries.first[:iterable_type])
496
+
497
+ inner_env = duplicate_env(env)
498
+ assign_items = iterable_entries.map.with_index do |entry, index|
499
+ item_value = collection_loop_item_value(iterable_refs[index], entry[:iterable_type], index_ref, entry[:element_type])
500
+ loop_item_value = if ref_type?(entry[:binding_type])
501
+ IR::AddressOf.new(expression: item_value, type: entry[:binding_type])
502
+ else
503
+ item_value
504
+ end
505
+ binding_field = async_info[:local_fields].fetch(entry[:binding].name)
506
+ binding_target = async_frame_field_expression(frame_expr, binding_field[:field_name], entry[:binding_type])
507
+ inner_env[:scopes].last[entry[:binding].name] = local_binding(
508
+ type: entry[:binding_type],
509
+ linkage_name: async_frame_field_c_name(binding_field[:field_name]),
510
+ mutable: true,
511
+ pointer: false,
512
+ )
513
+ IR::Assignment.new(target: binding_target, operator: "=", value: loop_item_value)
514
+ end
515
+ inner_loop_flow = loop_flow(break_target: loop_exit_break(break_label), continue_target: loop_exit_continue(continue_label))
516
+ body_stmts = lower_async_body(statement.body, env: inner_env, frame_expr:, raw_frame_expr:, resume_linkage_name:, async_info:, active_defers:, loop_flow: inner_loop_flow)
517
+ body_stmts << IR::LabelStmt.new(name: continue_label) if contains_label_target?(body_stmts, continue_label)
518
+
519
+ length_checks = iterable_entries.drop(1).each_with_index.map do |entry, offset|
520
+ IR::IfStmt.new(
521
+ condition: IR::Binary.new(
522
+ operator: "!=",
523
+ left: collection_loop_stop_value(iterable_refs[offset + 1], entry[:iterable_type]),
524
+ right: stop_value,
525
+ type: @ctx.types.fetch("bool"),
526
+ ),
527
+ then_body: [lower_fatal_statement("parallel for iterables must have matching lengths", env:)],
528
+ else_body: nil,
529
+ )
530
+ end
531
+
532
+ stmts = [
533
+ *iterable_entries.flat_map { |entry| entry[:setup] },
534
+ *iterable_entries.each_with_index.map do |entry, index|
535
+ IR::Assignment.new(
536
+ target: iterable_refs[index],
537
+ operator: "=",
538
+ value: lower_expression(entry[:prepared_iterable], env:, expected_type: entry[:iterable_type]),
539
+ )
540
+ end,
541
+ *length_checks,
542
+ IR::Assignment.new(target: index_ref, operator: "=", value: IR::IntegerLiteral.new(value: 0, type: @ctx.types.fetch("ptr_uint"))),
543
+ IR::WhileStmt.new(
544
+ condition: IR::Binary.new(operator: "<", left: index_ref, right: stop_value, type: @ctx.types.fetch("bool")),
545
+ body: assign_items + body_stmts + [
546
+ IR::Assignment.new(target: index_ref, operator: "+=", value: IR::IntegerLiteral.new(value: 1, type: @ctx.types.fetch("ptr_uint"))),
547
+ ],
548
+ ),
549
+ ]
550
+ stmts << IR::LabelStmt.new(name: break_label) if contains_label_target?(body_stmts, break_label)
551
+ stmts
552
+ end
553
+
554
+ def lower_async_cf_match_stmt(statement, env:, frame_expr:, raw_frame_expr:, resume_linkage_name:, async_info:, active_defers:, loop_flow:)
555
+ expr_setup, prepared_expr = prepare_expression_for_inline_lowering(statement.expression, env:)
556
+ match_expr = lower_contextual_expression(prepared_expr, env:, expected_type: nil)
557
+ match_type = infer_expression_type(statement.expression, env:)
558
+ arm_loop_flow = switch_loop_flow(loop_flow, [])
559
+
560
+ if match_type.is_a?(Types::Variant)
561
+ if statement.arms.any? { |arm| arm.binding_name && !wildcard_arm_pattern?(arm.pattern) } &&
562
+ !duplicable_foreign_argument_expression?(match_expr)
563
+ scrutinee_linkage_name = fresh_c_temp_name(env, "match_value")
564
+ expr_setup << IR::LocalDecl.new(name: scrutinee_linkage_name, linkage_name: scrutinee_linkage_name, type: match_type, value: match_expr)
565
+ match_expr = IR::Name.new(name: scrutinee_linkage_name, type: match_type, pointer: false)
566
+ end
567
+
568
+ kind_type = @ctx.types.fetch("int")
569
+ kind_expr = IR::Member.new(receiver: match_expr, member: "kind", type: kind_type)
570
+ cases = statement.arms.map do |arm|
571
+ arm_env, binding_decl = async_variant_match_arm_binding(arm, match_expr, match_type, env:, frame_expr:, local_fields: async_info[:local_fields])
572
+ arm_body = lower_async_body(arm.body, env: arm_env, frame_expr:, raw_frame_expr:, resume_linkage_name:, async_info:, active_defers:, loop_flow: arm_loop_flow)
573
+ body = [binding_decl, *arm_body].compact + [IR::BreakStmt.new]
574
+ if wildcard_arm_pattern?(arm.pattern)
575
+ IR::SwitchDefaultCase.new(body: body)
576
+ else
577
+ arm_name = variant_match_arm_name_from_pattern(arm.pattern)
578
+ IR::SwitchCase.new(value: IR::Name.new(name: enum_member_c_name(match_type, "kind_#{arm_name}"), type: kind_type, pointer: false), body: body)
579
+ end
580
+ end
581
+
582
+ return expr_setup + [IR::SwitchStmt.new(expression: kind_expr, cases:, exhaustive: true)]
583
+ end
584
+
585
+ cases = statement.arms.map do |arm|
586
+ arm_body = lower_async_body(arm.body, env:, frame_expr:, raw_frame_expr:, resume_linkage_name:, async_info:, active_defers:, loop_flow: arm_loop_flow)
587
+ if wildcard_arm_pattern?(arm.pattern)
588
+ IR::SwitchDefaultCase.new(body: arm_body + [IR::BreakStmt.new])
589
+ else
590
+ IR::SwitchCase.new(value: lower_expression(arm.pattern, env:, expected_type: match_type), body: arm_body + [IR::BreakStmt.new])
591
+ end
592
+ end
593
+
594
+ expr_setup + [IR::SwitchStmt.new(expression: match_expr, cases:, exhaustive: true)]
595
+ end
596
+ def lower_async_non_await_statements(statements, env:, frame_expr:, raw_frame_expr:, async_info:, active_defers: [], loop_flow: nil)
597
+ local_env = duplicate_env(env)
598
+ lowered = []
599
+ local_defers = []
600
+ local_env[:return_context] = async_return_context(
601
+ return_type: async_info[:result_type],
602
+ active_defers:,
603
+ local_defers:,
604
+ frame_expr:,
605
+ raw_frame_expr:,
606
+ async_info:,
607
+ )
608
+
609
+ statements.each do |statement|
610
+ case statement
611
+ when AST::LocalDecl
612
+ else_env = duplicate_env(local_env) if statement.else_body
613
+ type, storage_type = async_local_decl_types(statement, env: local_env)
614
+ linkage_name = c_local_name(statement.name)
615
+ if statement.value
616
+ prepared_setup, prepared_value = prepare_expression_for_inline_lowering(
617
+ statement.value, env: local_env, expected_type: storage_type, allow_root_statement_foreign: true
618
+ )
619
+ lowered.concat(prepared_setup)
620
+ value = lower_contextual_expression(
621
+ prepared_value, env: local_env, expected_type: storage_type,
622
+ contextual_int_to_float: statement.type && contextual_int_to_float_target?(type)
623
+ )
624
+ else
625
+ value = IR::ZeroInit.new(type: storage_type)
626
+ end
627
+ lowered << IR::LocalDecl.new(name: statement.name, linkage_name:, type: storage_type, value:)
628
+ current_actual_scope(local_env[:scopes])[statement.name] = local_binding(type:, storage_type:, linkage_name:, mutable: statement.kind == :var, pointer: false)
629
+ if statement.else_body
630
+ else_body = lower_async_non_await_statements(
631
+ statement.else_body,
632
+ env: else_env,
633
+ frame_expr:,
634
+ raw_frame_expr:,
635
+ async_info:,
636
+ active_defers: active_defers + local_defers,
637
+ loop_flow: nested_loop_flow(loop_flow, local_defers),
638
+ )
639
+ lowered << IR::IfStmt.new(
640
+ condition: IR::Binary.new(
641
+ operator: "==",
642
+ left: IR::Name.new(name: linkage_name, type: storage_type, pointer: false),
643
+ right: IR::NullLiteral.new(type: storage_type),
644
+ type: @ctx.types.fetch("bool"),
645
+ ),
646
+ then_body: else_body,
647
+ else_body: nil,
648
+ )
649
+ end
650
+ when AST::Assignment
651
+ lowered.concat(lower_async_assignment_statement(statement, env: local_env))
652
+ when AST::ExpressionStmt
653
+ lowered.concat(lower_async_expression_statement(statement, env: local_env))
654
+ when AST::ReturnStmt
655
+ lowered.concat(lower_async_return_statement(statement, env: local_env, frame_expr:, raw_frame_expr:, async_info:, cleanup: lower_async_cleanup_entries(local_defers, active_defers, frame_expr:, raw_frame_expr:, async_info:)))
656
+ when AST::IfStmt
657
+ branch_entries = statement.branches.map do |branch|
658
+ condition_setup, prepared_cond = prepare_expression_for_inline_lowering(
659
+ branch.condition, env: local_env, expected_type: @ctx.types.fetch("bool")
660
+ )
661
+ then_body = lower_async_non_await_statements(
662
+ branch.body, env: local_env, frame_expr:, raw_frame_expr:, async_info:, active_defers: active_defers + local_defers, loop_flow: nested_loop_flow(loop_flow, local_defers)
663
+ )
664
+ [condition_setup, lower_expression(prepared_cond, env: local_env, expected_type: @ctx.types.fetch("bool")), then_body]
665
+ end
666
+ else_body = statement.else_body ? lower_async_non_await_statements(
667
+ statement.else_body, env: local_env, frame_expr:, raw_frame_expr:, async_info:, active_defers: active_defers + local_defers, loop_flow: nested_loop_flow(loop_flow, local_defers)
668
+ ) : nil
669
+ nested = else_body || []
670
+ branch_entries.reverse_each do |cond_setup, cond, then_body|
671
+ nested = [*cond_setup, IR::IfStmt.new(condition: cond, then_body:, else_body: nested.empty? ? nil : nested)]
672
+ end
673
+ lowered.concat(nested)
674
+ when AST::MatchStmt
675
+ scrutinee_type = infer_expression_type(statement.expression, env: local_env)
676
+ expr_setup, prepared_expr = prepare_expression_for_inline_lowering(
677
+ statement.expression, env: local_env, expected_type: scrutinee_type
678
+ )
679
+ lowered.concat(expr_setup)
680
+ expr = lower_expression(prepared_expr, env: local_env, expected_type: scrutinee_type)
681
+ arm_loop_flow = switch_loop_flow(loop_flow, local_defers)
682
+ if scrutinee_type.is_a?(Types::Variant)
683
+ if statement.arms.any? { |arm| arm.binding_name && !wildcard_arm_pattern?(arm.pattern) } &&
684
+ !duplicable_foreign_argument_expression?(expr)
685
+ scrutinee_linkage_name = fresh_c_temp_name(local_env, "match_value")
686
+ lowered << IR::LocalDecl.new(name: scrutinee_linkage_name, linkage_name: scrutinee_linkage_name, type: scrutinee_type, value: expr)
687
+ expr = IR::Name.new(name: scrutinee_linkage_name, type: scrutinee_type, pointer: false)
688
+ end
689
+
690
+ kind_type = @ctx.types.fetch("int")
691
+ kind_expr = IR::Member.new(receiver: expr, member: "kind", type: kind_type)
692
+ cases = statement.arms.map do |arm|
693
+ arm_env, binding_decl = async_variant_match_arm_binding(arm, expr, scrutinee_type, env: local_env)
694
+ arm_body = lower_async_non_await_statements(
695
+ arm.body, env: arm_env, frame_expr:, raw_frame_expr:, async_info:, active_defers: active_defers + local_defers, loop_flow: arm_loop_flow
696
+ )
697
+ body = [binding_decl, *arm_body].compact
698
+ if wildcard_arm_pattern?(arm.pattern)
699
+ IR::SwitchDefaultCase.new(body: body)
700
+ else
701
+ arm_name = variant_match_arm_name_from_pattern(arm.pattern)
702
+ IR::SwitchCase.new(value: IR::Name.new(name: enum_member_c_name(scrutinee_type, "kind_#{arm_name}"), type: kind_type, pointer: false), body: body)
703
+ end
704
+ end
705
+ lowered << IR::SwitchStmt.new(expression: kind_expr, cases:, exhaustive: true)
706
+ else
707
+ cases = statement.arms.map do |arm|
708
+ arm_body = lower_async_non_await_statements(
709
+ arm.body, env: local_env, frame_expr:, raw_frame_expr:, async_info:, active_defers: active_defers + local_defers, loop_flow: arm_loop_flow
710
+ )
711
+ if wildcard_arm_pattern?(arm.pattern)
712
+ IR::SwitchDefaultCase.new(body: arm_body)
713
+ else
714
+ value = lower_expression(arm.pattern, env: local_env, expected_type: scrutinee_type)
715
+ IR::SwitchCase.new(value:, body: arm_body)
716
+ end
717
+ end
718
+ lowered << IR::SwitchStmt.new(expression: expr, cases:, exhaustive: true)
719
+ end
720
+ when AST::WhileStmt
721
+ lowered << lower_async_while_stmt(statement, env: local_env, frame_expr:, raw_frame_expr:, async_info:, active_defers: active_defers + local_defers)
722
+ when AST::ForStmt
723
+ lowered << lower_async_for_stmt(statement, env: local_env, frame_expr:, raw_frame_expr:, async_info:, active_defers: active_defers + local_defers)
724
+ when AST::DeferStmt
725
+ local_defers << lower_async_defer_cleanup(statement, env: local_env, async_info:)
726
+ when AST::PassStmt
727
+ nil
728
+ when AST::BreakStmt
729
+ if loop_flow
730
+ lowered.concat(lower_async_loop_exit(loop_flow[:break_target], local_defers, loop_flow[:break_defers], frame_expr:, raw_frame_expr:, async_info:))
731
+ else
732
+ lowered << IR::BreakStmt.new
733
+ end
734
+ when AST::ContinueStmt
735
+ if loop_flow
736
+ lowered.concat(lower_async_loop_exit(loop_flow[:continue_target], local_defers, loop_flow[:continue_defers], frame_expr:, raw_frame_expr:, async_info:))
737
+ else
738
+ lowered << IR::ContinueStmt.new
739
+ end
740
+ when AST::UnsafeStmt
741
+ lowered.concat(lower_async_non_await_statements(
742
+ statement.body, env: local_env, frame_expr:, raw_frame_expr:, async_info:, active_defers: active_defers + local_defers, loop_flow: nested_loop_flow(loop_flow, local_defers)
743
+ ))
744
+ when AST::StaticAssert
745
+ lowered << lower_static_assert(statement, env: local_env)
746
+ else
747
+ raise LoweringError.new("unsupported async non-await statement #{statement.class.name}", line: 0, column: 0, path: @ctx.current_analysis_path)
748
+ end
749
+ end
750
+
751
+ unless cfg_block_always_terminates?(statements)
752
+ lowered.concat(lower_async_cleanup_entries(local_defers, [], frame_expr:, raw_frame_expr:, async_info:))
753
+ end
754
+ lowered
755
+ end
756
+
757
+ def lower_async_while_stmt(statement, env:, frame_expr:, raw_frame_expr:, async_info:, active_defers: [])
758
+ continue_label = fresh_c_temp_name(env, "loop_continue")
759
+ break_label = fresh_c_temp_name(env, "loop_break")
760
+ condition_setup, prepared_cond = prepare_expression_for_inline_lowering(
761
+ statement.condition, env:, expected_type: @ctx.types.fetch("bool")
762
+ )
763
+ body = lower_async_non_await_statements(
764
+ statement.body,
765
+ env: duplicate_env(env),
766
+ frame_expr:,
767
+ raw_frame_expr:,
768
+ async_info:,
769
+ active_defers:,
770
+ loop_flow: loop_flow(break_target: loop_exit_break(break_label), continue_target: loop_exit_continue(continue_label)),
771
+ )
772
+ body << IR::LabelStmt.new(name: continue_label) if contains_label_target?(body, continue_label)
773
+ cond = lower_expression(prepared_cond, env:, expected_type: @ctx.types.fetch("bool"))
774
+
775
+ stmts = build_async_while_with_condition_setup(condition_setup, cond, body, break_label)
776
+ IR::BlockStmt.new(body: stmts)
777
+ end
778
+
779
+ def lower_async_for_stmt(statement, env:, frame_expr:, raw_frame_expr:, async_info:, active_defers: [])
780
+ return lower_async_parallel_collection_for_stmt(statement, env:, frame_expr:, raw_frame_expr:, async_info:, active_defers:) if statement.parallel?
781
+
782
+ return lower_async_range_for_stmt(statement, env:, frame_expr:, raw_frame_expr:, async_info:, active_defers:) if range_iterable?(statement.iterable)
783
+
784
+ lower_async_collection_for_stmt(statement, env:, frame_expr:, raw_frame_expr:, async_info:, active_defers:)
785
+ end
786
+
787
+ def lower_async_range_for_stmt(statement, env:, frame_expr:, raw_frame_expr:, async_info:, active_defers: [])
788
+ loop_type = infer_range_loop_type(statement.iterable, env:)
789
+ start_expr = range_start_of(statement.iterable)
790
+ stop_expr = range_end_of(statement.iterable)
791
+ start_setup, prepared_start = prepare_expression_for_inline_lowering(start_expr, env:, expected_type: loop_type)
792
+ stop_setup, prepared_stop = prepare_expression_for_inline_lowering(stop_expr, env:, expected_type: loop_type)
793
+ index_linkage_name = c_local_name(statement.name)
794
+ stop_linkage_name = fresh_c_temp_name(env, "for_stop")
795
+ continue_label = fresh_c_temp_name(env, "loop_continue")
796
+ break_label = fresh_c_temp_name(env, "loop_break")
797
+ index_ref = IR::Name.new(name: index_linkage_name, type: loop_type, pointer: false)
798
+ inline_stop = stop_setup.empty? && compile_time_numeric_const_expression?(prepared_stop)
799
+ stop_value = if inline_stop
800
+ lower_expression(prepared_stop, env:, expected_type: loop_type)
801
+ else
802
+ IR::Name.new(name: stop_linkage_name, type: loop_type, pointer: false)
803
+ end
804
+
805
+ while_env = duplicate_env(env)
806
+ current_actual_scope(while_env[:scopes])[statement.name] = local_binding(
807
+ type: loop_type, linkage_name: index_linkage_name, mutable: false, pointer: false
808
+ )
809
+ body = lower_async_non_await_statements(
810
+ statement.body,
811
+ env: while_env,
812
+ frame_expr:,
813
+ raw_frame_expr:,
814
+ async_info:,
815
+ active_defers:,
816
+ loop_flow: loop_flow(break_target: loop_exit_break(break_label), continue_target: loop_exit_continue(continue_label)),
817
+ )
818
+ body << IR::LabelStmt.new(name: continue_label) if contains_label_target?(body, continue_label)
819
+
820
+ for_statement = IR::ForStmt.new(
821
+ init: IR::LocalDecl.new(name: statement.name, linkage_name: index_linkage_name, type: loop_type, value: lower_expression(prepared_start, env:, expected_type: loop_type)),
822
+ condition: IR::Binary.new(operator: "<", left: index_ref, right: stop_value, type: @ctx.types.fetch("bool")),
823
+ post: IR::Assignment.new(target: index_ref, operator: "+=", value: IR::IntegerLiteral.new(value: 1, type: loop_type)),
824
+ body:,
825
+ )
826
+
827
+ stmts = [
828
+ *start_setup,
829
+ *stop_setup,
830
+ *(inline_stop ? [] : [IR::LocalDecl.new(name: stop_linkage_name, linkage_name: stop_linkage_name, type: loop_type, value: lower_expression(prepared_stop, env:, expected_type: loop_type))]),
831
+ for_statement,
832
+ ]
833
+ stmts << IR::LabelStmt.new(name: break_label) if contains_label_target?(body, break_label)
834
+ IR::BlockStmt.new(body: stmts)
835
+ end
836
+
837
+ def lower_async_collection_for_stmt(statement, env:, frame_expr:, raw_frame_expr:, async_info:, active_defers: [])
838
+ iterable_type = infer_expression_type(statement.iterable, env:)
839
+ element_type = collection_loop_type(iterable_type)
840
+ raise LoweringError.new("for loop expects start..stop, array[T, N], or span[T], got #{iterable_type}", line: 0, column: 0, path: @ctx.current_analysis_path) unless element_type
841
+
842
+ iterable_setup, prepared_iterable = prepare_expression_for_inline_lowering(statement.iterable, env:, expected_type: iterable_type)
843
+ iterable_linkage_name = fresh_c_temp_name(env, "for_items")
844
+ index_linkage_name = fresh_c_temp_name(env, "for_index")
845
+ continue_label = fresh_c_temp_name(env, "loop_continue")
846
+ break_label = fresh_c_temp_name(env, "loop_break")
847
+ iterable_ref = IR::Name.new(name: iterable_linkage_name, type: iterable_type, pointer: false)
848
+ index_ref = IR::Name.new(name: index_linkage_name, type: @ctx.types.fetch("ptr_uint"), pointer: false)
849
+
850
+ item_value = if array_type?(iterable_type)
851
+ IR::Index.new(receiver: iterable_ref, index: index_ref, type: element_type)
852
+ else
853
+ data_ref = IR::Member.new(receiver: iterable_ref, member: "data", type: pointer_to(element_type))
854
+ IR::Index.new(receiver: data_ref, index: index_ref, type: element_type)
855
+ end
856
+ stop_value = if array_type?(iterable_type)
857
+ IR::IntegerLiteral.new(value: array_length(iterable_type), type: @ctx.types.fetch("ptr_uint"))
858
+ else
859
+ IR::Member.new(receiver: iterable_ref, member: "len", type: @ctx.types.fetch("ptr_uint"))
860
+ end
861
+
862
+ while_env = duplicate_env(env)
863
+ current_actual_scope(while_env[:scopes])[statement.name] = local_binding(
864
+ type: element_type, linkage_name: c_local_name(statement.name), mutable: false, pointer: false
865
+ )
866
+ body = [IR::LocalDecl.new(name: statement.name, linkage_name: c_local_name(statement.name), type: element_type, value: item_value)]
867
+ body.concat(lower_async_non_await_statements(
868
+ statement.body,
869
+ env: while_env,
870
+ frame_expr:,
871
+ raw_frame_expr:,
872
+ async_info:,
873
+ active_defers:,
874
+ loop_flow: loop_flow(break_target: loop_exit_break(break_label), continue_target: loop_exit_continue(continue_label)),
875
+ ))
876
+ body << IR::LabelStmt.new(name: continue_label) if contains_label_target?(body, continue_label)
877
+
878
+ for_statement = IR::ForStmt.new(
879
+ init: IR::LocalDecl.new(name: index_linkage_name, linkage_name: index_linkage_name, type: @ctx.types.fetch("ptr_uint"), value: IR::IntegerLiteral.new(value: 0, type: @ctx.types.fetch("ptr_uint"))),
880
+ condition: IR::Binary.new(operator: "<", left: index_ref, right: stop_value, type: @ctx.types.fetch("bool")),
881
+ post: IR::Assignment.new(target: index_ref, operator: "+=", value: IR::IntegerLiteral.new(value: 1, type: @ctx.types.fetch("ptr_uint"))),
882
+ body:,
883
+ )
884
+
885
+ stmts = [
886
+ *iterable_setup,
887
+ IR::LocalDecl.new(name: iterable_linkage_name, linkage_name: iterable_linkage_name, type: iterable_type, value: lower_expression(prepared_iterable, env:, expected_type: iterable_type)),
888
+ for_statement,
889
+ ]
890
+ stmts << IR::LabelStmt.new(name: break_label) if contains_label_target?(body, break_label)
891
+ IR::BlockStmt.new(body: stmts)
892
+ end
893
+
894
+ def lower_async_parallel_collection_for_stmt(statement, env:, frame_expr:, raw_frame_expr:, async_info:, active_defers: [])
895
+ infos = statement.bindings.each_with_index.map do |binding, index|
896
+ iterable = statement.iterables[index]
897
+ iterable_type = infer_expression_type(iterable, env:)
898
+ element_type = collection_loop_type(iterable_type)
899
+ raise LoweringError.new("parallel for loops expect arrays or spans for each iterable, got #{iterable_type}", line: 0, column: 0, path: @ctx.current_analysis_path) unless element_type
900
+
901
+ {
902
+ binding:,
903
+ iterable:,
904
+ iterable_type:,
905
+ element_type:,
906
+ binding_type: collection_loop_binding_type(iterable_type, element_type) || element_type,
907
+ }
908
+ end
909
+
910
+ iterable_entries = infos.map do |info|
911
+ setup, prepared_iterable = prepare_expression_for_inline_lowering(info[:iterable], env:, expected_type: info[:iterable_type])
912
+ linkage_name = fresh_c_temp_name(env, "for_items")
913
+ info.merge(
914
+ setup:,
915
+ prepared_iterable:,
916
+ iterable_linkage_name: linkage_name,
917
+ iterable_ref: IR::Name.new(name: linkage_name, type: info[:iterable_type], pointer: false),
918
+ )
919
+ end
920
+
921
+ index_linkage_name = fresh_c_temp_name(env, "for_index")
922
+ continue_label = fresh_c_temp_name(env, "loop_continue")
923
+ break_label = fresh_c_temp_name(env, "loop_break")
924
+ index_ref = IR::Name.new(name: index_linkage_name, type: @ctx.types.fetch("ptr_uint"), pointer: false)
925
+ stop_value = collection_loop_stop_value(iterable_entries.first[:iterable_ref], iterable_entries.first[:iterable_type])
926
+
927
+ while_env = duplicate_env(env)
928
+ body = iterable_entries.map do |entry|
929
+ item_value = collection_loop_item_value(entry[:iterable_ref], entry[:iterable_type], index_ref, entry[:element_type])
930
+ loop_item_value = if ref_type?(entry[:binding_type])
931
+ IR::AddressOf.new(expression: item_value, type: entry[:binding_type])
932
+ else
933
+ item_value
934
+ end
935
+ binding = entry[:binding]
936
+ current_actual_scope(while_env[:scopes])[binding.name] = local_binding(type: entry[:binding_type], linkage_name: c_local_name(binding.name), mutable: false, pointer: false)
937
+ IR::LocalDecl.new(name: binding.name, linkage_name: c_local_name(binding.name), type: entry[:binding_type], value: loop_item_value)
938
+ end
939
+ body.concat(lower_async_non_await_statements(
940
+ statement.body,
941
+ env: while_env,
942
+ frame_expr:,
943
+ raw_frame_expr:,
944
+ async_info:,
945
+ active_defers:,
946
+ loop_flow: loop_flow(break_target: loop_exit_break(break_label), continue_target: loop_exit_continue(continue_label)),
947
+ ))
948
+ body << IR::LabelStmt.new(name: continue_label) if contains_label_target?(body, continue_label)
949
+
950
+ length_checks = iterable_entries.drop(1).map do |entry|
951
+ IR::IfStmt.new(
952
+ condition: IR::Binary.new(
953
+ operator: "!=",
954
+ left: collection_loop_stop_value(entry[:iterable_ref], entry[:iterable_type]),
955
+ right: stop_value,
956
+ type: @ctx.types.fetch("bool"),
957
+ ),
958
+ then_body: [lower_fatal_statement("parallel for iterables must have matching lengths", env:)],
959
+ else_body: nil,
960
+ )
961
+ end
962
+
963
+ for_statement = IR::ForStmt.new(
964
+ init: IR::LocalDecl.new(name: index_linkage_name, linkage_name: index_linkage_name, type: @ctx.types.fetch("ptr_uint"), value: IR::IntegerLiteral.new(value: 0, type: @ctx.types.fetch("ptr_uint"))),
965
+ condition: IR::Binary.new(operator: "<", left: index_ref, right: stop_value, type: @ctx.types.fetch("bool")),
966
+ post: IR::Assignment.new(target: index_ref, operator: "+=", value: IR::IntegerLiteral.new(value: 1, type: @ctx.types.fetch("ptr_uint"))),
967
+ body:,
968
+ )
969
+
970
+ stmts = [
971
+ *iterable_entries.flat_map { |entry| entry[:setup] },
972
+ *iterable_entries.map do |entry|
973
+ IR::LocalDecl.new(name: entry[:iterable_linkage_name], linkage_name: entry[:iterable_linkage_name], type: entry[:iterable_type], value: lower_expression(entry[:prepared_iterable], env:, expected_type: entry[:iterable_type]))
974
+ end,
975
+ *length_checks,
976
+ for_statement,
977
+ ]
978
+ stmts << IR::LabelStmt.new(name: break_label) if contains_label_target?(body, break_label)
979
+ IR::BlockStmt.new(body: stmts)
980
+ end
981
+
982
+ def lower_async_assignment_statement(statement, env:)
983
+ lowered = []
984
+ target = lower_assignment_target(statement.target, env:)
985
+ prepared_setup, prepared_value = prepare_expression_for_inline_lowering(
986
+ statement.value,
987
+ env:,
988
+ expected_type: target.type,
989
+ allow_root_statement_foreign: true,
990
+ )
991
+ lowered.concat(prepared_setup)
992
+
993
+ if (foreign_call = foreign_call_info(prepared_value, env))
994
+ setup, value, call_type, release_assignments, cleanup_statements = lower_foreign_call_components(
995
+ foreign_call,
996
+ env:,
997
+ expected_type: target.type,
998
+ statement_position: false,
999
+ )
1000
+ lowered.concat(setup)
1001
+ raise LoweringError.new("foreign call used in assignment must return a value", line: 0, column: 0, path: @ctx.current_analysis_path) if call_type == @ctx.types.fetch("void")
1002
+ raise LoweringError.new("consuming foreign calls must return void", line: 0, column: 0, path: @ctx.current_analysis_path) unless release_assignments.empty?
1003
+
1004
+ lowered << IR::Assignment.new(target:, operator: statement.operator, value:)
1005
+ lowered.concat(cleanup_statements)
1006
+ update_cstr_metadata_for_assignment!(statement, prepared_value, env)
1007
+ return lowered
1008
+ end
1009
+
1010
+ value = if statement.operator == "="
1011
+ lower_contextual_expression(
1012
+ prepared_value,
1013
+ env:,
1014
+ expected_type: target.type,
1015
+ external_numeric: external_numeric_assignment_target?(statement.target, env:),
1016
+ contextual_int_to_float: contextual_int_to_float_target?(target.type),
1017
+ )
1018
+ elsif ["+=", "-=", "*=", "/="].include?(statement.operator)
1019
+ lower_contextual_expression(
1020
+ prepared_value,
1021
+ env:,
1022
+ expected_type: target.type,
1023
+ contextual_int_to_float: contextual_int_to_float_target?(target.type),
1024
+ )
1025
+ else
1026
+ lower_expression(prepared_value, env:, expected_type: target.type)
1027
+ end
1028
+ update_cstr_metadata_for_assignment!(statement, prepared_value, env)
1029
+ operator = statement.operator
1030
+ if ["+=", "-=", "*=", "/="].include?(operator) &&
1031
+ (target.type.is_a?(Types::Vector) || target.type.is_a?(Types::Matrix) || target.type.is_a?(Types::Quaternion))
1032
+ binary_op = operator[0...-1]
1033
+ expanded = lower_vector_binary_operation(binary_op, target, target.type, value, value.type, target.type)
1034
+ value = expanded || IR::Binary.new(operator: binary_op, left: target, right: value, type: target.type)
1035
+ operator = "="
1036
+ end
1037
+ if operator == "=" && contains_proc_storage_type?(target.type)
1038
+ rhs_name = fresh_c_temp_name(env, "proc_assign")
1039
+ lowered << IR::LocalDecl.new(name: rhs_name, linkage_name: rhs_name, type: target.type, value:)
1040
+ rhs = IR::Name.new(name: rhs_name, type: target.type, pointer: false)
1041
+ lowered.concat(lower_proc_selective_retain_statements(rhs, statement.value, target.type))
1042
+ lowered.concat(lower_proc_nullable_release_statements(target, target.type))
1043
+ lowered << IR::Assignment.new(target:, operator: "=", value: rhs)
1044
+ else
1045
+ lowered << IR::Assignment.new(target:, operator:, value:)
1046
+ end
1047
+ lowered
1048
+ end
1049
+
1050
+ def lower_async_expression_statement(statement, env:)
1051
+ lowered = []
1052
+ expression_expected_type = if statement.expression.is_a?(AST::UnaryOp) && statement.expression.operator == "?"
1053
+ nil
1054
+ else
1055
+ infer_expression_type(statement.expression, env:)
1056
+ end
1057
+ prepared_setup, prepared_expression = prepare_expression_for_inline_lowering(
1058
+ statement.expression,
1059
+ env:,
1060
+ expected_type: expression_expected_type,
1061
+ allow_root_statement_foreign: true,
1062
+ allow_void_propagation: true,
1063
+ )
1064
+ lowered.concat(prepared_setup)
1065
+
1066
+ if prepared_expression && (foreign_call = foreign_call_info(prepared_expression, env))
1067
+ setup, = lower_foreign_call_statement(
1068
+ foreign_call,
1069
+ env:,
1070
+ expected_type: foreign_call[:binding].type.return_type,
1071
+ statement_position: true,
1072
+ discard_result: true,
1073
+ )
1074
+ lowered.concat(setup)
1075
+ elsif prepared_expression
1076
+ lowered << IR::ExpressionStmt.new(expression: lower_expression(prepared_expression, env:), line: statement.line, path: @ctx.current_analysis_path)
1077
+ end
1078
+
1079
+ lowered
1080
+ end
1081
+ def lower_async_return_statement(statement, env:, frame_expr:, raw_frame_expr:, async_info:, cleanup: [])
1082
+ lowered = []
1083
+ value = nil
1084
+ prepared_setup = []
1085
+ prepared_value = statement.value
1086
+
1087
+ if statement.value
1088
+ prepared_setup, prepared_value = prepare_expression_for_inline_lowering(
1089
+ statement.value,
1090
+ env:,
1091
+ expected_type: async_info[:result_type],
1092
+ allow_root_statement_foreign: true,
1093
+ )
1094
+ lowered.concat(prepared_setup)
1095
+ end
1096
+
1097
+ if prepared_value && (foreign_call = foreign_call_info(prepared_value, env))
1098
+ setup, value = lower_foreign_call_statement(foreign_call, env:, expected_type: async_info[:result_type], statement_position: false)
1099
+ lowered.concat(setup)
1100
+ elsif prepared_value
1101
+ value = lower_contextual_expression(
1102
+ prepared_value,
1103
+ env:,
1104
+ expected_type: async_info[:result_type],
1105
+ contextual_int_to_float: contextual_int_to_float_target?(async_info[:result_type]),
1106
+ )
1107
+ end
1108
+
1109
+ if async_info[:result_type] != @ctx.types.fetch("void") && value && cleanup.any? && !cleanup_safe_return_expression?(prepared_value)
1110
+ lowered << IR::Assignment.new(
1111
+ target: async_frame_field_expression(frame_expr, "result", async_info[:result_type]),
1112
+ operator: "=",
1113
+ value: value,
1114
+ )
1115
+ lowered.concat(cleanup)
1116
+ lowered.concat(async_complete_statements(frame_expr:, raw_frame_expr:, async_info:, value: nil, result_already_stored: true))
1117
+ else
1118
+ lowered.concat(cleanup)
1119
+ lowered.concat(async_complete_statements(frame_expr:, raw_frame_expr:, async_info:, value:))
1120
+ end
1121
+ lowered
1122
+ end
1123
+
1124
+ def lower_async_await_statement(statement, await_info:, env:, frame_expr:, raw_frame_expr:, resume_linkage_name:, async_info:, field_info: nil, cleanup: [], active_defers: [], loop_flow: nil)
1125
+ lowered = []
1126
+ await_expression = case statement
1127
+ when AST::LocalDecl then statement.value
1128
+ when AST::Assignment then statement.value
1129
+ when AST::ExpressionStmt then statement.expression
1130
+ when AST::ReturnStmt then statement.value
1131
+ end
1132
+ prepared_setup, prepared_task = prepare_expression_for_inline_lowering(
1133
+ await_expression.expression,
1134
+ env:,
1135
+ expected_type: await_info[:task_type],
1136
+ )
1137
+ lowered.concat(prepared_setup)
1138
+ raise LoweringError.new("await does not support foreign task expressions", line: 0, column: 0, path: @ctx.current_analysis_path) if foreign_call_info(prepared_task, env)
1139
+
1140
+ task_expr = async_frame_field_expression(frame_expr, await_info[:field_name], await_info[:task_type])
1141
+ task_frame_expr = async_task_frame_expression(task_expr, await_info[:task_type])
1142
+ ready_call = async_task_call(task_expr, await_info[:task_type], "ready", [task_frame_expr], @ctx.types.fetch("bool"))
1143
+ set_waiter_call = async_task_call(
1144
+ task_expr,
1145
+ await_info[:task_type],
1146
+ "set_waiter",
1147
+ [
1148
+ task_frame_expr,
1149
+ raw_frame_expr,
1150
+ IR::Name.new(name: resume_linkage_name, type: async_info[:wake_type], pointer: false),
1151
+ ],
1152
+ @ctx.types.fetch("void"),
1153
+ )
1154
+ take_result_call = async_task_call(task_expr, await_info[:task_type], "take_result", [task_frame_expr], await_info[:result_type])
1155
+ release_call = async_task_call(task_expr, await_info[:task_type], "release", [task_frame_expr], @ctx.types.fetch("void"))
1156
+
1157
+ unless await_info[:reuse_existing_storage]
1158
+ lowered << IR::Assignment.new(
1159
+ target: task_expr,
1160
+ operator: "=",
1161
+ value: lower_contextual_expression(prepared_task, env:, expected_type: await_info[:task_type]),
1162
+ )
1163
+ end
1164
+ lowered << IR::IfStmt.new(
1165
+ condition: IR::Unary.new(operator: "not", operand: ready_call, type: @ctx.types.fetch("bool")),
1166
+ then_body: [
1167
+ IR::Assignment.new(
1168
+ target: async_frame_field_expression(frame_expr, "state", @ctx.types.fetch("int")),
1169
+ operator: "=",
1170
+ value: IR::IntegerLiteral.new(value: await_info[:state], type: @ctx.types.fetch("int")),
1171
+ ),
1172
+ IR::ExpressionStmt.new(expression: set_waiter_call),
1173
+ IR::ReturnStmt.new(value: nil),
1174
+ ],
1175
+ else_body: nil,
1176
+ )
1177
+ lowered << IR::LabelStmt.new(name: async_state_label(resume_linkage_name, await_info[:state]))
1178
+
1179
+ case statement
1180
+ when AST::LocalDecl
1181
+ storage_type = field_info[:storage_type]
1182
+ target = async_frame_field_expression(frame_expr, field_info[:field_name], storage_type)
1183
+ lowered << IR::Assignment.new(target:, operator: "=", value: take_result_call)
1184
+ lowered << IR::ExpressionStmt.new(expression: release_call)
1185
+ if statement.else_body
1186
+ else_env = duplicate_env(env)
1187
+ if statement.else_binding
1188
+ current_actual_scope(else_env[:scopes])[statement.else_binding.name] = local_binding(
1189
+ type: let_else_error_type(storage_type),
1190
+ storage_type:,
1191
+ linkage_name: async_frame_field_c_name(field_info[:field_name]),
1192
+ mutable: false,
1193
+ pointer: false,
1194
+ projection: :result_failure_error,
1195
+ )
1196
+ end
1197
+ else_body = lower_async_body(
1198
+ statement.else_body,
1199
+ env: else_env,
1200
+ frame_expr:,
1201
+ raw_frame_expr:,
1202
+ resume_linkage_name:,
1203
+ async_info:,
1204
+ active_defers:,
1205
+ loop_flow:,
1206
+ )
1207
+ lowered << IR::IfStmt.new(
1208
+ condition: let_else_failure_condition(target, storage_type),
1209
+ then_body: else_body,
1210
+ else_body: nil,
1211
+ )
1212
+ end
1213
+ when AST::Assignment
1214
+ lowered << IR::Assignment.new(target: lower_assignment_target(statement.target, env:), operator: statement.operator, value: take_result_call)
1215
+ lowered << IR::ExpressionStmt.new(expression: release_call)
1216
+ when AST::ExpressionStmt
1217
+ lowered << IR::ExpressionStmt.new(expression: take_result_call)
1218
+ lowered << IR::ExpressionStmt.new(expression: release_call)
1219
+ when AST::ReturnStmt
1220
+ if await_info[:result_type] == @ctx.types.fetch("void")
1221
+ lowered << IR::ExpressionStmt.new(expression: take_result_call)
1222
+ lowered << IR::ExpressionStmt.new(expression: release_call)
1223
+ lowered.concat(cleanup)
1224
+ lowered.concat(async_complete_statements(frame_expr:, raw_frame_expr:, async_info:, value: nil, result_already_stored: true))
1225
+ else
1226
+ lowered << IR::Assignment.new(
1227
+ target: async_frame_field_expression(frame_expr, "result", async_info[:result_type]),
1228
+ operator: "=",
1229
+ value: take_result_call,
1230
+ )
1231
+ lowered << IR::ExpressionStmt.new(expression: release_call)
1232
+ lowered.concat(cleanup)
1233
+ lowered.concat(async_complete_statements(frame_expr:, raw_frame_expr:, async_info:, value: nil, result_already_stored: true))
1234
+ end
1235
+ end
1236
+
1237
+ lowered
1238
+ end
1239
+
1240
+ def lower_async_defer_cleanup(statement, env:, async_info:)
1241
+ { body: statement.body, env: snapshot_env(env) }
1242
+ end
1243
+
1244
+ def lower_async_cleanup_entries(local_defers, outer_defers, frame_expr:, raw_frame_expr:, async_info:)
1245
+ cleanup_entries = local_defers.reverse + outer_defers.reverse
1246
+ cleanup_entries.flat_map do |cleanup_entry|
1247
+ next [] if cleanup_entry[:body].empty?
1248
+
1249
+ cleanup_env = duplicate_env(cleanup_entry[:env])
1250
+ lower_async_body(
1251
+ cleanup_entry[:body],
1252
+ env: cleanup_env,
1253
+ frame_expr:,
1254
+ raw_frame_expr:,
1255
+ resume_linkage_name: async_info.fetch(:resume_linkage_name),
1256
+ async_info:,
1257
+ active_defers: [],
1258
+ loop_flow: nil,
1259
+ )
1260
+ end
1261
+ end
1262
+
1263
+ def async_return_context(return_type:, active_defers:, local_defers:, frame_expr:, raw_frame_expr:, async_info:, allow_return: true)
1264
+ {
1265
+ return_type:,
1266
+ active_defers:,
1267
+ local_defers:,
1268
+ allow_return:,
1269
+ frame_expr:,
1270
+ raw_frame_expr:,
1271
+ async_info:,
1272
+ }
1273
+ end
1274
+
1275
+ def async_complete_statements(frame_expr:, raw_frame_expr:, async_info:, value:, result_already_stored: false)
1276
+ lowered = []
1277
+
1278
+ if async_info[:result_type] != @ctx.types.fetch("void") && !result_already_stored
1279
+ lowered << IR::Assignment.new(
1280
+ target: async_frame_field_expression(frame_expr, "result", async_info[:result_type]),
1281
+ operator: "=",
1282
+ value: value,
1283
+ )
1284
+ end
1285
+
1286
+ lowered << IR::Assignment.new(
1287
+ target: async_frame_field_expression(frame_expr, "ready", @ctx.types.fetch("bool")),
1288
+ operator: "=",
1289
+ value: IR::BooleanLiteral.new(value: true, type: @ctx.types.fetch("bool")),
1290
+ )
1291
+
1292
+ waiter_frame_field = async_frame_field_expression(frame_expr, "waiter_frame", async_info[:void_ptr])
1293
+ lowered << IR::IfStmt.new(
1294
+ condition: IR::Binary.new(
1295
+ operator: "!=",
1296
+ left: waiter_frame_field,
1297
+ right: IR::NullLiteral.new(type: async_info[:void_ptr]),
1298
+ type: @ctx.types.fetch("bool"),
1299
+ ),
1300
+ then_body: [
1301
+ IR::LocalDecl.new(
1302
+ name: "waiter_frame",
1303
+ linkage_name: "__mt_waiter_frame",
1304
+ type: async_info[:void_ptr],
1305
+ value: waiter_frame_field,
1306
+ ),
1307
+ IR::Assignment.new(
1308
+ target: waiter_frame_field,
1309
+ operator: "=",
1310
+ value: IR::NullLiteral.new(type: async_info[:void_ptr]),
1311
+ ),
1312
+ IR::ExpressionStmt.new(
1313
+ expression: IR::Call.new(
1314
+ callee: async_frame_field_expression(frame_expr, "waiter", async_info[:wake_type]),
1315
+ arguments: [IR::Name.new(name: "__mt_waiter_frame", type: async_info[:void_ptr], pointer: false)],
1316
+ type: @ctx.types.fetch("void"),
1317
+ ),
1318
+ ),
1319
+ IR::ReturnStmt.new(value: nil),
1320
+ ],
1321
+ else_body: nil,
1322
+ )
1323
+ lowered << IR::ReturnStmt.new(value: nil)
1324
+ lowered
1325
+ end
1326
+
1327
+ def async_variant_match_arm_binding(arm, scrutinee_expr, scrutinee_type, env:, frame_expr: nil, local_fields: nil)
1328
+ arm_env = duplicate_env(env)
1329
+ binding_decl = nil
1330
+
1331
+ if arm.binding_name && !wildcard_arm_pattern?(arm.pattern)
1332
+ arm_name = variant_match_arm_name_from_pattern(arm.pattern)
1333
+ if arm_name && scrutinee_type.has_payload?(arm_name)
1334
+ fields = scrutinee_type.arm(arm_name)
1335
+ payload_type = Types::VariantArmPayload.new(scrutinee_type, arm_name, fields)
1336
+
1337
+ field_key = async_match_binding_field_key(arm)
1338
+ field_info = local_fields&.fetch(field_key, nil)
1339
+ if field_info && frame_expr
1340
+ target = async_frame_field_expression(frame_expr, field_info[:field_name], field_info[:storage_type])
1341
+ binding_c = async_frame_field_c_name(field_info[:field_name])
1342
+ arm_env[:scopes].last[arm.binding_name] = local_binding(type: payload_type, linkage_name: binding_c, mutable: false, pointer: false)
1343
+ data_expr = IR::Member.new(receiver: scrutinee_expr, member: "data", type: nil)
1344
+ arm_expr = IR::Member.new(receiver: data_expr, member: arm_name, type: payload_type)
1345
+ binding_decl = IR::Assignment.new(target:, operator: "=", value: arm_expr)
1346
+ else
1347
+ data_expr = IR::Member.new(receiver: scrutinee_expr, member: "data", type: nil)
1348
+ arm_expr = IR::Member.new(receiver: data_expr, member: arm_name, type: payload_type)
1349
+ binding_c = c_local_name(arm.binding_name)
1350
+ arm_env[:scopes].last[arm.binding_name] = local_binding(type: payload_type, linkage_name: binding_c, mutable: false, pointer: false)
1351
+ binding_decl = IR::LocalDecl.new(name: arm.binding_name, linkage_name: binding_c, type: payload_type, value: arm_expr)
1352
+ end
1353
+ end
1354
+ end
1355
+
1356
+ [arm_env, binding_decl]
1357
+ end
1358
+
1359
+ def bind_async_variant_match_arm_env!(arm_env, scrutinee_type, arm)
1360
+ return unless scrutinee_type.is_a?(Types::Variant)
1361
+ return unless arm.binding_name && !wildcard_arm_pattern?(arm.pattern)
1362
+
1363
+ arm_name = variant_match_arm_name_from_pattern(arm.pattern)
1364
+ return unless arm_name && scrutinee_type.has_payload?(arm_name)
1365
+
1366
+ fields = scrutinee_type.arm(arm_name)
1367
+ payload_type = Types::VariantArmPayload.new(scrutinee_type, arm_name, fields)
1368
+ arm_env[:scopes].last[arm.binding_name] = local_binding(type: payload_type, linkage_name: c_local_name(arm.binding_name), mutable: true, pointer: false)
1369
+ end
1370
+
1371
+ def lower_async_loop_exit(target, local_defers, outer_defers, frame_expr:, raw_frame_expr:, async_info:)
1372
+ cleanup = lower_async_cleanup_entries(local_defers, outer_defers, frame_expr:, raw_frame_expr:, async_info:)
1373
+ if cleanup.empty?
1374
+ [loop_exit_statement(target, local_defers:, outer_defers:)]
1375
+ else
1376
+ label = target[:label]
1377
+ raise LoweringError.new("structured loop exits with cleanup are unsupported", line: 0, column: 0, path: @ctx.current_analysis_path) unless label
1378
+
1379
+ cleanup + [IR::GotoStmt.new(label:)]
1380
+ end
1381
+ end
1382
+
1383
+ def async_local_decl_field_key(statement)
1384
+ return "__discard_#{statement.line}" if statement.name == "_"
1385
+
1386
+ statement.name
1387
+ end
1388
+
1389
+ def async_local_decl_field_name(statement)
1390
+ return "local_discard_#{statement.line}" if statement.name == "_"
1391
+
1392
+ "local_#{statement.name}"
1393
+ end
1394
+
1395
+ def async_match_binding_field_key(arm)
1396
+ @async_binding_counter ||= 0
1397
+ @async_binding_counter += 1
1398
+ "match_binding_#{@async_binding_counter}"
1399
+ end
1400
+
1401
+ def async_match_binding_field_name(arm)
1402
+ "local_match_binding_#{@async_binding_counter}"
1403
+ end
1404
+ end
1405
+ end
1406
+ end