lwjgl/src/java/org/lwjgl/util/mapped/MappedObjectTransformer.java

1323 lines
50 KiB
Java

/*
* Copyright (c) 2002-2011 LWJGL Project
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'LWJGL' nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.lwjgl.util.mapped;
import org.lwjgl.BufferUtils;
import org.lwjgl.LWJGLUtil;
import org.lwjgl.MemoryUtil;
import org.objectweb.asm.*;
import org.objectweb.asm.tree.*;
import org.objectweb.asm.tree.analysis.*;
import org.objectweb.asm.tree.analysis.Frame;
import org.objectweb.asm.util.TraceClassVisitor;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;
import static org.objectweb.asm.ClassWriter.*;
import static org.objectweb.asm.Opcodes.*;
/**
* This class implements the bytecode transformation that mapped object go through.
* Mapped object classes need to first be registered with the transformer, see {@link #register(Class)}.
* <p/>
* The transformer supports some debugging tools, enabled through JVM system properties:<br/>
* org.lwjgl.util.mapped.PrintTiming=true, prints timing information for the transformation step.<br/>
* org.lwjgl.util.mapped.PrintActivity=true, prints activity information.<br/>
* org.lwjgl.util.mapped.PrintBytecode=true, prints the transformed bytecode.<br/>
* org.lwjgl.util.Debug must also be set to true for the above to work.
*
* @author Riven
*/
public class MappedObjectTransformer {
static final boolean PRINT_ACTIVITY = LWJGLUtil.DEBUG && LWJGLUtil.getPrivilegedBoolean("org.lwjgl.util.mapped.PrintActivity");
static final boolean PRINT_TIMING = PRINT_ACTIVITY && LWJGLUtil.getPrivilegedBoolean("org.lwjgl.util.mapped.PrintTiming");
static final boolean PRINT_BYTECODE = LWJGLUtil.DEBUG && LWJGLUtil.getPrivilegedBoolean("org.lwjgl.util.mapped.PrintBytecode");
static final Map<String, MappedSubtypeInfo> className_to_subtype;
static final String MAPPED_OBJECT_JVM = jvmClassName(MappedObject.class);
static final String MAPPED_HELPER_JVM = jvmClassName(MappedHelper.class);
static final String MAPPEDSET_PREFIX = jvmClassName(MappedSet.class);
static final String MAPPED_SET2_JVM = jvmClassName(MappedSet2.class);
static final String MAPPED_SET3_JVM = jvmClassName(MappedSet3.class);
static final String MAPPED_SET4_JVM = jvmClassName(MappedSet4.class);
static final String CACHE_LINE_PAD_JVM = "L" + jvmClassName(CacheLinePad.class) + ";";
// Public methods
static final String VIEWADDRESS_METHOD_NAME = "getViewAddress";
static final String NEXT_METHOD_NAME = "next";
static final String ALIGN_METHOD_NAME = "getAlign";
static final String SIZEOF_METHOD_NAME = "getSizeof";
static final String CAPACITY_METHOD_NAME = "capacity"; // Used for .asArray().length
// Internal methods
static final String VIEW_CONSTRUCTOR_NAME = "constructView$LWJGL"; // Used by runViewConstructor
static final Map<Integer, String> OPCODE_TO_NAME = new HashMap<Integer, String>();
static final Map<Integer, String> INSNTYPE_TO_NAME = new HashMap<Integer, String>();
static boolean is_currently_computing_frames;
static {
getClassEnums(Opcodes.class, OPCODE_TO_NAME, "V1_", "ACC_", "T_", "F_", "MH_");
getClassEnums(AbstractInsnNode.class, INSNTYPE_TO_NAME);
className_to_subtype = new HashMap<String, MappedSubtypeInfo>();
{
// HACK: required for mapped.view++
//
// because the compiler generates:
// => GETFIELD MappedObject.view
// => ICONST_1
// => IADD
// => PUTFIELD MyMappedType.view
//
// instead of:
// => GETFIELD MyMappedType.view
// => ICONST_1
// => IADD
// => PUTFIELD MyMappedType.view
//
className_to_subtype.put(MAPPED_OBJECT_JVM, new MappedSubtypeInfo(MAPPED_OBJECT_JVM, null, -1, -1, -1, false));
}
final String vmName = System.getProperty("java.vm.name");
if ( vmName != null && !vmName.contains("Server") ) {
System.err.println("Warning: " + MappedObject.class.getSimpleName() + "s have inferiour performance on Client VMs, please consider switching to a Server VM.");
}
}
/**
* Registers a class as a mapped object.
* The class must extend {@link org.lwjgl.util.mapped.MappedObject} and be annotated with {@link org.lwjgl.util.mapped.MappedField}.
*
* @param type the mapped object class.
*/
public static void register(Class<? extends MappedObject> type) {
if ( MappedObjectClassLoader.FORKED )
return;
final MappedType mapped = type.getAnnotation(MappedType.class);
if ( mapped != null && mapped.padding() < 0 )
throw new ClassFormatError("Invalid mapped type padding: " + mapped.padding());
if ( type.getEnclosingClass() != null && !Modifier.isStatic(type.getModifiers()) )
throw new InternalError("only top-level or static inner classes are allowed");
final String className = jvmClassName(type);
final Map<String, FieldInfo> fields = new HashMap<String, FieldInfo>();
long sizeof = 0;
for ( Field field : type.getDeclaredFields() ) {
FieldInfo fieldInfo = registerField(mapped == null || mapped.autoGenerateOffsets(), className, sizeof, field);
if ( fieldInfo == null )
continue;
fields.put(field.getName(), fieldInfo);
sizeof = Math.max(sizeof, fieldInfo.offset + fieldInfo.lengthPadded);
}
int align = 4;
int padding = 0;
boolean cacheLinePadded = false;
if ( mapped != null ) {
align = mapped.align();
if ( mapped.cacheLinePadding() ) {
if ( mapped.padding() != 0 )
throw new ClassFormatError("Mapped type padding cannot be specified together with cacheLinePadding.");
final int cacheLineMod = (int)(sizeof % CacheUtil.getCacheLineSize());
if ( cacheLineMod != 0 )
padding = CacheUtil.getCacheLineSize() - cacheLineMod;
cacheLinePadded = true;
} else
padding = mapped.padding();
}
sizeof += padding;
final MappedSubtypeInfo mappedType = new MappedSubtypeInfo(className, fields, (int)sizeof, align, padding, cacheLinePadded);
if ( className_to_subtype.put(className, mappedType) != null )
throw new InternalError("duplicate mapped type: " + mappedType.className);
}
private static FieldInfo registerField(final boolean autoGenerateOffsets, final String className, long advancingOffset, final Field field) {
if ( Modifier.isStatic(field.getModifiers()) ) // static fields are never mapped
return null;
// we only support primitives and ByteBuffers
if ( !field.getType().isPrimitive() && field.getType() != ByteBuffer.class )
throw new ClassFormatError("field '" + className + "." + field.getName() + "' not supported: " + field.getType());
MappedField meta = field.getAnnotation(MappedField.class);
if ( meta == null && !autoGenerateOffsets )
throw new ClassFormatError("field '" + className + "." + field.getName() + "' missing annotation " + MappedField.class.getName() + ": " + className);
Pointer pointer = field.getAnnotation(Pointer.class);
if ( pointer != null && field.getType() != long.class )
throw new ClassFormatError("The @Pointer annotation can only be used on long fields. @Pointer field found: " + className + "." + field.getName() + ": " + field.getType());
if ( Modifier.isVolatile(field.getModifiers()) && (pointer != null || field.getType() == ByteBuffer.class) )
throw new ClassFormatError("The volatile keyword is not supported for @Pointer or ByteBuffer fields. Volatile field found: " + className + "." + field.getName() + ": " + field.getType());
// quick hack
long byteLength;
if ( field.getType() == long.class || field.getType() == double.class ) {
if ( pointer == null )
byteLength = 8;
else
byteLength = MappedObjectUnsafe.INSTANCE.addressSize();
} else if ( field.getType() == double.class )
byteLength = 8;
else if ( field.getType() == int.class || field.getType() == float.class )
byteLength = 4;
else if ( field.getType() == char.class || field.getType() == short.class )
byteLength = 2;
else if ( field.getType() == byte.class )
byteLength = 1;
else if ( field.getType() == ByteBuffer.class ) {
byteLength = meta.byteLength();
if ( byteLength < 0 )
throw new IllegalStateException("invalid byte length for mapped ByteBuffer field: " + className + "." + field.getName() + " [length=" + byteLength + "]");
} else
throw new ClassFormatError(field.getType().getName());
if ( field.getType() != ByteBuffer.class && (advancingOffset % byteLength) != 0 )
throw new IllegalStateException("misaligned mapped type: " + className + "." + field.getName());
CacheLinePad pad = field.getAnnotation(CacheLinePad.class);
long byteOffset = advancingOffset;
if ( meta != null && meta.byteOffset() != -1 ) {
if ( meta.byteOffset() < 0 )
throw new ClassFormatError("Invalid field byte offset: " + className + "." + field.getName() + " [byteOffset=" + meta.byteOffset() + "]");
if ( pad != null )
throw new ClassFormatError("A field byte offset cannot be specified together with cache-line padding: " + className + "." + field.getName());
byteOffset = meta.byteOffset();
}
long byteLengthPadded = byteLength;
if ( pad != null ) {
// Pad before
if ( pad.before() && byteOffset % CacheUtil.getCacheLineSize() != 0 )
byteOffset += CacheUtil.getCacheLineSize() - (byteOffset & (CacheUtil.getCacheLineSize() - 1));
// Pad after
if ( pad.after() && (byteOffset + byteLength) % CacheUtil.getCacheLineSize() != 0 )
byteLengthPadded += CacheUtil.getCacheLineSize() - (byteOffset + byteLength) % CacheUtil.getCacheLineSize();
assert !pad.before() || (byteOffset % CacheUtil.getCacheLineSize() == 0);
assert !pad.after() || ((byteOffset + byteLengthPadded) % CacheUtil.getCacheLineSize() == 0);
}
if (PRINT_ACTIVITY) {
long byteOffset1 = byteOffset;
LWJGLUtil.logger().log(() -> MappedObjectTransformer.class.getSimpleName() + ": " + className + '.' + field.getName() + " [type=" + field.getType().getSimpleName() + ", offset=" + byteOffset1 + ']');
}
return new FieldInfo(byteOffset, byteLength, byteLengthPadded, Type.getType(field.getType()), Modifier.isVolatile(field.getModifiers()), pointer != null);
}
/** Removes final from methods that will be overriden by subclasses. */
static byte[] transformMappedObject(byte[] bytecode) {
final ClassWriter cw = new ClassWriter(0);
ClassVisitor cv = new ClassAdapter(cw) {
private final String[] DEFINALIZE_LIST = {
VIEWADDRESS_METHOD_NAME,
NEXT_METHOD_NAME,
ALIGN_METHOD_NAME,
SIZEOF_METHOD_NAME,
CAPACITY_METHOD_NAME,
};
public MethodVisitor visitMethod(int access, final String name, final String desc, final String signature, final String[] exceptions) {
for ( String method : DEFINALIZE_LIST ) {
if ( name.equals(method) ) {
access &= ~ACC_FINAL;
break;
}
}
return super.visitMethod(access, name, desc, signature, exceptions);
}
};
new ClassReader(bytecode).accept(cv, 0);
return cw.toByteArray();
}
static byte[] transformMappedAPI(final String className, byte[] bytecode) {
final ClassWriter cw = new ClassWriter(COMPUTE_FRAMES) {
@Override
protected String getCommonSuperClass(String a, String b) {
// HACK: prevent user-code static-initialization-blocks to be executed
if ( is_currently_computing_frames && !a.startsWith("java/") || !b.startsWith("java/") )
return "java/lang/Object";
return super.getCommonSuperClass(a, b);
}
};
final TransformationAdapter ta = new TransformationAdapter(cw, className);
ClassVisitor cv = ta;
if ( className_to_subtype.containsKey(className) ) // Do a first pass to generate address getters
cv = getMethodGenAdapter(className, cv);
new ClassReader(bytecode).accept(cv, ClassReader.SKIP_FRAMES);
if ( !ta.transformed )
return bytecode;
bytecode = cw.toByteArray();
if (PRINT_BYTECODE) {
printBytecode(bytecode);
}
return bytecode;
}
private static ClassAdapter getMethodGenAdapter(final String className, final ClassVisitor cv) {
return new ClassAdapter(cv) {
@Override
public void visitEnd() {
final MappedSubtypeInfo mappedSubtype = className_to_subtype.get(className);
generateViewAddressGetter();
generateCapacity();
generateAlignGetter(mappedSubtype);
generateSizeofGetter();
generateNext();
for ( String fieldName : mappedSubtype.fields.keySet() ) {
final FieldInfo field = mappedSubtype.fields.get(fieldName);
if ( field.type.getDescriptor().length() > 1 ) { // ByteBuffer, getter only
generateByteBufferGetter(fieldName, field);
} else {
generateFieldGetter(fieldName, field);
generateFieldSetter(fieldName, field);
}
}
super.visitEnd();
}
private void generateViewAddressGetter() {
MethodVisitor mv = super.visitMethod(ACC_PUBLIC, VIEWADDRESS_METHOD_NAME, "(I)J", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, MAPPED_OBJECT_JVM, "baseAddress", "J");
mv.visitVarInsn(ILOAD, 1);
mv.visitFieldInsn(GETSTATIC, className, "SIZEOF", "I");
mv.visitInsn(IMUL);
mv.visitInsn(I2L);
mv.visitInsn(LADD);
if ( MappedObject.CHECKS ) {
mv.visitInsn(DUP2);
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESTATIC, MAPPED_HELPER_JVM, "checkAddress", "(JL" + MAPPED_OBJECT_JVM + ";)V");
}
mv.visitInsn(LRETURN);
mv.visitMaxs(3, 2);
mv.visitEnd();
}
private void generateCapacity() {
// return (backingByteBuffer().capacity() + (int)(MemoryUtil.getAddress0(backingByteBuffer()) - baseAddress)) / SIZEOF;
MethodVisitor mv = super.visitMethod(ACC_PUBLIC, CAPACITY_METHOD_NAME, "()I", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKEVIRTUAL, MAPPED_OBJECT_JVM, "backingByteBuffer", "()L" + jvmClassName(ByteBuffer.class) + ";");
mv.visitInsn(DUP);
mv.visitMethodInsn(INVOKEVIRTUAL, jvmClassName(ByteBuffer.class), "capacity", "()I");
mv.visitInsn(SWAP);
mv.visitMethodInsn(INVOKESTATIC, jvmClassName(MemoryUtil.class), "getAddress0", "(L" + jvmClassName(Buffer.class) + ";)J");
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, MAPPED_OBJECT_JVM, "baseAddress", "J");
mv.visitInsn(LSUB);
mv.visitInsn(L2I);
mv.visitInsn(IADD);
mv.visitFieldInsn(GETSTATIC, className, "SIZEOF", "I");
mv.visitInsn(IDIV);
mv.visitInsn(IRETURN);
mv.visitMaxs(3, 1);
mv.visitEnd();
}
private void generateAlignGetter(final MappedSubtypeInfo mappedSubtype) {
MethodVisitor mv = super.visitMethod(ACC_PUBLIC, ALIGN_METHOD_NAME, "()I", null, null);
mv.visitCode();
visitIntNode(mv, mappedSubtype.sizeof);
mv.visitInsn(IRETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
}
private void generateSizeofGetter() {
MethodVisitor mv = super.visitMethod(ACC_PUBLIC, SIZEOF_METHOD_NAME, "()I", null, null);
mv.visitCode();
mv.visitFieldInsn(GETSTATIC, className, "SIZEOF", "I");
mv.visitInsn(IRETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
}
private void generateNext() {
MethodVisitor mv = super.visitMethod(ACC_PUBLIC, NEXT_METHOD_NAME, "()V", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitInsn(DUP);
mv.visitFieldInsn(GETFIELD, MAPPED_OBJECT_JVM, "viewAddress", "J");
mv.visitFieldInsn(GETSTATIC, className, "SIZEOF", "I");
mv.visitInsn(I2L);
mv.visitInsn(LADD);
mv.visitMethodInsn(INVOKEVIRTUAL, className, "setViewAddress", "(J)V");
mv.visitInsn(RETURN);
mv.visitMaxs(3, 1);
mv.visitEnd();
}
private void generateByteBufferGetter(final String fieldName, final FieldInfo field) {
MethodVisitor mv = super.visitMethod(ACC_PUBLIC | ACC_STATIC, getterName(fieldName), "(L" + className + ";I)" + field.type.getDescriptor(), null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ILOAD, 1);
mv.visitMethodInsn(INVOKEVIRTUAL, className, VIEWADDRESS_METHOD_NAME, "(I)J");
visitIntNode(mv, (int)field.offset);
mv.visitInsn(I2L);
mv.visitInsn(LADD);
visitIntNode(mv, (int)field.length);
mv.visitMethodInsn(INVOKESTATIC, MAPPED_HELPER_JVM, "newBuffer", "(JI)L" + jvmClassName(ByteBuffer.class) + ";");
mv.visitInsn(ARETURN);
mv.visitMaxs(3, 2);
mv.visitEnd();
}
private void generateFieldGetter(final String fieldName, final FieldInfo field) {
MethodVisitor mv = super.visitMethod(ACC_PUBLIC | ACC_STATIC, getterName(fieldName), "(L" + className + ";I)" + field.type.getDescriptor(), null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ILOAD, 1);
mv.visitMethodInsn(INVOKEVIRTUAL, className, VIEWADDRESS_METHOD_NAME, "(I)J");
visitIntNode(mv, (int)field.offset);
mv.visitInsn(I2L);
mv.visitInsn(LADD);
mv.visitMethodInsn(INVOKESTATIC, MAPPED_HELPER_JVM, field.getAccessType() + "get", "(J)" + field.type.getDescriptor());
mv.visitInsn(field.type.getOpcode(IRETURN));
mv.visitMaxs(3, 2);
mv.visitEnd();
}
private void generateFieldSetter(final String fieldName, final FieldInfo field) {
MethodVisitor mv = super.visitMethod(ACC_PUBLIC | ACC_STATIC, setterName(fieldName), "(L" + className + ";I" + field.type.getDescriptor() + ")V", null, null);
mv.visitCode();
int load = 0;
switch ( field.type.getSort() ) {
case Type.BOOLEAN:
case Type.CHAR:
case Type.BYTE:
case Type.SHORT:
case Type.INT:
load = ILOAD;
break;
case Type.FLOAT:
load = FLOAD;
break;
case Type.LONG:
load = LLOAD;
break;
case Type.DOUBLE:
load = DLOAD;
break;
}
mv.visitVarInsn(load, 2);
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ILOAD, 1);
mv.visitMethodInsn(INVOKEVIRTUAL, className, VIEWADDRESS_METHOD_NAME, "(I)J");
visitIntNode(mv, (int)field.offset);
mv.visitInsn(I2L);
mv.visitInsn(LADD);
mv.visitMethodInsn(INVOKESTATIC, MAPPED_HELPER_JVM, field.getAccessType() + "put", "(" + field.type.getDescriptor() + "J)V");
mv.visitInsn(RETURN);
mv.visitMaxs(4, 4);
mv.visitEnd();
}
};
}
private static class TransformationAdapter extends ClassAdapter {
final String className;
boolean transformed;
TransformationAdapter(final ClassVisitor cv, final String className) {
super(cv);
this.className = className;
}
@Override
public FieldVisitor visitField(final int access, final String name, final String desc, final String signature, final Object value) {
// remove redirected fields
final MappedSubtypeInfo mappedSubtype = className_to_subtype.get(className);
if ( mappedSubtype != null && mappedSubtype.fields.containsKey(name) ) {
if (PRINT_ACTIVITY) {
LWJGLUtil.logger().log(() -> MappedObjectTransformer.class.getSimpleName() + ": discarding field: " + className + '.' + name + ':' + desc);
}
return null;
}
if ( (access & ACC_STATIC) == 0 ) {
return new FieldNode(access, name, desc, signature, value) {
public void visitEnd() {
if ( visibleAnnotations == null ) { // early-out
accept(cv);
return;
}
boolean before = false;
boolean after = false;
int byteLength = 0;
for ( AnnotationNode pad : visibleAnnotations ) {
if ( CACHE_LINE_PAD_JVM.equals(pad.desc) ) {
if ( "J".equals(desc) || "D".equals(desc) )
byteLength = 8;
else if ( "I".equals(desc) || "F".equals(desc) )
byteLength = 4;
else if ( "S".equals(desc) || "C".equals(desc) )
byteLength = 2;
else if ( "B".equals(desc) || "Z".equals(desc) )
byteLength = 1;
else
throw new ClassFormatError("The @CacheLinePad annotation cannot be used on non-primitive fields: " + className + "." + name);
transformed = true;
after = true;
if ( pad.values != null ) {
for ( int i = 0; i < pad.values.size(); i += 2 ) {
final boolean value = pad.values.get(i + 1).equals(Boolean.TRUE);
if ( "before".equals(pad.values.get(i)) )
before = value;
else
after = value;
}
}
break;
}
}
/*
We make the fields public to force the JVM to keep the fields in the object.
Instead of using only longs or integers, we use the same type as the original
field. That's because modern JVMs usually reorder fields by type:
longs, then doubles, then integers, then booleans, etc. This way it's more
likely that the padding will work as expected.
*/
if ( before ) {
final int count = CacheUtil.getCacheLineSize() / byteLength - 1;
for ( int i = count; i >= 1; i-- )
cv.visitField(access | ACC_PUBLIC | ACC_SYNTHETIC, name + "$PAD_" + i, desc, signature, null);
}
accept(cv);
if ( after ) {
final int count = CacheUtil.getCacheLineSize() / byteLength - 1;
for ( int i = 1; i <= count; i++ )
cv.visitField(access | ACC_PUBLIC | ACC_SYNTHETIC, name + "$PAD" + i, desc, signature, null);
}
}
};
} else
return super.visitField(access, name, desc, signature, value);
}
@Override
public MethodVisitor visitMethod(final int access, String name, final String desc, final String signature, final String[] exceptions) {
// Move MappedSubtype constructors to another method
if ( "<init>".equals(name) ) {
final MappedSubtypeInfo mappedSubtype = className_to_subtype.get(className);
if ( mappedSubtype != null ) {
if ( !"()V".equals(desc) )
throw new ClassFormatError(className + " can only have a default constructor, found: " + desc);
final MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, MAPPED_OBJECT_JVM, "<init>", "()V");
mv.visitInsn(RETURN);
mv.visitMaxs(0, 0);
// put the method body in another method
name = VIEW_CONSTRUCTOR_NAME;
}
}
final MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
return new MethodNode(access, name, desc, signature, exceptions) {
/** When true, the method has touched a mapped object and needs to be transformed. We track this
* so we can skip the expensive frame analysis and tree API usage. */
boolean needsTransformation;
@Override
public void visitMaxs(int a, int b) {
try {
is_currently_computing_frames = true;
super.visitMaxs(a, b);
} finally {
is_currently_computing_frames = false;
}
}
@Override
public void visitFieldInsn(final int opcode, final String owner, final String name, final String desc) {
if ( className_to_subtype.containsKey(owner) || owner.startsWith(MAPPEDSET_PREFIX) )
needsTransformation = true;
super.visitFieldInsn(opcode, owner, name, desc);
}
@Override
public void visitMethodInsn(final int opcode, final String owner, final String name, final String desc) {
if ( className_to_subtype.containsKey(owner) )
needsTransformation = true;
super.visitMethodInsn(opcode, owner, name, desc);
}
@Override
public void visitEnd() {
if ( needsTransformation ) { // Early-out for methods that do not touch a mapped object.
//System.err.println("\nTRANSFORMING: " + className + "." + name + desc);
transformed = true;
try {
transformMethod(analyse());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
// Pass the instruction stream to the adapter's MethodVisitor
accept(mv);
}
private Frame<BasicValue>[] analyse() throws AnalyzerException {
final Analyzer<BasicValue> a = new Analyzer<BasicValue>(new SimpleVerifier());
a.analyze(className, this);
return a.getFrames();
}
private void transformMethod(final Frame<BasicValue>[] frames) {
final InsnList instructions = this.instructions;
final Map<Integer, MappedSubtypeInfo> arrayVars = new HashMap<Integer, MappedSubtypeInfo>();
/*
We need this map because we insert/remove instructions from the stream and we need a way
to match each original instruction with the corresponding frame.
TODO: Can we keep track of everything more efficiently without a map?
*/
final Map<AbstractInsnNode, Frame<BasicValue>> frameMap = new HashMap<AbstractInsnNode, Frame<BasicValue>>();
for ( int i = 0; i < frames.length; i++ )
frameMap.put(instructions.get(i), frames[i]);
for ( int i = 0; i < instructions.size(); i++ ) { // f is a separate cursor for frames
final AbstractInsnNode instruction = instructions.get(i);
//System.out.println("MAIN LOOP #" + i + " - " + getOpcodeName(instruction));
switch ( instruction.getType() ) {
case AbstractInsnNode.VAR_INSN:
if ( instruction.getOpcode() == ALOAD ) {
VarInsnNode varInsn = (VarInsnNode)instruction;
final MappedSubtypeInfo mappedSubtype = arrayVars.get(varInsn.var);
if ( mappedSubtype != null )
i = transformArrayAccess(instructions, i, frameMap, varInsn, mappedSubtype, varInsn.var);
}
break;
case AbstractInsnNode.FIELD_INSN:
FieldInsnNode fieldInsn = (FieldInsnNode)instruction;
final InsnList list = transformFieldAccess(fieldInsn);
if ( list != null )
i = replace(instructions, i, instruction, list);
break;
case AbstractInsnNode.METHOD_INSN:
MethodInsnNode methodInsn = (MethodInsnNode)instruction;
final MappedSubtypeInfo mappedType = className_to_subtype.get(methodInsn.owner);
if ( mappedType != null )
i = transformMethodCall(instructions, i, frameMap, methodInsn, mappedType, arrayVars);
break;
}
}
}
};
}
}
static int transformMethodCall(final InsnList instructions, int i, final Map<AbstractInsnNode, Frame<BasicValue>> frameMap, final MethodInsnNode methodInsn, final MappedSubtypeInfo mappedType, final Map<Integer, MappedSubtypeInfo> arrayVars) {
switch ( methodInsn.getOpcode() ) {
case INVOKEVIRTUAL:
if ( "asArray".equals(methodInsn.name) && methodInsn.desc.equals("()[L" + MAPPED_OBJECT_JVM + ";") ) {
// Go forward and store the local variable index.
// We only allow this pattern: INVOKEVIRTUAL -> CHECKCAST -> ASTORE.
// We remove the first two and store the target MappedSubtype in the ASTORE variable
AbstractInsnNode nextInstruction;
checkInsnAfterIsArray(nextInstruction = methodInsn.getNext(), CHECKCAST);
checkInsnAfterIsArray(nextInstruction = nextInstruction.getNext(), ASTORE);
final Frame<BasicValue> frame = frameMap.get(nextInstruction);
final String targetType = frame.getStack(frame.getStackSize() - 1).getType().getElementType().getInternalName();
if ( !methodInsn.owner.equals(targetType) ) {
/*
This may happen with the current API, like so:
MappedA foo = MappedA.malloc(...);
MappedB[] cursor = foo.asArray();
We have to parameterize MappedObject to avoid this.
*/
throw new ClassCastException("Source: " + methodInsn.owner + " - Target: " + targetType);
}
final VarInsnNode varInstruction = (VarInsnNode)nextInstruction;
arrayVars.put(varInstruction.var, mappedType);
instructions.remove(methodInsn.getNext()); // Remove CHECKCAST
instructions.remove(methodInsn); // Remove INVOKEVIRTUAL
}
if ( "dup".equals(methodInsn.name) && methodInsn.desc.equals("()L" + MAPPED_OBJECT_JVM + ";") ) {
i = replace(instructions, i, methodInsn, generateDupInstructions(methodInsn));
break;
}
if ( "slice".equals(methodInsn.name) && methodInsn.desc.equals("()L" + MAPPED_OBJECT_JVM + ";") ) {
i = replace(instructions, i, methodInsn, generateSliceInstructions(methodInsn));
break;
}
if ( "runViewConstructor".equals(methodInsn.name) && "()V".equals(methodInsn.desc) ) {
i = replace(instructions, i, methodInsn, generateRunViewConstructorInstructions(methodInsn));
break;
}
if ( "copyTo".equals(methodInsn.name) && methodInsn.desc.equals("(L" + MAPPED_OBJECT_JVM + ";)V") ) {
i = replace(instructions, i, methodInsn, generateCopyToInstructions(mappedType));
break;
}
if ( "copyRange".equals(methodInsn.name) && methodInsn.desc.equals("(L" + MAPPED_OBJECT_JVM + ";I)V") ) {
i = replace(instructions, i, methodInsn, generateCopyRangeInstructions(mappedType));
break;
}
break;
case INVOKESPECIAL:
// super() in VIEW_CONSTRUCTOR_NAME, remove
if ( methodInsn.owner.equals(MAPPED_OBJECT_JVM) && "<init>".equals(methodInsn.name) && "()V".equals(methodInsn.desc) ) {
instructions.remove(methodInsn.getPrevious()); // ALOAD
instructions.remove(methodInsn); // INVOKESPECIAL
i -= 2;
}
break;
case INVOKESTATIC:
boolean isMapDirectMethod = "map".equals(methodInsn.name) && methodInsn.desc.equals("(JI)L" + MAPPED_OBJECT_JVM + ";");
boolean isMapBufferMethod = "map".equals(methodInsn.name) && methodInsn.desc.equals("(Ljava/nio/ByteBuffer;)L" + MAPPED_OBJECT_JVM + ";");
boolean isMallocMethod = "malloc".equals(methodInsn.name) && methodInsn.desc.equals("(I)L" + MAPPED_OBJECT_JVM + ";");
if ( (isMapDirectMethod || isMapBufferMethod) || isMallocMethod )
i = replace(instructions, i, methodInsn, generateMapInstructions(mappedType, methodInsn.owner, isMapDirectMethod, isMallocMethod));
break;
}
return i;
}
private static InsnList generateCopyRangeInstructions(final MappedSubtypeInfo mappedType) {
final InsnList list = new InsnList();
// stack: instances, target, this
list.add(getIntNode(mappedType.sizeof));
// stack: sizeof, instances, target, this
list.add(new InsnNode(IMUL));
// stack: bytes, target, this
list.add(new MethodInsnNode(INVOKESTATIC, MAPPED_HELPER_JVM, "copy", "(L" + MAPPED_OBJECT_JVM + ";L" + MAPPED_OBJECT_JVM + ";I)V"));
// stack: -
return list;
}
private static InsnList generateCopyToInstructions(final MappedSubtypeInfo mappedType) {
final InsnList list = new InsnList();
// stack: target, this
list.add(getIntNode(mappedType.sizeof - mappedType.padding));
// stack: sizeof, target, this
list.add(new MethodInsnNode(INVOKESTATIC, MAPPED_HELPER_JVM, "copy", "(L" + MAPPED_OBJECT_JVM + ";L" + MAPPED_OBJECT_JVM + ";I)V"));
// stack: -
return list;
}
private static InsnList generateRunViewConstructorInstructions(final MethodInsnNode methodInsn) {
final InsnList list = new InsnList();
// stack: this
list.add(new InsnNode(DUP));
// stack: this, this
list.add(new MethodInsnNode(INVOKEVIRTUAL, methodInsn.owner, VIEW_CONSTRUCTOR_NAME, "()V"));
// stack: this
return list;
}
private static InsnList generateSliceInstructions(final MethodInsnNode methodInsn) {
final InsnList list = new InsnList();
// stack: this
list.add(new TypeInsnNode(NEW, methodInsn.owner));
// stack: new, this
list.add(new InsnNode(DUP));
// stack: new, new, this
list.add(new MethodInsnNode(INVOKESPECIAL, methodInsn.owner, "<init>", "()V"));
// stack: new, this
list.add(new MethodInsnNode(INVOKESTATIC, MAPPED_HELPER_JVM, "slice", "(L" + MAPPED_OBJECT_JVM + ";L" + MAPPED_OBJECT_JVM + ";)L" + MAPPED_OBJECT_JVM + ";"));
// stack: new
return list;
}
private static InsnList generateDupInstructions(final MethodInsnNode methodInsn) {
final InsnList list = new InsnList();
// stack: this
list.add(new TypeInsnNode(NEW, methodInsn.owner));
// stack: new, this
list.add(new InsnNode(DUP));
// stack: new, new, this
list.add(new MethodInsnNode(INVOKESPECIAL, methodInsn.owner, "<init>", "()V"));
// stack: new, this
list.add(new MethodInsnNode(INVOKESTATIC, MAPPED_HELPER_JVM, "dup", "(L" + MAPPED_OBJECT_JVM + ";L" + MAPPED_OBJECT_JVM + ";)L" + MAPPED_OBJECT_JVM + ";"));
// stack: new
return list;
}
private static InsnList generateMapInstructions(final MappedSubtypeInfo mappedType, final String className, final boolean mapDirectMethod, final boolean mallocMethod) {
final InsnList trg = new InsnList();
if ( mallocMethod ) {
// stack: count
trg.add(getIntNode(mappedType.sizeof));
// stack: sizeof, count
trg.add(new InsnNode(IMUL));
// stack: bytes
trg.add(new MethodInsnNode(INVOKESTATIC, mappedType.cacheLinePadded ? jvmClassName(CacheUtil.class) : jvmClassName(BufferUtils.class), "createByteBuffer", "(I)L" + jvmClassName(ByteBuffer.class) + ";"));
// stack: buffer
} else if ( mapDirectMethod ) {
// stack: capacity, address
trg.add(new MethodInsnNode(INVOKESTATIC, MAPPED_HELPER_JVM, "newBuffer", "(JI)L" + jvmClassName(ByteBuffer.class) + ";"));
// stack: buffer
}
// stack: buffer
trg.add(new TypeInsnNode(NEW, className));
// stack: new, buffer
trg.add(new InsnNode(DUP));
// stack: new, new, buffer
trg.add(new MethodInsnNode(INVOKESPECIAL, className, "<init>", "()V"));
// stack: new, buffer
trg.add(new InsnNode(DUP_X1));
// stack: new, buffer, new
trg.add(new InsnNode(SWAP));
// stack: buffer, new, new
trg.add(getIntNode(mappedType.align));
// stack: int, buffer, new, new
trg.add(getIntNode(mappedType.sizeof));
// stack: int, int, buffer, new, new
trg.add(new MethodInsnNode(INVOKESTATIC, MAPPED_HELPER_JVM, "setup", "(L" + MAPPED_OBJECT_JVM + ";Ljava/nio/ByteBuffer;II)V"));
// stack: new
return trg;
}
static InsnList transformFieldAccess(final FieldInsnNode fieldInsn) {
final MappedSubtypeInfo mappedSubtype;
mappedSubtype = className_to_subtype.get(fieldInsn.owner);
if ( mappedSubtype == null ) { // early out
// MappedSet.view
outer:
if ( "view".equals(fieldInsn.name) && fieldInsn.owner.startsWith(MAPPEDSET_PREFIX) )
return generateSetViewInstructions(fieldInsn);
return null; // early out
}
if ( "SIZEOF".equals(fieldInsn.name) )
return generateSIZEOFInstructions(fieldInsn, mappedSubtype);
if ( "view".equals(fieldInsn.name) )
return generateViewInstructions(fieldInsn, mappedSubtype);
if ( "baseAddress".equals(fieldInsn.name) || "viewAddress".equals(fieldInsn.name) ) {
return generateAddressInstructions(fieldInsn);
}
final FieldInfo field = mappedSubtype.fields.get(fieldInsn.name);
if ( field == null ) // early out
return null;
// now we're going to transform ByteBuffer-typed field access
if ( fieldInsn.desc.equals("L" + jvmClassName(ByteBuffer.class) + ";") )
return generateByteBufferInstructions(fieldInsn, mappedSubtype, field.offset);
// we're now going to transform the field access
return generateFieldInstructions(fieldInsn, field);
}
private static InsnList generateSetViewInstructions(final FieldInsnNode fieldInsn) {
if ( fieldInsn.getOpcode() == GETFIELD )
throwAccessErrorOnReadOnlyField(fieldInsn.owner, fieldInsn.name);
if ( fieldInsn.getOpcode() != PUTFIELD )
throw new InternalError();
final InsnList list = new InsnList();
// stack: index, this
if ( MAPPED_SET2_JVM.equals(fieldInsn.owner) )
list.add(new MethodInsnNode(INVOKESTATIC, MAPPED_HELPER_JVM, "put_views", "(L" + MAPPED_SET2_JVM + ";I)V"));
else if ( MAPPED_SET3_JVM.equals(fieldInsn.owner) )
list.add(new MethodInsnNode(INVOKESTATIC, MAPPED_HELPER_JVM, "put_views", "(L" + MAPPED_SET3_JVM + ";I)V"));
else if ( MAPPED_SET4_JVM.equals(fieldInsn.owner) )
list.add(new MethodInsnNode(INVOKESTATIC, MAPPED_HELPER_JVM, "put_views", "(L" + MAPPED_SET4_JVM + ";I)V"));
else
throw new InternalError();
// stack: -
return list;
}
private static InsnList generateSIZEOFInstructions(final FieldInsnNode fieldInsn, final MappedSubtypeInfo mappedSubtype) {
if ( !"I".equals(fieldInsn.desc) )
throw new InternalError();
final InsnList list = new InsnList();
if ( fieldInsn.getOpcode() == GETSTATIC ) {
list.add(getIntNode(mappedSubtype.sizeof));
return list;
}
if ( fieldInsn.getOpcode() == PUTSTATIC )
throwAccessErrorOnReadOnlyField(fieldInsn.owner, fieldInsn.name);
throw new InternalError();
}
private static InsnList generateViewInstructions(final FieldInsnNode fieldInsn, final MappedSubtypeInfo mappedSubtype) {
if ( !"I".equals(fieldInsn.desc) )
throw new InternalError();
final InsnList list = new InsnList();
if ( fieldInsn.getOpcode() == GETFIELD ) {
if ( mappedSubtype.sizeof_shift != 0 ) {
// stack: instance
list.add(getIntNode(mappedSubtype.sizeof_shift));
// stack: sizeof, instance
list.add(new MethodInsnNode(INVOKESTATIC, MAPPED_HELPER_JVM, "get_view_shift", "(L" + MAPPED_OBJECT_JVM + ";I)I"));
// stack: view
} else {
// stack: instance
list.add(getIntNode(mappedSubtype.sizeof));
// stack: sizeof, instance
list.add(new MethodInsnNode(INVOKESTATIC, MAPPED_HELPER_JVM, "get_view", "(L" + MAPPED_OBJECT_JVM + ";I)I"));
// stack: view
}
return list;
}
if ( fieldInsn.getOpcode() == PUTFIELD ) {
if ( mappedSubtype.sizeof_shift != 0 ) {
// stack: view, instance
list.add(getIntNode(mappedSubtype.sizeof_shift));
// stack: sizeof, view, instance
list.add(new MethodInsnNode(INVOKESTATIC, MAPPED_HELPER_JVM, "put_view_shift", "(L" + MAPPED_OBJECT_JVM + ";II)V"));
// stack: -
} else {
// stack: view, instance
list.add(getIntNode(mappedSubtype.sizeof));
// stack: sizeof, view, instance
list.add(new MethodInsnNode(INVOKESTATIC, MAPPED_HELPER_JVM, "put_view", "(L" + MAPPED_OBJECT_JVM + ";II)V"));
// stack: -
}
return list;
}
throw new InternalError();
}
private static InsnList generateAddressInstructions(final FieldInsnNode fieldInsn) {
if ( !"J".equals(fieldInsn.desc) )
throw new IllegalStateException();
if ( fieldInsn.getOpcode() == GETFIELD ) // do not change a thing
return null;
if ( fieldInsn.getOpcode() == PUTFIELD )
throwAccessErrorOnReadOnlyField(fieldInsn.owner, fieldInsn.name);
throw new InternalError();
}
private static InsnList generateByteBufferInstructions(final FieldInsnNode fieldInsn, final MappedSubtypeInfo mappedSubtype, final long fieldOffset) {
if ( fieldInsn.getOpcode() == PUTFIELD )
throwAccessErrorOnReadOnlyField(fieldInsn.owner, fieldInsn.name);
if ( fieldInsn.getOpcode() == GETFIELD ) {
final InsnList list = new InsnList();
// stack: ref
list.add(new FieldInsnNode(GETFIELD, mappedSubtype.className, "viewAddress", "J"));
// stack: long
list.add(new LdcInsnNode(fieldOffset));
// stack: long, long
list.add(new InsnNode(LADD));
// stack: long
list.add(new LdcInsnNode(mappedSubtype.fields.get(fieldInsn.name).length));
// stack: long, long
list.add(new InsnNode(L2I));
// stack: int, long
list.add(new MethodInsnNode(INVOKESTATIC, MAPPED_HELPER_JVM, "newBuffer", "(JI)L" + jvmClassName(ByteBuffer.class) + ";"));
// stack: buffer
return list;
}
throw new InternalError();
}
private static InsnList generateFieldInstructions(final FieldInsnNode fieldInsn, final FieldInfo field) {
final InsnList list = new InsnList();
if ( fieldInsn.getOpcode() == PUTFIELD ) {
// stack: value, ref
list.add(getIntNode((int)field.offset));
// stack: fieldOffset, value, ref
list.add(new MethodInsnNode(INVOKESTATIC, MAPPED_HELPER_JVM, field.getAccessType() + "put", "(L" + MAPPED_OBJECT_JVM + ";" + fieldInsn.desc + "I)V"));
// stack -
return list;
}
if ( fieldInsn.getOpcode() == GETFIELD ) {
// stack: ref
list.add(getIntNode((int)field.offset));
// stack: fieldOffset, ref
list.add(new MethodInsnNode(INVOKESTATIC, MAPPED_HELPER_JVM, field.getAccessType() + "get", "(L" + MAPPED_OBJECT_JVM + ";I)" + fieldInsn.desc));
// stack: -
return list;
}
throw new InternalError();
}
static int transformArrayAccess(final InsnList instructions, int i, final Map<AbstractInsnNode, Frame<BasicValue>> frameMap, final VarInsnNode loadInsn, final MappedSubtypeInfo mappedSubtype, final int var) {
// We need to go forward in time to find how we use the array var
final int loadStackSize = frameMap.get(loadInsn).getStackSize() + 1;
AbstractInsnNode nextInsn = loadInsn;
while ( true ) {
nextInsn = nextInsn.getNext();
if ( nextInsn == null )
throw new InternalError();
Frame<BasicValue> frame = frameMap.get(nextInsn);
if ( frame == null )
continue;
int stackSize = frame.getStackSize();
if ( stackSize == loadStackSize + 1 && nextInsn.getOpcode() == AALOAD ) {
final AbstractInsnNode aaLoadInsn = nextInsn;
while ( true ) {
nextInsn = nextInsn.getNext();
if ( nextInsn == null )
break;
frame = frameMap.get(nextInsn);
if ( frame == null )
continue;
stackSize = frame.getStackSize();
if ( stackSize == loadStackSize + 1 && nextInsn.getOpcode() == PUTFIELD ) {
final FieldInsnNode fieldInsn = (FieldInsnNode)nextInsn;
// stack: value, view, ref
instructions.insert(nextInsn, new MethodInsnNode(INVOKESTATIC, mappedSubtype.className, setterName(fieldInsn.name), "(L" + mappedSubtype.className + ";I" + fieldInsn.desc + ")V"));
// stack: -
instructions.remove(nextInsn);
break;
} else if ( stackSize == loadStackSize && nextInsn.getOpcode() == GETFIELD ) {
final FieldInsnNode fieldInsn = (FieldInsnNode)nextInsn;
// stack: view, ref
instructions.insert(nextInsn, new MethodInsnNode(INVOKESTATIC, mappedSubtype.className, getterName(fieldInsn.name), "(L" + mappedSubtype.className + ";I)" + fieldInsn.desc));
// stack: value
instructions.remove(nextInsn);
break;
} else if ( stackSize == loadStackSize && nextInsn.getOpcode() == DUP && nextInsn.getNext().getOpcode() == GETFIELD ) {
// May happen with operator+assignment (e.g. cursor[i].value += 10)
final FieldInsnNode fieldInsn = (FieldInsnNode)nextInsn.getNext();
final MethodInsnNode getter = new MethodInsnNode(INVOKESTATIC, mappedSubtype.className, getterName(fieldInsn.name), "(L" + mappedSubtype.className + ";I)" + fieldInsn.desc);
// stack: view, ref
instructions.insert(nextInsn, new InsnNode(DUP2));
// stack: view, ref, view, ref
instructions.insert(nextInsn.getNext(), getter);
// stack: value, view, ref
instructions.remove(nextInsn);
instructions.remove(fieldInsn);
nextInsn = getter;
continue;
} else if ( stackSize < loadStackSize )
throw new ClassFormatError("Invalid " + mappedSubtype.className + " view array usage detected: " + getOpcodeName(nextInsn));
}
instructions.remove(aaLoadInsn);
return i;
} else if ( stackSize == loadStackSize && nextInsn.getOpcode() == ARRAYLENGTH ) {
if ( LWJGLUtil.DEBUG && loadInsn.getNext() != nextInsn )
throw new InternalError();
instructions.remove(nextInsn);
loadInsn.var = var;
instructions.insert(loadInsn, new MethodInsnNode(INVOKEVIRTUAL, mappedSubtype.className, CAPACITY_METHOD_NAME, "()I"));
return i + 1;
} else if ( stackSize < loadStackSize ) // Consumed by something other than AALOAD or ARRAYLENGTH
throw new ClassFormatError("Invalid " + mappedSubtype.className + " view array usage detected: " + getOpcodeName(nextInsn));
}
}
private static class FieldInfo {
final long offset;
final long length;
final long lengthPadded;
final Type type;
final boolean isVolatile;
final boolean isPointer;
FieldInfo(final long offset, final long length, final long lengthPadded, final Type type, final boolean isVolatile, final boolean isPointer) {
this.offset = offset;
this.length = length;
this.lengthPadded = lengthPadded;
this.type = type;
this.isVolatile = isVolatile;
this.isPointer = isPointer;
}
String getAccessType() {
return isPointer ? "a" : type.getDescriptor().toLowerCase() + (isVolatile ? "v" : "");
}
}
private static class MappedSubtypeInfo {
final String className;
final int sizeof;
final int sizeof_shift;
final int align;
final int padding;
final boolean cacheLinePadded;
final Map<String, FieldInfo> fields;
MappedSubtypeInfo(String className, Map<String, FieldInfo> fields, int sizeof, int align, int padding, final boolean cacheLinePadded) {
this.className = className;
this.sizeof = sizeof;
if ( ((sizeof - 1) & sizeof) == 0 )
this.sizeof_shift = getPoT(sizeof);
else
this.sizeof_shift = 0;
this.align = align;
this.padding = padding;
this.cacheLinePadded = cacheLinePadded;
this.fields = fields;
}
private static int getPoT(int value) {
int pot = -1;
while ( value > 0 ) {
pot++;
value >>= 1;
}
return pot;
}
}
// -------------------------------------------------------
// -------------------[ MACROS & UTILS ]------------------
// -------------------------------------------------------
private static void getClassEnums(final Class clazz, final Map<Integer, String> map, final String... prefixFilters) {
try {
OUTER:
for ( Field field : clazz.getFields() ) {
if ( !Modifier.isStatic(field.getModifiers()) || field.getType() != int.class )
continue;
for ( String filter : prefixFilters ) {
if ( field.getName().startsWith(filter) )
continue OUTER;
}
if ( map.put((Integer)field.get(null), field.getName()) != null )
throw new IllegalStateException();
}
} catch (Exception e) {
e.printStackTrace();
}
}
static String getOpcodeName(final AbstractInsnNode insn) {
final String op = OPCODE_TO_NAME.get(insn.getOpcode());
return INSNTYPE_TO_NAME.get(insn.getType()) + ": " + insn.getOpcode() + (op == null ? "" : " [" + OPCODE_TO_NAME.get(insn.getOpcode()) + "]");
}
static String jvmClassName(Class<?> type) {
return type.getName().replace('.', '/');
}
static String getterName(final String fieldName) {
return "get$" + Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1) + "$LWJGL";
}
static String setterName(final String fieldName) {
return "set$" + Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1) + "$LWJGL";
}
private static void checkInsnAfterIsArray(final AbstractInsnNode instruction, final int opcode) {
if ( instruction == null )
throw new ClassFormatError("Unexpected end of instructions after .asArray() method.");
if ( instruction.getOpcode() != opcode )
throw new ClassFormatError("The result of .asArray() must be stored to a local variable. Found: " + getOpcodeName(instruction));
}
static AbstractInsnNode getIntNode(final int value) {
if ( value <= 5 && -1 <= value )
return new InsnNode(ICONST_M1 + value + 1);
if ( value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE )
return new IntInsnNode(BIPUSH, value);
if ( value >= Short.MIN_VALUE && value <= Short.MAX_VALUE )
return new IntInsnNode(SIPUSH, value);
return new LdcInsnNode(value);
}
static void visitIntNode(final MethodVisitor mv, final int value) {
if ( value <= 5 && -1 <= value )
mv.visitInsn(ICONST_M1 + value + 1);
else if ( value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE )
mv.visitIntInsn(BIPUSH, value);
else if ( value >= Short.MIN_VALUE && value <= Short.MAX_VALUE )
mv.visitIntInsn(SIPUSH, value);
else
mv.visitLdcInsn(value);
}
/** Replace an instruction with a list of instructions. */
static int replace(final InsnList instructions, final int i, final AbstractInsnNode location, final InsnList list) {
final int size = list.size();
instructions.insert(location, list);
instructions.remove(location);
return i + (size - 1);
}
private static void throwAccessErrorOnReadOnlyField(String className, String fieldName) {
throw new IllegalAccessError("The " + className + "." + fieldName + " field is final.");
}
private static void printBytecode(byte[] bytecode) {
StringWriter sw = new StringWriter();
ClassVisitor tracer = new TraceClassVisitor(new ClassWriter(0), new PrintWriter(sw));
new ClassReader(bytecode).accept(tracer, 0);
LWJGLUtil.logger().log(sw::toString);
}
}