Skip to content

Commit a1eebbd

Browse files
liachwenshao
andcommitted
8339576: Speed up raw bytecode processing in ClassFile API
Co-authored-by: Shaojin Wen <[email protected]> Reviewed-by: asotona, redestad
1 parent a35fd38 commit a1eebbd

File tree

9 files changed

+372
-275
lines changed

9 files changed

+372
-275
lines changed

src/java.base/share/classes/jdk/internal/classfile/impl/BufWriterImpl.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
package jdk.internal.classfile.impl;
2727

2828

29-
import java.nio.ByteBuffer;
3029
import java.util.Arrays;
3130

3231
import java.lang.classfile.BufWriter;
@@ -247,8 +246,8 @@ public int size() {
247246
return offset;
248247
}
249248

250-
public ByteBuffer asByteBuffer() {
251-
return ByteBuffer.wrap(elems, 0, offset).slice();
249+
public RawBytecodeHelper.CodeRange bytecodeView() {
250+
return RawBytecodeHelper.of(elems, offset);
252251
}
253252

254253
public void copyTo(byte[] array, int bufferOffset) {
Lines changed: 223 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -24,23 +24,66 @@
2424
*/
2525
package jdk.internal.classfile.impl;
2626

27-
import java.nio.ByteBuffer;
28-
import static java.lang.classfile.ClassFile.ASTORE_3;
29-
import static java.lang.classfile.ClassFile.ISTORE;
30-
import static java.lang.classfile.ClassFile.LOOKUPSWITCH;
31-
import static java.lang.classfile.ClassFile.TABLESWITCH;
32-
import static java.lang.classfile.ClassFile.WIDE;
27+
import java.util.List;
28+
import java.util.function.BiFunction;
29+
import java.util.function.Function;
30+
31+
import jdk.internal.misc.Unsafe;
32+
import jdk.internal.util.Preconditions;
33+
import jdk.internal.vm.annotation.Stable;
34+
35+
import static java.lang.classfile.ClassFile.*;
3336

3437
public final class RawBytecodeHelper {
3538

39+
public static final BiFunction<String, List<Number>, IllegalArgumentException>
40+
IAE_FORMATTER = Preconditions.outOfBoundsExceptionFormatter(new Function<>() {
41+
@Override
42+
public IllegalArgumentException apply(String s) {
43+
return new IllegalArgumentException(s);
44+
}
45+
});
46+
47+
public record CodeRange(byte[] array, int length) {
48+
public RawBytecodeHelper start() {
49+
return new RawBytecodeHelper(this);
50+
}
51+
}
52+
3653
public static final int ILLEGAL = -1;
3754

38-
private static final byte[] LENGTHS = new byte[] {
39-
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 2, 3, 3, 2 | (4 << 4), 2 | (4 << 4), 2 | (4 << 4), 2 | (4 << 4), 2 | (4 << 4), 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
40-
2 | (4 << 4), 2 | (4 << 4), 2 | (4 << 4), 2 | (4 << 4), 2 | (4 << 4), 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
41-
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3 | (6 << 4), 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2 | (4 << 4), 0, 0, 1, 1, 1,
42-
1, 1, 1, 3, 3, 3, 3, 3, 3, 3, 5, 5, 3, 2, 3, 1, 1, 3, 3, 1, 1, 0, 4, 3, 3, 5, 5, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 4, 4, 4, 2, 4, 3, 3, 0, 0, 1, 3, 2, 3, 3, 3, 1, 2, 1,
43-
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
55+
/**
56+
* The length of opcodes, or -1 for no fixed length.
57+
* This is generated as if:
58+
* {@snippet lang=java :
59+
* var lengths = new byte[0x100];
60+
* Arrays.fill(lengths, (byte) -1);
61+
* for (var op : Opcode.values()) {
62+
* if (!op.isWide()) {
63+
* lengths[op.bytecode()] = (byte) op.sizeIfFixed();
64+
* }
65+
* }
66+
* }
67+
* Tested in UtilTest::testOpcodeLengthTable.
68+
*/
69+
// Note: Consider distinguishing non-opcode and non-fixed-length opcode
70+
public static final @Stable byte[] LENGTHS = new byte[] {
71+
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
72+
2, 3, 2, 3, 3, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1,
73+
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
74+
1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1,
75+
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
76+
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
77+
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
78+
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
79+
1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
80+
1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 3, 3, 3,
81+
3, 3, 3, 3, 3, 3, 3, 3, 3, 2, -1, -1, 1, 1, 1, 1,
82+
1, 1, 3, 3, 3, 3, 3, 3, 3, 5, 5, 3, 2, 3, 1, 1,
83+
3, 3, 1, 1, -1, 4, 3, 3, 5, 5, -1, -1, -1, -1, -1, -1,
84+
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
85+
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
86+
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
4487
};
4588

4689
public static boolean isStoreIntoLocal(int code) {
@@ -51,121 +94,200 @@ public static int align(int n) {
5194
return (n + 3) & ~3;
5295
}
5396

54-
private final ByteBuffer bytecode;
55-
public int bci, nextBci, endBci;
56-
public int rawCode;
57-
public boolean isWide;
97+
private static final Unsafe UNSAFE = Unsafe.getUnsafe();
98+
public final CodeRange code;
99+
private int nextBci;
100+
private int bci;
101+
private int opcode;
102+
private boolean isWide;
58103

59-
public RawBytecodeHelper(ByteBuffer bytecode) {
60-
this.bytecode = bytecode;
61-
this.bci = 0;
62-
this.nextBci = 0;
63-
this.endBci = bytecode.capacity();
104+
public static CodeRange of(byte[] array) {
105+
return new CodeRange(array, array.length);
64106
}
65107

66-
public boolean isLastBytecode() {
67-
return nextBci >= endBci;
108+
public static CodeRange of(byte[] array, int limit) {
109+
return new CodeRange(array, limit);
68110
}
69111

70-
public int getShort(int bci) {
71-
return bytecode.getShort(bci);
112+
private RawBytecodeHelper(CodeRange range) {
113+
this.code = range;
72114
}
73115

74-
public int dest() {
75-
return bci + getShort(bci + 1);
116+
// immutable states
117+
118+
/** {@return the end of the code array} */
119+
public int endBci() {
120+
return code.length;
76121
}
77122

78-
public int getInt(int bci) {
79-
return bytecode.getInt(bci);
123+
// setup
124+
125+
/**
126+
* Sets the starting bci for bytecode reading. Can be set to
127+
* {@link #endBci} to end scanning. Must be followed by a
128+
* {@link #next} before getter access.
129+
*/
130+
public void reset(int nextBci) {
131+
Preconditions.checkIndex(nextBci, endBci() + 1, IAE_FORMATTER);
132+
this.nextBci = nextBci;
80133
}
81134

82-
public int destW() {
83-
return bci + getInt(bci + 1);
135+
// getters after transition
136+
137+
/**
138+
* Returns the current functional opcode, or {@link #ILLEGAL} if
139+
* the next instruction is invalid in format.
140+
* If this returns a valid opcode, that instruction's format must
141+
* be valid and can be accessed unchecked.
142+
*/
143+
public int opcode() {
144+
return opcode;
84145
}
85146

86-
public int getIndexU1() {
87-
return bytecode.get(bci + 1) & 0xff;
147+
/**
148+
* Returns whether the current functional opcode is in wide.
149+
*/
150+
public boolean isWide() {
151+
return isWide;
88152
}
89153

154+
/**
155+
* Returns the last validated instruction's index.
156+
*/
157+
public int bci() {
158+
return bci;
159+
}
160+
161+
// general utilities
162+
90163
public int getU1(int bci) {
91-
return bytecode.get(bci) & 0xff;
164+
Preconditions.checkIndex(bci, endBci(), IAE_FORMATTER);
165+
return getU1Unchecked(bci);
92166
}
93167

94-
public int rawNext(int jumpTo) {
95-
this.nextBci = jumpTo;
96-
return rawNext();
168+
public int getU2(int bci) {
169+
Preconditions.checkFromIndexSize(bci, 2, endBci(), IAE_FORMATTER);
170+
return getU2Unchecked(bci);
97171
}
98172

99-
public int rawNext() {
100-
bci = nextBci;
101-
int code = bytecode.get(bci) & 0xff;
102-
int len = LENGTHS[code] & 0xf;
103-
if (len > 0 && (bci <= endBci - len)) {
104-
isWide = false;
105-
nextBci += len;
106-
if (nextBci <= bci) {
107-
code = ILLEGAL;
108-
}
109-
rawCode = code;
110-
return code;
111-
} else {
112-
len = switch (bytecode.get(bci) & 0xff) {
113-
case WIDE -> {
114-
if (bci + 1 >= endBci) {
115-
yield -1;
116-
}
117-
yield LENGTHS[bytecode.get(bci + 1) & 0xff] >> 4;
118-
}
119-
case TABLESWITCH -> {
120-
int aligned_bci = align(bci + 1);
121-
if (aligned_bci + 3 * 4 >= endBci) {
122-
yield -1;
123-
}
124-
int lo = bytecode.getInt(aligned_bci + 1 * 4);
125-
int hi = bytecode.getInt(aligned_bci + 2 * 4);
126-
int l = aligned_bci - bci + (3 + hi - lo + 1) * 4;
127-
if (l > 0) yield l; else yield -1;
128-
}
129-
case LOOKUPSWITCH -> {
130-
int aligned_bci = align(bci + 1);
131-
if (aligned_bci + 2 * 4 >= endBci) {
132-
yield -1;
133-
}
134-
int npairs = bytecode.getInt(aligned_bci + 4);
135-
int l = aligned_bci - bci + (2 + 2 * npairs) * 4;
136-
if (l > 0) yield l; else yield -1;
137-
}
138-
default ->
139-
0;
140-
};
141-
if (len <= 0 || (bci > endBci - len) || (bci - len >= nextBci)) {
142-
code = ILLEGAL;
143-
} else {
144-
nextBci += len;
145-
isWide = false;
146-
if (code == WIDE) {
147-
if (bci + 1 >= endBci) {
148-
code = ILLEGAL;
149-
} else {
150-
code = bytecode.get(bci + 1) & 0xff;
151-
isWide = true;
152-
}
153-
}
154-
}
155-
rawCode = code;
156-
return code;
157-
}
173+
public int getShort(int bci) {
174+
Preconditions.checkFromIndexSize(bci, 2, endBci(), IAE_FORMATTER);
175+
return getShortUnchecked(bci);
176+
}
177+
178+
public int getInt(int bci) {
179+
Preconditions.checkFromIndexSize(bci, 4, endBci(), IAE_FORMATTER);
180+
return getIntUnchecked(bci);
181+
}
182+
183+
// Unchecked accessors: only if opcode() is validated
184+
185+
public int getU1Unchecked(int bci) {
186+
return Byte.toUnsignedInt(code.array[bci]);
187+
}
188+
189+
public int getU2Unchecked(int bci) {
190+
return UNSAFE.getCharUnaligned(code.array, (long) Unsafe.ARRAY_BYTE_BASE_OFFSET + bci, true);
158191
}
159192

193+
public int getShortUnchecked(int bci) {
194+
return UNSAFE.getShortUnaligned(code.array, (long) Unsafe.ARRAY_BYTE_BASE_OFFSET + bci, true);
195+
}
196+
197+
// used after switch validation
198+
public int getIntUnchecked(int bci) {
199+
return UNSAFE.getIntUnaligned(code.array, (long) Unsafe.ARRAY_BYTE_BASE_OFFSET + bci, true);
200+
}
201+
202+
// non-wide branches
203+
public int dest() {
204+
return bci + getShortUnchecked(bci + 1);
205+
}
206+
207+
// goto_w and jsr_w
208+
public int destW() {
209+
return bci + getIntUnchecked(bci + 1);
210+
}
211+
212+
// *load, *store, iinc
160213
public int getIndex() {
161-
return (isWide) ? getIndexU2Raw(bci + 2) : getIndexU1();
214+
return isWide ? getU2Unchecked(bci + 2) : getIndexU1();
162215
}
163216

217+
// ldc
218+
public int getIndexU1() {
219+
return getU1Unchecked(bci + 1);
220+
}
221+
222+
// usually cp entry index
164223
public int getIndexU2() {
165-
return getIndexU2Raw(bci + 1);
224+
return getU2Unchecked(bci + 1);
166225
}
167226

168-
public int getIndexU2Raw(int bci) {
169-
return bytecode.getShort(bci) & 0xffff;
227+
// Transition methods
228+
229+
/**
230+
* Transitions to the next instruction and returns whether scanning should
231+
* continue. If the next instruction is malformed, {@link #opcode()} returns
232+
* {@link #ILLEGAL}, so we can perform value access without bound checks if
233+
* we have a valid opcode.
234+
*/
235+
public boolean next() {
236+
var bci = nextBci;
237+
var end = endBci();
238+
if (bci >= end) {
239+
return false;
240+
}
241+
242+
int code = getU1Unchecked(bci);
243+
int len = LENGTHS[code & 0xFF]; // & 0xFF eliminates bound check
244+
this.bci = bci;
245+
opcode = code;
246+
isWide = false;
247+
if (len <= 0) {
248+
len = checkSpecialInstruction(bci, end, code); // sets opcode
249+
}
250+
251+
if (len <= 0 || (nextBci += len) > end) {
252+
opcode = ILLEGAL;
253+
}
254+
255+
return true;
256+
}
257+
258+
// Put rarely used code in another method to reduce code size
259+
private int checkSpecialInstruction(int bci, int end, int code) {
260+
if (code == WIDE) {
261+
if (bci + 1 >= end) {
262+
return -1;
263+
}
264+
opcode = code = getIndexU1();
265+
isWide = true;
266+
// Validated in UtilTest.testOpcodeLengthTable
267+
return LENGTHS[code] * 2;
268+
}
269+
if (code == TABLESWITCH) {
270+
int alignedBci = align(bci + 1);
271+
if (alignedBci + 3 * 4 >= end) {
272+
return -1;
273+
}
274+
int lo = getIntUnchecked(alignedBci + 1 * 4);
275+
int hi = getIntUnchecked(alignedBci + 2 * 4);
276+
long l = alignedBci - bci + (3L + (long) hi - lo + 1L) * 4L;
277+
return l > 0 && ((int) l == l) ? (int) l : -1;
278+
}
279+
if (code == LOOKUPSWITCH) {
280+
int alignedBci = align(bci + 1);
281+
if (alignedBci + 2 * 4 >= end) {
282+
return -1;
283+
}
284+
int npairs = getIntUnchecked(alignedBci + 4);
285+
if (npairs < 0) {
286+
return -1;
287+
}
288+
long l = alignedBci - bci + (2L + 2L * npairs) * 4L;
289+
return l > 0 && ((int) l == l) ? (int) l : -1;
290+
}
291+
return -1;
170292
}
171293
}

0 commit comments

Comments
 (0)