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
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
|
# Copyright (C) 2017 Vladimir Makarov, <vmakarov@redhat.com>
# This is a script to transform functions to static inline.
# Usage: transform_mjit_header.rb <c-compiler> <header file> <out>
require 'fileutils'
require 'tempfile'
module MJITHeader
ATTR_VALUE_REGEXP = /[^()]|\([^()]*\)/
ATTR_REGEXP = /__attribute__\s*\(\((#{ATTR_VALUE_REGEXP})*\)\)/
FUNC_HEADER_REGEXP = /\A(\s*#{ATTR_REGEXP})*[^\[{(]*\((#{ATTR_REGEXP}|[^()])*\)(\s*#{ATTR_REGEXP})*\s*/
# For MinGW's ras.h. Those macros have its name in its definition and can't be preprocessed multiple times.
RECURSIVE_MACROS = %w[
RASCTRYINFO
RASIPADDR
]
IGNORED_FUNCTIONS = [
'vm_search_method_slowpath', # This increases the time to compile when inlined. So we use it as external function.
'rb_equal_opt', # Not used from VM and not compilable
]
# Return start..stop of last decl in CODE ending STOP
def self.find_decl(code, stop)
level = 0
stop.downto(0) do |i|
if level == 0 && stop != i && decl_found?(code, i)
return decl_start(code, i)..stop
elsif code[i] == '}'
level += 1
elsif code[i] == '{'
level -= 1
end
end
0..-1
end
def self.decl_found?(code, i)
i == 0 || code[i] == ';' || code[i] == '}'
end
def self.decl_start(code, i)
if i == 0 && code[i] != ';' && code[i] != '}'
0
else
i + 1
end
end
# Given DECL return the name of it, nil if failed
def self.decl_name_of(decl)
ident_regex = /\w+/
decl = decl.gsub(/^#.+$/, '') # remove macros
reduced_decl = decl.gsub(ATTR_REGEXP, '') # remove attributes
su1_regex = /{[^{}]*}/
su2_regex = /{([^{}]|#{su1_regex})*}/
su3_regex = /{([^{}]|#{su2_regex})*}/ # 3 nested structs/unions is probably enough
reduced_decl.gsub!(su3_regex, '') # remove structs/unions in the header
id_seq_regex = /\s*(#{ident_regex}(\s+|\s*[*]+\s*))*/
# Process function header:
match = /\A#{id_seq_regex}(?<name>#{ident_regex})\s*\(/.match(reduced_decl)
return match[:name] if match
# Process non-function declaration:
reduced_decl.gsub!(/\s*=[^;]+(?=;)/, '') # remove initialization
match = /#{id_seq_regex}(?<name>#{ident_regex})/.match(reduced_decl);
return match[:name] if match
nil
end
# Return true if CC with CFLAGS compiles successfully the current code.
# Use STAGE in the message in case of a compilation failure
def self.check_code!(code, cc, cflags, stage)
Tempfile.open(['', '.c']) do |f|
f.puts code
f.close
unless system("#{cc} #{cflags} #{f.path} 2>#{File::NULL}")
STDERR.puts "error in #{stage} header file:"
system("#{cc} #{cflags} #{f.path}")
exit false
end
end
end
# Remove unpreprocessable macros
def self.remove_harmful_macros!(code)
code.gsub!(/^#define #{Regexp.union(RECURSIVE_MACROS)} .*$/, '')
end
# -dD outputs those macros, and it produces redefinition warnings
def self.remove_default_macros!(code)
code.gsub!(/^#define __STDC_.+$/, '')
code.gsub!(/^#define assert\([^\)]+\) .+$/, '')
end
# This makes easier to process code
def self.separate_macro_and_code(code)
code.lines.partition { |l| !l.start_with?('#') }.flatten.join('')
end
def self.write(code, out)
FileUtils.mkdir_p(File.dirname(out))
File.write("#{out}.new", code)
FileUtils.mv("#{out}.new", out)
end
# Note that this checks runruby. This conservatively covers platform names.
def self.windows?
RUBY_PLATFORM =~ /mswin|mingw|msys/
end
end
if ARGV.size != 3
abort 'Usage: transform_mjit_header.rb <c-compiler> <header file> <out>'
end
cc = ARGV[0]
code = File.read(ARGV[1]) # Current version of the header file.
outfile = ARGV[2]
if cc =~ /\Acl(\z| |\.exe)/
cflags = '-DMJIT_HEADER -Zs'
else
cflags = '-S -DMJIT_HEADER -fsyntax-only -Werror=implicit-function-declaration -Werror=implicit-int -Wfatal-errors'
end
if MJITHeader.windows?
MJITHeader.remove_harmful_macros!(code)
end
MJITHeader.remove_default_macros!(code)
# Check initial file correctness
MJITHeader.check_code!(code, cc, cflags, 'initial')
if MJITHeader.windows? # transformation is broken with Windows headers for now
puts "\nSkipped transforming external functions to static on Windows."
MJITHeader.write(code, outfile)
exit
end
puts "\nTransforming external functions to static:"
code = MJITHeader.separate_macro_and_code(code) # note: this does not work on MinGW
stop_pos = code.match(/^#/).begin(0) # See `separate_macro_and_code`. This ignores proprocessors.
extern_names = []
# This loop changes function declarations to static inline.
loop do
decl_range = MJITHeader.find_decl(code, stop_pos)
break if decl_range.end < 0
stop_pos = decl_range.begin - 1
decl = code[decl_range]
decl_name = MJITHeader.decl_name_of(decl)
if MJITHeader::IGNORED_FUNCTIONS.include?(decl_name) && /#{MJITHeader::FUNC_HEADER_REGEXP}{/.match(decl)
puts "transform_mjit_header: changing definition of '#{decl_name}' to declaration"
code[decl_range] = decl.sub(/{.+}/m, ';')
elsif extern_names.include?(decl_name) && (decl =~ /#{MJITHeader::FUNC_HEADER_REGEXP};/)
decl.sub!(/(extern|static|inline) /, ' ')
unless decl_name =~ /\Aattr_\w+_\w+\z/ # skip too-many false-positive warnings in insns_info.inc.
puts "transform_mjit_header: making declaration of '#{decl_name}' static inline"
end
code[decl_range] = "static inline #{decl}"
elsif (match = /#{MJITHeader::FUNC_HEADER_REGEXP}{/.match(decl)) && (header = match[0]) !~ /static/
extern_names << decl_name
decl[match.begin(0)...match.end(0)] = ''
if decl =~ /\bstatic\b/
puts "warning: a static decl inside external definition of '#{decl_name}'"
end
header.sub!(/(extern|inline) /, ' ')
unless decl_name =~ /\Aattr_\w+_\w+\z/ # skip too-many false-positive warnings in insns_info.inc.
puts "transform_mjit_header: making external definition of '#{decl_name}' static inline"
end
code[decl_range] = "static inline #{header}#{decl}"
end
end
# Check the final file correctness
MJITHeader.check_code!(code, cc, cflags, 'final')
MJITHeader.write(code, outfile)
|