1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00
ruby--ruby/tool/transform_mjit_header.rb
nobu e81687527a transform_mjit_header.rb: refactor messages and exit
* tool/transform_mjit_header.rb: print non-error messages to
  STDOUT instead of STDERR.  exit with false or abort instead of
  exit 1.

git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@62205 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2018-02-04 13:51:02 +00:00

183 lines
6.1 KiB
Ruby

# 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)