diff options
Diffstat (limited to 'tool')
-rw-r--r-- | tool/transform_mjit_header.rb | 183 |
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 0000000000..308379123b --- /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) |