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