Removed sizeof from @MappedType, it's calculated automatically now.

Added padding to @MappedType, defaults to 0.
Added support for @Pointer long fields for easier interaction with pointer data.
This commit is contained in:
Ioannis Tsakpinis 2011-07-23 22:02:01 +00:00
parent 896e363979
commit d0cb1f8c90
14 changed files with 388 additions and 221 deletions

View File

@ -462,7 +462,7 @@ public class LWJGLUtil {
*
* @param msg Message to print
*/
public static void log(String msg) {
public static void log(CharSequence msg) {
if (DEBUG) {
System.err.println("[LWJGL] " + msg);
}

View File

@ -35,7 +35,7 @@ import org.lwjgl.util.mapped.MappedObject;
import org.lwjgl.util.mapped.MappedType;
/** @author Riven */
@MappedType(sizeof = 4)
@MappedType
public class MappedFloat extends MappedObject {
public MappedFloat() {

View File

@ -82,7 +82,7 @@ public class MappedObjectTests3 {
System.out.println("current.view=" + some.view + ", not " + elementCount + ", as you might expect");
}
@MappedType(sizeof = 12)
@MappedType
public static class Xyz extends MappedObject {
int x, y, z;

View File

@ -32,7 +32,11 @@
package org.lwjgl.test.mapped;
import org.lwjgl.MemoryUtil;
import org.lwjgl.PointerBuffer;
import org.lwjgl.opengl.Display;
import org.lwjgl.util.mapped.MappedObject;
import org.lwjgl.util.mapped.MappedType;
import org.lwjgl.util.mapped.Pointer;
import java.io.File;
import java.nio.ByteBuffer;
@ -112,4 +116,35 @@ public class MappedObjectTests4 {
}
}
@MappedType
public static class MappedPointer extends MappedObject {
int foo;
@Pointer long pointer;
int bar;
}
public static void testPointer() {
MappedPointer data = MappedPointer.malloc(100);
assert (data.backingByteBuffer().capacity() == 100 * (4 + 4 + PointerBuffer.getPointerSize()));
for ( int i = 0; i < 100; i++ ) {
data.view = i;
data.foo = i;
data.pointer = i * 1000;
data.bar = i * 2;
}
for ( int i = 0; i < 100; i++ ) {
data.view = i;
assert (data.foo == i);
assert (data.pointer == i * 1000);
assert (data.bar == i * 2);
}
}
}

View File

@ -38,7 +38,7 @@ import org.lwjgl.util.mapped.MappedType;
import java.nio.ByteBuffer;
/** @author Riven */
@MappedType(sizeof = 64)
@MappedType
public class MappedSomething extends MappedObject {
@MappedField(byteOffset = 0)

View File

@ -35,7 +35,7 @@ import org.lwjgl.util.mapped.MappedObject;
import org.lwjgl.util.mapped.MappedType;
/** @author Riven */
@MappedType(sizeof = 8)
@MappedType
public class MappedVec2 extends MappedObject {
public float x;

View File

@ -35,7 +35,7 @@ import org.lwjgl.util.mapped.MappedObject;
import org.lwjgl.util.mapped.MappedType;
/** @author Riven */
@MappedType(sizeof = 12)
@MappedType
public class MappedVec3 extends MappedObject {
public float x;

View File

@ -51,6 +51,7 @@ public class TestMappedObject {
MappedObjectTransformer.register(MappedVec3.class);
MappedObjectTransformer.register(MappedSomething.class);
MappedObjectTransformer.register(MappedObjectTests3.Xyz.class);
MappedObjectTransformer.register(MappedObjectTests4.MappedPointer.class);
if ( MappedObjectClassLoader.fork(TestMappedObject.class, args) ) {
return;
@ -72,9 +73,8 @@ public class TestMappedObject {
MappedObjectTests3.testMappedSet();
MappedObjectTests4.testLocalView();
//MappedObjectTests4.testLWJGL();
MappedObjectTests4.testPointer();
System.out.println("done");
}

View File

@ -204,14 +204,14 @@ public final class SpriteShootoutMapped {
return texID;
}
@MappedType(sizeof = 4)
@MappedType
public static class Pixel4b extends MappedObject {
public byte r, g, b, a;
}
@MappedType(sizeof = 3, align = 3)
@MappedType(align = 3)
public static class Pixel3b extends MappedObject {
public byte r, g, b;
@ -413,15 +413,15 @@ public final class SpriteShootoutMapped {
Display.destroy();
}
@MappedType(sizeof = 4 * 4)
@MappedType
public static class Sprite extends MappedObject {
public float x, y;
public float dx, dy;
public float x, dx;
public float y, dy;
}
@MappedType(sizeof = 2 * 4)
@MappedType
public static class SpriteRender extends MappedObject {
public float x, y;
@ -536,12 +536,15 @@ public final class SpriteShootoutMapped {
x += dx * delta;
if ( x < ballRadius ) {
x = ballRadius;
sprites[b].dx = -dx;
dx = -dx;
} else if ( x > boundW ) {
x = boundW;
sprites[b].dx = -dx;
dx = -dx;
}
sprites[b].x = x;
sprites[b].dx = dx;
spritesRender[r].x = x;
float y = sprites[b].y;
float dy = sprites[b].dy;
@ -549,14 +552,14 @@ public final class SpriteShootoutMapped {
y += dy * delta;
if ( y < ballRadius ) {
y = ballRadius;
sprites[b].dy = -dy;
dy = -dy;
} else if ( y > boundH ) {
y = boundH;
sprites[b].dy = -dy;
dy = -dy;
}
sprites[b].y = y;
spritesRender[r].x = x;
sprites[b].y = y;
sprites[b].dy = dy;
spritesRender[r].y = y;
}
}

View File

@ -239,6 +239,24 @@ public class MappedHelper {
return MappedObjectUnsafe.INSTANCE.getLong(mapped.viewAddress + fieldOffset);
}
// address
public static void aput(long value, long addr) {
MappedObjectUnsafe.INSTANCE.putAddress(addr, value);
}
public static void aput(MappedObject mapped, long value, int fieldOffset) {
MappedObjectUnsafe.INSTANCE.putAddress(mapped.viewAddress + fieldOffset, value);
}
public static long aget(long addr) {
return MappedObjectUnsafe.INSTANCE.getAddress(addr);
}
public static long aget(MappedObject mapped, int fieldOffset) {
return MappedObjectUnsafe.INSTANCE.getAddress(mapped.viewAddress + fieldOffset);
}
// double
public static void dput(double value, long addr) {

View File

@ -96,9 +96,6 @@ public class MappedObjectClassLoader extends URLClassLoader {
final String name = MappedObject.class.getName();
String className = name.replace('.', '/');
if ( MappedObjectTransformer.PRINT_ACTIVITY )
LWJGLUtil.log(MappedObjectClassLoader.class.getSimpleName() + ": " + className);
byte[] bytecode = readStream(this.getResourceAsStream(className.concat(".class")));
long t0 = System.nanoTime();
@ -106,8 +103,8 @@ public class MappedObjectClassLoader extends URLClassLoader {
long t1 = System.nanoTime();
total_time_transforming += (t1 - t0);
if ( MappedObjectTransformer.PRINT_TIMING )
LWJGLUtil.log("transforming " + className + " took " + (t1 - t0) / 1000 + " micros (total: " + (total_time_transforming / 1000 / 1000) + "ms)");
if ( MappedObjectTransformer.PRINT_ACTIVITY )
printActivity(className, t0, t1);
Class<?> clazz = super.defineClass(name, bytecode, 0, bytecode.length);
resolveClass(clazz);
@ -128,25 +125,29 @@ public class MappedObjectClassLoader extends URLClassLoader {
if ( name.startsWith("sunw.") )
return super.loadClass(name, resolve);
if ( name.equals(MappedObjectClassLoader.class.getName()) )
if ( name.startsWith("org.objectweb.asm.") )
return super.loadClass(name, resolve);
if ( name.equals(MappedObjectClassLoader.class.getName()) || name.equals((MappedObjectTransformer.class.getName())) )
return super.loadClass(name, resolve);
String className = name.replace('.', '/');
if ( MappedObjectTransformer.PRINT_ACTIVITY )
LWJGLUtil.log(MappedObjectClassLoader.class.getSimpleName() + ": " + className);
byte[] bytecode = readStream(this.getResourceAsStream(className.concat(".class")));
// Classes in this package do not get transformed, but need to go through here because we have transformed MappedObject.
if ( !(name.startsWith(MAPPEDOBJECT_PACKAGE_PREFIX) && name.substring(MAPPEDOBJECT_PACKAGE_PREFIX.length()).indexOf('.') == -1) ) {
long t0 = System.nanoTime();
bytecode = MappedObjectTransformer.transformMappedAPI(className, bytecode);
final byte[] newBytecode = MappedObjectTransformer.transformMappedAPI(className, bytecode);
long t1 = System.nanoTime();
total_time_transforming += (t1 - t0);
if ( MappedObjectTransformer.PRINT_TIMING )
LWJGLUtil.log("transforming " + className + " took " + (t1 - t0) / 1000 + " micros (total: " + (total_time_transforming / 1000 / 1000) + "ms)");
if ( bytecode != newBytecode ) {
bytecode = newBytecode;
if ( MappedObjectTransformer.PRINT_ACTIVITY )
printActivity(className, t0, t1);
}
}
Class<?> clazz = super.defineClass(name, bytecode, 0, bytecode.length);
@ -155,6 +156,15 @@ public class MappedObjectClassLoader extends URLClassLoader {
return clazz;
}
private static void printActivity(final String className, final long t0, final long t1) {
final StringBuilder msg = new StringBuilder(MappedObjectClassLoader.class.getSimpleName() + ": " + className);
if ( MappedObjectTransformer.PRINT_TIMING )
msg.append("\n\ttransforming took " + (t1 - t0) / 1000 + " micros (total: " + (total_time_transforming / 1000 / 1000) + "ms)");
LWJGLUtil.log(msg);
}
private static byte[] readStream(InputStream in) {
byte[] bytecode = new byte[256];
int len = 0;

View File

@ -63,8 +63,8 @@ import static org.objectweb.asm.Opcodes.*;
*/
public class MappedObjectTransformer {
static final boolean PRINT_TIMING = LWJGLUtil.DEBUG && LWJGLUtil.getPrivilegedBoolean("org.lwjgl.util.mapped.PrintTiming");
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;
@ -113,7 +113,7 @@ public class MappedObjectTransformer {
// => IADD
// => PUTFIELD MyMappedType.view
//
className_to_subtype.put(MAPPED_OBJECT_JVM, new MappedSubtypeInfo(MAPPED_OBJECT_JVM, -1, -1));
className_to_subtype.put(MAPPED_OBJECT_JVM, new MappedSubtypeInfo(MAPPED_OBJECT_JVM, null, -1, -1));
}
final String vmName = System.getProperty("java.vm.name");
@ -129,39 +129,66 @@ public class MappedObjectTransformer {
* @param type the mapped object class.
*/
public static void register(Class<?> type) {
if ( MappedObjectClassLoader.FORKED )
return;
final MappedType mapped = type.getAnnotation(MappedType.class);
if ( mapped == null )
throw new InternalError("missing " + MappedType.class.getName() + " annotation");
throw new ClassFormatError("missing " + MappedType.class.getName() + " annotation");
if ( 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 MappedSubtypeInfo mappedType = new MappedSubtypeInfo(jvmClassName(type), mapped.sizeof(), mapped.align());
final String className = jvmClassName(type);
final Map<String, FieldInfo> fields = new HashMap<String, FieldInfo>();
int advancingOffset = 0;
for ( Field field : type.getDeclaredFields() )
advancingOffset += registerField(mapped, mappedType.className, mappedType, advancingOffset, field);
long sizeof = 0;
for ( Field field : type.getDeclaredFields() ) {
FieldInfo fieldInfo = registerField(mapped, className, advancingOffset, field);
if ( fieldInfo == null )
continue;
if ( className_to_subtype.put(mappedType.className, mappedType) != null )
fields.put(field.getName(), fieldInfo);
advancingOffset += fieldInfo.length;
sizeof = Math.max(sizeof, fieldInfo.offset + fieldInfo.length);
}
sizeof += mapped.padding();
final MappedSubtypeInfo mappedType = new MappedSubtypeInfo(className, fields, (int)sizeof, mapped.align());
if ( className_to_subtype.put(className, mappedType) != null )
throw new InternalError("duplicate mapped type: " + mappedType.className);
}
private static int registerField(final MappedType mapped, final String className, final MappedSubtypeInfo mappedType, int advancingOffset, final Field field) {
private static FieldInfo registerField(final MappedType mapped, final String className, int advancingOffset, final Field field) {
if ( Modifier.isStatic(field.getModifiers()) ) // static fields are never mapped
return 0;
return null;
// we only support primitives and ByteBuffers
if ( !field.getType().isPrimitive() && field.getType() != ByteBuffer.class )
throw new InternalError("field '" + className + "." + field.getName() + "' not supported: " + field.getType());
throw new ClassFormatError("field '" + className + "." + field.getName() + "' not supported: " + field.getType());
MappedField meta = field.getAnnotation(MappedField.class);
if ( meta == null && !mapped.autoGenerateOffsets() )
throw new InternalError("field '" + className + "." + field.getName() + "' missing annotation " + MappedField.class.getName() + ": " + className);
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. Field found: " + className + "." + field.getName() + ": " + field.getType());
// quick hack
long byteOffset = meta == null ? advancingOffset : meta.byteOffset();
long byteLength;
if ( field.getType() == long.class || field.getType() == double.class )
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;
@ -174,7 +201,7 @@ public class MappedObjectTransformer {
if ( byteLength < 0 )
throw new IllegalStateException("invalid byte length for mapped ByteBuffer field: " + className + "." + field.getName() + " [length=" + byteLength + "]");
} else
throw new InternalError(field.getType().getName());
throw new ClassFormatError(field.getType().getName());
if ( field.getType() != ByteBuffer.class && (advancingOffset % byteLength) != 0 )
throw new IllegalStateException("misaligned mapped type: " + className + "." + field.getName());
@ -182,11 +209,7 @@ public class MappedObjectTransformer {
if ( PRINT_ACTIVITY )
LWJGLUtil.log(MappedObjectTransformer.class.getSimpleName() + ": " + className + "." + field.getName() + " [type=" + field.getType().getSimpleName() + ", offset=" + byteOffset + "]");
mappedType.fieldToOffset.put(field.getName(), byteOffset);
mappedType.fieldToLength.put(field.getName(), byteLength);
mappedType.fieldToType.put(field.getName(), Type.getType(field.getType()));
return (int)byteLength;
return new FieldInfo(byteOffset, byteLength, Type.getType(field.getType()), pointer != null);
}
/** Removes final from methods that will be overriden by subclasses. */
@ -231,13 +254,18 @@ public class MappedObjectTransformer {
};
ClassVisitor cv = getTransformationAdapter(className, cw);
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);
bytecode = cw.toByteArray();
if ( !ta.transformed )
return bytecode;
bytecode = cw.toByteArray();
if ( PRINT_BYTECODE )
printBytecode(bytecode);
@ -257,14 +285,14 @@ public class MappedObjectTransformer {
generateSizeofGetter();
generateNext();
for ( String fieldName : mappedSubtype.fieldToOffset.keySet() ) {
final Type type = mappedSubtype.fieldToType.get(fieldName);
for ( String fieldName : mappedSubtype.fields.keySet() ) {
final FieldInfo field = mappedSubtype.fields.get(fieldName);
if ( type.getDescriptor().length() > 1 ) { // ByteBuffer, getter only
generateByteBufferGetter(mappedSubtype, fieldName, type);
if ( field.type.getDescriptor().length() > 1 ) { // ByteBuffer, getter only
generateByteBufferGetter(fieldName, field);
} else {
generateFieldGetter(mappedSubtype, fieldName, type);
generateFieldSetter(mappedSubtype, fieldName, type);
generateFieldGetter(fieldName, field);
generateFieldSetter(fieldName, field);
}
}
@ -342,42 +370,42 @@ public class MappedObjectTransformer {
mv.visitEnd();
}
private void generateByteBufferGetter(final MappedSubtypeInfo mappedSubtype, final String fieldName, final Type type) {
MethodVisitor mv = super.visitMethod(ACC_PUBLIC | ACC_STATIC, getterName(fieldName), "(L" + className + ";I)" + type.getDescriptor(), null, null);
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, mappedSubtype.fieldToOffset.get(fieldName).intValue());
visitIntNode(mv, (int)field.offset);
mv.visitInsn(I2L);
mv.visitInsn(LADD);
visitIntNode(mv, mappedSubtype.fieldToLength.get(fieldName).intValue());
visitIntNode(mv, (int)field.length);
mv.visitMethodInsn(INVOKESTATIC, MAPPED_HELPER_JVM, "newBuffer", "(JI)L" + jvmClassName(ByteBuffer.class) + ";");
mv.visitInsn(ARETURN);
mv.visitMaxs(4, 2);
mv.visitEnd();
}
private void generateFieldGetter(final MappedSubtypeInfo mappedSubtype, final String fieldName, final Type type) {
MethodVisitor mv = super.visitMethod(ACC_PUBLIC | ACC_STATIC, getterName(fieldName), "(L" + className + ";I)" + 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, mappedSubtype.fieldToOffset.get(fieldName).intValue());
mv.visitInsn(I2L);
mv.visitInsn(LADD);
mv.visitMethodInsn(INVOKESTATIC, MAPPED_HELPER_JVM, type.getDescriptor().toLowerCase() + "get", "(J)" + type.getDescriptor());
mv.visitInsn(type.getOpcode(IRETURN));
mv.visitMaxs(3, 2);
mv.visitEnd();
}
private void generateFieldSetter(final MappedSubtypeInfo mappedSubtype, final String fieldName, final Type type) {
MethodVisitor mv = super.visitMethod(ACC_PUBLIC | ACC_STATIC, setterName(fieldName), "(L" + className + ";I" + type.getDescriptor() + ")V", null, null);
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.isPointer ? "a" : field.type.getDescriptor().toLowerCase() + "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 ( type.getSort() ) {
switch ( field.type.getSort() ) {
case Type.BOOLEAN:
case Type.CHAR:
case Type.BYTE:
@ -399,156 +427,164 @@ public class MappedObjectTransformer {
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ILOAD, 1);
mv.visitMethodInsn(INVOKEVIRTUAL, className, VIEWADDRESS_METHOD_NAME, "(I)J");
visitIntNode(mv, mappedSubtype.fieldToOffset.get(fieldName).intValue());
visitIntNode(mv, (int)field.offset);
mv.visitInsn(I2L);
mv.visitInsn(LADD);
mv.visitMethodInsn(INVOKESTATIC, MAPPED_HELPER_JVM, type.getDescriptor().toLowerCase() + "put", "(" + type.getDescriptor() + "J)V");
mv.visitMethodInsn(INVOKESTATIC, MAPPED_HELPER_JVM, field.isPointer ? "a" : field.type.getDescriptor().toLowerCase() + "put", "(" + field.type.getDescriptor() + "J)V");
mv.visitInsn(RETURN);
mv.visitMaxs(4, 3);
mv.visitMaxs(4, 4);
mv.visitEnd();
}
};
}
private static ClassAdapter getTransformationAdapter(final String className, final ClassWriter cw) {
return new ClassAdapter(cw) {
private static class TransformationAdapter extends ClassAdapter {
@Override
public FieldVisitor visitField(final int access, final String name, final String desc, final String signature, final Object value) {
// remove redirected fields
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;
}
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 && mappedSubtype.fieldToOffset.containsKey(name) ) {
if ( PRINT_ACTIVITY )
LWJGLUtil.log(MappedObjectTransformer.class.getSimpleName() + ": discarding field: " + className + "." + name + ":" + desc);
return null;
}
if ( mappedSubtype != null ) {
if ( !"()V".equals(desc) )
throw new ClassFormatError(className + " can only have a default constructor, found: " + desc);
return super.visitField(access, name, desc, signature, value);
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;
}
}
@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);
return new MethodNode(access, name, desc, signature, exceptions) {
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);
/** 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;
// put the method body in another method
name = VIEW_CONSTRUCTOR_NAME;
@Override
public void visitMaxs(int a, int b) {
try {
is_currently_computing_frames = true;
super.visitMaxs(a, b);
} finally {
is_currently_computing_frames = false;
}
}
final MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
return new MethodNode(access, name, desc, signature, exceptions) {
@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;
/** 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;
super.visitFieldInsn(opcode, owner, name, desc);
}
@Override
public void visitMaxs(int a, int b) {
@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 {
is_currently_computing_frames = true;
super.visitMaxs(a, b);
} finally {
is_currently_computing_frames = false;
transformMethod(analyse());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@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;
// Pass the instruction stream to the adapter's MethodVisitor
accept(mv);
}
super.visitFieldInsn(opcode, owner, name, desc);
}
private Frame<BasicValue>[] analyse() throws AnalyzerException {
final Analyzer<BasicValue> a = new Analyzer<BasicValue>(new SimpleVerifier());
a.analyze(className, this);
return a.getFrames();
}
@Override
public void visitMethodInsn(final int opcode, final String owner, final String name, final String desc) {
if ( className_to_subtype.containsKey(owner) )
needsTransformation = true;
private void transformMethod(final Frame<BasicValue>[] frames) {
final InsnList instructions = this.instructions;
super.visitMethodInsn(opcode, owner, name, desc);
}
final Map<Integer, MappedSubtypeInfo> arrayVars = new HashMap<Integer, MappedSubtypeInfo>();
@Override
public void visitEnd() {
if ( needsTransformation ) { // Early-out for methods that do not touch a mapped object.
//System.err.println("\nTRANSFORMING: " + className + "." + name + desc);
try {
transformMethod(analyse());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/*
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]);
// Pass the instruction stream to the adapter's MethodVisitor
accept(mv);
}
for ( int i = 0; i < instructions.size(); i++ ) { // f is a separate cursor for frames
final AbstractInsnNode instruction = instructions.get(i);
private Frame<BasicValue>[] analyse() throws AnalyzerException {
final Analyzer<BasicValue> a = new Analyzer<BasicValue>(new SimpleVerifier());
a.analyze(className, this);
return a.getFrames();
}
//System.out.println("MAIN LOOP #" + i + " - " + getOpcodeName(instruction));
private void transformMethod(final Frame<BasicValue>[] frames) {
final InsnList instructions = this.instructions;
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 Map<Integer, MappedSubtypeInfo> arrayVars = new HashMap<Integer, MappedSubtypeInfo>();
final InsnList list = transformFieldAccess(fieldInsn);
if ( list != null )
i = replace(instructions, i, instruction, list);
/*
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;
}
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) {
@ -760,16 +796,16 @@ public class MappedObjectTransformer {
return generateAddressInstructions(fieldInsn);
}
final Long fieldOffset = mappedSubtype.fieldToOffset.get(fieldInsn.name);
if ( fieldOffset == null ) // early out
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, fieldOffset);
return generateByteBufferInstructions(fieldInsn, mappedSubtype, field.offset);
// we're now going to transform the field access
return generateFieldInstructions(fieldInsn, fieldOffset);
return generateFieldInstructions(fieldInsn, field);
}
private static InsnList generateSetViewInstructions(final FieldInsnNode fieldInsn) {
@ -867,13 +903,11 @@ public class MappedObjectTransformer {
throw new InternalError();
}
private static InsnList generateByteBufferInstructions(final FieldInsnNode fieldInsn, final MappedSubtypeInfo mappedSubtype, final Long fieldOffset) {
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 Long fieldLength = mappedSubtype.fieldToLength.get(fieldInsn.name);
final InsnList list = new InsnList();
// stack: ref
@ -883,7 +917,7 @@ public class MappedObjectTransformer {
// stack: long, long
list.add(new InsnNode(LADD));
// stack: long
list.add(new LdcInsnNode(fieldLength));
list.add(new LdcInsnNode(mappedSubtype.fields.get(fieldInsn.name).length));
// stack: long, long
list.add(new InsnNode(L2I));
// stack: int, long
@ -896,23 +930,25 @@ public class MappedObjectTransformer {
throw new InternalError();
}
private static InsnList generateFieldInstructions(final FieldInsnNode fieldInsn, final Long fieldOffset) {
private static InsnList generateFieldInstructions(final FieldInsnNode fieldInsn, final FieldInfo field) {
final InsnList list = new InsnList();
final String dataType = field.isPointer ? "a" : fieldInsn.desc.toLowerCase();
if ( fieldInsn.getOpcode() == PUTFIELD ) {
// stack: value, ref
list.add(getIntNode(fieldOffset.intValue()));
list.add(getIntNode((int)field.offset));
// stack: fieldOffset, value, ref
list.add(new MethodInsnNode(INVOKESTATIC, MAPPED_HELPER_JVM, fieldInsn.desc.toLowerCase() + "put", "(L" + MAPPED_OBJECT_JVM + ";" + fieldInsn.desc + "I)V"));
list.add(new MethodInsnNode(INVOKESTATIC, MAPPED_HELPER_JVM, dataType + "put", "(L" + MAPPED_OBJECT_JVM + ";" + fieldInsn.desc + "I)V"));
// stack -
return list;
}
if ( fieldInsn.getOpcode() == GETFIELD ) {
// stack: ref
list.add(getIntNode(fieldOffset.intValue()));
list.add(getIntNode((int)field.offset));
// stack: fieldOffset, ref
list.add(new MethodInsnNode(INVOKESTATIC, MAPPED_HELPER_JVM, fieldInsn.desc.toLowerCase() + "get", "(L" + MAPPED_OBJECT_JVM + ";I)" + fieldInsn.desc));
list.add(new MethodInsnNode(INVOKESTATIC, MAPPED_HELPER_JVM, dataType + "get", "(L" + MAPPED_OBJECT_JVM + ";I)" + fieldInsn.desc));
// stack: -
return list;
}
@ -1006,29 +1042,43 @@ public class MappedObjectTransformer {
}
}
private static class FieldInfo {
final long offset;
final long length;
final Type type;
final boolean isPointer;
FieldInfo(final long offset, final long length, final Type type, final boolean isPointer) {
this.offset = offset;
this.length = length;
this.type = type;
this.isPointer = isPointer;
}
}
private static class MappedSubtypeInfo {
public final String className;
final String className;
public int sizeof;
public int sizeof_shift;
public int align;
final int sizeof;
final int sizeof_shift;
final int align;
public Map<String, Long> fieldToOffset;
public Map<String, Long> fieldToLength;
public Map<String, Type> fieldToType;
final Map<String, FieldInfo> fields;
MappedSubtypeInfo(String className, int sizeof, int align) {
MappedSubtypeInfo(String className, Map<String, FieldInfo> fields, int sizeof, int align) {
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.fieldToOffset = new HashMap<String, Long>();
this.fieldToLength = new HashMap<String, Long>();
this.fieldToType = new HashMap<String, Type>();
this.fields = fields;
}
private static int getPoT(int value) {

View File

@ -60,11 +60,12 @@ import java.lang.annotation.Target;
public @interface MappedType {
/**
* The total size of the mapped object, in bytes.
* The number of bytes to add to the total byte size.
* SIZEOF will be calculated as <code>SIZEOF = max(field_offset + field_length) + padding</code>
*
* @return the byte size
* @return the padding amount
*/
int sizeof();
int padding() default 0;
/**
* The mapped data memory alignment, in bytes.

View File

@ -0,0 +1,50 @@
/*
* 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 java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* This annotation can be used on long fields of {@link MappedType} classes,
* to specify that the long value should be interpreted as a pointer. This
* will determine the actual byte size of the field at runtime (4 or 8 bytes).
*
* @author Spasi
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Pointer {
}