Module: MilkTea::CBackend::Statements

Included in:
MilkTea::CBackend
Defined in:
lib/milk_tea/core/c_backend/statements.rb

Instance Method Summary collapse

Instance Method Details

#block_requires_scope?(statements) ⇒ Boolean

Returns:

  • (Boolean)


527
528
529
# File 'lib/milk_tea/core/c_backend/statements.rb', line 527

def block_requires_scope?(statements)
  statements.any? { |statement| statement.is_a?(IR::LocalDecl) }
end

#bool_type?(type) ⇒ Boolean

Returns:

  • (Boolean)


383
384
385
# File 'lib/milk_tea/core/c_backend/statements.rb', line 383

def bool_type?(type)
  type.is_a?(Types::Primitive) && type.name == "bool"
end

#checked_index_alias(expression) ⇒ Object



518
519
520
521
522
523
524
525
# File 'lib/milk_tea/core/c_backend/statements.rb', line 518

def checked_index_alias(expression)
  @checked_index_alias_stack.reverse_each do |aliases|
    alias_name = aliases[expression]
    return alias_name if alias_name
  end

  nil
end

#checked_index_aliases_for_statement(statement) ⇒ Object



387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
# File 'lib/milk_tea/core/c_backend/statements.rb', line 387

def checked_index_aliases_for_statement(statement)
  expressions = case statement
  when IR::LocalDecl
    [statement.value]
  when IR::Assignment
    [statement.target, statement.value]
  when IR::ExpressionStmt
    [statement.expression]
  when IR::ReturnStmt
    statement.value ? [statement.value] : []
  else
    []
  end

  collect_checked_index_aliases(expressions.compact)
end

#collect_checked_index_alias_candidates(expression, counts, order) ⇒ Object



419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
# File 'lib/milk_tea/core/c_backend/statements.rb', line 419

def collect_checked_index_alias_candidates(expression, counts, order)
  case expression
  when IR::Member
    collect_checked_index_alias_candidates(expression.receiver, counts, order)
  when IR::Index
    collect_checked_index_alias_candidates(expression.receiver, counts, order)
    collect_checked_index_alias_candidates(expression.index, counts, order)
  when IR::CheckedIndex, IR::CheckedSpanIndex, IR::NullableIndex, IR::NullableSpanIndex
    order << expression unless counts.key?(expression)
    counts[expression] += 1
    collect_checked_index_alias_candidates(expression.receiver, counts, order)
    collect_checked_index_alias_candidates(expression.index, counts, order)
  when IR::Call
    collect_checked_index_alias_candidates(expression.callee, counts, order) unless expression.callee.is_a?(String)
    expression.arguments.each { |argument| collect_checked_index_alias_candidates(argument, counts, order) }
  when IR::Unary
    collect_checked_index_alias_candidates(expression.operand, counts, order)
  when IR::Binary
    collect_checked_index_alias_candidates(expression.left, counts, order)
    collect_checked_index_alias_candidates(expression.right, counts, order)
  when IR::Conditional
    collect_checked_index_alias_candidates(expression.condition, counts, order)
    collect_checked_index_alias_candidates(expression.then_expression, counts, order)
    collect_checked_index_alias_candidates(expression.else_expression, counts, order)
  when IR::ReinterpretExpr, IR::AddressOf, IR::Cast
    collect_checked_index_alias_candidates(expression.expression, counts, order)
  when IR::AggregateLiteral
    expression.fields.each { |field| collect_checked_index_alias_candidates(field.value, counts, order) }
  when IR::ArrayLiteral
    expression.elements.each { |element| collect_checked_index_alias_candidates(element, counts, order) }
  when IR::VariantLiteral
    expression.fields.each { |field| collect_checked_index_alias_candidates(field.value, counts, order) }
  end
end

#collect_checked_index_aliases(expressions) ⇒ Object



404
405
406
407
408
409
410
411
412
413
414
415
416
417
# File 'lib/milk_tea/core/c_backend/statements.rb', line 404

def collect_checked_index_aliases(expressions)
  counts = Hash.new(0)
  order = []
  expressions.each do |expression|
    collect_checked_index_alias_candidates(expression, counts, order)
  end

  order.each_with_object({}) do |expression, aliases|
    next unless counts[expression] > 1
    next unless hoistable_checked_index_alias?(expression)

    aliases[expression] = fresh_checked_index_alias_name
  end
end

#compact_generated_statement_sequence(statements) ⇒ Object



186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
# File 'lib/milk_tea/core/c_backend/statements.rb', line 186

def compact_generated_statement_sequence(statements)
  transformed = statements.map { |statement| transform_compactable_nested_bodies(statement) }
  compacted = []
  index = 0
  reachable = true

  while index < transformed.length
    current = transformed[index]

    unless reachable
      if current.is_a?(IR::LabelStmt)
        compacted << current
        reachable = true
      end
      index += 1
      next
    end

    following = transformed[index + 1]
    remaining = transformed[(index + 2)..] || []

    if following && (folded_local_alias = fold_single_use_local_alias(current, following, remaining))
      compacted << folded_local_alias
      reachable = !statement_prevents_sequential_fallthrough?(folded_local_alias)
      index += 2
      next
    end

    if following && (folded_if = fold_single_use_bool_if_temp(current, following, remaining))
      compacted << folded_if
      reachable = !statement_prevents_sequential_fallthrough?(folded_if)
      index += 2
      next
    end

    compacted << current
    reachable = !statement_prevents_sequential_fallthrough?(current)
    index += 1
  end

  compacted
end

#compiler_generated_local_name?(name) ⇒ Boolean

Returns:

  • (Boolean)


279
280
281
# File 'lib/milk_tea/core/c_backend/statements.rb', line 279

def compiler_generated_local_name?(name)
  name.start_with?("__mt_")
end

#emit_assignment_expression(assignment, indent) ⇒ Object



560
561
562
# File 'lib/milk_tea/core/c_backend/statements.rb', line 560

def emit_assignment_expression(assignment, indent)
  ["#{indent}#{emit_expression(assignment.target)} #{assignment.operator} #{emit_expression(assignment.value)};"]
end

#emit_checked_index_alias_declarations(aliases, indent) ⇒ Object



489
490
491
492
493
# File 'lib/milk_tea/core/c_backend/statements.rb', line 489

def emit_checked_index_alias_declarations(aliases, indent)
  aliases.map do |expression, alias_name|
    "#{indent}#{c_declaration(pointer_to(expression.type), alias_name)} = #{emit_checked_index_pointer(expression)};"
  end
end

#emit_checked_index_pointer(expression) ⇒ Object



495
496
497
498
499
500
501
502
503
504
# File 'lib/milk_tea/core/c_backend/statements.rb', line 495

def emit_checked_index_pointer(expression)
  case expression
  when IR::CheckedIndex
    "#{checked_array_index_helper_name(expression.receiver_type)}(#{emit_address_of_operand(expression.receiver)}, #{emit_expression(expression.index)})"
  when IR::CheckedSpanIndex
    "#{checked_span_index_helper_name(expression.receiver_type)}(#{emit_expression(expression.receiver)}, #{emit_expression(expression.index)})"
  else
    raise CBackendError.new("unsupported checked index alias expression #{expression.class.name}", line: 0, column: 0, path: @path)
  end
end

#emit_for_clause_statement(statement) ⇒ Object



531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
# File 'lib/milk_tea/core/c_backend/statements.rb', line 531

def emit_for_clause_statement(statement)
  case statement
  when IR::LocalDecl
    raise CBackendError.new("array for-loop init declarations are unsupported", line: 0, column: 0, path: @path) if array_type?(statement.type)

    "#{c_declaration(statement.type, statement.linkage_name)} = #{emit_initializer(statement.value)}"
  when IR::Assignment
    if array_type?(statement.target.type) && statement.operator == "="
      raise CBackendError.new("array for-loop assignment clauses are unsupported", line: 0, column: 0, path: @path)
    end

    "#{emit_expression(statement.target)} #{statement.operator} #{emit_expression(statement.value)}"
  when IR::ExpressionStmt
    emit_expression(statement.expression)
  else
    raise CBackendError.new("unsupported for-loop clause #{statement.class.name}", line: 0, column: 0, path: @path)
  end
end

#emit_statement(statement, level, function:, used_labels:, loop_continue_label: nil, loop_break_label: nil, remaining_statements: []) ⇒ Object



20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/milk_tea/core/c_backend/statements.rb', line 20

def emit_statement(statement, level, function:, used_labels:, loop_continue_label: nil, loop_break_label: nil, remaining_statements: [])
  indent = INDENT * level
  aliases = checked_index_aliases_for_statement(statement)
  alias_lines = emit_checked_index_alias_declarations(aliases, indent)
  if statement.respond_to?(:line) && statement.line
    @current_line = statement.line
    @source_path ||= statement.path if statement.respond_to?(:path) && statement.path
  end
  line_directive = if @emit_line_directives && statement.respond_to?(:line) && statement.line
    sp = (statement.respond_to?(:path) && statement.path) || @source_path
    sp ? ["#line #{statement.line} #{sp.inspect}"] : []
  else
    []
  end
  statement_lines = with_checked_index_aliases(aliases) do
    case statement
    when IR::LocalDecl
      if array_type?(statement.type) && statement.value.is_a?(IR::Call)
        lines = ["#{indent}#{c_declaration(statement.type, statement.linkage_name)};"]
        lines << emit_array_call_statement(statement.value, emit_array_out_argument(statement.linkage_name), indent)
        lines
      elsif array_type?(statement.type) && !statement.value.is_a?(IR::ArrayLiteral) && !statement.value.is_a?(IR::ZeroInit)
        lines = ["#{indent}#{c_declaration(statement.type, statement.linkage_name)};"]
        lines << emit_array_copy_statement(statement.linkage_name, statement.value, indent)
        lines
      else
        [
          "#{indent}#{c_declaration(statement.type, statement.linkage_name)} = #{emit_initializer(statement.value)};",
        ]
      end
    when IR::Assignment
      if array_type?(statement.target.type) && statement.operator == "=" && statement.value.is_a?(IR::Call)
        [emit_array_call_statement(statement.value, emit_array_out_argument(emit_expression(statement.target)), indent)]
      elsif array_type?(statement.target.type) && statement.operator == "="
        [emit_array_copy_statement(emit_expression(statement.target), statement.value, indent)]
      else
        ["#{indent}#{emit_expression(statement.target)} #{statement.operator} #{emit_expression(statement.value)};"]
      end
    when IR::BlockStmt
      if block_requires_scope?(statement.body)
        lines = ["#{indent}{"]
        lines.concat(emit_statement_sequence(statement.body, level + 1, function:, used_labels:, loop_continue_label:, loop_break_label:))
        lines << "#{indent}}"
        lines
      else
        emit_statement_sequence(statement.body, level, function:, used_labels:, loop_continue_label:, loop_break_label:)
      end
    when IR::ExpressionStmt
      if statement.expression.is_a?(IR::Assignment)
        emit_assignment_expression(statement.expression, indent)
      elsif statement.expression.is_a?(IR::Call)
        ["#{indent}#{emit_expression(statement.expression)};"]
      else
        # A pure-value expression discarded as a statement (e.g. the
        # `T<-x` touch idiom) needs a void cast, otherwise the value
        # is unused and triggers -Wunused-value.
        ["#{indent}(void)(#{emit_expression(statement.expression)});"]
      end
    when IR::ReturnStmt
      if statement.value
        if array_type?(function.return_type)
          emit_array_return(statement.value, indent)
        else
          ["#{indent}return #{emit_expression(statement.value)};"]
        end
      else
        ["#{indent}return;"]
      end
    when IR::WhileStmt
      body_continue_label = loop_continue_label_name(statement.body)
      body = body_continue_label ? statement.body[0...-1] : statement.body
      body_break_label = loop_break_label_name(body, remaining_statements)
      @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)
      if @debug_guards
        @loop_guard_id += 1
        guard_name = "__mt_loop_#{@loop_guard_id}"
        lines = ["#{indent}{ uintptr_t #{guard_name} = 0;"]
        lines << "#{indent}while (#{emit_expression(statement.condition)}) {"
        guard = "#{INDENT * (level + 1)}if (++#{guard_name} > #{LOOP_GUARD_MAX_ITERATIONS}) mt_fatal(\"loop iteration limit exceeded in #{function.linkage_name}\");"
        lines << guard
        lines.concat(emit_statement_sequence(body, level + 1, function:, used_labels:, loop_continue_label: body_continue_label, loop_break_label: body_break_label))
        lines << "#{indent}}"
        lines << "#{indent}}"
        lines
      else
        lines = ["#{indent}while (#{emit_expression(statement.condition)}) {"]
        lines.concat(emit_statement_sequence(body, level + 1, function:, used_labels:, loop_continue_label: body_continue_label, loop_break_label: body_break_label))
        lines << "#{indent}}"
        lines
      end
    when IR::ForStmt
      body_continue_label = loop_continue_label_name(statement.body)
      body = body_continue_label ? statement.body[0...-1] : statement.body
      body_break_label = loop_break_label_name(body, remaining_statements)
      @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)
      if @debug_guards
        @loop_guard_id += 1
        guard_name = "__mt_loop_#{@loop_guard_id}"
        lines = ["#{indent}{ uintptr_t #{guard_name} = 0;"]
        lines << "#{indent}for (#{emit_for_clause_statement(statement.init)}; #{emit_expression(statement.condition)}; #{emit_for_clause_statement(statement.post)}) {"
        guard = "#{INDENT * (level + 1)}if (++#{guard_name} > #{LOOP_GUARD_MAX_ITERATIONS}) mt_fatal(\"loop iteration limit exceeded in #{function.linkage_name}\");"
        lines << guard
        lines.concat(emit_statement_sequence(body, level + 1, function:, used_labels:, loop_continue_label: body_continue_label, loop_break_label: body_break_label))
        lines << "#{indent}}"
        lines << "#{indent}}"
        lines
      else
        lines = ["#{indent}for (#{emit_for_clause_statement(statement.init)}; #{emit_expression(statement.condition)}; #{emit_for_clause_statement(statement.post)}) {"]
        lines.concat(emit_statement_sequence(body, level + 1, function:, used_labels:, loop_continue_label: body_continue_label, loop_break_label: body_break_label))
        lines << "#{indent}}"
        lines
      end
    when IR::BreakStmt
      ["#{indent}break;"]
    when IR::ContinueStmt
      ["#{indent}continue;"]
    when IR::GotoStmt
      return ["#{indent}continue;"] if loop_continue_label && statement.label == loop_continue_label
      return ["#{indent}break;"] if loop_break_label && statement.label == loop_break_label

      ["#{indent}goto #{statement.label};"]
    when IR::LabelStmt
      return [] if @suppressed_labels.include?(statement.name)
      return [] if loop_continue_label && statement.name == loop_continue_label
      return [] unless used_labels.include?(statement.name)

      ["#{indent}#{statement.name}:;"]
    when IR::StaticAssert
      ["#{indent}#{emit_static_assert(statement)}"]
    when IR::IfStmt
      emit_if_statement(statement, level, function:, used_labels:, loop_continue_label:, loop_break_label:)
    when IR::SwitchStmt
      if switch_emittable_as_if?(statement, loop_break_label:)
        emit_switch_as_if_statement(statement, level, function:, used_labels:, loop_continue_label:, loop_break_label:)
      else
      lines = ["#{indent}switch (#{emit_expression(statement.expression)}) {"]
      has_default = false
      statement.cases.each do |switch_case|
        if switch_case.is_a?(IR::SwitchDefaultCase)
          has_default = true
          lines << "#{indent}#{INDENT}default: {"
        else
          lines << "#{indent}#{INDENT}case #{emit_expression(switch_case.value)}: {"
        end
        lines.concat(emit_statement_sequence(switch_case.body, level + 2, function:, used_labels:, loop_continue_label:))
        lines << "#{indent}#{INDENT}#{INDENT}break;" unless body_terminates?(switch_case.body)
        lines << "#{indent}#{INDENT}}"
      end
      # An exhaustive enum/variant match covers every arm, so the C
      # `default` path is unreachable. Emitting it lets the compiler
      # prove non-void functions always return, avoiding spurious
      # -Wreturn-type warnings on the implicit no-match path.
      if statement.exhaustive && !has_default
        lines << "#{indent}#{INDENT}default: __builtin_unreachable();"
      end
      lines << "#{indent}}"
      lines
      end
    else
      raise CBackendError.new("unsupported IR statement #{statement.class.name}", line: 0, column: 0, path: @path)
    end
  end

  alias_lines + line_directive + statement_lines
end

#emit_statement_sequence(statements, level, function:, used_labels:, loop_continue_label: nil, loop_break_label: nil) ⇒ Object



6
7
8
9
10
11
12
13
14
15
16
17
18
# File 'lib/milk_tea/core/c_backend/statements.rb', line 6

def emit_statement_sequence(statements, level, function:, used_labels:, loop_continue_label: nil, loop_break_label: nil)
  statements.each_with_index.flat_map do |statement, index|
    emit_statement(
      statement,
      level,
      function:,
      used_labels:,
      loop_continue_label:,
      loop_break_label:,
      remaining_statements: statements[(index + 1)..] || [],
    )
  end
end

#emit_static_assert(statement) ⇒ Object



550
551
552
553
554
555
556
557
558
# File 'lib/milk_tea/core/c_backend/statements.rb', line 550

def emit_static_assert(statement)
  message = if statement.message.is_a?(IR::StringLiteral)
    statement.message.value.inspect
  else
    emit_expression(statement.message)
  end

  "_Static_assert(#{emit_expression(statement.condition)}, #{message});"
end

#fold_single_use_bool_if_temp(local_decl, if_stmt, remaining_statements) ⇒ Object



283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
# File 'lib/milk_tea/core/c_backend/statements.rb', line 283

def fold_single_use_bool_if_temp(local_decl, if_stmt, remaining_statements)
  return unless local_decl.is_a?(IR::LocalDecl)
  return unless if_stmt.is_a?(IR::IfStmt)
  return unless bool_type?(local_decl.type)

  condition_kind = single_use_bool_if_condition_kind(if_stmt.condition, local_decl.linkage_name)
  return unless condition_kind
  return unless name_reference_count_in_statements(if_stmt.then_body, local_decl.linkage_name).zero?
  return unless name_reference_count_in_statements(if_stmt.else_body || [], local_decl.linkage_name).zero?
  return unless name_reference_count_in_statements(remaining_statements, local_decl.linkage_name).zero?

  condition = if condition_kind == :direct
                local_decl.value
              else
                IR::Unary.new(operator: "not", operand: local_decl.value, type: local_decl.type)
              end

  IR::IfStmt.new(condition:, then_body: if_stmt.then_body, else_body: if_stmt.else_body)
end

#fold_single_use_local_alias(source_decl, alias_decl, remaining_statements) ⇒ Object



267
268
269
270
271
272
273
274
275
276
277
# File 'lib/milk_tea/core/c_backend/statements.rb', line 267

def fold_single_use_local_alias(source_decl, alias_decl, remaining_statements)
  return unless source_decl.is_a?(IR::LocalDecl)
  return unless alias_decl.is_a?(IR::LocalDecl)
  return unless compiler_generated_local_name?(source_decl.linkage_name)
  return if array_type?(source_decl.type) || array_type?(alias_decl.type)
  return unless source_decl.type == alias_decl.type
  return unless alias_decl.value.is_a?(IR::Name) && alias_decl.value.name == source_decl.linkage_name
  return unless name_reference_count_in_statements(remaining_statements, source_decl.linkage_name).zero?

  IR::LocalDecl.new(name: alias_decl.name, linkage_name: alias_decl.linkage_name, type: alias_decl.type, value: source_decl.value)
end

#fresh_checked_index_alias_nameObject



506
507
508
509
# File 'lib/milk_tea/core/c_backend/statements.rb', line 506

def fresh_checked_index_alias_name
  @checked_index_alias_id += 1
  "__mt_checked_index_ptr_#{@checked_index_alias_id}"
end

#hoistable_checked_index_alias?(expression) ⇒ Boolean

Returns:

  • (Boolean)


454
455
456
# File 'lib/milk_tea/core/c_backend/statements.rb', line 454

def hoistable_checked_index_alias?(expression)
  side_effect_free_expression?(expression.receiver) && side_effect_free_expression?(expression.index)
end

#name_reference_count_in_expression(expression, name) ⇒ Object



349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
# File 'lib/milk_tea/core/c_backend/statements.rb', line 349

def name_reference_count_in_expression(expression, name)
  case expression
  when IR::Name
    expression.name == name ? 1 : 0
  when IR::Member
    name_reference_count_in_expression(expression.receiver, name)
  when IR::Index, IR::CheckedIndex, IR::CheckedSpanIndex, IR::NullableIndex, IR::NullableSpanIndex
    name_reference_count_in_expression(expression.receiver, name) + name_reference_count_in_expression(expression.index, name)
  when IR::Call
    callee_count = expression.callee.is_a?(String) ? 0 : name_reference_count_in_expression(expression.callee, name)
    callee_count + expression.arguments.sum { |argument| name_reference_count_in_expression(argument, name) }
  when IR::Unary
    name_reference_count_in_expression(expression.operand, name)
  when IR::Binary
    name_reference_count_in_expression(expression.left, name) + name_reference_count_in_expression(expression.right, name)
  when IR::Conditional
    name_reference_count_in_expression(expression.condition, name) +
      name_reference_count_in_expression(expression.then_expression, name) +
      name_reference_count_in_expression(expression.else_expression, name)
  when IR::ReinterpretExpr
    name_reference_count_in_expression(expression.expression, name)
  when IR::AddressOf, IR::Cast
    name_reference_count_in_expression(expression.expression, name)
  when IR::AggregateLiteral
    expression.fields.sum { |field| name_reference_count_in_expression(field.value, name) }
  when IR::ArrayLiteral
    expression.elements.sum { |element| name_reference_count_in_expression(element, name) }
  when IR::VariantLiteral
    expression.fields.sum { |field| name_reference_count_in_expression(field.value, name) }
  else
    0
  end
end

#name_reference_count_in_statement(statement, name) ⇒ Object



317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
# File 'lib/milk_tea/core/c_backend/statements.rb', line 317

def name_reference_count_in_statement(statement, name)
  case statement
  when IR::LocalDecl
    name_reference_count_in_expression(statement.value, name)
  when IR::Assignment
    name_reference_count_in_expression(statement.target, name) + name_reference_count_in_expression(statement.value, name)
  when IR::BlockStmt, IR::WhileStmt, IR::ForStmt
    count = name_reference_count_in_statements(statement.body, name)
    return count unless statement.is_a?(IR::WhileStmt) || statement.is_a?(IR::ForStmt)

    count += name_reference_count_in_expression(statement.condition, name)
    count += name_reference_count_in_statement(statement.init, name) if statement.is_a?(IR::ForStmt)
    count += name_reference_count_in_statement(statement.post, name) if statement.is_a?(IR::ForStmt)
    count
  when IR::IfStmt
    name_reference_count_in_expression(statement.condition, name) +
      name_reference_count_in_statements(statement.then_body, name) +
      name_reference_count_in_statements(statement.else_body || [], name)
  when IR::SwitchStmt
    name_reference_count_in_expression(statement.expression, name) +
      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) }
  when IR::StaticAssert
    name_reference_count_in_expression(statement.condition, name) + name_reference_count_in_expression(statement.message, name)
  when IR::ReturnStmt
    statement.value ? name_reference_count_in_expression(statement.value, name) : 0
  when IR::ExpressionStmt
    name_reference_count_in_expression(statement.expression, name)
  else
    0
  end
end

#name_reference_count_in_statements(statements, name) ⇒ Object



313
314
315
# File 'lib/milk_tea/core/c_backend/statements.rb', line 313

def name_reference_count_in_statements(statements, name)
  statements.sum { |statement| name_reference_count_in_statement(statement, name) }
end

#side_effect_free_expression?(expression) ⇒ Boolean

Returns:

  • (Boolean)


458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
# File 'lib/milk_tea/core/c_backend/statements.rb', line 458

def side_effect_free_expression?(expression)
  case expression
  when IR::Name, IR::IntegerLiteral, IR::FloatLiteral, IR::StringLiteral, IR::BooleanLiteral, IR::NullLiteral, IR::ZeroInit, IR::SizeofExpr, IR::AlignofExpr, IR::OffsetofExpr
    true
  when IR::Member
    side_effect_free_expression?(expression.receiver)
  when IR::Index, IR::CheckedIndex, IR::CheckedSpanIndex, IR::NullableIndex, IR::NullableSpanIndex
    side_effect_free_expression?(expression.receiver) && side_effect_free_expression?(expression.index)
  when IR::Unary
    side_effect_free_expression?(expression.operand)
  when IR::Binary
    side_effect_free_expression?(expression.left) && side_effect_free_expression?(expression.right)
  when IR::Conditional
    side_effect_free_expression?(expression.condition) &&
      side_effect_free_expression?(expression.then_expression) &&
      side_effect_free_expression?(expression.else_expression)
  when IR::ReinterpretExpr, IR::AddressOf, IR::Cast
    side_effect_free_expression?(expression.expression)
  when IR::AggregateLiteral
    expression.fields.all? { |field| side_effect_free_expression?(field.value) }
  when IR::VariantLiteral
    expression.fields.all? { |field| side_effect_free_expression?(field.value) }
  when IR::ArrayLiteral
    expression.elements.all? { |element| side_effect_free_expression?(element) }
  when IR::Call
    false
  else
    false
  end
end

#single_use_bool_if_condition_kind(condition, temp_name) ⇒ Object



303
304
305
306
307
308
309
310
311
# File 'lib/milk_tea/core/c_backend/statements.rb', line 303

def single_use_bool_if_condition_kind(condition, temp_name)
  return :direct if condition.is_a?(IR::Name) && condition.name == temp_name

  if condition.is_a?(IR::Unary) && condition.operator == "not" && condition.operand.is_a?(IR::Name) && condition.operand.name == temp_name
    return :negated
  end

  nil
end

#transform_compactable_nested_bodies(statement) ⇒ Object



229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
# File 'lib/milk_tea/core/c_backend/statements.rb', line 229

def transform_compactable_nested_bodies(statement)
  case statement
  when IR::BlockStmt
    IR::BlockStmt.new(body: compact_generated_statement_sequence(statement.body))
  when IR::WhileStmt
    canonicalize_top_guarded_while(
      IR::WhileStmt.new(condition: statement.condition, body: compact_generated_statement_sequence(statement.body))
    )
  when IR::ForStmt
    IR::ForStmt.new(
      init: statement.init,
      condition: statement.condition,
      post: statement.post,
      body: compact_generated_statement_sequence(statement.body),
    )
  when IR::IfStmt
    IR::IfStmt.new(
      condition: statement.condition,
      then_body: compact_generated_statement_sequence(statement.then_body),
      else_body: statement.else_body ? compact_generated_statement_sequence(statement.else_body) : nil,
    )
  when IR::SwitchStmt
    IR::SwitchStmt.new(
      expression: statement.expression,
      exhaustive: statement.exhaustive,
      cases: statement.cases.map do |switch_case|
        if switch_case.is_a?(IR::SwitchDefaultCase)
          IR::SwitchDefaultCase.new(body: compact_generated_statement_sequence(switch_case.body))
        else
          IR::SwitchCase.new(value: switch_case.value, body: compact_generated_statement_sequence(switch_case.body))
        end
      end,
    )
  else
    statement
  end
end

#with_checked_index_aliases(aliases) ⇒ Object



511
512
513
514
515
516
# File 'lib/milk_tea/core/c_backend/statements.rb', line 511

def with_checked_index_aliases(aliases)
  @checked_index_alias_stack << aliases
  yield
ensure
  @checked_index_alias_stack.pop
end