Skip to content
Prev Previous commit
Allow calling disasm on instructions
  • Loading branch information
kddnewton committed Nov 29, 2022
commit 46ab8292ef0f88f5969e4dece3c45a2c8c968d74
9 changes: 9 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ Layout/LineLength:
Lint/AmbiguousBlockAssociation:
Enabled: false

Lint/AmbiguousOperatorPrecedence:
Enabled: false

Lint/AmbiguousRange:
Enabled: false

Lint/BooleanSymbol:
Enabled: false

Expand Down Expand Up @@ -91,6 +97,9 @@ Style/ExplicitBlockArgument:
Style/FormatString:
Enabled: false

Style/FormatStringToken:
Enabled: false

Style/GuardClause:
Enabled: false

Expand Down
1 change: 1 addition & 0 deletions lib/syntax_tree.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
require_relative "syntax_tree/yarv"
require_relative "syntax_tree/yarv/bf"
require_relative "syntax_tree/yarv/compiler"
require_relative "syntax_tree/yarv/disasm_formatter"
require_relative "syntax_tree/yarv/disassembler"
require_relative "syntax_tree/yarv/instruction_sequence"
require_relative "syntax_tree/yarv/instructions"
Expand Down
211 changes: 211 additions & 0 deletions lib/syntax_tree/yarv/disasm_formatter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
# frozen_string_literal: true

module SyntaxTree
module YARV
class DisasmFormatter
attr_reader :output, :queue
attr_reader :current_prefix, :current_iseq

def initialize
@output = StringIO.new
@queue = []

@current_prefix = ""
@current_iseq = nil
end

########################################################################
# Helpers for various instructions
########################################################################

def calldata(value)
flag_names = []
flag_names << :ARGS_SPLAT if value.flag?(CallData::CALL_ARGS_SPLAT)
if value.flag?(CallData::CALL_ARGS_BLOCKARG)
flag_names << :ARGS_BLOCKARG
end
flag_names << :FCALL if value.flag?(CallData::CALL_FCALL)
flag_names << :VCALL if value.flag?(CallData::CALL_VCALL)
flag_names << :ARGS_SIMPLE if value.flag?(CallData::CALL_ARGS_SIMPLE)
flag_names << :BLOCKISEQ if value.flag?(CallData::CALL_BLOCKISEQ)
flag_names << :KWARG if value.flag?(CallData::CALL_KWARG)
flag_names << :KW_SPLAT if value.flag?(CallData::CALL_KW_SPLAT)
flag_names << :TAILCALL if value.flag?(CallData::CALL_TAILCALL)
flag_names << :SUPER if value.flag?(CallData::CALL_SUPER)
flag_names << :ZSUPER if value.flag?(CallData::CALL_ZSUPER)
flag_names << :OPT_SEND if value.flag?(CallData::CALL_OPT_SEND)
flag_names << :KW_SPLAT_MUT if value.flag?(CallData::CALL_KW_SPLAT_MUT)

parts = []
parts << "mid:#{value.method}" if value.method
parts << "argc:#{value.argc}"
parts << "kw:[#{value.kw_arg.join(", ")}]" if value.kw_arg
parts << flag_names.join("|") if flag_names.any?

"<calldata!#{parts.join(", ")}>"
end

def enqueue(iseq)
queue << iseq
end

def event(name)
case name
when :RUBY_EVENT_B_CALL
"Bc"
when :RUBY_EVENT_B_RETURN
"Br"
when :RUBY_EVENT_CALL
"Ca"
when :RUBY_EVENT_CLASS
"Cl"
when :RUBY_EVENT_END
"En"
when :RUBY_EVENT_LINE
"Li"
when :RUBY_EVENT_RETURN
"Re"
else
raise "Unknown event: #{name}"
end
end

def inline_storage(cache)
"<is:#{cache}>"
end

def instruction(name, operands = [])
operands.empty? ? name : "%-38s %s" % [name, operands.join(", ")]
end

def label(value)
value.name["label_".length..]
end

def local(index, explicit: nil, implicit: nil)
current = current_iseq
(explicit || implicit).times { current = current.parent_iseq }

value = "#{current.local_table.name_at(index)}@#{index}"
value << ", #{explicit}" if explicit
value
end

def object(value)
value.inspect
end

########################################################################
# Main entrypoint
########################################################################

def format!
while (@current_iseq = queue.shift)
output << "\n" if output.pos > 0
format_iseq(@current_iseq)
end

output.string
end

private

def format_iseq(iseq)
output << "#{current_prefix}== disasm: "
output << "#<ISeq:#{iseq.name}@<compiled>:1 "

location = iseq.location
output << "(#{location.start_line},#{location.start_column})-"
output << "(#{location.end_line},#{location.end_column})"
output << "> "

if iseq.catch_table.any?
output << "(catch: TRUE)\n"
output << "#{current_prefix}== catch table\n"

with_prefix("#{current_prefix}| ") do
iseq.catch_table.each do |entry|
case entry
when InstructionSequence::CatchBreak
output << "#{current_prefix}catch type: break\n"
format_iseq(entry.iseq)
when InstructionSequence::CatchNext
output << "#{current_prefix}catch type: next\n"
when InstructionSequence::CatchRedo
output << "#{current_prefix}catch type: redo\n"
when InstructionSequence::CatchRescue
output << "#{current_prefix}catch type: rescue\n"
format_iseq(entry.iseq)
end
end
end

output << "#{current_prefix}|#{"-" * 72}\n"
else
output << "(catch: FALSE)\n"
end

if (local_table = iseq.local_table) && !local_table.empty?
output << "#{current_prefix}local table (size: #{local_table.size})\n"

locals =
local_table.locals.each_with_index.map do |local, index|
"[%2d] %s@%d" % [local_table.offset(index), local.name, index]
end

output << "#{current_prefix}#{locals.join(" ")}\n"
end

length = 0
events = []
lines = []

iseq.insns.each do |insn|
case insn
when Integer
lines << insn
when Symbol
events << event(insn)
when InstructionSequence::Label
# skip
else
output << "#{current_prefix}%04d " % length

disasm = insn.disasm(self)
output << disasm

if lines.any?
output << " " * (65 - disasm.length) if disasm.length < 65
elsif events.any?
output << " " * (39 - disasm.length) if disasm.length < 39
end

if lines.any?
output << "(%4d)" % lines.last
lines.clear
end

if events.any?
output << "[#{events.join}]"
events.clear
end

output << "\n"
length += insn.length
end
end
end

def with_prefix(value)
previous = @current_prefix

begin
@current_prefix = value
yield
ensure
@current_prefix = previous
end
end
end
end
end
37 changes: 10 additions & 27 deletions lib/syntax_tree/yarv/instruction_sequence.rb
Original file line number Diff line number Diff line change
Expand Up @@ -272,30 +272,9 @@ def to_a
end

def disasm
output = StringIO.new
output << "== disasm: #<ISeq:#{name}@<compiled>:1 (#{location.start_line},#{location.start_column})-(#{location.end_line},#{location.end_column})> (catch: FALSE)\n"

length = 0
events = []

insns.each do |insn|
case insn
when Integer
# skip
when Symbol
events << insn
when Label
# skip
else
output << "%04d " % length
output << insn.disasm(self)
output << "\n"
end

length += insn.length
end

output.string
formatter = DisasmFormatter.new
formatter.enqueue(self)
formatter.format!
end

# This method converts our linked list of instructions into a final array
Expand Down Expand Up @@ -375,7 +354,8 @@ def specialize_instructions!
when Send
calldata = value.calldata

if !value.block_iseq && !calldata.flag?(CallData::CALL_ARGS_BLOCKARG)
if !value.block_iseq &&
!calldata.flag?(CallData::CALL_ARGS_BLOCKARG)
# Specialize the send instruction. If it doesn't have a block
# attached, then we will replace it with an opt_send_without_block
# and do further specializations based on the called method and
Expand Down Expand Up @@ -980,8 +960,11 @@ def self.from(source, options = Compiler::Options.new, parent_iseq = nil)

# set up all of the instructions
source[13].each do |insn|
# skip line numbers
next if insn.is_a?(Integer)
# add line numbers
if insn.is_a?(Integer)
iseq.push(insn)
next
end

# add events and labels
if insn.is_a?(Symbol)
Expand Down
Loading