diff options
Diffstat (limited to 'tool/transform_mjit_header.rb')
-rw-r--r-- | tool/transform_mjit_header.rb | 326 |
1 files changed, 0 insertions, 326 deletions
diff --git a/tool/transform_mjit_header.rb b/tool/transform_mjit_header.rb deleted file mode 100644 index 2359ceab7c..0000000000 --- a/tool/transform_mjit_header.rb +++ /dev/null @@ -1,326 +0,0 @@ -# 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' - -PROGRAM = File.basename($0, ".*") - -module MJITHeader - ATTR_VALUE_REGEXP = /[^()]|\([^()]*\)/ - ATTR_REGEXP = /__attribute__\s*\(\(#{ATTR_VALUE_REGEXP}*\)\)/ - # Example: - # VALUE foo(int bar) - # VALUE __attribute__ ((foo)) bar(int baz) - # __attribute__ ((foo)) VALUE bar(int baz) - FUNC_HEADER_REGEXP = /\A[^\[{(]*(\s*#{ATTR_REGEXP})*[^\[{(]*\((#{ATTR_REGEXP}|[^()])*\)(\s*#{ATTR_REGEXP})*\s*/ - TARGET_NAME_REGEXP = /\A(rb|ruby|vm|insn|attr|Init)_/ - - # Predefined macros for compilers which are already supported by MJIT. - # We're going to support cl.exe too (WIP) but `cl.exe -E` can't produce macro. - SUPPORTED_CC_MACROS = [ - '__GNUC__', # gcc - '__clang__', # clang - ] - - # These macros are relied on this script's transformation - PREFIXED_MACROS = [ - 'ALWAYS_INLINE', - 'COLDFUNC', - 'inline', - 'RBIMPL_ATTR_COLD', - ] - - # 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_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 - ] - - ALWAYS_INLINED_FUNCTIONS = [ - 'vm_opt_plus', - 'vm_opt_minus', - 'vm_opt_mult', - 'vm_opt_div', - 'vm_opt_mod', - 'vm_opt_neq', - 'vm_opt_lt', - 'vm_opt_le', - 'vm_opt_gt', - 'vm_opt_ge', - 'vm_opt_ltlt', - 'vm_opt_and', - 'vm_opt_or', - 'vm_opt_aref', - 'vm_opt_aset', - 'vm_opt_aref_with', - 'vm_opt_aset_with', - 'vm_opt_not', - ] - - COLD_FUNCTIONS = %w[ - setup_parameters_complex - vm_call_iseq_setup - vm_call_iseq_setup_2 - vm_call_iseq_setup_tailcall - vm_call_method_each_type - vm_ic_update - ] - - # Return start..stop of last decl in CODE ending STOP - def self.find_decl(code, stop) - level = 0 - i = stop - while i = code.rindex(/[;{}]/, i) - if level == 0 && stop != i && decl_found?($&, i) - return decl_start($&, i)..stop - end - case $& - when '}' - level += 1 - when '{' - level -= 1 - end - i -= 1 - end - nil - end - - def self.decl_found?(code, i) - i == 0 || code == ';' || code == '}' - end - - def self.decl_start(code, i) - if i == 0 && code != ';' && code != '}' - 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) - with_code(code) do |path| - cmd = "#{cc} #{cflags} #{path}" - out = IO.popen(cmd, err: [:child, :out], &:read) - unless $?.success? - STDERR.puts "error in #{stage} header file:\n#{out}" - 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 or errors - # This assumes common.mk passes `-DMJIT_HEADER` first when it creates rb_mjit_header.h. - def self.remove_predefined_macros!(code) - code.sub!(/\A(#define [^\n]+|\n)*(#define MJIT_HEADER 1\n)/, '\2') - end - - # Return [macro, others]. But others include PREFIXED_MACROS to be used in code. - def self.separate_macro_and_code(code) - code.lines.partition do |l| - l.start_with?('#') && PREFIXED_MACROS.all? { |m| !l.start_with?("#define #{m}") } - end.map! { |lines| lines.join('') } - end - - def self.write(code, out) - # create with strict permission, then will install proper - # permission - FileUtils.mkdir_p(File.dirname(out), mode: 0700) - File.binwrite("#{out}.new", code, perm: 0600) - 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 - - def self.cl_exe?(cc) - cc =~ /\Acl(\z| |\.exe)/ - end - - # If code has macro which only supported compilers predefine, return true. - def self.supported_header?(code) - SUPPORTED_CC_MACROS.any? { |macro| code =~ /^#\s*define\s+#{Regexp.escape(macro)}\b/ } - end - - # This checks if syntax check outputs one of the following messages. - # "error: conflicting types for 'restrict'" - # "error: redefinition of parameter 'restrict'" - # If it's true, this script regards platform as AIX or Solaris and adds -std=c99 as workaround. - def self.conflicting_types?(code, cc, cflags) - with_code(code) do |path| - cmd = "#{cc} #{cflags} #{path}" - out = IO.popen(cmd, err: [:child, :out], &:read) - !$?.success? && - (out.match?(/error: conflicting types for '[^']+'/) || - out.match?(/error: redefinition of parameter '[^']+'/)) - end - end - - def self.with_code(code) - # for `system_header` pragma which can't be in the main file. - Tempfile.open(['', '.h'], mode: File::BINARY) do |f| - f.puts code - f.close - Tempfile.open(['', '.c'], mode: File::BINARY) do |c| - c.puts <<SRC -#include "#{f.path}" -SRC - c.close - return yield(c.path) - end - end - end - private_class_method :with_code -end - -if ARGV.size != 3 - abort "Usage: #{$0} <c-compiler> <header file> <out>" -end - -if STDOUT.tty? - require_relative 'lib/colorize' - color = Colorize.new -end -cc = ARGV[0] -code = File.binread(ARGV[1]) # Current version of the header file. -outfile = ARGV[2] -if MJITHeader.cl_exe?(cc) - cflags = '-DMJIT_HEADER -Zs' -else - cflags = '-S -DMJIT_HEADER -fsyntax-only -Werror=implicit-function-declaration -Werror=implicit-int -Wfatal-errors' -end - -if !MJITHeader.cl_exe?(cc) && !MJITHeader.supported_header?(code) - puts "This compiler (#{cc}) looks not supported for MJIT. Giving up to generate MJIT header." - MJITHeader.write("#error MJIT does not support '#{cc}' yet", outfile) - exit -end - -MJITHeader.remove_predefined_macros!(code) - -if MJITHeader.windows? # transformation is broken with Windows headers for now - MJITHeader.remove_harmful_macros!(code) - MJITHeader.check_code!(code, cc, cflags, 'initial') - puts "\nSkipped transforming external functions to static on Windows." - MJITHeader.write(code, outfile) - exit -end - -macro, code = MJITHeader.separate_macro_and_code(code) # note: this does not work on MinGW -code = <<header + code -#ifdef __GNUC__ -# pragma GCC system_header -#endif -header -code_to_check = "#{code}#{macro}" # macro should not affect code again - -if MJITHeader.conflicting_types?(code_to_check, cc, cflags) - cflags = "#{cflags} -std=c99" # For AIX gcc -end - -# Check initial file correctness in the manner of final output. -MJITHeader.check_code!(code_to_check, cc, cflags, 'initial') - -stop_pos = -1 -extern_names = [] -transform_logs = Hash.new { |h, k| h[k] = [] } - -# This loop changes function declarations to static inline. -while (decl_range = MJITHeader.find_decl(code, stop_pos)) - 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) - transform_logs[:def_to_decl] << decl_name - code[decl_range] = decl.sub(/{.+}/m, ';') - elsif MJITHeader::COLD_FUNCTIONS.include?(decl_name) && match = /#{MJITHeader::FUNC_HEADER_REGEXP}{/.match(decl) - header = match[0].sub(/{\z/, '').strip - header = "static #{header.sub(/\A((static|inline) )+/, '')}" - decl[match.begin(0)...match.end(0)] = '{' # remove header - code[decl_range] = "\nCOLDFUNC #{header} #{decl}" - elsif MJITHeader::ALWAYS_INLINED_FUNCTIONS.include?(decl_name) && match = /#{MJITHeader::FUNC_HEADER_REGEXP}{/.match(decl) - header = match[0].sub(/{\z/, '').strip - header = "static inline #{header.sub(/\A((static|inline) )+/, '')}" - decl[match.begin(0)...match.end(0)] = '{' # remove header - code[decl_range] = "\nALWAYS_INLINE(#{header});\n#{header} #{decl}" - 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. - transform_logs[:static_inline_decl] << decl_name - end - - code[decl_range] = "static inline #{decl}" - elsif (match = /#{MJITHeader::FUNC_HEADER_REGEXP}{/.match(decl)) && (header = match[0]) !~ /static/ - unless decl_name.match(MJITHeader::TARGET_NAME_REGEXP) - transform_logs[:skipped] << decl_name - next - end - - extern_names << decl_name - decl[match.begin(0)...match.end(0)] = '' - - if decl =~ /\bstatic\b/ - abort "#{PROGRAM}: a static decl was found inside external definition #{decl_name.dump}" - end - - header.sub!(/(extern|inline) /, ' ') - unless decl_name =~ /\Aattr_\w+_\w+\z/ # skip too-many false-positive warnings in insns_info.inc. - transform_logs[:static_inline_def] << decl_name - end - code[decl_range] = "static inline #{header}#{decl}" - end -end - -code << macro - -# Check the final file correctness -MJITHeader.check_code!(code, cc, cflags, 'final') - -MJITHeader.write(code, outfile) - -messages = { - def_to_decl: 'changing definition to declaration', - static_inline_def: 'making external definition static inline', - static_inline_decl: 'making declaration static inline', - skipped: 'SKIPPED to transform', -} -transform_logs.each do |key, decl_names| - decl_names = decl_names.map { |s| color.bold(s) } if color - puts("#{PROGRAM}: #{messages.fetch(key)}: #{decl_names.join(', ')}") -end |