| Class | Ruva::JVM |
| In: |
lib/ruva/vm.rb
|
| Parent: | Object |
This is the top-level class in Ruva
| bottom_frame | [R] | |
| class_pool_manager | [RW] | |
| frames | [R] | |
| natives | [R] | |
| opcodes | [RW] |
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.
# 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
# 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
# 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
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.
# 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.
# 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
Throw the supplied java Exception instance, unwinding the stack to the first suitable handler (if found).
# 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
Init a new object, and push to the stack. Returns the stack.
# 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
# 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
# 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
This is a callback, overriden in initialize if trace_exec is supplied.
# 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.
# 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
# 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
Perform a single step. PC is left at start of op_next, or the target insn if modified by the opcode.
# 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