1

When a method is called via invokevirtual, the calling method pops off the values to pass to the called method along with the objectref and places them in the new stack frame.

How does it know which stack entry is the objectref? My guess is that it does so by looking at the type of the called method and parses this to determine how many values to pop off, but this seems extremely inefficient. Is there some other mechanism that I'm overlooking?

Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
platypusguy
  • 435
  • 1
  • 5
  • 7

2 Answers2

1

When you use the class file format as starting point, the method descriptor is the only way to determine, which values from the operand stack have to become the first local variables of the new stack frame.

As an exception to the rule, the invokeinterface instruction has an embedded count which can be used to determine the number of (type 1) elements to consume. As the documentation states:

The count operand of the invokeinterface instruction records a measure of the number of argument values, where an argument value of type long or type double contributes two units to the count value and an argument of any other type contributes one unit. This information can also be derived from the descriptor of the selected method. The redundancy is historical.

This historical redundancy doesn’t change the fact that the JVM has to cope with method descriptors as the source of this information, e.g. for invokevirtual, invokestatic, invokespecial, or invokedynamic. Further, a conforming JVM is required to verify this information, to throw an error if the invokeinterface’s count differs from the count derived from the method descriptor.

Generally, the verifier is responsible for detecting when method invocations are inconsistent to the stack frame’s state and therefore, has to process the method descriptors and model their effect on the operand stack. This implies that, unless you’re using a JVM that verifies each instruction right before its actual execution, it has to handle these descriptors even without executing an actual invocation. The obvious solution is to convert the method descriptors into an easier-to-process internal representation in a first step.

In short, these method descriptors are inefficient but with a reasonable JVM implementation you’re paying the costs only once, not for every invocation.

Holger
  • 285,553
  • 42
  • 434
  • 765
0

There's no one "right" way to do this, but the simplest strategy is to leave the values on the stack, and the called method refers to them via negative offsets. For example, if the called method has 3 params, they're referenced from the base stack offset minus 3, 2, and 1. Each is copied to a local variable and then referenced in the usual manner. The stack offset can be updated to reflect that the params have been consumed. Of course, each local param can also be initially assigned by a bunch of pops, one for each param.

Other tricks can be performed to speed things up. There's no reason that local variables need to be stored differently than the stack. They can be stored on the stack itself. The passed in params occupy their original locations on the stack, and then additional space is allocated for the remaining local variables by just updating the stack offset. A base stack offset is remembered, and all local variables are referenced via the base offset.

Essentially, a local variable is just like a stack slot, except it can be accessed at any time, regardless of what's currently been pushed on top.

boneill
  • 1,478
  • 1
  • 11
  • 18
  • If I understand you right, are you then creating a new frame, but using the operand stack of the calling method rather than creating a new operand stack for the frame? – platypusguy Nov 20 '21 at 06:44
  • Yes, there's no reason to create a new operand stack. Just extend it. – boneill Nov 20 '21 at 15:24
  • @platypusguy not exactly; the new frame will have a new operand stack on its own. The key point is that the JVM uses a frame layout where the *local variables* are at the beginning and creates the new frame overlapping to the old one, such that the old frame’s method arguments on the operand stack are right where the new frame’s local variables (including `this` when non-`static`) are expected. Note that this still requires knowledge about the number of method arguments, to determine the correct amount of overlapping (and the receiver in case of virtual calls). – Holger Nov 22 '21 at 13:30