summaryrefslogtreecommitdiff
path: root/tool/mk_builtin_loader.rb
blob: efb2d7d0fbca993a948c6ba3347b66b4da7235be (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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

def collect_builtin base, iseq_ary, bs, inlines
  code = iseq_ary[13]
  params = iseq_ary[10]
  prev_insn = nil
  lineno = nil

  code.each{|insn|
    case insn
    when Array
      # ok
    when Integer
      lineno = insn
      next
    else
      next
    end

    next unless Array === insn
    case insn[0]
    when :send
      ci = insn[1]
      if /\A__builtin_(.+)/ =~ ci[:mid]
        func_name = $1
        argc = ci[:orig_argc]

        if func_name ==  'inline!'
          raise "argc (#{argc}) of inline! should be 1" unless argc == 1
          raise "1st argument should be string literal" unless prev_insn[0] == :putstring
          text = prev_insn[1].rstrip

          func_name = "rb_compiled_inline#{inlines.size}"
          inlines << [func_name, [lineno, text, params]]
          argc -= 1
        end

        if bs[func_name] &&
           bs[func_name] != argc
          raise "same builtin function \"#{func_name}\", but different arity (was #{bs[func_name]} but #{argc})"
        end

        bs[func_name] = argc
      end
    else
      insn[1..-1].each{|op|
        if op.is_a?(Array) && op[0] == "YARVInstructionSequence/SimpleDataFormat"
          collect_builtin base, op, bs, inlines
        end
      }
    end
    prev_insn = insn
  }
end
# ruby mk_builtin_loader.rb TARGET_FILE.rb
# #=> generate TARGET_FILE.rbinc
#

def mk_builtin_header file
  base = File.basename(file, '.rb')
  ofile = "#{base}.rbinc"

  # bs = { func_name => argc }
  collect_builtin(base, RubyVM::InstructionSequence.compile_file(file, false).to_a, bs = {}, inlines = [])

  open(ofile, 'w'){|f|
    f.puts "// -*- c -*-"
    f.puts "// DO NOT MODIFY THIS FILE DIRECTLY."
    f.puts "// auto-generated file"
    f.puts "//   by #{__FILE__}"
    f.puts "//   with #{file}"
    f.puts
    lineno = 6

    inlines.each{|name, (body_lineno, text, params)|
      f.puts "static VALUE #{name}(rb_execution_context_t *ec, const VALUE self) {"
      lineno += 1

      params.reverse_each.with_index{|param, i|
        next unless Symbol === param
        f.puts "MAYBE_UNUSED(const VALUE) #{param} = rb_vm_lvar(ec, #{-3 - i});"
        lineno += 1
      }
      f.puts "#line #{body_lineno} \"#{file}\""
      lineno += 1

      f.puts text
      lineno += text.count("\n") + 1

      f.puts "#line #{lineno + 2} \"#{ofile}\"" # TODO: restore line number.
      f.puts "}"
      lineno += 2
    }

    f.puts "static void load_#{base}(void)"
    f.puts "{"

    table = "#{base}_table"
    f.puts "  // table definition"
    f.puts "  static const struct rb_builtin_function #{table}[] = {"
    bs.each.with_index{|(func, argc), i|
      f.puts "    RB_BUILTIN_FUNCTION(#{i}, #{func}, #{argc}),"
    }
    f.puts "    RB_BUILTIN_FUNCTION(-1, NULL, 0),"
    f.puts "  };"

    f.puts
    f.puts "  // arity_check"
    f.puts "COMPILER_WARNING_PUSH"
    f.puts "#if GCC_VERSION_SINCE(5, 1, 0) || __clang__"
    f.puts "COMPILER_WARNING_ERROR(-Wincompatible-pointer-types)"
    f.puts "#endif"
    bs.each{|func, argc|
      f.puts "  if (0) rb_builtin_function_check_arity#{argc}(#{func});"
    }
    f.puts "COMPILER_WARNING_POP"


    f.puts
    f.puts "  // load"
    f.puts "  rb_load_with_builtin_functions(#{base.dump}, #{table});"

    f.puts "}"
  }
end

ARGV.each{|file|
  # feature.rb => load_feature.inc
  mk_builtin_header file
}