rubinius/rubinius

View on GitHub
build/scripts/field_extract.rb

Summary

Maintainability
F
1 wk
Test Coverage
class BasicPrimitive
  attr_accessor :pass_state
  attr_accessor :pass_self
  attr_accessor :pass_call_frame
  attr_accessor :pass_message
  attr_accessor :pass_arguments
  attr_accessor :raw
  attr_accessor :safe
  attr_accessor :can_fail

  def output_header(str)
    str << "Object* Primitives::#{@name}(STATE, Executable* exec, Module* mod, Arguments& args) {\n"
    str << "  Object* ret;\n"
    return str if @raw
    str << "  Object* self;\n" if @pass_self
  end

  def output_args(str, arg_types)
    unless @pass_arguments
      str << "  if(unlikely(args.total() != #{arg_types.size}))\n"
      str << "    goto fail;\n\n"
    end

    args = []
    i = -1
    arg_types.each do |t|
      i += 1
      case t
      when :arguments
        args << "args"
      else
        str << "  #{t}* a#{i};\n"
        str << "  a#{i} = try_as<#{t}>(args.get_argument(#{i}));\n"
        str << "  if(unlikely(a#{i} == NULL))\n"
        str << "    goto fail;\n\n"
        args << "a#{i}"
      end
    end
    args.unshift "self" if @pass_self
    args.unshift "state" if @pass_state

    if @pass_message
      args.push "exec"
      args.push "mod"
    end

    return args
  end

  def prim_return(str, indent=2)
    str << "#{' ' * indent}return ret;\n"
  end

  def output_call(str, call, args)
    str << "\n"
    str << "  try {\n"
    str << "    RUBINIUS_METHOD_PRIMITIVE_ENTRY_HOOK(state, mod, args.name());\n"
    str << "    ret = #{call}(#{args.join(', ')});\n"
    str << "    RUBINIUS_METHOD_PRIMITIVE_RETURN_HOOK(state, mod, args.name());\n"
    str << "  } catch(const RubyException& exc) {\n"
    str << "    if(exc.exception->locations()->nil_p()) {\n"
    str << "      exc.exception->locations(state, Location::from_call_stack(state));\n"
    str << "    }\n"
    str << "    state->raise_exception(exc.exception);\n"
    str << "    RUBINIUS_METHOD_PRIMITIVE_RETURN_HOOK(state, mod, args.name());\n"
    str << "    return NULL;\n"
    str << "  }\n"
    str << "\n"
    str << "  if(unlikely(ret == reinterpret_cast<Object*>(kPrimitiveFailed)))\n"
    str << "    goto fail;\n\n"
    prim_return(str);
    str << "fail:\n"
    str << "  return CompiledCode::primitive_failed(state, exec, mod, args);\n"
    str << "}\n\n"
  end

end

class CPPPrimitive < BasicPrimitive
  def initialize(name, type)
    @type = type
    @name = name
  end

  attr_accessor :type, :name
  attr_accessor :cpp_name, :return_type, :arg_types

  def arg_count
    arg_types.size
  end

  def generate_glue
    str = ""
    if @raw
      arg_count = nil
    else
      arg_count = arg_types.size
    end

    output_header str

    str << "  #{@type}* recv = try_as<#{@type}>(args.recv());\n"
    str << "  if(unlikely(recv == NULL)) goto fail;\n"

    if @raw
      str << "\n"
      str << "  try {\n"
      str << "    ret = recv->#{@cpp_name}(state, exec, mod, args);\n"
      str << "  } catch(const RubyException& exc) {\n"
      str << "    if(exc.exception->locations()->nil_p()) {\n"
      str << "      exc.exception->locations(state, Location::from_call_stack(state));\n"
      str << "    }\n"
      str << "    state->raise_exception(exc.exception);\n"
      str << "    return NULL;\n"
      str << "  }\n"
      str << "  if(ret == Primitives::failure()) goto fail;\n"
      str << "  return ret;\n"
      str << "\n"
      str << "fail:\n"
      str << "  return CompiledCode::primitive_failed(state, exec, mod, args);\n"
      str << "}\n\n"
    else
      args = output_args str, arg_types
      str << "    self = recv;\n" if @pass_self

      output_call str, "recv->#{@cpp_name}", args
    end

    return str
  end

  def generate_invoke_stub
    return if @raw or @pass_arguments

    str = ""
    if arg_types.empty?
      arg_list = ""
    else
      list = []
      arg_types.size.times { |i| list << "Object* ra#{i}" }
      arg_list = ", " + list.join(", ")
    end

    str << "extern \"C\" Object* invoke_#{@name}(STATE, Object** args, int arg_count) {\n"
    str << "  Object* ret;\n"

    i = 0
    arg_types.each do |t|
      str << "  #{t}* a#{i};\n"
      i += 1
    end

    arity = arg_types.size + 1

    if @type == "Object"
      str << "  Object* self;\n"
    else
      str << "  #{@type}* self;\n"
    end

    str << "  if(arg_count != #{arity}) goto fail;\n"

    if @type == "Object"
      str << "  self = args[0];\n"
    else
      str << "  self = try_as<#{@type}>(args[0]);\n"
      str << "  if(unlikely(self == NULL)) goto fail;\n"
    end

    args = []
    i = 0
    arg_types.each do |t|
      emit_fail = true
      str << "  a#{i} = try_as<#{t}>(args[#{i + 1}]);\n"
      str << "  if(unlikely(a#{i} == NULL)) goto fail;\n"
      args << "a#{i}"
      i += 1
    end

    args.unshift "recv" if @pass_self
    args.unshift "state" if @pass_state

    str << "\n"
    str << "  try {\n"
    str << "    ret = self->#{@cpp_name}(#{args.join(', ')});\n"
    str << "  } catch(const RubyException& exc) {\n"
    str << "    if(exc.exception->locations()->nil_p()) {\n"
    str << "      exc.exception->locations(state, Location::from_call_stack(state));\n"
    str << "    }\n"
    str << "    state->raise_exception(exc.exception);\n"
    str << "    return NULL;\n"
    str << "  }\n"
    str << "\n"
    str << "  if(unlikely(ret == reinterpret_cast<Object*>(kPrimitiveFailed)))\n"
    str << "    goto fail;\n\n"
    str << "  return ret;\n"

    str << "fail:\n"
    str << "  Exception::internal_error(state, \"invoked primitive failed\");"
    str << "  return NULL;\n"

    str << "}\n\n"
  end
end

class CPPStaticPrimitive < CPPPrimitive
  def generate_glue
    str = ""
    output_header str

    if @raw
      str << "\n"
      str << "  try {\n"
      str << "    return #{@type}::#{@cpp_name}(state, exec, mod);\n"
      str << "  } catch(const RubyException& exc) {\n"
      str << "    if(exc.exception->locations()->nil_p()) {\n"
      str << "      exc.exception->locations(state, Location::from_call_stack(state));\n"
      str << "    }\n"
      str << "    state->raise_exception(exc.exception);\n"
      str << "    return NULL;\n"
      str << "  }\n"
      str << "\n"
      str << "}\n\n"
    else
      args = output_args str, arg_types
      str << "    self = args.recv();\n" if @pass_self
      output_call str, "#{@type}::#{@cpp_name}", args
    end
    return str
  end

  def generate_invoke_stub
    return if @raw or @pass_arguments

    str = ""
    if arg_types.empty?
      arg_list = ""
    else
      list = []
      arg_types.size.times { |i| list << "Object* ra#{i}" }
      arg_list = ", " + list.join(", ")
    end

    str << "extern \"C\" Object* invoke_#{@name}(STATE, Object** args, int arg_count) {\n"
    str << "  Object* ret;\n"

    i = 0
    arg_types.each do |t|
      str << "  #{t}* a#{i};\n"
      i += 1
    end

    arity = arg_types.size
    arity += 1 if @pass_self
    str << "  if(arg_count != #{arity}) goto fail;\n"

    args = []
    i = 0
    arg_types.each do |t|
      str << "  a#{i} = try_as<#{t}>(args[#{i}]);\n"
      str << "  if(unlikely(a#{i} == NULL)) goto fail;\n"
      args << "a#{i}"
      i += 1
    end

    args.unshift "args[arg_count-1]" if @pass_self
    args.unshift "state" if @pass_state

    str << "\n"
    str << "  try {\n"
    str << "    ret = #{@type}::#{@cpp_name}(#{args.join(', ')});\n"
    str << "  } catch(const RubyException& exc) {\n"
    str << "    if(exc.exception->locations()->nil_p()) {\n"
    str << "      exc.exception->locations(state, Location::from_call_stack(state));\n"
    str << "    }\n"
    str << "    state->raise_exception(exc.exception);\n"
    str << "    return NULL;\n"
    str << "  }\n"
    str << "\n"
    str << "  if(unlikely(ret == reinterpret_cast<Object*>(kPrimitiveFailed)))\n"
    str << "    goto fail;\n\n"
    str << "  return ret;\n"

    str << "fail:\n"
    str << "  Exception::internal_error(state, \"invoked primitive failed\");"
    str << "  return NULL;\n"

    str << "}\n\n"
  end
end

class CPPOverloadedPrimitive < BasicPrimitive
  def initialize(prim)
    @name     = prim.name
    @type     = prim.type
    @cpp_name = prim.cpp_name
    @kinds    = [prim]
  end

  def add_kind(prim)
    if prim.arg_types.size != 1
      raise "Invalid overloaded primitive: Only 2nd arg can be overloaded."
    end
    @kinds << prim
  end

  def generate_glue
    str = ""
    output_header str

    str << "  #{@type}* recv = try_as<#{@type}>(args.recv());\n"
    str << "  if(likely(recv)) {\n"
    str << "    if(args.total() != 1) goto fail;\n"

    @kinds.each do |prim|
      type = prim.arg_types.first
      str << "    if(#{type}* arg = try_as<#{type}>(args.get_argument(0))) {\n"
      str << "      try {\n"
      if @pass_state
        call = "        ret = recv->#{@cpp_name}(state, arg);\n"
      else
        call = "        ret = recv->#{@cpp_name}(arg);\n"
      end
      str << call
      str << "      } catch(const RubyException& exc) {\n"
      str << "        if(exc.exception->locations()->nil_p()) {\n"
      str << "          exc.exception->locations(state, Location::from_call_stack(state));\n"
      str << "        }\n"
      str << "        state->raise_exception(exc.exception);\n"
      str << "        return NULL;\n"
      str << "      }\n"
      str << "      if(likely(ret != reinterpret_cast<Object*>(kPrimitiveFailed))) {\n"
      prim_return(str, 8);
      str << "      }\n"
      str << "    }\n"
      str << "\n"
    end

    str << "  }\n"
    str << "fail:\n"
    str << "  return CompiledCode::primitive_failed(state, exec, mod, args);\n"
    str << "}\n\n"
    return str
  end
end

class CPPClass
  def initialize(name)
    @name = name
    @super = nil
    @fields = []
    @flags = {}
    @primitives = {}
    @num_fields = 0
    @subclasses = []
  end

  attr_accessor :name, :super, :fields, :primitives, :flags, :num_fields
  attr_accessor :subclasses

  def add_field(idx, name, type, flag=nil)
    if flag
      @flags[name] = flag
    end

    @fields << [name, type, idx]
  end

  def field_index_offset
    return @super.all_fields if @super
    return 0
  end

  def all_fields
    if @super
      ary = @super.all_fields
    else
      ary = []
    end

    offset = ary.size
    @fields.each do |n,t,i|
      if readonly?(n)
        flags = {:readonly => true}
      else
        flags = {}
      end
      ary << [n, t, i + offset, flags]
    end

    return ary
  end

  def access_primitives
    all_fields.map do |name, type, offset, flags|
      "access_#{@name}_#{name}"
    end
  end

  def readonly?(name)
    @flags[name] == "readonly"
  end

  def add_primitive(name, cpp_name, ret, args, overload=false)
    prim = CPPPrimitive.new(name, @name)
    prim.cpp_name = cpp_name
    prim.return_type = ret
    prim.arg_types = args

    if cur = @primitives[name]
      unless overload
        raise "Illegal overloading of primitive #{name}"
      end

      unless prim.arg_types.size == 1
        raise "Overloaded variant of #{name} must only take one arg"
      end

      if cur.kind_of? CPPOverloadedPrimitive
        cur.add_kind prim
        return cur
      else
        over = CPPOverloadedPrimitive.new(cur)
        @primitives[name] = over
        over.add_kind prim
        return over
      end

    else
      @primitives[name] = prim
    end

    return prim
  end

  def add_static_primitive(name, cpp_name, ret, args)
    prim = CPPStaticPrimitive.new(name, @name)
    prim.cpp_name = cpp_name
    prim.return_type = ret
    prim.arg_types = args

    if @primitives[name]
      raise "Already defined primitive #{name}"
    end

    @primitives[name] = prim
    return prim
  end

  def generate_gets
    str = ""
    all_fields.each do |name, type, idx, flags|
      str << "  case #{idx}:\n"
      str << "    return target->#{name}();\n"
    end

    return str
  end

  def generate_sets
    str = ""
    all_fields.each do |name, type, idx, flags|
      str << "  case #{idx}:\n"
      if flags[:readonly]
        str << "    Exception::raise_assertion_error(state, \"#{name} is readonly\");\n"
      else
        str << "    target->#{name}(state, val->nil_p() ? nil<#{type}>() : as<#{type}>(val));\n"
      end
      str << "    return;\n"
    end

    return str
  end

  def generate_accessors
    str = ""
    all_fields.each do |name, type, idx, flags|
      str << "Object* Primitives::access_#{@name}_#{name}(STATE, Executable* exec, Module* mod,
                   Arguments& args) {\n"
      str << "  AccessVariable* access = as<AccessVariable>(exec);\n"
      str << "  if(access->write()->true_p()) {\n"
      str << <<-ARGS
        if(args.total() != 1) {
          Exception::raise_argument_error(state, 1, args.total());
          return NULL;
        }
      ARGS
      str << "    Object* val = args.get_argument(0);\n"
      str << "    if(val->nil_p()) {\n"
      str << "      as<#{@name}>(args.recv())->#{name}(state, nil<#{type}>());\n"
      str << "    } else {\n"
      str << "      as<#{@name}>(args.recv())->#{name}(state, as<#{type}>(val));\n"
      str << "    }\n"
      str << "    return args.get_argument(0);\n"
      str << "  }\n"
      str << <<-ARGS
        if(args.total() != 0) {
          Exception::raise_argument_error(state, 0, args.total());
          return NULL;
        }
      ARGS
      str << "  return as<#@name>(args.recv())->#{name}();\n"
      str << "}\n\n"
    end

    return str

  end

  def generate_typechecks
    out = generate_sets()
    return '' if out.strip.empty?

    <<-EOF
void #{@name}::Info::set_field(STATE, Object* _t, size_t index, Object* val) {
  #{@name}* target = as<#{@name}>(_t);

  switch(index) {
#{out}  }
}

Object* #{@name}::Info::get_field(STATE, Object* _t, size_t index) {
  #{@name}* target = as<#{@name}>(_t);

  switch(index) {
#{generate_gets}  }

  std::ostringstream error;
  error << "Unable to access field " << index << " in a #{@name} instance";

  Exception::raise_assertion_error(state, error.str().c_str());

  return cNil; // never reached
}

    EOF
  end

  def generate_marks(cpp)
    str = ''

    str << generate_marks(cpp.super) if cpp.super

    cpp.fields.each do |name, type, idx|
      str << <<-EOF
  f(state, obj->p_#{name}());
      EOF
    end

    str
  end

  def generate_visits(cpp)
    str = ''

    str << generate_visits(cpp.super) if cpp.super

    cpp.fields.each do |name, type, idx|
      str << <<-EOF
  f(state, obj->p_#{name}());
      EOF
    end

    str
  end

  def generate_mark
    marks = generate_marks(self).rstrip

    str = ''

    str << <<-EOF unless marks.empty?
void #{@name}::Info::auto_mark(STATE, Object* object, std::function<void (STATE, Object**)> f) {
  #{@name}* obj = as<#{@name}>(object);

#{marks}
}

    EOF

    str
  end

  def generate_visit
    visits = generate_visits(self).rstrip

    str = ''

    str << <<-EOF unless visits.empty?
void #{@name}::Info::visit(STATE, Object* object, std::function<void (STATE, Object**)> f) {
  #{@name}* obj = as<#{@name}>(object);

  before_visit(state, object, f);

#{visits}

  after_visit(state, object, f);
}

    EOF

    str
  end

  def kind_of_code(what)
    case @name
    when "Fixnum"
      return "#{what}->fixnum_p()"
    when "Symbol"
      return "#{what}->symbol_p()"
    when "TrueClass"
      return "#{what}->true_p()"
    when "FalseClass"
      return "#{what}->false_p()"
    when "NilClass"
      return "#{what}->nil_p()"
    else
      return "(#{what}->reference_p() && #{what}->type_id() == #{@name}Type)"
    end
  end

  def kind_of_conditions(what)
    [kind_of_code(what)] + @subclasses.map do |cpp|
      cpp.kind_of_conditions(what)
    end
  end

  def generate_kind_of
    checks = kind_of_conditions("obj")

    # We forward declare the class in here to keep everything happy
    str = <<-CPP
template <>
SPECIALIZATION_STORAGE bool kind_of<#{@name}>(const Object* obj) {
  return #{checks.join(' || ')};
}

    CPP
  end

  def class_has_object_size
    @flags[:object_size] = true
  end

  def class_has_object_size?
    @flags[:object_size]
  end
end

class CPPParser

  attr_reader :classes, :class_order

  def initialize
    @classes = Hash.new
    @class_order = []

    @type_map = {
      "InstructionSequence" => :InstructionSequence,
      "Symbol" => :Symbol,
      "Object" => :Object,
      "Tuple"  => :Tuple,
      "MemoryPointer" => :MemoryPointer,
      "LexicalScope" => :LexicalScope,
      "Integer" => :Integer,
      "Fixnum" => :Fixnum,
      "CompiledCode" => :CompiledCode,
      "String" => :String,
      "Module" => :Module,
      "Class" => :Class,
      "Float" => :Float,
      "Bignum" => :Bignum,
      "Hash" => :Hash,
      "Channel" => :Channel,
      "DataHeader" => :DataHeader,
    }

  end

  def add_class(name, obj)
    @class_order << name unless @class_order.include? name
    @classes[name] = obj
  end

  def strip_and_map(type, map)
    if type[-1] == ?*
      type = type[0..-2]
    end

    if val = map[type]
      return val
    end

    raise "Unable to resolve type '#{type}'"
  end

  def parse_stream(f)
    class_pattern = /class\s+([^\s]+)\s*:\s*public\s+([^\s]+)/
    slot_pattern = %r!^\s*(\w+)\*?\s+\*?_(\w+)_\s*;\s*//\s*slot(.*)!
    accessor_pattern = %r!^\s*attr_(accessor|reader|writer)\((\w+),\s*(\w+)\)!
    primitive_pattern = %r%^\s*//\s+Rubinius.primitive([?!\+])?\s+:(.*)\s*$%
    prototype_pattern = %r!\s*(static\s+)?([\w\*]+)\s+([\w]+)\((.*)\)!
    object_size_pattern = %r|size_t\s+object_size\s*\(const\s+ObjectHeader\s*|

    while l = f.gets
      next unless m = class_pattern.match(l)
      if m[2] == "DataHeader"
        cpp = CPPClass.new(m[1])
      elsif m[2] != "Object" and m[2] != "ObjectHeader"
        # We've found a class definition that doesn't extend Object.
        # If we recognize the superclass, make a new CPPClass instance
        # for this class definition.
        if sup = @classes[m[2]]
          cpp = CPPClass.new(m[1])
          cpp.super = sup
          sup.subclasses << cpp
        else
          next
        end
      else
        # A class definition directly subclassing Object or ObjectHeader
        cpp = CPPClass.new(m[1])
      end

      add_class(cpp.name, cpp)

      @type_map[cpp.name] = cpp.name.to_sym

      # We are finished with this class if it was a one-liner.
      next if /\};/.match(l)

      # Otherwise, keep scanning to look for fields and primitives
      idx = 0
      while l = f.gets
        break if /^\s*class\s+/.match(l)

        # Field declarations marked with a "// slot" comment
        if m = slot_pattern.match(l)
          type = m[1]
          name = m[2]

          # Optional 'flag' argument after '// slot'
          # Currently 'readonly' is the only supported flag
          if m[3]
            flag = m[3].strip
          else
            flag = nil
          end

          field_type = @type_map[type] || type.to_s

          cpp.add_field idx, name, field_type, flag
          idx += 1
        # A attr_{accessor, reader, writer}(variable, Type) definition
        elsif m = accessor_pattern.match(l)
          name = m[2]
          type = m[3]

          field_type = @type_map[type] || type.to_s

          cpp.add_field idx, name, field_type, nil
          idx += 1
        # A primitive declaration marked with '// Rubinius.primitive'
        elsif m = primitive_pattern.match(l)
          overload = m[1] == "!"
          raw = m[1] == "?"
          safe = m[1] == "+"
          prim = m[2]
          prototype = f.gets
          pass_state = false
          pass_self = false
          pass_call_frame = false
          pass_message = false
          pass_arguments = false

          m = prototype_pattern.match(prototype)
          unless m
            raise "Unable to parse: '#{prototype}'"
          end
          args = m[4].split(/\s*,\s*/)
          # If the first argument is the +STATE+ macro, handle it in +output_args+
          if args.first == "STATE"
            args.shift and pass_state = true

            # If the second argument is +Object* self+, we will automatically pass
            # in the receiver of the primitive message in +output_call+
            if args.first == "Object* self"
              args.shift and pass_self = true
            end
          end

          if i = args.index("Arguments& args")
            pass_arguments = true
            args[i] = :arguments
          end

          if raw then
            arg_types = []
          else
            arg_types = args.map do |a|
              case a
              when Symbol
                a
              else
                strip_and_map(a.split(/\s+/, 2).first, @type_map)
              end
            end
          end

          if m[1]
            if overload
              raise "Unable to overload static primitives."
            end
            obj = cpp.add_static_primitive prim, m[3], @type_map[m[2]], arg_types
          else
            obj = cpp.add_primitive prim, m[3], @type_map[m[2]], arg_types, overload
          end

          obj.safe = safe
          obj.raw = raw
          obj.pass_state = pass_state
          obj.pass_self = pass_self
          obj.pass_call_frame = pass_call_frame
          obj.pass_message = pass_message
          obj.pass_arguments = pass_arguments
        elsif object_size_pattern.match(l)
          cpp.class_has_object_size
        end
      end

      # If ::Info class has object_size method, we cannot simply return
      # sizeof of it.
      if l =~ /TypeInfo/
        while l = f.gets
          break if /^\s*class/.match(l)
          cpp.class_has_object_size if object_size_pattern.match(l)
        end
      elsif l && m = l.match(/public\s+(\S+)::Info/)
        # A class definition subclasses another Info class. Probably, should check
        # here for object_size either if subclass doesn't have one.
        superclass = @classes[m[1]]
        cpp.class_has_object_size if superclass && superclass.class_has_object_size?
      end

      cpp.num_fields = idx + 1
      redo if l
    end
  end
end

parser = CPPParser.new

includes = ARGV.map do |include|
  "#include \"#{include.sub(/^vm\//, '')}\""
end.join "\n"

inputs = ARGV.sort
builtins = Dir['machine/builtin/*.hpp'].sort

missing = builtins - inputs

DEV_NULL = RUBY_PLATFORM =~ /mingw|mswin/ ? 'NUL' : '/dev/null'

unless missing.empty? then
  abort "Missing #{missing.join ', '} in rakelib/vm.rake's field_extract list"
end

ARGV.each do |file|
  File.open(file) do |f|
    parser.parse_stream f
  end
end

def write_if_new(path)
  tmp_path = "#{path}.tmp"

  File.open tmp_path, 'w' do |f|
    yield f
  end

  File.rename tmp_path, path unless system("diff -q #{path} #{tmp_path} > #{DEV_NULL} 2>&1")
ensure
  File.unlink tmp_path if File.exist? tmp_path
end

write_if_new 'machine/includes.hpp' do |f|
  f << <<-EOF
#ifndef GEN_INCLUDES_HPP
#define GEN_INCLUDES_HPP

// DO NOT INCLUDE THIS IN SOMETHING THAT DOES NOT INCLUDE A GENERATED FILE

#{includes}

#endif
  EOF
end

write_if_new "machine/typechecks.hpp" do |f|
  f.puts "size_t TypeInfo::instance_sizes[(int)LastObjectType] = {ObjectHeader::align(sizeof(Object))};"
  f.puts "void TypeInfo::auto_init(Memory* memory) {"
  parser.classes.sort_by {|n, _| n }.each do |n, cpp|
    f.puts "  {"
    f.puts "    TypeInfo *ti = new #{n}::Info(#{n}::type);"
    f.puts "    ti->type_name = std::string(\"#{n}\");"
    if cpp.class_has_object_size?
      f.puts "    TypeInfo::instance_sizes[#{n}::type] = 0;"
    else
      f.puts "    TypeInfo::instance_sizes[#{n}::type] = ObjectHeader::align(sizeof(#{n}));"
    end
    f.puts "    ti->instance_size = ObjectHeader::align(sizeof(#{n}));"
    f.puts "    memory->add_type_info(memory, ti);"
    f.puts "  }"
    f.puts
  end
  f.puts "}"
  f.puts

  f.puts "void TypeInfo::auto_learn_fields(STATE) {"
  parser.classes.sort_by {|n, _| n }.each do |n, cpp|
    f.puts "  {"
    f.puts "    TypeInfo* ti = state->memory()->find_type(#{n}::type);"

    fields = cpp.all_fields
    f.puts "    ti->slot_accessors.resize(#{fields.size});\n"
    f.puts "    ti->slot_types.resize(#{fields.size});\n"
    fields.each do |name, type, idx|
      symbol_name = "access_#{n}_#{name}"
      f.puts "    ti->slots[state->symbol(\"@#{name}\")->index()] = #{idx};"
      f.puts "    ti->slot_types[#{idx}] = #{type}::type;"
      f.puts "    ti->slot_accessors[#{idx}] = &Primitives::#{symbol_name};"
    end
    f.puts "    ti->populate_slot_locations();"
    f.puts "  }"
    f.puts
  end
  f.puts "}"

  parser.classes.sort_by {|n, _| n }.each do |n, cpp|
    next if cpp.all_fields.size == 0

    f.puts "void #{n}::Info::populate_slot_locations() {"

    if cpp.super
      f.puts "  this->#{cpp.super.name}::Info::populate_slot_locations();\n"
      offset = cpp.super.all_fields.size
    else
      offset = 0
    end

    f.puts "  slot_locations.resize(#{cpp.all_fields.size});\n"

    cpp.fields.each do |name, type, idx|
      f.puts "  slot_locations[#{offset + idx}] = FIELD_OFFSET(#{n}, _#{name}_);\n"
    end
    f.puts "}"
  end

  parser.classes.sort_by {|n, _| n }.each do |n, cpp|
    f.puts cpp.generate_typechecks
    f.puts cpp.generate_mark
    f.puts cpp.generate_visit
  end
end

write_if_new "machine/primitives_declare.hpp" do |f|
  total_prims = 0
  parser.classes.sort_by {|n, _| n }.each do |n, cpp|
    total_prims += cpp.primitives.size
    cpp.primitives.map{|pn, prim| pn }.sort.each do |pn|
      f.puts "static Object* #{pn}(STATE, Executable* exec, Module* mod, Arguments& args);"
    end

    total_prims += cpp.access_primitives.size
    cpp.access_primitives.sort.each do |name|
      f.puts "static Object* #{name}(STATE, Executable* exec, Module* mod, Arguments& args);"
    end
  end

  f.puts "const static int cTotalPrimitives = #{total_prims};"
end

write_if_new "machine/object_types.hpp" do |f|
  stripped = parser.classes.reject {|n, cpp| cpp.name =~ /(Nil|True|False)Class/ }

  f.puts "#ifndef RBX_VM_OBJECT_TYPES_HPP"
  f.puts "#define RBX_VM_OBJECT_TYPES_HPP"
  f.puts
  f.puts "typedef enum {"
  f.puts "  InvalidType = 0,"
  f.puts "  NilType,"
  f.puts "  TrueType,"
  f.puts "  FalseType,"
  stripped.map { |n, cpp| cpp.name }.sort.each do |name|
    f.puts "  #{name}Type,"
  end
  f.puts "  LastObjectType"
  f.puts "} object_type;"
  f.puts
  f.puts "#endif"
end

write_if_new "machine/kind_of.hpp" do |f|
  parser.classes.sort_by {|n, _| n }.each do |n, cpp|
    next if cpp.name == "Object"
    f.puts "class #{cpp.name};"
    f.puts cpp.generate_kind_of
  end
end

class PrimitiveCodeGenerator
  def initialize(parser)
    classes = parser.classes.sort_by { |n, | n }

    @indexes = {}
    @invoke_functions = []

    @names = []
    @primitives = []

    @files = classes.map do |n, file|
      primitives = file.primitives.sort_by { |n, | n }
      primitives.each { |n, p| @names << n }

      @names += file.access_primitives

      @primitives += primitives

      file
    end

    @names.sort!
  end

  def generate
    method_primitives
    invoke_primitives
    accessor_primitives
    method_resolver
    invoke_resolver
  end

  def method_primitives
    write_if_new "machine/method_primitives.hpp" do |f|
      @primitives.each { |_, p| f << p.generate_glue }
    end
  end

  def invoke_primitives
    write_if_new "machine/invoke_primitives.hpp" do |f|
      @primitives.each do |n, p|
        if p.respond_to? :generate_invoke_stub
          code = p.generate_invoke_stub
          if code
            f << code
            @invoke_functions << [n, p]
          end
        end
      end
    end
  end

  def accessor_primitives
    write_if_new "machine/accessor_primitives.hpp" do |f|
      @files.each { |file| f << file.generate_accessors }
    end
  end

  def method_resolver
    write_if_new "machine/method_resolver.hpp" do |f|
      f.puts "executor Primitives::resolve_primitive(STATE, Symbol* name, int* index) {"

      @names.each_with_index do |name, index|
        @indexes[name] = index

        f.puts <<-EOC
  if(name == state->symbol("#{name}", #{name.bytesize})) {
    if(index) *index = #{index};
    return &Primitives::#{name};
  }

        EOC
      end

      f.puts <<-EOC
  return &Primitives::unknown_primitive;
  // commented out while we have soft primitive failures
  // throw std::runtime_error(msg.c_str());
}
      EOC
    end
  end

  def invoke_resolver
    write_if_new "machine/invoke_resolver.hpp" do |f|
      f.puts "InvokePrimitive Primitives::get_invoke_stub(STATE, Symbol* name) {"

      @invoke_functions.each do |n, |
        f.puts <<-EOC
  if(name == state->symbol("#{n}", #{n.bytesize})) {
    return &invoke_#{n};
  }
        EOC
      end

      f.puts "  return &invoke_unknown_primitive;"
      f.puts "}"
    end
  end
end

PrimitiveCodeGenerator.new(parser).generate