Chapter 57 - profile instance

Back edge counting, ProfileData and Layout have been introduced before. Let's take a specific example to see how MethodData uses ProfileData to record detailed runtime information. Examples are as follows:

package com.test;

import java.util.LinkedList;

public class CompilationDemo {
	public static void main(String args[]){
		fact(60010*2);
	}
	
	public static int fact(int n) {
		int p = 1;
		while (n > 0) {
			p++;
		}
		return p;
	}
}

Configure the following command to let the HotSpot VM run the bytecode compiled with Javac as above, as follows:

-cp .:/media/mazhi/sourcecode/workspace/projectjava/projectjava01/bin  -XX:+TraceOnStackReplacement  com.test/CompilationDemo

The generated bytecode is as follows:

public static int fact(int);
    descriptor: (I)I
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=2, args_size=1
         0: iconst_1
         1: istore_1
         2: iload_0
         3: ifle          12 // Jump when the value of int type at the top of the stack is less than or equal to 0
         6: iinc          1, 1
         9: goto          2
        12: iload_1
        13: ireturn

For the above bytecode, the two bytecodes ifle and goto with bytecode indexes of 3 and 9 need corresponding ProfileData, specifically BranchData and JumpData. The data layout is as follows:

BranchData and JumpData layout data according to DataLayout format. DataLayout was introduced in detail in the previous article, as follows:

class DataLayout VALUE_OBJ_CLASS_SPEC {
private:
  union {
     // intptr_t type takes up 8 bytes
     intptr_t _bits; 
     struct {
      u1 _tag;
          // The format of flags is [recompile:1 | reason:3 | flags:4]
      u1 _flags; 
      u2 _bci;
     } _struct;
  } _header;
 
 
  // There can be many cells, and the address of the first cell is through the following_ Save the cells attribute,
  // The size of the specific cells array should also be determined according to the specific ProfileData
  intptr_t _cells[1];
 
  // ...
}

For the examples in this chapter, the specific values are as follows:

(1)BranchData

DataLayout::_bits=0
DataLayout::_struct._tag=DataLayout::branch_data_tag
_header._struct._bci=3

_ The size of the cells array is 3 and the size of each array element is 8 bytes_ The initialization of cells is mainly to call post_ in the subclasses of each ProfileData. The initialize () function is completed. For BranchData, call the post of BranchData class_ Initialize() function_ When cells are initialized, they will be subscripted as display_ off_ Store 56 at set = 1. Because the destination instruction to which the ifle instruction jumps does not have corresponding profiledata data data, data is directly jumped out_ Size area.

BranchData class is defined as follows:

class BranchData : public JumpData {
protected:
  enum {
    not_taken_off_set = jump_cell_count, // jump_ cell_ The value of count is 2
    branch_cell_count  // branch_ cell_ The value of count is 3
  };
  // ...
}

Note that the BranchData class inherits from the jumpdata class, so the BranchData class_ Three cells are required: JumpData::taken_off_set,JumpData::displacement_off_set and BranchData::not_taken_off_set.

Call the following function to initialize BranchData. The implementation of the function is as follows:

void BranchData::post_initialize(BytecodeStream* stream, MethodData* mdo) {
  assert(stream->bci() == bci(), "wrong pos");
  int target = stream->dest();
  // Call dp() function to get profiledata:_ The value of the data attribute, combined with MethodData::_data calculation
  // BranchData out relative to methoddata:_ Index value of data
  int my_di = mdo->dp_to_di(dp());
  // Obtain target data index through the subscript index of bytecode instruction
  int target_di = mdo->bci_to_di(target);
  int offset = target_di - my_di;
  // Store the offset in the display of JumpData_ off
  set_displacement(offset);
} 

You can see that jumpdata:: display in BranchData will be initialized_ off_ The value of the set property. Others_ The value of cells is 0.   

(2)JumpData

DataLayout::_bits=0
DataLayout::_struct._tag=DataLayout::jump_data_tag
_header._struct._bci=9

_ The size of the cells array is 2, and the subscript is display during initialization_ off_ - 32 is stored at set = 1. Because goto instruction will jump to the bytecode with subscript index 2, although this instruction does not have ProfileData, ifle has corresponding BranchData data data, so JumpData_ data_ The value of index plus - 32 is 0. If ifle is encountered during operation, it can be passed directly_ data_index find the corresponding BranchData.

The JumpData class is defined as follows:

class JumpData : public ProfileData {
protected:
  enum {
    taken_off_set,        // 0
    displacement_off_set, // 1
    jump_cell_count       // 2
  };
  
  // ...
}

Memory size occupied by JumpData except datalayout:_ In addition to the header, two cells are allocated. Each cell has a size of 8 bytes and is used to store the token respectively_ Off and display_ off. taken_off indicates the number of jumps, while display_ Off is used to adjust the method data pointer to the corresponding ProfileData position, which corresponds to the skipped bytecode instruction. In this way, if the skipped target bytecode instruction also has some information to be recorded, the corresponding ProfileData can be found and recorded directly through the method data pointer.

JumpData::post_ The initialize() function is implemented as follows:

void JumpData::post_initialize(BytecodeStream* stream, MethodData* mdo) {
  int target;
  Bytecodes::Code c = stream->code();
  // Gets the index of the target jump bytecode instruction of the jump instruction
  if (c == Bytecodes::_goto_w || c == Bytecodes::_jsr_w) {
    target = stream->dest_w();
  } else {
    target = stream->dest();
  }
  // dp() function gets jumpdata:_ The first address of data, and then combined with MethodData::_data calculation
  // Outputting JumpData in methoddata::_ Index value of data
  int my_di = mdo->dp_to_di(dp()); // 
  // Obtain target data index through the subscript index of bytecode instruction
  int target_di = mdo->bci_to_di(target);
  int offset = target_di - my_di;
  // Store the offset in the display of JumpData_ off
  set_displacement(offset);
}

You can see the display in JumpData_ Off stores_ The offset of data. method data pointer actually points to_ A certain ProfileData in data, offset = display_ After off, it still points to another ProfileData.  

(3)ArgInfoData

DataLayout::_bits=0
DataLayout::_struct._tag=DataLayout::arg_info_data_tag
_header._struct._bci=0

_ The size of the cells array is 2. In the array with subscript 0, the length of the array is stored, which is 1. It can be understood that the maximum subscript index is 1.

Let's see how the control transfer instruction passes through method:_ method_ In data_ The data property records runtime information.

The assembly code corresponding to the ifle instruction is as follows:

// The first and second operands are logically and. If it is 0, ZF is set to 0
0x00007fffe101ba07: test %eax,%eax
// If it is greater than 0, jump to ---- not_taken ----
0x00007fffe101ba09: jg 0x00007fffe101bd69

// Find Method * and store it in% rcx
0x00007fffe101ba0f: mov -0x18(%rbp),%rcx
// Find the method data pointer. If it is NULL, jump directly
0x00007fffe101ba13: mov -0x20(%rbp),%rax
0x00007fffe101ba17: test %rax,%rax
0x00007fffe101ba1a: je 0x00007fffe101ba38

// According to method:_ method_ Get jumpdata:: token from data_ off_ Set the value of the attribute at offset and store it in% rbx
0x00007fffe101ba20: mov 0x8(%rax),%rbx
// Add DataLayout::counter_increment, the value is 1
0x00007fffe101ba24: add $0x1,%rbx
0x00007fffe101ba28: sbb $0x0,%rbx
// Store back to jumpdata:: Token_ off_ Set offset
0x00007fffe101ba2c: mov %rbx,0x8(%rax)

// %method data pointer is stored in rax
// Get jumpdata:: display according to method data pointer_ off_ Value at set offset
0x00007fffe101ba30: add 0x10(%rax),%rax
// Update the value stored in% rax to the interpreter in the stack_ frame_ mdx_ Offset offset
0x00007fffe101ba34: mov %rax,-0x20(%rbp)


// ....


// **** not_takne ****


// If the method data pointer is NULL, directly jump to -- profile_continue ----
0x00007fffe101bd69: mov -0x20(%rbp),%rax
0x00007fffe101bd6d: test %rax,%rax
0x00007fffe101bd70: je 0x00007fffe101bd88

// Add branchdata:: not_ taken_ off_ Value at set = 2, plus 1
0x00007fffe101bd76: addq $0x1,0x18(%rax)

// Increase $0x20 according to the method data pointer, that is, the size of BranchData
0x00007fffe101bd7b: sbbq $0x0,0x18(%rax)
0x00007fffe101bd80: add $0x20,%rax
0x00007fffe101bd84: mov %rax,-0x20(%rbp)

// **** profile_continue ****
  

Note that the method data pointer above points to_ data_ The address of the location with index 0. For BranchData_ The size of the cells array is 3 and stores jumpdata:: token respectively_ off_ set,JumpData::displacement_off_set and BranchData::not_taken_off_set, so the runtime specific data of relevant instructions will be recorded in detail, which is very conducive to advanced optimization by subsequent compilers.   

When introducing goto bytecode instructions, the called TemplateTable::branch() function will call InterpreterMacroAssembler::profile_taken_branch() function, the generated assembly code is as follows:

// If the option ProfileInterpreter is enabled, performance statistics related to branch jump are performed

// %mdp (method data pointer) is saved in rax
0x00007fffe101dd14: mov    -0x20(%rbp),%rax
// If method:_ method_ If the value of data is NULL, jump to -- profile_continue ----
0x00007fffe101dd18: test   %rax,%rax
0x00007fffe101dd1b: je     0x00007fffe101dd39
 
 
// When the code is executed here, it means method:_ method_ The value of data is not NULL
 
// According to method:_ method_ Get jumpdata:: token from data_ off_ Set the value of the attribute at offset and store it in% rbx
0x00007fffe101dd21: mov    0x8(%rax),%rbx
// Add DataLayout::counter_increment, the value is 1
0x00007fffe101dd25: add    $0x1,%rbx
// sbb is a subtraction instruction with borrow
0x00007fffe101dd29: sbb    $0x0,%rbx
// Store back to jumpdata:: Token_ off_ Set offset
0x00007fffe101dd2d: mov    %rbx,0x8(%rax)
 
 
// %method data pointer is stored in rax
// Get jumpdata:: display according to method data pointer_ off_ Value at set offset
0x00007fffe101dd31: add    0x10(%rax),%rax
// Update the value stored in% rax to the interpreter in the stack_ frame_ mdx_ Offset offset
0x00007fffe101dd35: mov    %rax,-0x20(%rbp)

When method::_ method_ When the data is not NULL, it will be sent to methoddata::_ Record the number of control transfers in data (note that this is the number of control transfers, not the number of edge returns). Record through JumpData, which has been in methoddata::_ The corresponding location on the data has been initialized, initializing some commonly used attributes in the DataLayout::initialize() function, and then calling post_ The initialize () function initializes some specific attributes. Let's take a look at JumpData.

In the process of using the program, it is often necessary to use bci, bcp, method data pointer (this value is stored at interpreter_frame_mdx_offset in the stack) and data index. For example, the function of converting bci to data index is as follows:

int bci_to_di(int bci) {
  address x = bci_to_dp(bci);
  return dp_to_di(x);
}

First, find the method data pointer through bci. The functions called are as follows:

address MethodData::bci_to_dp(int bci) {
  ResourceMark  rm;
  ProfileData*  data = data_before(bci);
  ProfileData*  prev = NULL;
  for ( ; is_valid(data); data = next_data(data)) {
    if (data->bci() >= bci) { // If you enter this loop, you must return
      if (data->bci() == bci){
    	  int x = dp_to_di(data->dp());
    	  set_hint_di(x);
      }
      else if (prev != NULL){
    	  int x = dp_to_di(prev->dp());
    	  set_hint_di(x);
      }
      return data->dp();
    }
    prev = data;
  }
  return (address)limit_data_position();
}

ProfileData* data_before(int bci) {
    // avoid SEGV on this edge case
    if (data_size() == 0){
      return NULL;
    }
    int hint = hint_di();
    if (data_layout_at(hint)->bci() <= bci){
      return data_at(hint);
    }
    return first_data();
  }
  

The function to convert method data pointer to data index is as follows:

int dp_to_di(address dp) {
    return dp - ((address)_data);
}

Sometimes, when method::_ method_ Interpreterruntime:: bcp needs to be called when the value of the data property is not NULL_ to_ The di() function converts bcp to data index. The implementation of this function is as follows:

IRT_LEAF(jint, InterpreterRuntime::bcp_to_di(
 Method* method, 
 address cur_bcp)
)
  int bci = method->bci_from(cur_bcp);
  MethodData* mdo = method->method_data();
  if (mdo == NULL)  
     return 0;
  return mdo->bci_to_di(bci);
IRT_END

Call bci first_ The from() function obtains the bytecode index bci. The implementation of the function is as follows:

int Method::bci_from(address bcp) const {
  return bcp - code_base();
}

Then call bci_. to_ The di () function is sufficient.  

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.

 

 

 

 

 

 

 

  

 

Added by bache on Thu, 20 Jan 2022 03:43:00 +0200