8341006: Optimize StackMapGenerator detect frames
Reviewed-by: liach
This commit is contained in:
parent
eb93e6952b
commit
12028000db
@ -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());
|
||||
@ -305,29 +329,14 @@ public final class StackMapGenerator {
|
||||
: 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) {
|
||||
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.get(i + 1).offset : bytecode.length()) - 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);
|
||||
@ -335,8 +344,6 @@ public final class StackMapGenerator {
|
||||
//patch handlers
|
||||
removeRangeFromExcTable(frame.offset, end + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void removeRangeFromExcTable(int rangeStart, int rangeEnd) {
|
||||
var it = handlers.listIterator();
|
||||
@ -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 {
|
||||
|
Loading…
x
Reference in New Issue
Block a user