# Generate hir_effect.inc.rs. To do this, we build up a DAG that
# represents the ZJIT effect hierarchy.

require 'set'

# Effect represents not just a Ruby class but a named union of other effects.
class Effect
  attr_accessor :name, :subeffects

  def initialize name, subeffects=nil
    @name = name
    @subeffects = subeffects || []
  end

  def all_subeffects
    subeffects.flat_map { |subeffect| subeffect.all_subeffects } + subeffects
  end

  def subeffect name
    result = Effect.new name
    @subeffects << result
    result
  end
end

# Helper to generate graphviz.
def to_graphviz_rec effect, f
  effect.subeffects.each {|subeffect|
    f.puts effect.name + "->" + subeffect.name + ";"
  }
  effect.subeffects.each {|subeffect|
    to_graphviz_rec subeffect, f
  }
end

# Generate graphviz.
def to_graphviz effect, f
  f.puts "digraph G {"
  to_graphviz_rec effect, f
  f.puts "}"
end

# ===== Start generating the effect DAG =====

# Start at Any. All effects are subeffects of Any.
any = Effect.new 'Any'
# Build the effect universe.
allocator = any.subeffect 'Allocator'
control = any.subeffect 'Control'
memory = any.subeffect 'Memory'
patch_point = any.subeffect 'PatchPoint'
interrupt_flag = memory.subeffect 'InterruptFlag'
other = memory.subeffect 'Other'
frame = memory.subeffect 'Frame'
pc = frame.subeffect 'PC'
locals = frame.subeffect 'Locals'
stack = frame.subeffect 'Stack'

# Use the smallest unsigned value needed to describe all effect bits
# If it becomes an issue, this can be generated but for now we do it manually
$int_label = 'u8'

# Assign individual bits to effect leaves and union bit patterns to nodes with subeffects
num_bits = 0
$bits = {"Empty" => ["0#{$int_label}"]}
$numeric_bits = {"Empty" => 0}
Set[any, *any.all_subeffects].sort_by(&:name).each {|effect|
  subeffects = effect.subeffects
  if subeffects.empty?
    # Assign bits for leaves
    $bits[effect.name] = ["1#{$int_label} << #{num_bits}"]
    $numeric_bits[effect.name] = 1 << num_bits
    num_bits += 1
  else
    # Assign bits for unions
    $bits[effect.name] = subeffects.map(&:name).sort
  end
}
[*any.all_subeffects, any].each {|effect|
  subeffects = effect.subeffects
  unless subeffects.empty?
    $numeric_bits[effect.name] = subeffects.map {|ty| $numeric_bits[ty.name]}.reduce(&:|)
  end
}

# ===== Finished generating the DAG; write Rust code =====

puts "// This file is @generated by src/hir/gen_hir_effect.rb."
puts "mod bits {"
$bits.keys.sort.map {|effect_name|
  subeffects = $bits[effect_name].join(" | ")
  puts "  pub const #{effect_name}: #{$int_label} = #{subeffects};"
}
puts "  pub const AllBitPatterns: [(&str, #{$int_label}); #{$bits.size}] = ["
# Sort the bit patterns by decreasing value so that we can print the densest
# possible to-string representation of an Effect. For example, Frame instead of
# PC|Stack|Locals
$numeric_bits.sort_by {|key, val| -val}.each {|effect_name, _|
  puts "    (\"#{effect_name}\", #{effect_name}),"
}
puts "  ];"
puts "  pub const NumEffectBits: #{$int_label} = #{num_bits};
}"

puts "pub mod effect_types {"
puts "  pub type EffectBits = #{$int_label};"
puts "}"

puts "pub mod abstract_heaps {
  use super::*;"
$bits.keys.sort.map {|effect_name|
    puts "  pub const #{effect_name}: AbstractHeap = AbstractHeap::from_bits(bits::#{effect_name});"
}
puts "}"

puts "pub mod effects {
  use super::*;"
$bits.keys.sort.map {|effect_name|
    puts "  pub const #{effect_name}: Effect = Effect::promote(abstract_heaps::#{effect_name});"
}
puts "}"

File.open("zjit_effects.dot", "w") do |f|
  to_graphviz(any, f)
end

