Module: Optimize::Codec::CatchTable

Defined in:
lib/optimize/codec/catch_table.rb

Overview

Decode/encode a single iseq’s catch table.

The on-disk format uses start/end/cont positions expressed as YARV-slot offsets into the instruction stream. IR expresses them as IR::Instruction references. The caller provides a position <=> instruction mapping since the stream’s slot layout is owned by InstructionStream.

Binary format per entry (6 small_values, from ibf-format.md §4.4):

iseq_index  — iseq-list index of handler iseq (0xFFFFFFFF = none)
type        — rb_catch_type enum value
start       — YARV slot index (start of guarded range)
end         — YARV slot index (end of guarded range, exclusive)
cont        — YARV slot index (continuation point)
sp          — stack pointer (depth) at catch point

Constant Summary collapse

TYPE_TO_SYM =

rb_catch_type enum values. The C enum is defined as INT2FIX(N), i.e. the raw stored value is (N << 1) | 1 — a Ruby Fixnum tag. So:

CATCH_TYPE_RESCUE = INT2FIX(1) = 3
CATCH_TYPE_ENSURE = INT2FIX(2) = 5
CATCH_TYPE_RETRY  = INT2FIX(3) = 7
CATCH_TYPE_BREAK  = INT2FIX(4) = 9
CATCH_TYPE_REDO   = INT2FIX(5) = 11
CATCH_TYPE_NEXT   = INT2FIX(6) = 13

(Verified empirically against Ruby 4.0.2 binary output.)

{
  3  => :rescue,
  5  => :ensure,
  7  => :retry,
  9  => :break,
  11 => :redo,
  13 => :next,
}.freeze
SYM_TO_TYPE =
TYPE_TO_SYM.invert.freeze
NO_ISEQ =

Sentinel for “no associated iseq”. CRuby stores -1 as a small_value, which decodes to the max uint64 (0xFFFFFFFFFFFFFFFF = 2^64-1).

0xFFFFFFFFFFFFFFFF

Class Method Summary collapse

Class Method Details

.decode(reader, count, slot_to_inst) ⇒ Array<IR::CatchEntry>

Decode catch table entries from reader.

Parameters:

  • reader (BinaryReader)

    positioned at the start of the catch table

  • count (Integer)

    number of entries

  • slot_to_inst (Hash{Integer=>IR::Instruction})

    YARV slot → instruction

Returns:



53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/optimize/codec/catch_table.rb', line 53

def decode(reader, count, slot_to_inst)
  Array.new(count) do
    iseq_index  = reader.read_small_value
    type_num    = reader.read_small_value
    start_pos   = reader.read_small_value
    end_pos     = reader.read_small_value
    cont_pos    = reader.read_small_value
    stack_depth = reader.read_small_value

    # Normalize the iseq_index sentinel. CRuby writes -1 as uint64 max.
    iseq_index = nil if iseq_index == NO_ISEQ

    type_sym = TYPE_TO_SYM.fetch(type_num) do
      raise MalformedBinary, "unknown catch type #{type_num}"
    end

    start_inst = slot_to_inst[start_pos] or
      raise MalformedBinary, "catch table start position #{start_pos} does not align with any instruction"
    end_inst   = slot_to_inst[end_pos] or
      raise MalformedBinary, "catch table end position #{end_pos} does not align with any instruction"
    # cont_pos is always a real slot (never a sentinel); resolve it.
    # When cont_pos == 0 it points to the first instruction of the iseq.
    cont_inst = slot_to_inst[cont_pos] or
      raise MalformedBinary, "catch table cont position #{cont_pos} does not align with any instruction"

    IR::CatchEntry.new(
      type:        type_sym,
      iseq_index:  iseq_index,
      start_inst:  start_inst,
      end_inst:    end_inst,
      cont_inst:   cont_inst,
      stack_depth: stack_depth,
    )
  end
end

.encode(writer, entries, inst_to_slot) ⇒ Object

Encode catch table entries into writer.

Entries where any non-nil instruction reference (start_inst, end_inst, cont_inst) is missing from inst_to_slot (dangling ref — the instruction was deleted) are silently dropped from the output.

Parameters:



98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/optimize/codec/catch_table.rb', line 98

def encode(writer, entries, inst_to_slot)
  live_entries = entries.select do |e|
    inst_to_slot.key?(e.start_inst) &&
      inst_to_slot.key?(e.end_inst) &&
      (e.cont_inst.nil? || inst_to_slot.key?(e.cont_inst))
  end
  live_entries.each do |e|
    writer.write_small_value(e.iseq_index.nil? ? NO_ISEQ : e.iseq_index)
    writer.write_small_value(SYM_TO_TYPE.fetch(e.type))
    writer.write_small_value(inst_to_slot.fetch(e.start_inst))
    writer.write_small_value(inst_to_slot.fetch(e.end_inst))
    writer.write_small_value(inst_to_slot.fetch(e.cont_inst))
    writer.write_small_value(e.stack_depth)
  end
end