summaryrefslogtreecommitdiff
path: root/tool
diff options
context:
space:
mode:
authorAlan Wu <XrXr@users.noreply.github.com>2020-09-23 03:02:01 -0400
committerAlan Wu <XrXr@users.noreply.github.com>2021-10-20 18:19:24 -0400
commit3d87eadf16a086d2f3bec0b556760c0ebfe1e7cd (patch)
tree9a43222482c933b9cd2d53e19d3c83bbb35cd717 /tool
parent2eaf55fcf1f7afbd0190e47ffe352b29047e6144 (diff)
Refactor ujit_examples.h generator. Remove dwarfdump dependency
Diffstat (limited to 'tool')
-rw-r--r--tool/ruby_vm/models/instructions.rb63
-rw-r--r--tool/ruby_vm/models/micro_jit.rb125
-rw-r--r--tool/ruby_vm/models/micro_jit/example_instructions.rb71
-rw-r--r--tool/ruby_vm/views/ujit_examples.inc.erb16
-rw-r--r--tool/ruby_vm/views/vm.inc.erb2
5 files changed, 215 insertions, 62 deletions
diff --git a/tool/ruby_vm/models/instructions.rb b/tool/ruby_vm/models/instructions.rb
index 83dff9c5b0..065ac7dbf2 100644
--- a/tool/ruby_vm/models/instructions.rb
+++ b/tool/ruby_vm/models/instructions.rb
@@ -13,71 +13,12 @@
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
+require_relative 'micro_jit'
RubyVM::Instructions = RubyVM::BareInstructions.to_a + \
RubyVM::OperandsUnifications.to_a + \
RubyVM::InstructionsUnifications.to_a + \
- RubyVM::UJITExampleInstructions.to_a
+ RubyVM::MicroJIT::ExampleInstructions.to_a
diff --git a/tool/ruby_vm/models/micro_jit.rb b/tool/ruby_vm/models/micro_jit.rb
new file mode 100644
index 0000000000..eee829a2e6
--- /dev/null
+++ b/tool/ruby_vm/models/micro_jit.rb
@@ -0,0 +1,125 @@
+#! /your/favourite/path/to/ruby
+# -*- Ruby -*-
+# -*- frozen_string_literal: true; -*-
+# -*- warn_indent: true; -*-
+#
+# Copyright (c) 2020 Wu, Alan. All rights reserved.
+#
+# This file is a part of the programming language Ruby. Permission is hereby
+# granted, to either redistribute and/or modify this file, provided that the
+# conditions mentioned in the file COPYING are met. Consult the file for
+# details.
+
+module RubyVM::MicroJIT
+ class << self
+ 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] if $DEBUG
+ 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] if $DEBUG
+ 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 if $DEBUG
+ disassembly.each_line do |line|
+ line = line.strip
+ match_data = /\h+: ((?:\h\h\s?)+)\s+(\w+)/.match(line)
+ if match_data
+ bytes = match_data[1]
+ mnemonic = match_data[2]
+ instructions << [bytes, mnemonic, line]
+ break if mnemonic == 'jmp'
+ elsif !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)]
+
+ puts "Disassembly for the example handler:"
+ puts handler_instructions.map {|_, _, line| line}
+
+
+ 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 an 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 "the jmp instruction seems to be relative which isn't copiable" if instructions[jmp_idx][0].split.size > 4
+ raise 'found multiple call instructions' if handler_instructions.count { |_, mnemonic, _| mnemonic == 'call' } > 1
+ call_idx = handler_instructions.find_index { |_, mnemonic, _| mnemonic == 'call' }
+
+
+ @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, handler_instructions.size].each do |bytes, _, _|
+ @post_call_bytes += bytes.split
+ end
+ end
+
+ def scrape
+ instruction_id = RubyVM::Instructions.find_index { |insn| insn.name == 'ujit_call_example' }
+ 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 if $DEBUG
+ p fileoff if $DEBUG
+ p tc_table_offset.to_s(16) if $DEBUG
+ offset_to_insn_in_tc_table = fileoff + tc_table_offset + 8 * instruction_id
+ p offset_to_insn_in_tc_table if $DEBUG
+ offset_to_handler_code_from_vm_exec_core = readint8b(offset_to_insn_in_tc_table)
+ p offset_to_handler_code_from_vm_exec_core if $DEBUG
+ disassemble(vm_exec_core_offset + offset_to_handler_code_from_vm_exec_core)
+ end
+
+ def comma_separated_hex_string(nums)
+ nums.map{ |byte| '0x'+byte}.join(', ')
+ end
+
+ def pre_call_bytes
+ scrape unless @pre_call_bytes
+ comma_separated_hex_string(@pre_call_bytes)
+ end
+
+ def post_call_bytes
+ scrape unless @post_call_bytes
+ comma_separated_hex_string(@post_call_bytes)
+ end
+ end
+end
+
+require_relative 'micro_jit/example_instructions'
diff --git a/tool/ruby_vm/models/micro_jit/example_instructions.rb b/tool/ruby_vm/models/micro_jit/example_instructions.rb
new file mode 100644
index 0000000000..5117d1c519
--- /dev/null
+++ b/tool/ruby_vm/models/micro_jit/example_instructions.rb
@@ -0,0 +1,71 @@
+#! /your/favourite/path/to/ruby
+# -*- Ruby -*-
+# -*- frozen_string_literal: true; -*-
+# -*- warn_indent: true; -*-
+#
+# Copyright (c) 2020 Wu, Alan. All rights reserved.
+#
+# This file is a part of the programming language Ruby. Permission is hereby
+# granted, to either redistribute and/or modify this file, provided that the
+# conditions mentioned in the file COPYING are met. Consult the file for
+# details.
+
+class RubyVM::MicroJIT::ExampleInstructions
+ 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
diff --git a/tool/ruby_vm/views/ujit_examples.inc.erb b/tool/ruby_vm/views/ujit_examples.inc.erb
new file mode 100644
index 0000000000..a301c5ca74
--- /dev/null
+++ b/tool/ruby_vm/views/ujit_examples.inc.erb
@@ -0,0 +1,16 @@
+/* -*- C -*- */
+
+%# Copyright (c) 2020 Wu, Alan. All rights reserved.
+%#
+%# This file is a part of the programming language Ruby. Permission is hereby
+%# granted, to either redistribute and/or modify this file, provided that the
+%# conditions mentioned in the file COPYING are met. Consult the file for
+%# details.
+<%= render 'copyright' %>
+<%= render 'notice', locals: {
+ this_file: 'contains raw instruction bytes that helps MicroJIT generate code',
+ edit: __FILE__,
+} -%>
+
+static const uint8_t ujit_pre_call_bytes[] = { <%= RubyVM::MicroJIT.pre_call_bytes %> };
+static const uint8_t ujit_post_call_bytes[] = { <%= RubyVM::MicroJIT.post_call_bytes %> };
diff --git a/tool/ruby_vm/views/vm.inc.erb b/tool/ruby_vm/views/vm.inc.erb
index 7942a3ef87..3c7b602859 100644
--- a/tool/ruby_vm/views/vm.inc.erb
+++ b/tool/ruby_vm/views/vm.inc.erb
@@ -28,7 +28,7 @@
% RubyVM::TraceInstructions.to_a.each do |insn|
<%= render 'trace_instruction', locals: { insn: insn } -%>
% end
-% RubyVM::UJITExampleInstructions.to_a.each do |insn|
+% RubyVM::MicroJIT::ExampleInstructions.to_a.each do |insn|
INSN_ENTRY(<%= insn.name %>)
{
START_OF_ORIGINAL_INSN(<%= insn.name %>);