Class Ruva::JVM
In: lib/ruva/vm.rb
Parent: Object

This is the top-level class in Ruva

Methods

Included Modules

VM::Helpers

Attributes

bottom_frame  [R] 
class_pool_manager  [RW] 
frames  [R] 
natives  [R] 
opcodes  [RW] 

Public Class methods

Convenience method that creates a VM based on the supplied options hash. Valid options:

  :main           Hash options for the main class
    :class        The internal name of the main class
    :method       The name of the main method
    :desc         The descriptor for the main method
    :args         Arguments for main method (TODO main args not yet type-checked!)

  :class_paths    Array of class-paths for the bootstrap classloader.

  :trace_exec     Object to be used for execution tracing. This object must respond_to? :trace
                  with the signature:
                     #trace(current_frame, bytecode_operand_count, current_opcode, operands)
                  This method is called just prior to the execution of the opcode. The frame's
                  PC will already point to the next instruction. Note that the bytecode operand
                  count won't necessarily equal the actual operand count (in operands array)
                  since certain instructions are implemented via implicit operands. The count
                  is of the actual number of operand bytes following the opcode.
                  Tracing is disabled unless this option is specified.

Advanced options, if you‘re setting up a VM with existing stack-frames or whatever.

  :opcodes        Custom opcode array. See VM::OPCODES.
  :frames         Initial stack frames.
  :class_loaders  Initial class-loaders for the VM (:class_paths ignored if specified)
  :class_pool     The Ruva::VM::ClassPoolManager to use (:class_paths, :class_loaders ignored if specified)

*N.B.* If supplying your own class_loaders or class_pool, the Ruva runtime library path will not be added automatically to your classpaths. You‘ll probably want to make sure it‘s there yourself. The path to the runtime library is available in the Ruva::RUVA_RUNTIME constant.

[Source]

     # File lib/ruva/vm.rb, line 177
177:       def create(opts = {})
178:         if main = opts[:main]
179:           main_class, main_method, main_desc, main_args = main[:class], main[:method], main[:desc], main[:args] || []
180:         end       
181: 
182:         trace_exec = opts[:trace_exec]          
183:         opcodes = opts[:opcodes] || VM::Opcodes
184:         frames = opts[:frames] || []
185:           
186:         # the interdependency two-step
187:         if opts[:class_pool]
188:           class_pool = opts[:class_pool] 
189:         else
190:           class_pool = VM::ClassPoolManager.new(nil, 
191:               opts[:class_loaders] || [VM::ClassLoader.new(nil, *(Ruva::RUVA_RUNTIME + (opts[:class_paths] || ['.'])))])
192:         end         
193:         vm = new(frames, class_pool, main_class, main_method, main_desc, main_args, trace_exec, opcodes)                
194:         class_pool.vm = vm
195:         vm
196:       end

[Source]

     # File lib/ruva/vm.rb, line 199
199:     def initialize(frames = [], class_pool = VM::ClassPoolManager.new(self), main_class_name = nil, main_method_name = nil, main_method_desc = nil, main_args = [], trace_exec = false, opcodes = VM::Opcodes)
200:       @frames = [@bottom_frame = VM::Stack::BottomFrame.new(main_class_name, main_method_name, main_method_desc, main_args), *frames]
201:       @class_pool_manager = class_pool    
202:       @opcodes = opcodes
203:       
204:       if @tracer = trace_exec
205:         class << self
206:           def print_trace(opnum, num_bytecode_operands, operands)
207:             @tracer.trace(frame, opnum, num_bytecode_operands, operands)
208:           end
209:         end
210:       end    
211:       
212:       @natives = Module.new
213:       @natives.const_set(:VM, ::Ruva::VM)      
214:       @natives.const_set(:MyVM, self)      
215:       
216:       yield self if block_given?
217:       
218:       # This caches message names for executed opcodes. Saves having 
219:       # string interpolation running all the time.
220:       @__opmsgs = []      
221:     end

[Source]

     # File lib/ruva/vm.rb, line 206
206:           def print_trace(opnum, num_bytecode_operands, operands)
207:             @tracer.trace(frame, opnum, num_bytecode_operands, operands)
208:           end

Public Instance methods

Call a method immediately. The method will be pushed in a new frame, and the VM run until it returns. It‘s return value will be pushed onto the stack of the current top frame as with a normal return.

If the method is an instance method, the first arg must be the ObjectRef to call the method on.

[Source]

     # File lib/ruva/vm.rb, line 296
296:     def call_immediate(clz, method_name, method_desc, *args)
297:       st_frames = frames.length
298:       method = method_name + method_desc
299:       oclz = clz
300:       
301:       until clz.nil? || method_idx = clz.method_lookup[method]
302:         clz = class_pool_manager.find_class(clz.super_name)
303:       end
304:                       
305:       raise VM::NoSuchMethodError, "Method #{method} not found for #{oclz}" unless method_idx
306:         
307:       push_frame(new_frame(clz, method_idx, [], args))        
308:       step until frames.length == st_frames
309: 
310:       nil
311:     end

Like call_immediate, but if the method called returns a value it is popped and returned by the method, leaving the stack in the same state as it currently is.

[Source]

     # File lib/ruva/vm.rb, line 316
316:     def call_immediate_and_pop(clz, method_name, method_desc, *args)
317:       st_frames = frames.length
318:       method = method_name + method_desc
319:       oclz = clz
320:       
321:       until clz.nil? || method_idx = clz.method_lookup[method]
322:         clz = class_pool_manager.find_class(clz.super_name)
323:       end
324:                       
325:       raise VM::NoSuchMethodError, "Method #{method} not found for #{oclz}" unless method_idx
326:       
327:       push_frame(new_frame(clz, method_idx, [], args))        
328:       step until frames.length == st_frames
329:       
330:       stack.pop unless clz.methods[method_idx].void?
331:     end

Get the frame of the caller that invoked the current frame. N.B. This doesn‘t remove reflection-invocation methods!

[Source]

     # File lib/ruva/vm.rb, line 239
239:     def calling_frame
240:       frames[-2]
241:     end

Throw the supplied java Exception instance, unwinding the stack to the first suitable handler (if found).

[Source]

     # File lib/ruva/vm.rb, line 360
360:     def exception_unwind(exception)        
361:       handler = nil
362:       while frame && handler.nil?
363:         # FIXME wow, this is convoluted and slow
364:         if (handler_clz = (exs = frame.method.exception_handlers).keys.find { |handler_clz| exception.java_instof?(self, handler_clz) })
365:           # pc - 1 to account for the fact that pc is already on next insn
366:           last_frame = pop_frame unless exs[handler_clz].any? { |h| h.in_range?(frame.pc - 1) && handler = h }
367:         else
368:           last_frame = pop_frame        
369:         end
370:       end
371:       
372:       if handler
373:         stack.push(exception)          
374:         frame.pc = handler.handler_pc
375:       else
376:         # need to reinstate bottom frame
377:         frames << last_frame
378: 
379:         # TODO reinstate once system.out stuff is up
380:         #call_immediate(exception.clz, 'printStackTrace', '()V', exception)                                     
381:         
382:         traceeleclz = find_class('java/lang/StackTraceElement')
383:         
384:         msg = call_immediate_and_pop(exception.clz, 'getMessage', '()Ljava/lang/String;', exception)
385:         msg = VM::Types::ConstStringRef.unwrap(msg)
386:         
387:         trace = call_immediate_and_pop(exception.clz, 'getStackTrace', '()[Ljava/lang/StackTraceElement;', exception)        
388:         trace = trace.map do |e| 
389:           "#{e.unwrap_str_field('fileName')}:#{e.get_field('lineNumber', 'I')}" +
390:           ": in #{e.unwrap_str_field('className')}#{e.unwrap_str_field('methodName')}" +
391:           "#{' (native)' if e.get_field('native','Z') == 1}"
392:         end
393:         
394:         stack.push(exception)        
395:         raise VM::AbnormalTermination, "Uncaught exception #{exception.clz}: #{msg}", trace
396:       end
397:     end

Get the currently active frame

[Source]

     # File lib/ruva/vm.rb, line 233
233:     def frame
234:       @frame ||= frames[-1]
235:     end

Init a new object, and push to the stack. Returns the stack.

[Source]

     # File lib/ruva/vm.rb, line 334
334:     def init_new_object(clz, ctor_desc, *ctor_args)
335:       ref = VM::Types::ObjectRef.new(clz.is_a?(VM::Class) ? clz : find_class(clz.to_s))
336:       call_immediate(ref.clz, '<init>', ctor_desc, ref, *ctor_args)
337:       stack.push(ref)      
338:     end

[Source]

     # File lib/ruva/vm.rb, line 263
263:     def inspect
264:       "#<Ruva::JVM:#{object_id} @frames=#{@frames} @class_pool_manager=#{@class_pool_manager.inspect} (@opcodes ommitted)>"
265:     end

[Source]

     # File lib/ruva/vm.rb, line 340
340:     def jexception_for_error(ex)    
341:       case ex
342:       when VM::VMException      
343:         eclz = class_pool_manager.find_class("java/lang/#{ex.class.name[/::(\w+)Error$/,1]}Exception")
344:       when VM::VMError
345:         eclz = class_pool_manager.find_class("java/lang/#{ex.class.name[/::(?:VM)?(\w+)$/,1]}")
346:       else
347:         eclz = nil
348:       end
349:       
350:       if eclz
351:         jex = init_new_object(eclz, "(Ljava/lang/String;)V", wrap_str(ex.message)).pop
352:         call_immediate_and_pop(eclz, 'fillInStackTrace', '()Ljava/lang/Throwable;', jex)
353:       else
354:         nil
355:       end
356:     end

Get the current frame‘s locals

[Source]

     # File lib/ruva/vm.rb, line 249
249:     def locals
250:       frame.locals unless frames.empty?
251:     end

Get the current frame‘s PC

[Source]

     # File lib/ruva/vm.rb, line 254
254:     def pc
255:       frame.pc
256:     end

Set the current frame‘s PC

[Source]

     # File lib/ruva/vm.rb, line 259
259:     def pc=(npc)
260:       frame.pc = npc
261:     end

Pop a frame.

[Source]

     # File lib/ruva/vm.rb, line 284
284:     def pop_frame
285:       @frame = nil
286:       frames.pop    
287:     end

This is a callback, overriden in initialize if trace_exec is supplied.

[Source]

     # File lib/ruva/vm.rb, line 229
229:     def print_trace(opnum, num_bytecode_operands, operands)
230:     end

Push a new stack frame. You must use this rather than manually pushing to frames, because clinit is checked and run here too.

[Source]

     # File lib/ruva/vm.rb, line 269
269:     def push_frame(new_frame)
270:       frames.push(top = new_frame)
271:     
272:       #new_frame.clz.instof_classes(self).select { |clz| !clz.initialized? }.each do |clz|        
273:       unless (clz = new_frame.clz).initialized? || new_frame.method.name == '<clinit>'
274:         if method_idx = clz.method_lookup['<clinit>()V']
275:           frames.push(top = Ruva::VM::Stack::Frame.new(clz, method_idx))
276:         end
277:         clz.initialized = true
278:       end    
279:       
280:       @frame = top
281:     end

[Source]

     # File lib/ruva/vm.rb, line 445
445:     def run
446:       # manually step to start execution off the bottom frame
447:       step
448:       
449:       # keep going until we're back there
450:       step while frames.length > 1
451:       @bottom_frame.stack[0]
452:     end

Get the current frame‘s stack

[Source]

     # File lib/ruva/vm.rb, line 244
244:     def stack
245:       frame.stack unless frames.empty?
246:     end

Perform a single step. PC is left at start of op_next, or the target insn if modified by the opcode.

[Source]

     # File lib/ruva/vm.rb, line 401
401:     def step    
402:       # If this is a new frame, and it's method is native, we just call it then set up 
403:       # a NATIVERETURN, which'll pop us right back to the previous frame.
404:       fr = frame
405:       
406:       if (m = fr.method).native?
407:         unless natives = (clz = fr.clz).natives(self.natives)
408:           pop_frame   # stop the failed native frame appearing in trace
409:           raise VM::UnsatisfiedLinkError, "Native implementation not found for #{clz.name}.#{m.name}#{m.desc}"
410:         end          
411:         
412:         unless natives.respond_to?(mname = (m.name =~ /<([a-zA-Z0-9_]+)>/) ? "__sp_#{$1}" : m.name)
413:           pop_frame   # stop the failed native frame appearing in trace
414:           raise VM::UnsatisfiedLinkError, "Native implementation not found for #{clz.name}.#{m.name}#{m.desc}"
415:         end      
416: 
417:         native = true        
418:         ret = natives.__send__(mname, self, m.desc, clz, *frame.locals)
419:         
420:         stack.push(ret) unless m.void?
421:         opnum = 0xff            # IMPDEP2 - Ruva NATIVERETURN
422:       else      
423:         # It's not native, so we're just executing the next opcode.
424:         raise VM::VMError, "Execution fell off code array" unless native || opnum = fr.code[fr.pc] 
425:       end
426: 
427:       noperands = VM::OP_ARGC[opnum]
428: 
429:       # This incs pc, too.
430:       if noperands > 0
431:         operands = (0...noperands).map { |i| fr.code[fr.pc += 1] }
432:       else
433:         operands = []
434:       end
435: 
436:       fr.pc += 1     
437:      
438:       print_trace(noperands, opnum, operands)
439:       
440:       opcodes.send((@__opmsgs[opnum] ||= "op#{opnum}".intern), self, *operands)
441:     rescue VM::VMError => ex    
442:       exception_unwind(jexception_for_error(ex))
443:     end

[Validate]