INVOKEVIRTUAL (0xB6)


The INVOKEVIRTUAL instruction invokes a method. Its operand is a two-byte unsigned offset. The address of the method is stored in the constant pool at address CPP+offset.

Interpreter Microcode

The microinstructions are listed in the order in which they are executed; not the order in which they are stored in the control store. Since microinstructions can do several things at once, there is no simple progression of tasks; the tasks are somewhat intermingled. Some comments have been included in an effort to make it easier to follow what is happening in the code.

0x0b6  PC=PC+1; fetch; goto 0x42
...
0x042  H=MBRU<<8; goto 0x43
0x043  H=H OR MBRU; goto 0x44     // H = offset
0x044  MAR=H+CPP; rd; goto 0x45   // MAR = CPP+offset (address of method address)
0x045  OPC=PC+1; goto 0x46
0x046  PC=MDR; fetch; goto 0x47   // PC = method address
0x047  PC=PC+1; fetch; goto 0x48
0x048  H=MBRU<<8; goto 0x49
0x049  H=H OR MBRU; goto 0x4a     // H = # of parameters
0x04a  PC=PC+1; fetch; goto 0x4b
0x04b  TOS=SP-H; goto 0x4c
0x04c  TOS=MAR=TOS+1; goto 0x4d
0x04d  PC=PC+1; fetch; goto 0x4e
0x04e  H=MBRU<<8; goto 0x4f
0x04f  H=H OR MBRU; goto 0x50     // H = # of local variables
0x050  MDR=H+SP+1; wr; goto 0x51  // Write return address pointer to stack frame
0x051  SP=MAR=MDR; goto 0x52
0x052  MDR=OPC; wr; goto 0x53     // Write the return address to the stack frame
0x053  SP=MAR=SP+1; goto 0x54
0x054  MDR=LV; wr; goto 0x55      // Write the old LV to the stack frame
0x055  PC=PC+1; fetch; goto 0x56  // Fetch first opcode in method
0x056  LV=TOS; goto 0x2           // Set LV to point to the base of the stack frame

Example Program

//---------------------------------------------
// Demonstrate the INVOKEVIRTUAL and IRETURN
// instructions.
//
// 1. Clear Memory
// 2. Assemble this program.
// 3. Reset the computer.
// 4. Click the "Display Words" radio button
//    below the memory display.
// 5. Click the "Run" button.
//
// The local variable 'sum' in main (at offset
// zero from LV) will contain the value 10.
//---------------------------------------------
.constant
    OBJREF 0
.end-constant

.main
    .var
        sum
    .end-var

    ldc_w OBJREF
    bipush 6
    bipush 4
    invokevirtual add
    istore sum
    halt
.end-main

//---------------------------------------------
// Note: normally, you would not use a local
// variable in the method below; it is not
// needed. You would just execute 'ireturn'
// immediately after the 'iadd' instruction.
// The variable was included to help illustrate
// how stack frames are created.
//---------------------------------------------
.method add(a, b)
    .var
        sum
    .end-var

    iload a
    iload b
    iadd
    istore sum  // Store sum

    iload sum   // Place sum on stack as return value
    ireturn
.end-method

Before invoking a method two things must be done. First, an object reference must be placed onto the stack. In a real Java machine, this would identify the object that is invoking the method. Since objects are not supported in the IJVM, the value of this parameter is irrelevant. However, it still serves a purpose. The address of the object reference parameter is the base address of the stack frame for the method. When the stack frame is created, the value of the object reference parameter is replaced by a pointer to the return address. When the method terminates, the address of the object reference parameter becomes the address of the top of the stack and the value returned by the method is stored there.

When writing assembly code, declare a constant named OBJREF and give it an arbitrary value. Use the LDC_W instruction to load this constant onto the top of the stack as the object reference.

The second thing you must do before invoking a method is to push all of the explicit function parameters (if any) onto the stack. Note: the number of parameters in the method header includes the object reference (an implicit parameter) as well as the explicit method parameters. Consequently, every method has a least one parameter (the object reference).

As you can see by looking at the microcode above, a fair amount of work must be done to invoke a method. Following the execution of the INVOKEVIRTUAL instruction, a stack frame has been created with the following properties:

  1. Space is reserved on the stack for local variables (following the method parameters).
  2. The return address (the byte-address of the next opcode following the INVOKEVIRTUAL instruction) is placed on the stack following the last local variable.
  3. The OBJREF value is replaced by a pointer to the return address.
  4. The LV register is set to point at the base of the stack frame (which contains the pointer to the return address).
  5. The previous LV value is placed on the stack following the return address.
  6. The SP register is set to point to the top of the stack frame (which contains the previous LV value).

In the graphic below are two "snapshots" of memory: one taken just before the execution of the INVOKEVIRTUAL instruction in the example program above and one taken immediately afterward. In the first snapshot, the stack contains the object reference (OBJREF) and the two parameters that were pushed onto the stack.

The second snapshot illustrates the stack frame following the invocation of the INVOKEVIRTUAL instruction. The red numbers correspond to the stack frame properties listed above.

Every IJVM method returns a value. Following the termination of a method (see IRETURN), the return value will be on the top of the stack. When implementing a "value-returning" function, the calling routine should do something with this value. When implementing a void function (a procedure), the calling routine should just pop this value off the stack.