mt-lang 0.2.0

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 (258) hide show
  1. checksums.yaml +7 -0
  2. data/.ruby-version +1 -0
  3. data/AUTHORS +3 -0
  4. data/Gemfile +19 -0
  5. data/Gemfile.lock +82 -0
  6. data/LICENSE +21 -0
  7. data/README.md +1208 -0
  8. data/Rakefile +332 -0
  9. data/bin/mtc +8 -0
  10. data/bin/profile-mtc-checks +133 -0
  11. data/bin/tracy-profiler +0 -0
  12. data/docs/build-guide.md +536 -0
  13. data/docs/index.html +2778 -0
  14. data/docs/language-design.md +1519 -0
  15. data/docs/language-manual.md +1534 -0
  16. data/lib/milk_tea/base.rb +49 -0
  17. data/lib/milk_tea/bindings/bindgen/ast_parser.rb +398 -0
  18. data/lib/milk_tea/bindings/bindgen/declaration.rb +319 -0
  19. data/lib/milk_tea/bindings/bindgen/emitter.rb +387 -0
  20. data/lib/milk_tea/bindings/bindgen/overrides.rb +134 -0
  21. data/lib/milk_tea/bindings/bindgen/type_mapper.rb +622 -0
  22. data/lib/milk_tea/bindings/bindgen.rb +207 -0
  23. data/lib/milk_tea/bindings/cli.rb +121 -0
  24. data/lib/milk_tea/bindings/imported_bindings/defaults.rb +210 -0
  25. data/lib/milk_tea/bindings/imported_bindings/generator.rb +1114 -0
  26. data/lib/milk_tea/bindings/imported_bindings/method_source.rb +190 -0
  27. data/lib/milk_tea/bindings/imported_bindings/naming.rb +286 -0
  28. data/lib/milk_tea/bindings/imported_bindings.rb +215 -0
  29. data/lib/milk_tea/bindings/opengl_registry.rb +547 -0
  30. data/lib/milk_tea/bindings/raw_bindings/defaults.rb +1338 -0
  31. data/lib/milk_tea/bindings/raw_bindings.rb +269 -0
  32. data/lib/milk_tea/bindings/steamworks.rb +629 -0
  33. data/lib/milk_tea/bindings/upstream_sources.rb +352 -0
  34. data/lib/milk_tea/bindings/vendored_box2d.rb +79 -0
  35. data/lib/milk_tea/bindings/vendored_c_library.rb +322 -0
  36. data/lib/milk_tea/bindings/vendored_cjson.rb +52 -0
  37. data/lib/milk_tea/bindings/vendored_flecs.rb +78 -0
  38. data/lib/milk_tea/bindings/vendored_glfw.rb +77 -0
  39. data/lib/milk_tea/bindings/vendored_libuv.rb +75 -0
  40. data/lib/milk_tea/bindings/vendored_pcre2.rb +84 -0
  41. data/lib/milk_tea/bindings/vendored_raylib.rb +131 -0
  42. data/lib/milk_tea/bindings/vendored_sdl3.rb +78 -0
  43. data/lib/milk_tea/bindings/vendored_steamworks.rb +71 -0
  44. data/lib/milk_tea/bindings/vendored_tool.rb +71 -0
  45. data/lib/milk_tea/bindings/vendored_tools.rb +23 -0
  46. data/lib/milk_tea/bindings/vendored_tracy.rb +37 -0
  47. data/lib/milk_tea/bindings.rb +23 -0
  48. data/lib/milk_tea/core/ast.rb +311 -0
  49. data/lib/milk_tea/core/async_runtime_installer.rb +28 -0
  50. data/lib/milk_tea/core/binding_types.rb +18 -0
  51. data/lib/milk_tea/core/bindings/attribute_binding.rb +58 -0
  52. data/lib/milk_tea/core/bindings/function_binding.rb +5 -0
  53. data/lib/milk_tea/core/bindings/module_binding.rb +58 -0
  54. data/lib/milk_tea/core/bindings/value_binding.rb +21 -0
  55. data/lib/milk_tea/core/c_backend/aggregate_utils.rb +103 -0
  56. data/lib/milk_tea/core/c_backend/control_flow_emission.rb +412 -0
  57. data/lib/milk_tea/core/c_backend/expressions.rb +468 -0
  58. data/lib/milk_tea/core/c_backend/feature_detection.rb +483 -0
  59. data/lib/milk_tea/core/c_backend/reachability.rb +398 -0
  60. data/lib/milk_tea/core/c_backend/reinterpret.rb +226 -0
  61. data/lib/milk_tea/core/c_backend/runtime_helpers.rb +1080 -0
  62. data/lib/milk_tea/core/c_backend/statements.rb +563 -0
  63. data/lib/milk_tea/core/c_backend/type_collectors.rb +1545 -0
  64. data/lib/milk_tea/core/c_backend/type_declaration.rb +223 -0
  65. data/lib/milk_tea/core/c_backend/type_system.rb +345 -0
  66. data/lib/milk_tea/core/c_backend.rb +287 -0
  67. data/lib/milk_tea/core/compatibility_helpers.rb +79 -0
  68. data/lib/milk_tea/core/compile_time/const_eval.rb +187 -0
  69. data/lib/milk_tea/core/compile_time.rb +329 -0
  70. data/lib/milk_tea/core/control_flow/builder.rb +582 -0
  71. data/lib/milk_tea/core/control_flow/constant_propagation.rb +143 -0
  72. data/lib/milk_tea/core/control_flow/dataflow.rb +74 -0
  73. data/lib/milk_tea/core/control_flow/definite_assignment.rb +79 -0
  74. data/lib/milk_tea/core/control_flow/graph.rb +90 -0
  75. data/lib/milk_tea/core/control_flow/liveness.rb +24 -0
  76. data/lib/milk_tea/core/control_flow/nullability_flow.rb +43 -0
  77. data/lib/milk_tea/core/control_flow/reachability.rb +22 -0
  78. data/lib/milk_tea/core/control_flow/termination.rb +31 -0
  79. data/lib/milk_tea/core/control_flow.rb +34 -0
  80. data/lib/milk_tea/core/cst.rb +48 -0
  81. data/lib/milk_tea/core/cst_builder.rb +19 -0
  82. data/lib/milk_tea/core/ir.rb +85 -0
  83. data/lib/milk_tea/core/keywords.rb +97 -0
  84. data/lib/milk_tea/core/lexer/character_classes.rb +60 -0
  85. data/lib/milk_tea/core/lexer/format_strings.rb +225 -0
  86. data/lib/milk_tea/core/lexer/heredocs.rb +199 -0
  87. data/lib/milk_tea/core/lexer/indentation.rb +62 -0
  88. data/lib/milk_tea/core/lexer/numbers.rb +96 -0
  89. data/lib/milk_tea/core/lexer/recovery.rb +32 -0
  90. data/lib/milk_tea/core/lexer/strings.rb +167 -0
  91. data/lib/milk_tea/core/lexer/symbols.rb +71 -0
  92. data/lib/milk_tea/core/lexer/trivia.rb +53 -0
  93. data/lib/milk_tea/core/lexer.rb +430 -0
  94. data/lib/milk_tea/core/lowering/artifacts.rb +26 -0
  95. data/lib/milk_tea/core/lowering/async/analysis.rb +245 -0
  96. data/lib/milk_tea/core/lowering/async/lowering.rb +1399 -0
  97. data/lib/milk_tea/core/lowering/async/normalization.rb +459 -0
  98. data/lib/milk_tea/core/lowering/async.rb +714 -0
  99. data/lib/milk_tea/core/lowering/block.rb +1052 -0
  100. data/lib/milk_tea/core/lowering/calls.rb +1565 -0
  101. data/lib/milk_tea/core/lowering/declarations.rb +214 -0
  102. data/lib/milk_tea/core/lowering/dyn.rb +206 -0
  103. data/lib/milk_tea/core/lowering/events.rb +1054 -0
  104. data/lib/milk_tea/core/lowering/expressions.rb +1645 -0
  105. data/lib/milk_tea/core/lowering/foreign_cstr.rb +206 -0
  106. data/lib/milk_tea/core/lowering/functions.rb +242 -0
  107. data/lib/milk_tea/core/lowering/loops.rb +1087 -0
  108. data/lib/milk_tea/core/lowering/lowering_context.rb +80 -0
  109. data/lib/milk_tea/core/lowering/proc.rb +419 -0
  110. data/lib/milk_tea/core/lowering/resolve.rb +2516 -0
  111. data/lib/milk_tea/core/lowering/scans.rb +220 -0
  112. data/lib/milk_tea/core/lowering/str_buffer.rb +125 -0
  113. data/lib/milk_tea/core/lowering/utils.rb +1453 -0
  114. data/lib/milk_tea/core/lowering.rb +378 -0
  115. data/lib/milk_tea/core/module_binder.rb +181 -0
  116. data/lib/milk_tea/core/module_loader/errors.rb +21 -0
  117. data/lib/milk_tea/core/module_loader.rb +479 -0
  118. data/lib/milk_tea/core/module_path_resolver.rb +153 -0
  119. data/lib/milk_tea/core/module_roots.rb +88 -0
  120. data/lib/milk_tea/core/parser/attributes.rb +71 -0
  121. data/lib/milk_tea/core/parser/blocks.rb +119 -0
  122. data/lib/milk_tea/core/parser/declarations.rb +749 -0
  123. data/lib/milk_tea/core/parser/expressions.rb +624 -0
  124. data/lib/milk_tea/core/parser/recovery.rb +131 -0
  125. data/lib/milk_tea/core/parser/statements.rb +756 -0
  126. data/lib/milk_tea/core/parser/types.rb +271 -0
  127. data/lib/milk_tea/core/parser.rb +400 -0
  128. data/lib/milk_tea/core/prelude_installer.rb +29 -0
  129. data/lib/milk_tea/core/pretty_printer/ast_formatter.rb +917 -0
  130. data/lib/milk_tea/core/pretty_printer/base_formatter.rb +87 -0
  131. data/lib/milk_tea/core/pretty_printer/ir_formatter.rb +300 -0
  132. data/lib/milk_tea/core/pretty_printer.rb +17 -0
  133. data/lib/milk_tea/core/semantic_analyzer/analysis_context.rb +314 -0
  134. data/lib/milk_tea/core/semantic_analyzer/attributes.rb +184 -0
  135. data/lib/milk_tea/core/semantic_analyzer/calls.rb +992 -0
  136. data/lib/milk_tea/core/semantic_analyzer/expressions.rb +1735 -0
  137. data/lib/milk_tea/core/semantic_analyzer/flow_refinement.rb +355 -0
  138. data/lib/milk_tea/core/semantic_analyzer/foreign_functions.rb +155 -0
  139. data/lib/milk_tea/core/semantic_analyzer/function_binding.rb +354 -0
  140. data/lib/milk_tea/core/semantic_analyzer/generics.rb +383 -0
  141. data/lib/milk_tea/core/semantic_analyzer/interface_conformance.rb +78 -0
  142. data/lib/milk_tea/core/semantic_analyzer/module_context.rb +35 -0
  143. data/lib/milk_tea/core/semantic_analyzer/name_resolution.rb +1438 -0
  144. data/lib/milk_tea/core/semantic_analyzer/nullability.rb +421 -0
  145. data/lib/milk_tea/core/semantic_analyzer/statements.rb +1308 -0
  146. data/lib/milk_tea/core/semantic_analyzer/top_level.rb +588 -0
  147. data/lib/milk_tea/core/semantic_analyzer/type_compatibility.rb +307 -0
  148. data/lib/milk_tea/core/semantic_analyzer/type_declaration.rb +851 -0
  149. data/lib/milk_tea/core/semantic_analyzer.rb +327 -0
  150. data/lib/milk_tea/core/token.rb +26 -0
  151. data/lib/milk_tea/core/token_stream.rb +30 -0
  152. data/lib/milk_tea/core/types/layout.rb +243 -0
  153. data/lib/milk_tea/core/types/predicates.rb +609 -0
  154. data/lib/milk_tea/core/types/registry.rb +83 -0
  155. data/lib/milk_tea/core/types/types.rb +1696 -0
  156. data/lib/milk_tea/core/types/visitor.rb +442 -0
  157. data/lib/milk_tea/core.rb +25 -0
  158. data/lib/milk_tea/dap/backends/lldb_dap.rb +158 -0
  159. data/lib/milk_tea/dap/protocol.rb +58 -0
  160. data/lib/milk_tea/dap/server/breakpoints.rb +66 -0
  161. data/lib/milk_tea/dap/server/debug_map.rb +291 -0
  162. data/lib/milk_tea/dap/server/handlers.rb +374 -0
  163. data/lib/milk_tea/dap/server/launch.rb +261 -0
  164. data/lib/milk_tea/dap/server/lldb_backend.rb +380 -0
  165. data/lib/milk_tea/dap/server/pause_diagnostics.rb +109 -0
  166. data/lib/milk_tea/dap/server/utilities.rb +160 -0
  167. data/lib/milk_tea/dap/server/wire.rb +55 -0
  168. data/lib/milk_tea/dap/server.rb +131 -0
  169. data/lib/milk_tea/dap/session.rb +152 -0
  170. data/lib/milk_tea/dap.rb +6 -0
  171. data/lib/milk_tea/lsp/dependency_resolution.rb +52 -0
  172. data/lib/milk_tea/lsp/diagnostics.rb +611 -0
  173. data/lib/milk_tea/lsp/protocol.rb +104 -0
  174. data/lib/milk_tea/lsp/server/call_hierarchy.rb +274 -0
  175. data/lib/milk_tea/lsp/server/code_actions.rb +446 -0
  176. data/lib/milk_tea/lsp/server/code_lens.rb +97 -0
  177. data/lib/milk_tea/lsp/server/completion.rb +1099 -0
  178. data/lib/milk_tea/lsp/server/configuration.rb +167 -0
  179. data/lib/milk_tea/lsp/server/debug_info.rb +45 -0
  180. data/lib/milk_tea/lsp/server/definition.rb +779 -0
  181. data/lib/milk_tea/lsp/server/diagnostics_scheduling.rb +239 -0
  182. data/lib/milk_tea/lsp/server/execute_command.rb +25 -0
  183. data/lib/milk_tea/lsp/server/folding_range.rb +153 -0
  184. data/lib/milk_tea/lsp/server/formatting.rb +575 -0
  185. data/lib/milk_tea/lsp/server/hover.rb +1465 -0
  186. data/lib/milk_tea/lsp/server/inlay_hints.rb +204 -0
  187. data/lib/milk_tea/lsp/server/lifecycle.rb +234 -0
  188. data/lib/milk_tea/lsp/server/linked_editing_range.rb +43 -0
  189. data/lib/milk_tea/lsp/server/on_type_formatting.rb +73 -0
  190. data/lib/milk_tea/lsp/server/progress.rb +47 -0
  191. data/lib/milk_tea/lsp/server/references.rb +433 -0
  192. data/lib/milk_tea/lsp/server/rename.rb +598 -0
  193. data/lib/milk_tea/lsp/server/selection_range.rb +130 -0
  194. data/lib/milk_tea/lsp/server/semantic_tokens.rb +1745 -0
  195. data/lib/milk_tea/lsp/server/signature_help.rb +200 -0
  196. data/lib/milk_tea/lsp/server/text_documents.rb +125 -0
  197. data/lib/milk_tea/lsp/server/type_hierarchy.rb +167 -0
  198. data/lib/milk_tea/lsp/server/utilities.rb +520 -0
  199. data/lib/milk_tea/lsp/server.rb +415 -0
  200. data/lib/milk_tea/lsp/workspace/analysis.rb +209 -0
  201. data/lib/milk_tea/lsp/workspace/caches.rb +260 -0
  202. data/lib/milk_tea/lsp/workspace/collection.rb +98 -0
  203. data/lib/milk_tea/lsp/workspace/definition_index.rb +184 -0
  204. data/lib/milk_tea/lsp/workspace/dependency_graph.rb +282 -0
  205. data/lib/milk_tea/lsp/workspace/store.rb +154 -0
  206. data/lib/milk_tea/lsp/workspace/utilities.rb +490 -0
  207. data/lib/milk_tea/lsp/workspace.rb +127 -0
  208. data/lib/milk_tea/lsp.rb +13 -0
  209. data/lib/milk_tea/packages/atomic_write.rb +63 -0
  210. data/lib/milk_tea/packages/dependency_solver.rb +194 -0
  211. data/lib/milk_tea/packages/graph.rb +139 -0
  212. data/lib/milk_tea/packages/lock.rb +421 -0
  213. data/lib/milk_tea/packages/manager_cli.rb +485 -0
  214. data/lib/milk_tea/packages/manifest.rb +356 -0
  215. data/lib/milk_tea/packages/manifest_editor.rb +134 -0
  216. data/lib/milk_tea/packages/registry_metadata_provider.rb +34 -0
  217. data/lib/milk_tea/packages/registry_store.rb +420 -0
  218. data/lib/milk_tea/packages/services.rb +41 -0
  219. data/lib/milk_tea/packages/source_cache.rb +71 -0
  220. data/lib/milk_tea/packages/source_fetcher.rb +124 -0
  221. data/lib/milk_tea/packages/source_resolver.rb +389 -0
  222. data/lib/milk_tea/packages/version.rb +164 -0
  223. data/lib/milk_tea/packages.rb +16 -0
  224. data/lib/milk_tea/tooling/asset_pack.rb +141 -0
  225. data/lib/milk_tea/tooling/build.rb +1124 -0
  226. data/lib/milk_tea/tooling/build_cache.rb +240 -0
  227. data/lib/milk_tea/tooling/cli.rb +2688 -0
  228. data/lib/milk_tea/tooling/cst_formatter.rb +13 -0
  229. data/lib/milk_tea/tooling/debug_info_formatter.rb +456 -0
  230. data/lib/milk_tea/tooling/debug_map.rb +248 -0
  231. data/lib/milk_tea/tooling/docs_app.rb +369 -0
  232. data/lib/milk_tea/tooling/error_formatter.rb +86 -0
  233. data/lib/milk_tea/tooling/formatter.rb +787 -0
  234. data/lib/milk_tea/tooling/linter/doc_tags.rb +212 -0
  235. data/lib/milk_tea/tooling/linter/fix_engine.rb +237 -0
  236. data/lib/milk_tea/tooling/linter/flow_rules.rb +380 -0
  237. data/lib/milk_tea/tooling/linter/imports_platform.rb +841 -0
  238. data/lib/milk_tea/tooling/linter/release_rules.rb +744 -0
  239. data/lib/milk_tea/tooling/linter/reserved_names.rb +199 -0
  240. data/lib/milk_tea/tooling/linter/rules.rb +593 -0
  241. data/lib/milk_tea/tooling/linter/source_helpers.rb +407 -0
  242. data/lib/milk_tea/tooling/linter/trailing_comma.rb +139 -0
  243. data/lib/milk_tea/tooling/linter/visitors.rb +1286 -0
  244. data/lib/milk_tea/tooling/linter.rb +798 -0
  245. data/lib/milk_tea/tooling/project_scaffold.rb +82 -0
  246. data/lib/milk_tea/tooling/public/css/docs.css +166 -0
  247. data/lib/milk_tea/tooling/public/js/docs.js +94 -0
  248. data/lib/milk_tea/tooling/run.rb +408 -0
  249. data/lib/milk_tea/tooling/templates/wasm_shell.html +48 -0
  250. data/lib/milk_tea/tooling/toolchain_cli.rb +157 -0
  251. data/lib/milk_tea/tooling/views/404.erb +7 -0
  252. data/lib/milk_tea/tooling/views/index.erb +87 -0
  253. data/lib/milk_tea/tooling/views/layout.erb +69 -0
  254. data/lib/milk_tea/tooling/views/module.erb +97 -0
  255. data/lib/milk_tea/tooling/views/stdlib.erb +21 -0
  256. data/lib/milk_tea/tooling.rb +20 -0
  257. data/lib/milk_tea.rb +8 -0
  258. metadata +426 -0
@@ -0,0 +1,563 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MilkTea
4
+ class CBackend
5
+ module Statements
6
+ private
7
+
8
+ def emit_statement_sequence(statements, level, function:, used_labels:, loop_continue_label: nil, loop_break_label: nil)
9
+ statements.each_with_index.flat_map do |statement, index|
10
+ emit_statement(
11
+ statement,
12
+ level,
13
+ function:,
14
+ used_labels:,
15
+ loop_continue_label:,
16
+ loop_break_label:,
17
+ remaining_statements: statements[(index + 1)..] || [],
18
+ )
19
+ end
20
+ end
21
+
22
+ def emit_statement(statement, level, function:, used_labels:, loop_continue_label: nil, loop_break_label: nil, remaining_statements: [])
23
+ indent = INDENT * level
24
+ aliases = checked_index_aliases_for_statement(statement)
25
+ alias_lines = emit_checked_index_alias_declarations(aliases, indent)
26
+ line_directive = if @emit_line_directives && statement.respond_to?(:line) && statement.line
27
+ sp = (statement.respond_to?(:source_path) && statement.source_path) || @source_path
28
+ sp ? ["#line #{statement.line} #{sp.inspect}"] : []
29
+ else
30
+ []
31
+ end
32
+ statement_lines = with_checked_index_aliases(aliases) do
33
+ case statement
34
+ when IR::LocalDecl
35
+ if array_type?(statement.type) && statement.value.is_a?(IR::Call)
36
+ lines = ["#{indent}#{c_declaration(statement.type, statement.linkage_name)};"]
37
+ lines << emit_array_call_statement(statement.value, emit_array_out_argument(statement.linkage_name), indent)
38
+ lines
39
+ elsif array_type?(statement.type) && !statement.value.is_a?(IR::ArrayLiteral) && !statement.value.is_a?(IR::ZeroInit)
40
+ lines = ["#{indent}#{c_declaration(statement.type, statement.linkage_name)};"]
41
+ lines << emit_array_copy_statement(statement.linkage_name, statement.value, indent)
42
+ lines
43
+ else
44
+ [
45
+ "#{indent}#{c_declaration(statement.type, statement.linkage_name)} = #{emit_initializer(statement.value)};",
46
+ ]
47
+ end
48
+ when IR::Assignment
49
+ if array_type?(statement.target.type) && statement.operator == "=" && statement.value.is_a?(IR::Call)
50
+ [emit_array_call_statement(statement.value, emit_array_out_argument(emit_expression(statement.target)), indent)]
51
+ elsif array_type?(statement.target.type) && statement.operator == "="
52
+ [emit_array_copy_statement(emit_expression(statement.target), statement.value, indent)]
53
+ else
54
+ ["#{indent}#{emit_expression(statement.target)} #{statement.operator} #{emit_expression(statement.value)};"]
55
+ end
56
+ when IR::BlockStmt
57
+ if block_requires_scope?(statement.body)
58
+ lines = ["#{indent}{"]
59
+ lines.concat(emit_statement_sequence(statement.body, level + 1, function:, used_labels:, loop_continue_label:, loop_break_label:))
60
+ lines << "#{indent}}"
61
+ lines
62
+ else
63
+ emit_statement_sequence(statement.body, level, function:, used_labels:, loop_continue_label:, loop_break_label:)
64
+ end
65
+ when IR::ExpressionStmt
66
+ if statement.expression.is_a?(IR::Assignment)
67
+ emit_assignment_expression(statement.expression, indent)
68
+ elsif statement.expression.is_a?(IR::Call)
69
+ ["#{indent}#{emit_expression(statement.expression)};"]
70
+ else
71
+ # A pure-value expression discarded as a statement (e.g. the
72
+ # `T<-x` touch idiom) needs a void cast, otherwise the value
73
+ # is unused and triggers -Wunused-value.
74
+ ["#{indent}(void)(#{emit_expression(statement.expression)});"]
75
+ end
76
+ when IR::ReturnStmt
77
+ if statement.value
78
+ if array_type?(function.return_type)
79
+ emit_array_return(statement.value, indent)
80
+ else
81
+ ["#{indent}return #{emit_expression(statement.value)};"]
82
+ end
83
+ else
84
+ ["#{indent}return;"]
85
+ end
86
+ when IR::WhileStmt
87
+ body_continue_label = loop_continue_label_name(statement.body)
88
+ body = body_continue_label ? statement.body[0...-1] : statement.body
89
+ body_break_label = loop_break_label_name(body, remaining_statements)
90
+ @suppressed_labels << body_break_label if body_break_label && !statements_need_explicit_break_label_after_emission?(body, body_break_label, loop_break_label_active: true)
91
+ if @debug_guards
92
+ @loop_guard_id += 1
93
+ guard_name = "__mt_loop_#{@loop_guard_id}"
94
+ lines = ["#{indent}{ uintptr_t #{guard_name} = 0;"]
95
+ lines << "#{indent}while (#{emit_expression(statement.condition)}) {"
96
+ guard = "#{INDENT * (level + 1)}if (++#{guard_name} > #{LOOP_GUARD_MAX_ITERATIONS}) mt_fatal(\"loop iteration limit exceeded in #{function.linkage_name}\");"
97
+ lines << guard
98
+ lines.concat(emit_statement_sequence(body, level + 1, function:, used_labels:, loop_continue_label: body_continue_label, loop_break_label: body_break_label))
99
+ lines << "#{indent}}"
100
+ lines << "#{indent}}"
101
+ lines
102
+ else
103
+ lines = ["#{indent}while (#{emit_expression(statement.condition)}) {"]
104
+ lines.concat(emit_statement_sequence(body, level + 1, function:, used_labels:, loop_continue_label: body_continue_label, loop_break_label: body_break_label))
105
+ lines << "#{indent}}"
106
+ lines
107
+ end
108
+ when IR::ForStmt
109
+ body_continue_label = loop_continue_label_name(statement.body)
110
+ body = body_continue_label ? statement.body[0...-1] : statement.body
111
+ body_break_label = loop_break_label_name(body, remaining_statements)
112
+ @suppressed_labels << body_break_label if body_break_label && !statements_need_explicit_break_label_after_emission?(body, body_break_label, loop_break_label_active: true)
113
+ if @debug_guards
114
+ @loop_guard_id += 1
115
+ guard_name = "__mt_loop_#{@loop_guard_id}"
116
+ lines = ["#{indent}{ uintptr_t #{guard_name} = 0;"]
117
+ lines << "#{indent}for (#{emit_for_clause_statement(statement.init)}; #{emit_expression(statement.condition)}; #{emit_for_clause_statement(statement.post)}) {"
118
+ guard = "#{INDENT * (level + 1)}if (++#{guard_name} > #{LOOP_GUARD_MAX_ITERATIONS}) mt_fatal(\"loop iteration limit exceeded in #{function.linkage_name}\");"
119
+ lines << guard
120
+ lines.concat(emit_statement_sequence(body, level + 1, function:, used_labels:, loop_continue_label: body_continue_label, loop_break_label: body_break_label))
121
+ lines << "#{indent}}"
122
+ lines << "#{indent}}"
123
+ lines
124
+ else
125
+ lines = ["#{indent}for (#{emit_for_clause_statement(statement.init)}; #{emit_expression(statement.condition)}; #{emit_for_clause_statement(statement.post)}) {"]
126
+ lines.concat(emit_statement_sequence(body, level + 1, function:, used_labels:, loop_continue_label: body_continue_label, loop_break_label: body_break_label))
127
+ lines << "#{indent}}"
128
+ lines
129
+ end
130
+ when IR::BreakStmt
131
+ ["#{indent}break;"]
132
+ when IR::ContinueStmt
133
+ ["#{indent}continue;"]
134
+ when IR::GotoStmt
135
+ return ["#{indent}continue;"] if loop_continue_label && statement.label == loop_continue_label
136
+ return ["#{indent}break;"] if loop_break_label && statement.label == loop_break_label
137
+
138
+ ["#{indent}goto #{statement.label};"]
139
+ when IR::LabelStmt
140
+ return [] if @suppressed_labels.include?(statement.name)
141
+ return [] if loop_continue_label && statement.name == loop_continue_label
142
+ return [] unless used_labels.include?(statement.name)
143
+
144
+ ["#{indent}#{statement.name}:;"]
145
+ when IR::StaticAssert
146
+ ["#{indent}#{emit_static_assert(statement)}"]
147
+ when IR::IfStmt
148
+ emit_if_statement(statement, level, function:, used_labels:, loop_continue_label:, loop_break_label:)
149
+ when IR::SwitchStmt
150
+ if switch_emittable_as_if?(statement, loop_break_label:)
151
+ emit_switch_as_if_statement(statement, level, function:, used_labels:, loop_continue_label:, loop_break_label:)
152
+ else
153
+ lines = ["#{indent}switch (#{emit_expression(statement.expression)}) {"]
154
+ has_default = false
155
+ statement.cases.each do |switch_case|
156
+ if switch_case.is_a?(IR::SwitchDefaultCase)
157
+ has_default = true
158
+ lines << "#{indent}#{INDENT}default: {"
159
+ else
160
+ lines << "#{indent}#{INDENT}case #{emit_expression(switch_case.value)}: {"
161
+ end
162
+ lines.concat(emit_statement_sequence(switch_case.body, level + 2, function:, used_labels:, loop_continue_label:))
163
+ lines << "#{indent}#{INDENT}#{INDENT}break;" unless body_terminates?(switch_case.body)
164
+ lines << "#{indent}#{INDENT}}"
165
+ end
166
+ # An exhaustive enum/variant match covers every arm, so the C
167
+ # `default` path is unreachable. Emitting it lets the compiler
168
+ # prove non-void functions always return, avoiding spurious
169
+ # -Wreturn-type warnings on the implicit no-match path.
170
+ if statement.exhaustive && !has_default
171
+ lines << "#{indent}#{INDENT}default: __builtin_unreachable();"
172
+ end
173
+ lines << "#{indent}}"
174
+ lines
175
+ end
176
+ else
177
+ raise CBackendError, "unsupported IR statement #{statement.class.name}"
178
+ end
179
+ end
180
+
181
+ alias_lines + line_directive + statement_lines
182
+ end
183
+
184
+ def compact_generated_statement_sequence(statements)
185
+ transformed = statements.map { |statement| transform_compactable_nested_bodies(statement) }
186
+ compacted = []
187
+ index = 0
188
+ reachable = true
189
+
190
+ while index < transformed.length
191
+ current = transformed[index]
192
+
193
+ unless reachable
194
+ if current.is_a?(IR::LabelStmt)
195
+ compacted << current
196
+ reachable = true
197
+ end
198
+ index += 1
199
+ next
200
+ end
201
+
202
+ following = transformed[index + 1]
203
+ remaining = transformed[(index + 2)..] || []
204
+
205
+ if following && (folded_local_alias = fold_single_use_local_alias(current, following, remaining))
206
+ compacted << folded_local_alias
207
+ reachable = !statement_prevents_sequential_fallthrough?(folded_local_alias)
208
+ index += 2
209
+ next
210
+ end
211
+
212
+ if following && (folded_if = fold_single_use_bool_if_temp(current, following, remaining))
213
+ compacted << folded_if
214
+ reachable = !statement_prevents_sequential_fallthrough?(folded_if)
215
+ index += 2
216
+ next
217
+ end
218
+
219
+ compacted << current
220
+ reachable = !statement_prevents_sequential_fallthrough?(current)
221
+ index += 1
222
+ end
223
+
224
+ compacted
225
+ end
226
+
227
+ def transform_compactable_nested_bodies(statement)
228
+ case statement
229
+ when IR::BlockStmt
230
+ IR::BlockStmt.new(body: compact_generated_statement_sequence(statement.body))
231
+ when IR::WhileStmt
232
+ canonicalize_top_guarded_while(
233
+ IR::WhileStmt.new(condition: statement.condition, body: compact_generated_statement_sequence(statement.body))
234
+ )
235
+ when IR::ForStmt
236
+ IR::ForStmt.new(
237
+ init: statement.init,
238
+ condition: statement.condition,
239
+ post: statement.post,
240
+ body: compact_generated_statement_sequence(statement.body),
241
+ )
242
+ when IR::IfStmt
243
+ IR::IfStmt.new(
244
+ condition: statement.condition,
245
+ then_body: compact_generated_statement_sequence(statement.then_body),
246
+ else_body: statement.else_body ? compact_generated_statement_sequence(statement.else_body) : nil,
247
+ )
248
+ when IR::SwitchStmt
249
+ IR::SwitchStmt.new(
250
+ expression: statement.expression,
251
+ exhaustive: statement.exhaustive,
252
+ cases: statement.cases.map do |switch_case|
253
+ if switch_case.is_a?(IR::SwitchDefaultCase)
254
+ IR::SwitchDefaultCase.new(body: compact_generated_statement_sequence(switch_case.body))
255
+ else
256
+ IR::SwitchCase.new(value: switch_case.value, body: compact_generated_statement_sequence(switch_case.body))
257
+ end
258
+ end,
259
+ )
260
+ else
261
+ statement
262
+ end
263
+ end
264
+
265
+ def fold_single_use_local_alias(source_decl, alias_decl, remaining_statements)
266
+ return unless source_decl.is_a?(IR::LocalDecl)
267
+ return unless alias_decl.is_a?(IR::LocalDecl)
268
+ return unless compiler_generated_local_name?(source_decl.linkage_name)
269
+ return if array_type?(source_decl.type) || array_type?(alias_decl.type)
270
+ return unless source_decl.type == alias_decl.type
271
+ return unless alias_decl.value.is_a?(IR::Name) && alias_decl.value.name == source_decl.linkage_name
272
+ return unless name_reference_count_in_statements(remaining_statements, source_decl.linkage_name).zero?
273
+
274
+ IR::LocalDecl.new(name: alias_decl.name, linkage_name: alias_decl.linkage_name, type: alias_decl.type, value: source_decl.value)
275
+ end
276
+
277
+ def compiler_generated_local_name?(name)
278
+ name.start_with?("__mt_")
279
+ end
280
+
281
+ def fold_single_use_bool_if_temp(local_decl, if_stmt, remaining_statements)
282
+ return unless local_decl.is_a?(IR::LocalDecl)
283
+ return unless if_stmt.is_a?(IR::IfStmt)
284
+ return unless bool_type?(local_decl.type)
285
+
286
+ condition_kind = single_use_bool_if_condition_kind(if_stmt.condition, local_decl.linkage_name)
287
+ return unless condition_kind
288
+ return unless name_reference_count_in_statements(if_stmt.then_body, local_decl.linkage_name).zero?
289
+ return unless name_reference_count_in_statements(if_stmt.else_body || [], local_decl.linkage_name).zero?
290
+ return unless name_reference_count_in_statements(remaining_statements, local_decl.linkage_name).zero?
291
+
292
+ condition = if condition_kind == :direct
293
+ local_decl.value
294
+ else
295
+ IR::Unary.new(operator: "not", operand: local_decl.value, type: local_decl.type)
296
+ end
297
+
298
+ IR::IfStmt.new(condition:, then_body: if_stmt.then_body, else_body: if_stmt.else_body)
299
+ end
300
+
301
+ def single_use_bool_if_condition_kind(condition, temp_name)
302
+ return :direct if condition.is_a?(IR::Name) && condition.name == temp_name
303
+
304
+ if condition.is_a?(IR::Unary) && condition.operator == "not" && condition.operand.is_a?(IR::Name) && condition.operand.name == temp_name
305
+ return :negated
306
+ end
307
+
308
+ nil
309
+ end
310
+
311
+ def name_reference_count_in_statements(statements, name)
312
+ statements.sum { |statement| name_reference_count_in_statement(statement, name) }
313
+ end
314
+
315
+ def name_reference_count_in_statement(statement, name)
316
+ case statement
317
+ when IR::LocalDecl
318
+ name_reference_count_in_expression(statement.value, name)
319
+ when IR::Assignment
320
+ name_reference_count_in_expression(statement.target, name) + name_reference_count_in_expression(statement.value, name)
321
+ when IR::BlockStmt, IR::WhileStmt, IR::ForStmt
322
+ count = name_reference_count_in_statements(statement.body, name)
323
+ return count unless statement.is_a?(IR::WhileStmt) || statement.is_a?(IR::ForStmt)
324
+
325
+ count += name_reference_count_in_expression(statement.condition, name)
326
+ count += name_reference_count_in_statement(statement.init, name) if statement.is_a?(IR::ForStmt)
327
+ count += name_reference_count_in_statement(statement.post, name) if statement.is_a?(IR::ForStmt)
328
+ count
329
+ when IR::IfStmt
330
+ name_reference_count_in_expression(statement.condition, name) +
331
+ name_reference_count_in_statements(statement.then_body, name) +
332
+ name_reference_count_in_statements(statement.else_body || [], name)
333
+ when IR::SwitchStmt
334
+ name_reference_count_in_expression(statement.expression, name) +
335
+ statement.cases.sum { |switch_case| (switch_case.is_a?(IR::SwitchCase) ? name_reference_count_in_expression(switch_case.value, name) : 0) + name_reference_count_in_statements(switch_case.body, name) }
336
+ when IR::StaticAssert
337
+ name_reference_count_in_expression(statement.condition, name) + name_reference_count_in_expression(statement.message, name)
338
+ when IR::ReturnStmt
339
+ statement.value ? name_reference_count_in_expression(statement.value, name) : 0
340
+ when IR::ExpressionStmt
341
+ name_reference_count_in_expression(statement.expression, name)
342
+ else
343
+ 0
344
+ end
345
+ end
346
+
347
+ def name_reference_count_in_expression(expression, name)
348
+ case expression
349
+ when IR::Name
350
+ expression.name == name ? 1 : 0
351
+ when IR::Member
352
+ name_reference_count_in_expression(expression.receiver, name)
353
+ when IR::Index, IR::CheckedIndex, IR::CheckedSpanIndex, IR::NullableIndex, IR::NullableSpanIndex
354
+ name_reference_count_in_expression(expression.receiver, name) + name_reference_count_in_expression(expression.index, name)
355
+ when IR::Call
356
+ callee_count = expression.callee.is_a?(String) ? 0 : name_reference_count_in_expression(expression.callee, name)
357
+ callee_count + expression.arguments.sum { |argument| name_reference_count_in_expression(argument, name) }
358
+ when IR::Unary
359
+ name_reference_count_in_expression(expression.operand, name)
360
+ when IR::Binary
361
+ name_reference_count_in_expression(expression.left, name) + name_reference_count_in_expression(expression.right, name)
362
+ when IR::Conditional
363
+ name_reference_count_in_expression(expression.condition, name) +
364
+ name_reference_count_in_expression(expression.then_expression, name) +
365
+ name_reference_count_in_expression(expression.else_expression, name)
366
+ when IR::ReinterpretExpr
367
+ name_reference_count_in_expression(expression.expression, name)
368
+ when IR::AddressOf, IR::Cast
369
+ name_reference_count_in_expression(expression.expression, name)
370
+ when IR::AggregateLiteral
371
+ expression.fields.sum { |field| name_reference_count_in_expression(field.value, name) }
372
+ when IR::ArrayLiteral
373
+ expression.elements.sum { |element| name_reference_count_in_expression(element, name) }
374
+ when IR::VariantLiteral
375
+ expression.fields.sum { |field| name_reference_count_in_expression(field.value, name) }
376
+ else
377
+ 0
378
+ end
379
+ end
380
+
381
+ def bool_type?(type)
382
+ type.is_a?(Types::Primitive) && type.name == "bool"
383
+ end
384
+
385
+ def checked_index_aliases_for_statement(statement)
386
+ expressions = case statement
387
+ when IR::LocalDecl
388
+ [statement.value]
389
+ when IR::Assignment
390
+ [statement.target, statement.value]
391
+ when IR::ExpressionStmt
392
+ [statement.expression]
393
+ when IR::ReturnStmt
394
+ statement.value ? [statement.value] : []
395
+ else
396
+ []
397
+ end
398
+
399
+ collect_checked_index_aliases(expressions.compact)
400
+ end
401
+
402
+ def collect_checked_index_aliases(expressions)
403
+ counts = Hash.new(0)
404
+ order = []
405
+ expressions.each do |expression|
406
+ collect_checked_index_alias_candidates(expression, counts, order)
407
+ end
408
+
409
+ order.each_with_object({}) do |expression, aliases|
410
+ next unless counts[expression] > 1
411
+ next unless hoistable_checked_index_alias?(expression)
412
+
413
+ aliases[expression] = fresh_checked_index_alias_name
414
+ end
415
+ end
416
+
417
+ def collect_checked_index_alias_candidates(expression, counts, order)
418
+ case expression
419
+ when IR::Member
420
+ collect_checked_index_alias_candidates(expression.receiver, counts, order)
421
+ when IR::Index
422
+ collect_checked_index_alias_candidates(expression.receiver, counts, order)
423
+ collect_checked_index_alias_candidates(expression.index, counts, order)
424
+ when IR::CheckedIndex, IR::CheckedSpanIndex, IR::NullableIndex, IR::NullableSpanIndex
425
+ order << expression unless counts.key?(expression)
426
+ counts[expression] += 1
427
+ collect_checked_index_alias_candidates(expression.receiver, counts, order)
428
+ collect_checked_index_alias_candidates(expression.index, counts, order)
429
+ when IR::Call
430
+ collect_checked_index_alias_candidates(expression.callee, counts, order) unless expression.callee.is_a?(String)
431
+ expression.arguments.each { |argument| collect_checked_index_alias_candidates(argument, counts, order) }
432
+ when IR::Unary
433
+ collect_checked_index_alias_candidates(expression.operand, counts, order)
434
+ when IR::Binary
435
+ collect_checked_index_alias_candidates(expression.left, counts, order)
436
+ collect_checked_index_alias_candidates(expression.right, counts, order)
437
+ when IR::Conditional
438
+ collect_checked_index_alias_candidates(expression.condition, counts, order)
439
+ collect_checked_index_alias_candidates(expression.then_expression, counts, order)
440
+ collect_checked_index_alias_candidates(expression.else_expression, counts, order)
441
+ when IR::ReinterpretExpr, IR::AddressOf, IR::Cast
442
+ collect_checked_index_alias_candidates(expression.expression, counts, order)
443
+ when IR::AggregateLiteral
444
+ expression.fields.each { |field| collect_checked_index_alias_candidates(field.value, counts, order) }
445
+ when IR::ArrayLiteral
446
+ expression.elements.each { |element| collect_checked_index_alias_candidates(element, counts, order) }
447
+ when IR::VariantLiteral
448
+ expression.fields.each { |field| collect_checked_index_alias_candidates(field.value, counts, order) }
449
+ end
450
+ end
451
+
452
+ def hoistable_checked_index_alias?(expression)
453
+ side_effect_free_expression?(expression.receiver) && side_effect_free_expression?(expression.index)
454
+ end
455
+
456
+ def side_effect_free_expression?(expression)
457
+ case expression
458
+ when IR::Name, IR::IntegerLiteral, IR::FloatLiteral, IR::StringLiteral, IR::BooleanLiteral, IR::NullLiteral, IR::ZeroInit, IR::SizeofExpr, IR::AlignofExpr, IR::OffsetofExpr
459
+ true
460
+ when IR::Member
461
+ side_effect_free_expression?(expression.receiver)
462
+ when IR::Index, IR::CheckedIndex, IR::CheckedSpanIndex, IR::NullableIndex, IR::NullableSpanIndex
463
+ side_effect_free_expression?(expression.receiver) && side_effect_free_expression?(expression.index)
464
+ when IR::Unary
465
+ side_effect_free_expression?(expression.operand)
466
+ when IR::Binary
467
+ side_effect_free_expression?(expression.left) && side_effect_free_expression?(expression.right)
468
+ when IR::Conditional
469
+ side_effect_free_expression?(expression.condition) &&
470
+ side_effect_free_expression?(expression.then_expression) &&
471
+ side_effect_free_expression?(expression.else_expression)
472
+ when IR::ReinterpretExpr, IR::AddressOf, IR::Cast
473
+ side_effect_free_expression?(expression.expression)
474
+ when IR::AggregateLiteral
475
+ expression.fields.all? { |field| side_effect_free_expression?(field.value) }
476
+ when IR::VariantLiteral
477
+ expression.fields.all? { |field| side_effect_free_expression?(field.value) }
478
+ when IR::ArrayLiteral
479
+ expression.elements.all? { |element| side_effect_free_expression?(element) }
480
+ when IR::Call
481
+ false
482
+ else
483
+ false
484
+ end
485
+ end
486
+
487
+ def emit_checked_index_alias_declarations(aliases, indent)
488
+ aliases.map do |expression, alias_name|
489
+ "#{indent}#{c_declaration(pointer_to(expression.type), alias_name)} = #{emit_checked_index_pointer(expression)};"
490
+ end
491
+ end
492
+
493
+ def emit_checked_index_pointer(expression)
494
+ case expression
495
+ when IR::CheckedIndex
496
+ "#{checked_array_index_helper_name(expression.receiver_type)}(#{emit_address_of_operand(expression.receiver)}, #{emit_expression(expression.index)})"
497
+ when IR::CheckedSpanIndex
498
+ "#{checked_span_index_helper_name(expression.receiver_type)}(#{emit_expression(expression.receiver)}, #{emit_expression(expression.index)})"
499
+ else
500
+ raise CBackendError, "unsupported checked index alias expression #{expression.class.name}"
501
+ end
502
+ end
503
+
504
+ def fresh_checked_index_alias_name
505
+ @checked_index_alias_id += 1
506
+ "__mt_checked_index_ptr_#{@checked_index_alias_id}"
507
+ end
508
+
509
+ def with_checked_index_aliases(aliases)
510
+ @checked_index_alias_stack << aliases
511
+ yield
512
+ ensure
513
+ @checked_index_alias_stack.pop
514
+ end
515
+
516
+ def checked_index_alias(expression)
517
+ @checked_index_alias_stack.reverse_each do |aliases|
518
+ alias_name = aliases[expression]
519
+ return alias_name if alias_name
520
+ end
521
+
522
+ nil
523
+ end
524
+
525
+ def block_requires_scope?(statements)
526
+ statements.any? { |statement| statement.is_a?(IR::LocalDecl) }
527
+ end
528
+
529
+ def emit_for_clause_statement(statement)
530
+ case statement
531
+ when IR::LocalDecl
532
+ raise CBackendError, "array for-loop init declarations are unsupported" if array_type?(statement.type)
533
+
534
+ "#{c_declaration(statement.type, statement.linkage_name)} = #{emit_initializer(statement.value)}"
535
+ when IR::Assignment
536
+ if array_type?(statement.target.type) && statement.operator == "="
537
+ raise CBackendError, "array for-loop assignment clauses are unsupported"
538
+ end
539
+
540
+ "#{emit_expression(statement.target)} #{statement.operator} #{emit_expression(statement.value)}"
541
+ when IR::ExpressionStmt
542
+ emit_expression(statement.expression)
543
+ else
544
+ raise CBackendError, "unsupported for-loop clause #{statement.class.name}"
545
+ end
546
+ end
547
+
548
+ def emit_static_assert(statement)
549
+ message = if statement.message.is_a?(IR::StringLiteral)
550
+ statement.message.value.inspect
551
+ else
552
+ emit_expression(statement.message)
553
+ end
554
+
555
+ "_Static_assert(#{emit_expression(statement.condition)}, #{message});"
556
+ end
557
+
558
+ def emit_assignment_expression(assignment, indent)
559
+ ["#{indent}#{emit_expression(assignment.target)} #{assignment.operator} #{emit_expression(assignment.value)};"]
560
+ end
561
+ end
562
+ end
563
+ end