summaryrefslogtreecommitdiff
path: root/tool/transform_mjit_header.rb
diff options
context:
space:
mode:
Diffstat (limited to 'tool/transform_mjit_header.rb')
-rw-r--r--tool/transform_mjit_header.rb183
1 files changed, 183 insertions, 0 deletions
diff --git a/tool/transform_mjit_header.rb b/tool/transform_mjit_header.rb
new file mode 100644
index 00000000000..308379123b5
--- /dev/null
+++ b/tool/transform_mjit_header.rb
@@ -0,0 +1,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 = [
+ '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 strutcs/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 1
+ 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
+ STDERR.puts 'Usage: transform_mjit_header.rb <c-compiler> <header file> <out>'
+ exit 1
+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
+ STDERR.puts "\nSkipped transforming external functions to static on Windows."
+ MJITHeader.write(code, outfile)
+ exit 0
+end
+STDERR.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)
+ STDERR.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.
+ STDERR.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 =~ /static/
+ STDERR.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.
+ STDERR.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)