8341006: Optimize StackMapGenerator detect frames

Reviewed-by: liach
This commit is contained in:
Shaojin Wen 2024-10-03 14:34:05 +00:00
parent eb93e6952b
commit 12028000db

View File

@ -1,5 +1,6 @@
/*
* Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2024, Alibaba Group Holding Limited. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -39,7 +40,6 @@ import java.lang.constant.ClassDesc;
import java.lang.constant.MethodTypeDesc;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
@ -58,8 +58,8 @@ import static jdk.internal.classfile.impl.RawBytecodeHelper.*;
* <p>
* The {@linkplain #generate() frames computation} consists of following steps:
* <ol>
* <li>{@linkplain #detectFrameOffsets() Detection} of mandatory stack map frames offsets:<ul>
* <li>Mandatory stack map frame offsets include all jump and switch instructions targets,
* <li>{@linkplain #detectFrames() Detection} of mandatory stack map frames:<ul>
* <li>Mandatory stack map frame include all jump and switch instructions targets,
* offsets immediately following {@linkplain #noControlFlow(int) "no control flow"}
* and all exception table handlers.
* <li>Detection is performed in a single fast pass through the bytecode,
@ -163,6 +163,7 @@ public final class StackMapGenerator {
private static final int FLAG_THIS_UNINIT = 0x01;
private static final int FRAME_DEFAULT_CAPACITY = 10;
private static final int T_BOOLEAN = 4, T_LONG = 11;
private static final Frame[] EMPTY_FRAME_ARRAY = new Frame[0];
private static final int ITEM_TOP = 0,
ITEM_INTEGER = 1,
@ -198,7 +199,8 @@ public final class StackMapGenerator {
private final ClassHierarchyImpl classHierarchy;
private final boolean patchDeadCode;
private final boolean filterDeadLabels;
private List<Frame> frames;
private Frame[] frames = EMPTY_FRAME_ARRAY;
private int framesCount = 0;
private final Frame currentFrame;
private int maxStack, maxLocals;
@ -262,10 +264,10 @@ public final class StackMapGenerator {
private Frame getFrame(int offset) {
//binary search over frames ordered by offset
int low = 0;
int high = frames.size() - 1;
int high = framesCount - 1;
while (low <= high) {
int mid = (low + high) >>> 1;
var f = frames.get(mid);
var f = frames[mid];
if (f.offset < offset)
low = mid + 1;
else if (f.offset > offset)
@ -283,8 +285,8 @@ public final class StackMapGenerator {
private int exMin, exMax;
private boolean isAnyFrameDirty() {
for (var f : frames) {
if (f.dirty) return true;
for (int i = 0; i < framesCount; i++) {
if (frames[i].dirty) return true;
}
return false;
}
@ -292,7 +294,29 @@ public final class StackMapGenerator {
private void generate() {
exMin = bytecode.length();
exMax = -1;
for (var exhandler : handlers) {
if (!handlers.isEmpty()) {
generateHandlers();
}
detectFrames();
do {
processMethod();
} while (isAnyFrameDirty());
maxLocals = currentFrame.frameMaxLocals;
maxStack = currentFrame.frameMaxStack;
//dead code patching
for (int i = 0; i < framesCount; i++) {
var frame = frames[i];
if (frame.flags == -1) {
deadCodePatching(frame, i);
}
}
}
private void generateHandlers() {
var labelContext = this.labelContext;
for (int i = 0; i < handlers.size(); i++) {
var exhandler = handlers.get(i);
int start_pc = labelContext.labelToBci(exhandler.tryStart());
int end_pc = labelContext.labelToBci(exhandler.tryEnd());
int handler_pc = labelContext.labelToBci(exhandler.handler());
@ -302,40 +326,23 @@ public final class StackMapGenerator {
var catchType = exhandler.catchType();
rawHandlers.add(new RawExceptionCatch(start_pc, end_pc, handler_pc,
catchType.isPresent() ? cpIndexToType(catchType.get().index(), cp)
: Type.THROWABLE_TYPE));
: Type.THROWABLE_TYPE));
}
}
BitSet frameOffsets = detectFrameOffsets();
int framesCount = frameOffsets.cardinality();
frames = new ArrayList<>(framesCount);
int offset = -1;
for (int i = 0; i < framesCount; i++) {
offset = frameOffsets.nextSetBit(offset + 1);
frames.add(new Frame(offset, classHierarchy));
}
do {
processMethod();
} while (isAnyFrameDirty());
maxLocals = currentFrame.frameMaxLocals;
maxStack = currentFrame.frameMaxStack;
}
//dead code patching
for (int i = 0; i < framesCount; i++) {
var frame = frames.get(i);
if (frame.flags == -1) {
if (!patchDeadCode) throw generatorError("Unable to generate stack map frame for dead code", frame.offset);
//patch frame
frame.pushStack(Type.THROWABLE_TYPE);
if (maxStack < 1) maxStack = 1;
int end = (i < framesCount - 1 ? frames.get(i + 1).offset : bytecode.length()) - 1;
//patch bytecode
var arr = bytecode.array();
Arrays.fill(arr, frame.offset, end, (byte) NOP);
arr[end] = (byte) ATHROW;
//patch handlers
removeRangeFromExcTable(frame.offset, end + 1);
}
}
private void deadCodePatching(Frame frame, int i) {
if (!patchDeadCode) throw generatorError("Unable to generate stack map frame for dead code", frame.offset);
//patch frame
frame.pushStack(Type.THROWABLE_TYPE);
if (maxStack < 1) maxStack = 1;
int end = (i < framesCount - 1 ? frames[i + 1].offset : bytecode.length()) - 1;
//patch bytecode
var arr = bytecode.array();
Arrays.fill(arr, frame.offset, end, (byte) NOP);
arr[end] = (byte) ATHROW;
//patch handlers
removeRangeFromExcTable(frame.offset, end + 1);
}
private void removeRangeFromExcTable(int rangeStart, int rangeEnd) {
@ -380,14 +387,15 @@ public final class StackMapGenerator {
* @return <code>StackMapTableAttribute</code> or null if stack map is empty
*/
public Attribute<? extends StackMapTableAttribute> stackMapTableAttribute() {
return frames.isEmpty() ? null : new UnboundAttribute.AdHocAttribute<>(Attributes.stackMapTable()) {
return framesCount == 0 ? null : new UnboundAttribute.AdHocAttribute<>(Attributes.stackMapTable()) {
@Override
public void writeBody(BufWriterImpl b) {
b.writeU2(frames.size());
b.writeU2(framesCount);
Frame prevFrame = new Frame(classHierarchy);
prevFrame.setLocalsFromArg(methodName, methodDesc, isStatic, thisType);
prevFrame.trimAndCompress();
for (var fr : frames) {
for (int i = 0; i < framesCount; i++) {
var fr = frames[i];
fr.trimAndCompress();
fr.writeTo(b, prevFrame, cp);
prevFrame = fr;
@ -412,19 +420,19 @@ public final class StackMapGenerator {
boolean ncf = false;
while (bcs.next()) {
currentFrame.offset = bcs.bci();
if (stackmapIndex < frames.size()) {
int thisOffset = frames.get(stackmapIndex).offset;
if (stackmapIndex < framesCount) {
int thisOffset = frames[stackmapIndex].offset;
if (ncf && thisOffset > bcs.bci()) {
throw generatorError("Expecting a stack map frame");
}
if (thisOffset == bcs.bci()) {
Frame nextFrame = frames.get(stackmapIndex++);
Frame nextFrame = frames[stackmapIndex++];
if (!ncf) {
currentFrame.checkAssignableTo(nextFrame);
}
while (!nextFrame.dirty) { //skip unmatched frames
if (stackmapIndex == frames.size()) return; //skip the rest of this round
nextFrame = frames.get(stackmapIndex++);
if (stackmapIndex == framesCount) return; //skip the rest of this round
nextFrame = frames[stackmapIndex++];
}
bcs.reset(nextFrame.offset); //skip code up-to the next frame
bcs.next();
@ -841,18 +849,10 @@ public final class StackMapGenerator {
}
/**
* Performs detection of mandatory stack map frames offsets
* in a single bytecode traversing pass
* @return <code>java.lang.BitSet</code> of detected frames offsets
* Performs detection of mandatory stack map frames in a single bytecode traversing pass
* @return detected frames
*/
private BitSet detectFrameOffsets() {
var offsets = new BitSet() {
@Override
public void set(int i) {
Preconditions.checkIndex(i, bytecode.length(), RawBytecodeHelper.IAE_FORMATTER);
super.set(i);
}
};
private void detectFrames() {
var bcs = bytecode.start();
boolean no_control_flow = false;
int opcode, bci = 0;
@ -860,22 +860,22 @@ public final class StackMapGenerator {
opcode = bcs.opcode();
bci = bcs.bci();
if (no_control_flow) {
offsets.set(bci);
addFrame(bci);
}
no_control_flow = switch (opcode) {
case GOTO -> {
offsets.set(bcs.dest());
addFrame(bcs.dest());
yield true;
}
case GOTO_W -> {
offsets.set(bcs.destW());
addFrame(bcs.destW());
yield true;
}
case IF_ICMPEQ, IF_ICMPNE, IF_ICMPLT, IF_ICMPGE,
IF_ICMPGT, IF_ICMPLE, IFEQ, IFNE,
IFLT, IFGE, IFGT, IFLE, IF_ACMPEQ,
IF_ACMPNE , IFNULL , IFNONNULL -> {
offsets.set(bcs.dest());
addFrame(bcs.dest());
yield false;
}
case TABLESWITCH, LOOKUPSWITCH -> {
@ -891,9 +891,9 @@ public final class StackMapGenerator {
keys = bcs.getIntUnchecked(aligned_bci + 4);
delta = 2;
}
offsets.set(bci + default_ofset);
addFrame(bci + default_ofset);
for (int i = 0; i < keys; i++) {
offsets.set(bci + bcs.getIntUnchecked(aligned_bci + (3 + i * delta) * 4));
addFrame(bci + bcs.getIntUnchecked(aligned_bci + (3 + i * delta) * 4));
}
yield true;
}
@ -904,13 +904,36 @@ public final class StackMapGenerator {
} catch (IllegalArgumentException iae) {
throw generatorError("Detected branch target out of bytecode range", bci);
}
for (var exhandler : rawHandlers) try {
offsets.set(exhandler.handler());
for (int i = 0; i < rawHandlers.size(); i++) try {
addFrame(rawHandlers.get(i).handler());
} catch (IllegalArgumentException iae) {
if (!filterDeadLabels)
throw generatorError("Detected exception handler out of bytecode range");
}
return offsets;
}
private void addFrame(int offset) {
Preconditions.checkIndex(offset, bytecode.length(), RawBytecodeHelper.IAE_FORMATTER);
var frames = this.frames;
int i = 0, framesCount = this.framesCount;
for (; i < framesCount; i++) {
var frameOffset = frames[i].offset;
if (frameOffset == offset) {
return;
}
if (frameOffset > offset) {
break;
}
}
if (framesCount >= frames.length) {
int newCapacity = framesCount + 8;
this.frames = frames = framesCount == 0 ? new Frame[newCapacity] : Arrays.copyOf(frames, newCapacity);
}
if (i != framesCount) {
System.arraycopy(frames, i, frames, i + 1, framesCount - i);
}
frames[i] = new Frame(offset, classHierarchy);
this.framesCount = framesCount + 1;
}
private final class Frame {