/* * 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 ) LWJGLUtil.log(MappedObjectTransformer.class.getSimpleName() + ": " + className + "." + field.getName() + " [type=" + field.getType().getSimpleName() + ", offset=" + byteOffset + "]"); 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.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); String dump = sw.toString(); LWJGLUtil.log(dump); } }