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
130
131
132
133
134
135
136
137
|
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 = "#{file}inc"
# bs = { func_name => argc }
collect_builtin(base, RubyVM::InstructionSequence.compile_file(file, false).to_a, bs = {}, inlines = [])
begin
f = open(ofile, 'w')
rescue Errno::EACCESS
# Fall back to the current directory
f = open(File.basename(ofile), 'w')
end
begin
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 "}"
ensure
f.close
end
end
ARGV.each{|file|
# feature.rb => load_feature.inc
mk_builtin_header file
}
|