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.rb327
1 files changed, 0 insertions, 327 deletions
diff --git a/tool/transform_mjit_header.rb b/tool/transform_mjit_header.rb
deleted file mode 100644
index 8867c556f0..0000000000
--- a/tool/transform_mjit_header.rb
+++ /dev/null
@@ -1,327 +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
- 'ruby_abi_version',
- ]
-
- 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