Part 37 - interpreter:_ invoke_ return_ Entry and other routines

We introduced the execution logic of the return bytecode instruction before. This bytecode instruction will only release the lock and exit the current stack frame. However, when the control is transferred to the caller, it is also necessary to restore the caller's stack frame state, such as making% r13 point to bcp and% r14 point to the local variable table. In addition, it is also necessary to pop up the pressed arguments Jump to the next bytecode instruction of the caller and continue to execute. All these operations are performed by interpreter::_ return_ The entry routine is responsible for. This routine was described earlier when introducing bytecode instructions such as invokevirtual and invokeinterface. When calling a method with these bytecode instructions, the interpreter will be pressed according to the return type of the method:_ return_ Entry is the address of the corresponding routine stored in the one-dimensional array, so that this routine will be executed after the return bytecode instruction is executed.

In bytecode instructions such as invokevirtual and invokeinterface, obtain the corresponding routine entry by calling the following functions:

address* TemplateInterpreter::invoke_return_entry_table_for(Bytecodes::Code code) {
  switch (code) {
  case Bytecodes::_invokestatic:
  case Bytecodes::_invokespecial:
  case Bytecodes::_invokevirtual:
  case Bytecodes::_invokehandle:
    return Interpreter::invoke_return_entry_table();
  case Bytecodes::_invokeinterface:
    return Interpreter::invokeinterface_return_entry_table();
    fatal(err_msg("invalid bytecode: %s", Bytecodes::name(code)));
    return NULL;

You can see the invokeinterface bytecode from interpreter:_ invokeinterface_ return_ Get the corresponding routine from the entry array, and others from the Interpreter::_invoke_return_entry is obtained from a one-dimensional array. As follows:

address TemplateInterpreter::_invoke_return_entry[TemplateInterpreter::number_of_return_addrs];
address TemplateInterpreter::_invokeinterface_return_entry[TemplateInterpreter::number_of_return_addrs];
address TemplateInterpreter::_invokedynamic_return_entry[TemplateInterpreter::number_of_return_addrs];

When a one-dimensional array is returned, the routine entry address will be further determined according to the method return type. Let's take a look at the generation process of these routines.  

TemplateInterpreterGenerator::generate_ Interpreter will be generated in the all() function:_ return_ Entry, as follows:

    CodeletMark cm(_masm, "invoke return entry points");
    const TosState states[]           = {itos, itos, itos, itos, ltos, ftos, dtos, atos, vtos};
    const int invoke_length           = Bytecodes::length_for(Bytecodes::_invokestatic);     // invoke_length=3
    const int invokeinterface_length  = Bytecodes::length_for(Bytecodes::_invokeinterface);  // invokeinterface=5

    for (int i = 0; i < Interpreter::number_of_return_addrs; i++) { // number_of_return_addrs = 9
       TosState state = states[i]; // TosState is an enumeration type
       Interpreter::_invoke_return_entry[i] = generate_return_entry_for(state, invoke_length, sizeof(u2)); 
       Interpreter::_invokeinterface_return_entry[i] = generate_return_entry_for(state, invokeinterface_length, sizeof(u2));

Except for invokedynamic bytecode instructions, other method call instructions need to be called by generate after interpretation and execution_ return_ entry_ The routine generated by the for() function generates the generate of the routine_ return_ entry_ The for() function is implemented as follows:

address TemplateInterpreterGenerator::generate_return_entry_for(TosState state, int step, size_t index_size) {

  // Restore stack bottom in case i2c adjusted stack
  __ movptr(rsp, Address(rbp, frame::interpreter_frame_last_sp_offset * wordSize)); // interpreter_frame_last_sp_offset=-2
  // and NULL it as marker that esp is now tos until next java call
  __ movptr(Address(rbp, frame::interpreter_frame_last_sp_offset * wordSize), (int32_t)NULL_WORD);

  __ restore_bcp();
  __ restore_locals();

  // ...

  const Register cache = rbx;
  const Register index = rcx;
  __ get_cache_and_index_at_bcp(cache, index, 1, index_size);

  const Register flags = cache;
  __ movl(flags, Address(cache, index, Address::times_ptr, ConstantPoolCache::base_offset() + ConstantPoolCacheEntry::flags_offset()));
  __ andl(flags, ConstantPoolCacheEntry::parameter_size_mask);
  __ lea(rsp, Address(rsp, flags, Interpreter::stackElementScale())   ); // Stack element scalar is 8
  __ dispatch_next(state, step);

  return entry;

According to different states (different return types of methods), when selecting the next bytecode instruction of the caller's method, it will decide which entry to start from. Let's look at the assembly code generated when the passed state is itos (that is, when the return type of the method is int):

// After storing - 0x10(%rbp) to% rsp, set - 0x10(%rbp) to null
0x00007fffe1006ce0: mov    -0x10(%rbp),%rsp   // Change rsp
0x00007fffe1006ce4: movq   $0x0,-0x10(%rbp)   // Change the value of a specific location in the stack
// Restore bcp and locals so that% r14 points to the local variable table and% r13 points to bcp
0x00007fffe1006cec: mov    -0x38(%rbp),%r13
0x00007fffe1006cf0: mov    -0x30(%rbp),%r14
 // Get the index of ConstantPoolCacheEntry and load it into% ecx
0x00007fffe1006cf4: movzwl 0x1(%r13),%ecx     

 // Get the ConstantPoolCache of - 0x28(%rbp) in the stack and load it into% ecx
0x00007fffe1006cf9: mov    -0x28(%rbp),%rbx   
// shl is a logical shift left to obtain the word offset
0x00007fffe1006cfd: shl    $0x2,%ecx           
// Gets the value in the ConstantPoolCacheEntry_ flags attribute value
0x00007fffe1006d00: mov    0x28(%rbx,%rcx,8),%ebx
// Get_ The size of the parameter saved in the lower 8 bits of flags
0x00007fffe1006d04: and    $0xff,%ebx          

// The lea instruction loads the address into the memory register, which is what the stack looks like before calling the method
0x00007fffe1006d0a: lea    (%rsp,%rbx,8),%rsp  

// Jump to the next instruction execution
0x00007fffe1006d0e: movzbl 0x3(%r13),%ebx  
0x00007fffe1006d13: add    $0x3,%r13
0x00007fffe1006d17: movabs $0x7ffff73b7ca0,%r10
0x00007fffe1006d21: jmpq   *(%r10,%rbx,8)  

The logic of the above assembly code is very simple and will not be introduced here.   

The official account is analyzed in depth. Java virtual machine HotSpot has updated the VM source code to analyze the related articles to 60+. Welcome to the attention. If there are any problems, add WeChat mazhimazh, pull you into the virtual cluster communication.







Posted on Thu, 04 Nov 2021 00:53:33 -0400 by majik_sheff