diff options
-rw-r--r-- | gen-ujit-example-header.rb | 105 | ||||
-rw-r--r-- | iseq.c | 11 | ||||
-rw-r--r-- | iseq.h | 3 | ||||
-rw-r--r-- | tool/ruby_vm/models/instructions.rb | 65 | ||||
-rw-r--r-- | tool/ruby_vm/views/vm.inc.erb | 10 |
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) + @@ -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) { @@ -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 |