[前][次][番号順一覧][スレッド一覧]

ruby-changes:68554

From: Alan <ko1@a...>
Date: Thu, 21 Oct 2021 08:08:13 +0900 (JST)
Subject: [ruby-changes:68554] cec197696f (master): Add example handler for ujit and scrape it from vm.o

https://git.ruby-lang.org/ruby.git/commit/?id=cec197696f

From cec197696f3edcff553373e9597130fde2d1f7be Mon Sep 17 00:00:00 2001
From: Alan Wu <XrXr@u...>
Date: Thu, 3 Sep 2020 11:08:16 -0400
Subject: Add example handler for ujit and scrape it from vm.o

---
 gen-ujit-example-header.rb          | 105 ++++++++++++++++++++++++++++++++++++
 iseq.c                              |  11 +++-
 iseq.h                              |   3 ++
 tool/ruby_vm/models/instructions.rb |  65 +++++++++++++++++++++-
 tool/ruby_vm/views/vm.inc.erb       |  10 ++++
 5 files changed, 192 insertions(+), 2 deletions(-)
 create mode 100644 gen-ujit-example-header.rb

diff --git a/gen-ujit-example-header.rb b/gen-ujit-example-header.rb
new file mode 100644
index 0000000000..5ad0c65f02
--- /dev/null
+++ b/gen-ujit-example-header.rb
@@ -0,0 +1,105 @@ https://github.com/ruby/ruby/blob/trunk/gen-ujit-example-header.rb#L1
+def get_example_instruction_id
+  # TODO we could get this from the script that generates vm.inc instead of dothings this song and dance
+  `dwarfdump --name='YARVINSN_ujit_call_example' vm.o`.each_line do |line|
+    if (id = line[/DW_AT_const_value\s\((\d+\))/, 1])
+      p [__method__, line]
+      return id.to_i
+    end
+  end
+  raise
+end
+
+def get_fileoff
+  # use the load command to figure out the offset to the start of the content of vm.o
+  `otool -l vm.o`.each_line do |line|
+    if (fileoff = line[/fileoff (\d+)/, 1])
+      p [__method__, line]
+      return fileoff.to_i
+    end
+  end
+  raise
+end
+
+def get_symbol_offset(symbol)
+  `nm vm.o`.each_line do |line|
+    if (offset = line[Regexp.compile('(\h+).+' + Regexp.escape(symbol) + '\Z'), 1])
+      p [__method__, line]
+      return Integer(offset, 16)
+    end
+  end
+  raise
+end
+
+def readint8b(offset)
+  bytes = IO.binread('vm.o', 8, offset)
+  bytes.unpack('q').first #  this is native endian but we want little endian. it's fine if the host moachine is x86
+end
+
+
+def disassemble(offset)
+  command = "objdump --x86-asm-syntax=intel --start-address=#{offset} --stop-address=#{offset+50} -d vm.o"
+  puts "Running: #{command}"
+  puts "feel free to verify with --reloc"
+  disassembly = `#{command}`
+  instructions = []
+  puts disassembly
+  disassembly.each_line do |line|
+    line = line.strip
+    match = /\h+: ((?:\h\h\s?)+)\s+(\w+)/.match(line) do |match_data|
+      bytes = match_data[1]
+      mnemonic = match_data[2]
+      instructions << [bytes, mnemonic, line]
+    end
+    if !match && !instructions.empty?
+      p line
+      raise "expected a continuous sequence of disassembly lines"
+    end
+  end
+
+  jmp_idx = instructions.find_index { |_, mnemonic, _| mnemonic == 'jmp' }
+  raise 'failed to find jmp' unless jmp_idx
+  raise 'generated code for example too long' unless jmp_idx < 10
+  handler_instructions = instructions[(0..jmp_idx)]
+  raise 'rip reference in example makes copying unsafe' if handler_instructions.any? { |_, _, full_line| full_line.downcase.include?('rip') }
+  acceptable_mnemonics = %w(mov jmp lea call)
+  unrecognized = nil
+  handler_instructions.each { |i| unrecognized = i unless acceptable_mnemonics.include?(i[1]) }
+  raise "found a unrecognized \"#{unrecognized[1]}\" instruction in the example. List of recognized instructions: #{acceptable_mnemonics.join(', ')}" if unrecognized
+  raise 'found multiple jmp instructions' if handler_instructions.count { |_, mnemonic, _| mnemonic == 'jmp' } > 1
+  raise 'found multiple call instructions' if handler_instructions.count { |_, mnemonic, _| mnemonic == 'call' } > 1
+  call_idx = handler_instructions.find_index { |_, mnemonic, _| mnemonic == 'call' }
+
+
+  puts "\n\nDisassembly for the handler:"
+  puts handler_instructions.map{|_,_,line|line}
+
+  pre_call_bytes = []
+  post_call_bytes = []
+  handler_instructions.take(call_idx).each do |bytes, mnemonic, _|
+    pre_call_bytes += bytes.split
+  end
+  handler_instructions[((call_idx+1)...)].each do |bytes, _, _|
+    post_call_bytes += bytes.split
+  end
+
+  File.write("ujit_examples.h", <<-EOF)
+static const uint8_t ujit_precall_bytes[] = { #{pre_call_bytes.map{ |byte| '0x'+byte}.join(', ')} };
+static const uint8_t ujit_postall_bytes[] = { #{post_call_bytes.map{ |byte| '0x'+byte}.join(', ')} };
+  EOF
+  puts "file:"
+  puts File.binread("ujit_examples.h")
+end
+
+instruction_id = get_example_instruction_id
+fileoff = get_fileoff
+tc_table_offset = get_symbol_offset('vm_exec_core.insns_address_table')
+vm_exec_core_offset = get_symbol_offset('vm_exec_core')
+p instruction_id
+p fileoff
+p tc_table_offset.to_s(16)
+offset_to_insn_in_tc_table = fileoff + tc_table_offset + 8 * instruction_id
+p offset_to_insn_in_tc_table
+offset_to_handler_code_from_vm_exec_core = readint8b(offset_to_insn_in_tc_table)
+p offset_to_handler_code_from_vm_exec_core
+disassemble(vm_exec_core_offset + offset_to_handler_code_from_vm_exec_core)
+
diff --git a/iseq.c b/iseq.c
index e04642fd92..6928a711ed 100644
--- a/iseq.c
+++ b/iseq.c
@@ -3455,6 +3455,16 @@ trace_set_i(void *vstart, void *vend, size_t stride, void *data) https://github.com/ruby/ruby/blob/trunk/iseq.c#L3455
     return 0;
 }
 
+VALUE *
+rb_ujit_empty_func(rb_control_frame_t *cfp)
+{
+    // okay, not really empty, so maybe think of another name.
+    // it's put in this file instead of say, compile.c to dodge long C compile time.
+    // it just needs to be in a different unit from vm.o so the compiler can't see the definition
+    // and is forced to emit a call that respects the calling convention.
+    return NULL;
+}
+
 void
 rb_iseq_trace_set_all(rb_event_flag_t turnon_events)
 {
@@ -3570,7 +3580,6 @@ struct succ_index_table { https://github.com/ruby/ruby/blob/trunk/iseq.c#L3580
 #define imm_block_rank_get(v, i) (((int)((v) >> ((i) * 7))) & 0x7f)
 #define small_block_rank_set(v, i, r) (v) |= (uint64_t)(r) << (9 * ((i) - 1))
 #define small_block_rank_get(v, i) ((i) == 0 ? 0 : (((int)((v) >> (((i) - 1) * 9))) & 0x1ff))
-
 static struct succ_index_table *
 succ_index_table_create(int max_pos, int *data, int size)
 {
diff --git a/iseq.h b/iseq.h
index 25c130e7b7..ace5a45ba3 100644
--- a/iseq.h
+++ b/iseq.h
@@ -313,6 +313,9 @@ VALUE rb_iseq_defined_string(enum defined_type type); https://github.com/ruby/ruby/blob/trunk/iseq.h#L313
 /* vm.c */
 VALUE rb_iseq_local_variables(const rb_iseq_t *iseq);
 
+NOINLINE(VALUE *rb_ujit_empty_func(rb_control_frame_t *cfp));
+
+
 RUBY_SYMBOL_EXPORT_END
 
 #endif /* RUBY_ISEQ_H */
diff --git a/tool/ruby_vm/models/instructions.rb b/tool/ruby_vm/models/instructions.rb
index 1198c7a4a6..83dff9c5b0 100644
--- a/tool/ruby_vm/models/instructions.rb
+++ b/tool/ruby_vm/models/instructions.rb
@@ -14,9 +14,72 @@ require_relative 'bare_instructions' https://github.com/ruby/ruby/blob/trunk/tool/ruby_vm/models/instructions.rb#L14
 require_relative 'operands_unifications'
 require_relative 'instructions_unifications'
 
+class RubyVM::UJITExampleInstructions
+  include RubyVM::CEscape
+
+  attr_reader :name
+
+  def initialize name
+    @name = name
+  end
+
+  def pretty_name
+    return sprintf "%s(...)(...)(...)", @name
+  end
+
+  def jump_destination
+    return @orig.name
+  end
+
+  def bin
+    return sprintf "BIN(%s)", @name
+  end
+
+  def width
+    1
+  end
+
+  def operands_info
+    ""
+  end
+
+  def rets
+    return ['...']
+  end
+
+  def pops
+    return ['...']
+  end
+
+  def attributes
+    return []
+  end
+
+  def has_attribute? *;
+    return false
+  end
+
+  def handles_sp?
+    false
+  end
+
+  def always_leaf?
+    false
+  end
+
+  @all_examples = [new('ujit_call_example')]
+
+  def self.to_a
+    @all_examples
+  end
+end
+
 RubyVM::Instructions = RubyVM::BareInstructions.to_a + \
                        RubyVM::OperandsUnifications.to_a + \
-                       RubyVM::InstructionsUnifications.to_a
+                       RubyVM::InstructionsUnifications.to_a + \
+                       RubyVM::UJITExampleInstructions.to_a
+
+                
 
 require_relative 'trace_instructions'
 RubyVM::Instructions.freeze
diff --git a/tool/ruby_vm/views/vm.inc.erb b/tool/ruby_vm/views/vm.inc.erb
index c1a3faf60a..7942a3ef87 100644
--- a/tool/ruby_vm/views/vm.inc.erb
+++ b/tool/ruby_vm/views/vm.inc.erb
@@ -28,3 +28,13 @@ https://github.com/ruby/ruby/blob/trunk/tool/ruby_vm/views/vm.inc.erb#L28
 % RubyVM::TraceInstructions.to_a.each do |insn|
 <%= render 'trace_instruction', locals: { insn: insn } -%>
 % end
+% RubyVM::UJITExampleInstructions.to_a.each do |insn|
+INSN_ENTRY(<%= insn.name %>)
+{
+    START_OF_ORIGINAL_INSN(<%= insn.name %>);
+    // assumes USE_MACHINE_REGS, aka reg_pc setup,
+    // aka #define SET_PC(x) (reg_cfp->pc = reg_pc = (x))
+    reg_pc = rb_ujit_empty_func(GET_CFP());
+    END_INSN(<%= insn.name %>);
+}
+% end
-- 
cgit v1.2.1


--
ML: ruby-changes@q...
Info: http://www.atdot.net/~ko1/quickml/

[前][次][番号順一覧][スレッド一覧]