summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlan Wu <XrXr@users.noreply.github.com>2020-09-03 11:08:16 -0400
committerAlan Wu <XrXr@users.noreply.github.com>2021-10-20 18:19:22 -0400
commitcec197696f3edcff553373e9597130fde2d1f7be (patch)
tree1a68c70ef9f972829fd59c4b9809e21bdc9a1ee7
parentb0c3f18ec519d505527e1929e25ec264c831a89e (diff)
Add example handler for ujit and scrape it from vm.o
-rw-r--r--gen-ujit-example-header.rb105
-rw-r--r--iseq.c11
-rw-r--r--iseq.h3
-rw-r--r--tool/ruby_vm/models/instructions.rb65
-rw-r--r--tool/ruby_vm/views/vm.inc.erb10
5 files changed, 192 insertions, 2 deletions
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 @@
+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)
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 {
#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);
/* 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'
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 @@
% 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