Class: RubyVM::RJIT::Context

Inherits:
Struct show all
Defined in:
lib/ruby_vm/rjit/context.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods inherited from Struct

#as_json, json_create, #pretty_print, #pretty_print_cycle, #to_json

Constructor Details

#initialize(stack_size: 0, sp_offset: 0, chain_depth: 0, local_types: [Type::Unknown] * MAX_LOCAL_TYPES, temp_types: [Type::Unknown] * MAX_TEMP_TYPES, self_type: Type::Unknown, temp_mapping: [MapToStack] * MAX_TEMP_TYPES) ⇒ Context

Returns a new instance of Context.



26
27
28
29
30
31
32
33
34
# File 'lib/ruby_vm/rjit/context.rb', line 26

def initialize(
  stack_size:   0,
  sp_offset:    0,
  chain_depth:  0,
  local_types:  [Type::Unknown] * MAX_LOCAL_TYPES,
  temp_types:   [Type::Unknown] * MAX_TEMP_TYPES,
  self_type:    Type::Unknown,
  temp_mapping: [MapToStack] * MAX_TEMP_TYPES
) = super

Instance Attribute Details

#chain_depthObject

Returns the value of attribute chain_depth

Returns:

  • (Object)

    the current value of chain_depth



17
18
19
# File 'lib/ruby_vm/rjit/context.rb', line 17

def chain_depth
  @chain_depth
end

#local_typesObject

Returns the value of attribute local_types

Returns:

  • (Object)

    the current value of local_types



17
18
19
# File 'lib/ruby_vm/rjit/context.rb', line 17

def local_types
  @local_types
end

#self_typeObject

Returns the value of attribute self_type

Returns:

  • (Object)

    the current value of self_type



17
18
19
# File 'lib/ruby_vm/rjit/context.rb', line 17

def self_type
  @self_type
end

#sp_offsetObject

Returns the value of attribute sp_offset

Returns:

  • (Object)

    the current value of sp_offset



17
18
19
# File 'lib/ruby_vm/rjit/context.rb', line 17

def sp_offset
  @sp_offset
end

#stack_sizeObject

Returns the value of attribute stack_size

Returns:

  • (Object)

    the current value of stack_size



17
18
19
# File 'lib/ruby_vm/rjit/context.rb', line 17

def stack_size
  @stack_size
end

#temp_mappingObject

Returns the value of attribute temp_mapping

Returns:

  • (Object)

    the current value of temp_mapping



17
18
19
# File 'lib/ruby_vm/rjit/context.rb', line 17

def temp_mapping
  @temp_mapping
end

#temp_typesObject

Returns the value of attribute temp_types

Returns:

  • (Object)

    the current value of temp_types



17
18
19
# File 'lib/ruby_vm/rjit/context.rb', line 17

def temp_types
  @temp_types
end

Instance Method Details

#clear_local_typesObject

Erase local variable type information eg: because of a call we can’t track



282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
# File 'lib/ruby_vm/rjit/context.rb', line 282

def clear_local_types
  # When clearing local types we must detach any stack mappings to those
  # locals. Even if local values may have changed, stack values will not.
  MAX_TEMP_TYPES.times do |stack_idx|
    case self.temp_mapping[stack_idx]
    in MapToStack
      # noop
    in MapToSelf
      # noop
    in MapToLocal[local_idx]
      self.temp_types[stack_idx] = self.local_types[local_idx]
      self.temp_mapping[stack_idx] = MapToStack
    end
  end

  # Clear the local types
  self.local_types = [Type::Unknown] * MAX_LOCAL_TYPES
end

#diff(dst) ⇒ Object

Compute a difference score for two context objects



302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
# File 'lib/ruby_vm/rjit/context.rb', line 302

def diff(dst)
  # Self is the source context (at the end of the predecessor)
  src = self

  # Can only lookup the first version in the chain
  if dst.chain_depth != 0
    return TypeDiff::Incompatible
  end

  # Blocks with depth > 0 always produce new versions
  # Sidechains cannot overlap
  if src.chain_depth != 0
    return TypeDiff::Incompatible
  end

  if dst.stack_size != src.stack_size
    return TypeDiff::Incompatible
  end

  if dst.sp_offset != src.sp_offset
    return TypeDiff::Incompatible
  end

  # Difference sum
  diff = 0

  # Check the type of self
  diff += case src.self_type.diff(dst.self_type)
  in TypeDiff::Compatible[diff] then diff
  in TypeDiff::Incompatible then return TypeDiff::Incompatible
  end

  # For each local type we track
  src.local_types.size.times do |i|
    t_src = src.local_types[i]
    t_dst = dst.local_types[i]
    diff += case t_src.diff(t_dst)
    in TypeDiff::Compatible[diff] then diff
    in TypeDiff::Incompatible then return TypeDiff::Incompatible
    end
  end

  # For each value on the temp stack
  src.stack_size.times do |i|
    src_mapping, src_type = src.get_opnd_mapping(StackOpnd[i])
    dst_mapping, dst_type = dst.get_opnd_mapping(StackOpnd[i])

    # If the two mappings aren't the same
    if src_mapping != dst_mapping
      if dst_mapping == MapToStack
        # We can safely drop information about the source of the temp
        # stack operand.
        diff += 1
      else
        return TypeDiff::Incompatible
      end
    end

    diff += case src_type.diff(dst_type)
    in TypeDiff::Compatible[diff] then diff
    in TypeDiff::Incompatible then return TypeDiff::Incompatible
    end
  end

  return TypeDiff::Compatible[diff]
end

#dupObject

Deep dup by default for safety



37
38
39
40
41
42
43
# File 'lib/ruby_vm/rjit/context.rb', line 37

def dup
  ctx = super
  ctx.local_types = ctx.local_types.dup
  ctx.temp_types = ctx.temp_types.dup
  ctx.temp_mapping = ctx.temp_mapping.dup
  ctx
end

#get_local_type(idx) ⇒ Object

Get the currently tracked type for a local variable



173
174
175
# File 'lib/ruby_vm/rjit/context.rb', line 173

def get_local_type(idx)
  self.local_types[idx] || Type::Unknown
end

#get_opnd_mapping(opnd) ⇒ Object

Get both the type and mapping (where the value originates) of an operand. This is can be used with stack_push_mapping or set_opnd_mapping to copy a stack value’s type while maintaining the mapping.



211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
# File 'lib/ruby_vm/rjit/context.rb', line 211

def get_opnd_mapping(opnd)
  opnd_type = self.get_opnd_type(opnd)

  case opnd
  in SelfOpnd
    return [MapToSelf, opnd_type]
  in StackOpnd[idx]
    assert(idx < self.stack_size)
    stack_idx = self.stack_size - 1 - idx

    if stack_idx < MAX_TEMP_TYPES
      return [self.temp_mapping[stack_idx], opnd_type]
    else
      # We can't know the source of this stack operand, so we assume it is
      # a stack-only temporary. type will be UNKNOWN
      assert(opnd_type == Type::Unknown)
      return [MapToStack, opnd_type]
    end
  end
end

#get_opnd_type(opnd) ⇒ Object

Get the type of an instruction operand



145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/ruby_vm/rjit/context.rb', line 145

def get_opnd_type(opnd)
  case opnd
  in SelfOpnd
    self.self_type
  in StackOpnd[idx]
    assert(idx < self.stack_size)
    stack_idx = self.stack_size - 1 - idx

    # If outside of tracked range, do nothing
    if stack_idx >= MAX_TEMP_TYPES
      return Type::Unknown
    end

    mapping = self.temp_mapping[stack_idx]

    case mapping
    in MapToSelf
      self.self_type
    in MapToStack
      self.temp_types[self.stack_size - 1 - idx]
    in MapToLocal[idx]
      assert(idx < MAX_LOCAL_TYPES)
      self.local_types[idx]
    end
  end
end

#set_local_type(local_idx, local_type) ⇒ Object

Set the type of a local variable



255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
# File 'lib/ruby_vm/rjit/context.rb', line 255

def set_local_type(local_idx, local_type)
  if local_idx >= MAX_LOCAL_TYPES
    return
  end

  # If any values on the stack map to this local we must detach them
  MAX_TEMP_TYPES.times do |stack_idx|
    case self.temp_mapping[stack_idx]
    in MapToStack
      # noop
    in MapToSelf
      # noop
    in MapToLocal[idx]
      if idx == local_idx
        self.temp_types[stack_idx] = self.local_types[idx]
        self.temp_mapping[stack_idx] = MapToStack
      else
        # noop
      end
    end
  end

  self.local_types[local_idx] = local_type
end

#set_opnd_mapping(opnd, mapping_opnd_type) ⇒ Object

Overwrite both the type and mapping of a stack operand.



233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
# File 'lib/ruby_vm/rjit/context.rb', line 233

def set_opnd_mapping(opnd, mapping_opnd_type)
  case opnd
  in SelfOpnd
    raise 'self always maps to self'
  in StackOpnd[idx]
    assert(idx < self.stack_size)
    stack_idx = self.stack_size - 1 - idx

    # If outside of tracked range, do nothing
    if stack_idx >= MAX_TEMP_TYPES
      return
    end

    mapping, opnd_type = mapping_opnd_type
    self.temp_mapping[stack_idx] = mapping

    # Only used when mapping == MAP_STACK
    self.temp_types[stack_idx] = opnd_type
  end
end

#shift_stack(argc) ⇒ Object



130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/ruby_vm/rjit/context.rb', line 130

def shift_stack(argc)
  assert(argc < self.stack_size)

  method_name_index = self.stack_size - argc - 1

  (method_name_index...(self.stack_size - 1)).each do |i|
    if i + 1 < MAX_TEMP_TYPES
      self.temp_types[i] = self.temp_types[i + 1]
      self.temp_mapping[i] = self.temp_mapping[i + 1]
    end
  end
  self.stack_pop(1)
end

#sp_opnd(offset_bytes = 0) ⇒ Object



59
60
61
# File 'lib/ruby_vm/rjit/context.rb', line 59

def sp_opnd(offset_bytes = 0)
  [SP, (C.VALUE.size * self.sp_offset) + offset_bytes]
end

#stack_opnd(depth_from_top) ⇒ Object



55
56
57
# File 'lib/ruby_vm/rjit/context.rb', line 55

def stack_opnd(depth_from_top)
  [SP, C.VALUE.size * (self.sp_offset - 1 - depth_from_top)]
end

#stack_pop(n = 1) ⇒ Object

Pop N values off the stack Return a pointer to the stack top before the pop operation



109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/ruby_vm/rjit/context.rb', line 109

def stack_pop(n = 1)
  assert(n <= self.stack_size)

  top = self.stack_opnd(0)

  # Clear the types of the popped values
  n.times do |i|
    idx = self.stack_size - i - 1

    if idx < MAX_TEMP_TYPES
      self.temp_types[idx] = Type::Unknown
      self.temp_mapping[idx] = MapToStack
    end
  end

  self.stack_size -= n
  self.sp_offset -= n

  return top
end

#stack_push(val_type) ⇒ Object

Push one new value on the temp stack Return a pointer to the new stack top



89
90
91
# File 'lib/ruby_vm/rjit/context.rb', line 89

def stack_push(val_type)
  return self.stack_push_mapping([MapToStack, val_type])
end

#stack_push_local(local_idx) ⇒ Object

Push a local variable on the stack



99
100
101
102
103
104
105
# File 'lib/ruby_vm/rjit/context.rb', line 99

def stack_push_local(local_idx)
  if local_idx >= MAX_LOCAL_TYPES
    return self.stack_push(Type::Unknown)
  end

  return self.stack_push_mapping([MapToLocal[local_idx], Type::Unknown])
end

#stack_push_mapping(mapping_temp_type) ⇒ Object

Push one new value on the temp stack with an explicit mapping Return a pointer to the new stack top



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/ruby_vm/rjit/context.rb', line 65

def stack_push_mapping(mapping_temp_type)
  stack_size = self.stack_size

  # Keep track of the type and mapping of the value
  if stack_size < MAX_TEMP_TYPES
    mapping, temp_type = mapping_temp_type
    self.temp_mapping[stack_size] = mapping
    self.temp_types[stack_size] = temp_type

    case mapping
    in MapToLocal[idx]
      assert(idx < MAX_LOCAL_TYPES)
    else
    end
  end

  self.stack_size += 1
  self.sp_offset += 1

  return self.stack_opnd(0)
end

#stack_push_selfObject

Push the self value on the stack



94
95
96
# File 'lib/ruby_vm/rjit/context.rb', line 94

def stack_push_self
  return self.stack_push_mapping([MapToStack, Type::Unknown])
end

#upgrade_opnd_type(opnd, opnd_type) ⇒ Object

Upgrade (or “learn”) the type of an instruction operand This value must be compatible and at least as specific as the previously known type. If this value originated from self, or an lvar, the learned type will be propagated back to its source.



181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
# File 'lib/ruby_vm/rjit/context.rb', line 181

def upgrade_opnd_type(opnd, opnd_type)
  case opnd
  in SelfOpnd
    self.self_type = self.self_type.upgrade(opnd_type)
  in StackOpnd[idx]
    assert(idx < self.stack_size)
    stack_idx = self.stack_size - 1 - idx

    # If outside of tracked range, do nothing
    if stack_idx >= MAX_TEMP_TYPES
      return
    end

    mapping = self.temp_mapping[stack_idx]

    case mapping
    in MapToSelf
      self.self_type = self.self_type.upgrade(opnd_type)
    in MapToStack
      self.temp_types[stack_idx] = self.temp_types[stack_idx].upgrade(opnd_type)
    in MapToLocal[idx]
      assert(idx < MAX_LOCAL_TYPES)
      self.local_types[idx] = self.local_types[idx].upgrade(opnd_type)
    end
  end
end

#with_stack_size(stack_size) ⇒ Object

Create a new Context instance with a given stack_size and sp_offset adjusted accordingly. This is useful when you want to virtually rewind a stack_size for generating a side exit while considering past sp_offset changes on gen_save_sp.



48
49
50
51
52
53
# File 'lib/ruby_vm/rjit/context.rb', line 48

def with_stack_size(stack_size)
  ctx = self.dup
  ctx.sp_offset -= ctx.stack_size - stack_size
  ctx.stack_size = stack_size
  ctx
end