diff --git a/libs/asm-util.jar b/libs/asm-util.jar new file mode 100644 index 00000000..499d2290 Binary files /dev/null and b/libs/asm-util.jar differ diff --git a/libs/asm.jar b/libs/asm.jar new file mode 100644 index 00000000..334e7fdc Binary files /dev/null and b/libs/asm.jar differ diff --git a/src/java/org/lwjgl/util/mapped/MappedField.java b/src/java/org/lwjgl/util/mapped/MappedField.java new file mode 100644 index 00000000..0ffae166 --- /dev/null +++ b/src/java/org/lwjgl/util/mapped/MappedField.java @@ -0,0 +1,19 @@ +/* + * Created on Jun 24, 2011 + */ + +package org.lwjgl.util.mapped; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * @author Riven + */ + +@Retention(RetentionPolicy.RUNTIME) +public @interface MappedField { + long byteOffset(); + + long byteLength() default -1; +} diff --git a/src/java/org/lwjgl/util/mapped/MappedForeach.java b/src/java/org/lwjgl/util/mapped/MappedForeach.java new file mode 100644 index 00000000..3c254e5e --- /dev/null +++ b/src/java/org/lwjgl/util/mapped/MappedForeach.java @@ -0,0 +1,52 @@ +/* + * Created on Jul 4, 2011 + */ + +package org.lwjgl.util.mapped; + +import java.util.Iterator; + +/** + * @author Riven + */ + +public class MappedForeach implements Iterable +{ + final T mapped; + final int elementCount; + + MappedForeach(T mapped, int elementCount) + { + this.mapped = mapped; + this.elementCount = elementCount; + } + + @Override + public Iterator iterator() + { + return new Iterator() + { + private int index = 0; + + @Override + public boolean hasNext() + { + return this.index < (MappedForeach.this.elementCount); + } + + @Override + public T next() + { + mapped.viewAddress = mapped.baseAddress + (this.index++) * mapped.stride; + + return mapped; + } + + @Override + public void remove() + { + throw new UnsupportedOperationException(); + } + }; + } +} diff --git a/src/java/org/lwjgl/util/mapped/MappedHelper.java b/src/java/org/lwjgl/util/mapped/MappedHelper.java new file mode 100644 index 00000000..ab749f21 --- /dev/null +++ b/src/java/org/lwjgl/util/mapped/MappedHelper.java @@ -0,0 +1,181 @@ +/* + * Created on Jun 28, 2011 + */ + +package org.lwjgl.util.mapped; + +import java.nio.ByteBuffer; + +/** + * @author Riven + */ + +public class MappedHelper +{ + public static void setup(MappedObject mo, ByteBuffer buffer, int align, int sizeof) + { + if (mo.baseAddress != 0L) + throw new IllegalStateException("this method should not be called by user-code"); + + if (buffer == null) + throw new NullPointerException("buffer"); + if (!buffer.isDirect()) + throw new IllegalArgumentException("bytebuffer must be direct"); + mo.preventGC = buffer; + + if (align <= 0) + throw new IllegalArgumentException("invalid alignment"); + mo.align = align; + + if (sizeof % align != 0) + throw new IllegalStateException("sizeof not a multiple of alignment"); + mo.stride = sizeof; + + long addr = MappedObjectUnsafe.getBufferBaseAddress(buffer); + if (addr % align != 0) + throw new IllegalStateException("buffer address not aligned on " + align + " bytes"); + + mo.baseAddress = mo.viewAddress = addr; + } + + public static void put_views(MappedSet2 set, int view) + { + set.view(view); + } + + public static void put_views(MappedSet3 set, int view) + { + set.view(view); + } + + public static void put_views(MappedSet4 set, int view) + { + set.view(view); + } + + public static void put_view(MappedObject mapped, int view) + { + mapped.viewAddress = mapped.baseAddress + view * mapped.stride; + } + + public static int get_view(MappedObject mapped) + { + return (int) (mapped.viewAddress - mapped.baseAddress) / mapped.stride; + } + + public static MappedObject dup(MappedObject src, MappedObject dst) + { + dst.baseAddress = src.baseAddress; + dst.viewAddress = src.viewAddress; + dst.stride = src.stride; + dst.align = src.align; + dst.preventGC = src.preventGC; + return dst; + } + + public static MappedObject slice(MappedObject src, MappedObject dst) + { + dst.baseAddress = src.viewAddress; // ! + dst.viewAddress = src.viewAddress; + dst.stride = src.stride; + dst.align = src.align; + dst.preventGC = src.preventGC; + return dst; + } + + public static void copy(MappedObject src, MappedObject dst, int bytes) + { + MappedObjectUnsafe.INSTANCE.copyMemory(src.viewAddress, dst.viewAddress, bytes); + } + + public static ByteBuffer newBuffer(long address, int capacity) + { + return MappedObjectUnsafe.newBuffer(address, capacity); + } + + // ---- primitive fields read/write + + // byte + + public static void bput(byte value, long addr) + { + MappedObjectUnsafe.INSTANCE.putByte(addr, value); + } + + public static byte bget(long addr) + { + return MappedObjectUnsafe.INSTANCE.getByte(addr); + } + + // short + + public static void sput(short value, long addr) + { + MappedObjectUnsafe.INSTANCE.putShort(addr, value); + } + + public static short sget(long addr) + { + return MappedObjectUnsafe.INSTANCE.getShort(addr); + } + + // char + + public static void cput(char value, long addr) + { + MappedObjectUnsafe.INSTANCE.putChar(addr, value); + } + + public static char cget(long addr) + { + return MappedObjectUnsafe.INSTANCE.getChar(addr); + } + + // int + + public static void iput(int value, long addr) + { + MappedObjectUnsafe.INSTANCE.putInt(addr, value); + } + + public static int iget(long addr) + { + return MappedObjectUnsafe.INSTANCE.getInt(addr); + } + + // float + + public static void fput(float value, long addr) + { + MappedObjectUnsafe.INSTANCE.putFloat(addr, value); + } + + public static float fget(long addr) + { + return MappedObjectUnsafe.INSTANCE.getFloat(addr); + } + + // long + + public static void jput(long value, long addr) + { + MappedObjectUnsafe.INSTANCE.putLong(addr, value); + } + + public static long jget(long addr) + { + return MappedObjectUnsafe.INSTANCE.getLong(addr); + } + + // double + + public static void dput(double value, long addr) + { + MappedObjectUnsafe.INSTANCE.putDouble(addr, value); + } + + public static double dget(long addr) + { + return MappedObjectUnsafe.INSTANCE.getDouble(addr); + } +} \ No newline at end of file diff --git a/src/java/org/lwjgl/util/mapped/MappedObject.java b/src/java/org/lwjgl/util/mapped/MappedObject.java new file mode 100644 index 00000000..2d05ce5b --- /dev/null +++ b/src/java/org/lwjgl/util/mapped/MappedObject.java @@ -0,0 +1,185 @@ +/* + * Created on Jun 25, 2011 + */ + +package org.lwjgl.util.mapped; + +import java.nio.ByteBuffer; + +/** + * @author Riven + */ + +public class MappedObject +{ + public MappedObject() + { + // + } + + // these fields are not assignable/writable by user-code + public long baseAddress; + public long viewAddress; + public int stride; + public int align; + + /** + * Holds the value of sizeof of the sub-type of this MappedObject
+ *
+ * The behavior of this (transformed) method does not follow the normal Java behavior.
+ * Vec2.SIZEOF will yield 8 (2 floats)
+ * Vec3.SIZEOF will yield 12 (3 floats)
+ * This (required) notation might cause compiler warnings, which can be suppressed with @SuppressWarnings("static-access").
+ * Using Java 5.0's static-import on this method will break functionality. + */ + + // any method that calls these field will have its call-site modified + public static int SIZEOF = -1; // 'final' per subtype + public int view; // read/write + + public final void next() + { + this.viewAddress += this.stride; + } + + /** + * Creates a MappedObject instance, mapping the memory region of the specified direct ByteBuffer.
+ *
+ * The behavior of this (transformed) method does not follow the normal Java behavior.
+ * Vec2.map(buffer) will return a mapped Vec2 instance.
+ * Vec3.map(buffer) will return a mapped Vec3 instance.
+ * This (required) notation might cause compiler warnings, which can be suppressed with @SuppressWarnings("static-access").
+ * Using Java 5.0's static-import on this method will break functionality. + */ + + @SuppressWarnings("unused") + public static T map(ByteBuffer bb) + { + // any method that calls this method will have its call-site modified + throw new InternalError("type not registered"); + } + + @SuppressWarnings("unused") + public static T map(long address, int capacity) + { + // any method that calls this method will have its call-site modified + throw new InternalError("type not registered"); + } + + /** + * Creates a MappedObject instance, mapping the memory region of an allocated direct ByteBuffer with a capacity of elementCount*SIZEOF + *
+ * The behavior of this (transformed) method does not follow the normal Java behavior.
+ * Vec2.malloc(int) will return a mapped Vec2 instance.
+ * Vec3.malloc(int) will return a mapped Vec3 instance.
+ * This (required) notation might cause compiler warnings, which can be suppressed with @SuppressWarnings("static-access").
+ * Using Java 5.0's static-import on this method will break functionality. + */ + + @SuppressWarnings("unused") + public static T malloc(int elementCount) + { + // any method that calls this method will have its call-site modified + throw new InternalError("type not registered"); + } + + /** + * Creates an identical new MappedObject instance, comparable to the + * contract of ByteBuffer.duplicate() + */ + + public final T dup() + { + // any method that calls this method will have its call-site modified + throw new InternalError("type not registered"); + } + + /** + * Creates a new MappedObject instance, with a base offset equal to + * the offset of the current view, comparable to the contract of ByteBuffer.slice() + */ + + public final T slice() + { + // any method that calls this method will have its call-site modified + throw new InternalError("type not registered"); + } + + public final void runViewConstructor() + { + // any method that calls this method will have its call-site modified + throw new InternalError("type not registered"); + } + + /** + * Copies and amount of SIZEOF bytes, from the current + * mapped object, to the specified mapped object. + */ + + @SuppressWarnings("unused") + public final void copyTo(T target) + { + // any method that calls this method will have its call-site modified + throw new InternalError("type not registered"); + } + + /** + * Copies and amount of SIZEOF*instances bytes, from the + * current mapped object, to the specified mapped object. + */ + + @SuppressWarnings("unused") + public final void copyRange(T target, int instances) + { + // any method that calls this method will have its call-site modified + throw new InternalError("type not registered"); + } + + /** + * Creates an Iterable that will step through + * elementCount views, leaving the view at + * the last valid value.
+ *
+ * For convenience you are encouraged to static-import this specific method: + * import static org.lwjgl.util.mapped.MappedObject.foreach; + */ + + public static MappedForeach foreach(T mapped, int elementCount) + { + return new MappedForeach(mapped, elementCount); + } + + /** + * Configures a newly initiated mapped object with the specified stride and offset. + * @throws IllegalStateException if view is not at index 0 + */ + + public static final T configure(T mapped, int stride, int offset) + { + if (mapped.baseAddress != mapped.viewAddress) + throw new IllegalStateException("view must be zero"); + + if (offset < 0) + throw new IllegalStateException("offset must not be negative: " + offset); + if (offset % mapped.align != 0) + throw new IllegalStateException("offset not a multiple of alignment: " + offset); + + if (stride < mapped.stride) + throw new IllegalStateException("new stride must not be smaller than current stride: " + stride); + if (stride % mapped.align != 0) + throw new IllegalStateException("stride not a multiple of alignment: " + stride); + + mapped.baseAddress += offset; + mapped.viewAddress += offset; + mapped.stride = stride; + + return mapped; + } + + ByteBuffer preventGC; + + public ByteBuffer backingByteBuffer() + { + return this.preventGC; + } +} diff --git a/src/java/org/lwjgl/util/mapped/MappedObjectClassLoader.java b/src/java/org/lwjgl/util/mapped/MappedObjectClassLoader.java new file mode 100644 index 00000000..a8fd803e --- /dev/null +++ b/src/java/org/lwjgl/util/mapped/MappedObjectClassLoader.java @@ -0,0 +1,136 @@ +/* + * Created on Jun 24, 2011 + */ + +package org.lwjgl.util.mapped; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.URLClassLoader; +import java.util.Arrays; + +/** + * @author Riven + */ + +public class MappedObjectClassLoader extends URLClassLoader +{ + static final String MAPPEDOBJECT_PACKAGE_PREFIX; + + static + { + MAPPEDOBJECT_PACKAGE_PREFIX = MappedObjectClassLoader.class.getPackage().getName() + "."; + } + + static boolean FORKED = false; + + public static boolean fork(Class< ? > mainClass, String[] args) + { + if (FORKED) + { + return false; + } + + FORKED = true; + + try + { + URLClassLoader loader = new MappedObjectClassLoader(mainClass); + Class< ? > replacedMainClass = loader.loadClass(mainClass.getName()); + Method mainMethod = replacedMainClass.getMethod("main", String[].class); + mainMethod.invoke(null, new Object[] { args }); + } + catch (InvocationTargetException exc) + { + Thread.currentThread().getUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), exc.getCause()); + } + catch (Throwable cause) + { + throw new Error("failed to fork", cause); + } + + return true; + } + + private MappedObjectClassLoader(Class< ? > mainClass) + { + super(((URLClassLoader) mainClass.getClassLoader()).getURLs()); + } + + private static long total_time_transforming; + + @Override + protected synchronized Class< ? > loadClass(String name, boolean resolve) throws ClassNotFoundException + { + if (name.startsWith("java.")) + return super.loadClass(name, resolve); + if (name.startsWith("javax.")) + return super.loadClass(name, resolve); + + if (name.startsWith("sun.")) + return super.loadClass(name, resolve); + if (name.startsWith("sunw.")) + return super.loadClass(name, resolve); + + // never transform classes in this very package, sub-packages should be transformed + if (name.startsWith(MAPPEDOBJECT_PACKAGE_PREFIX)) + if (name.substring(MAPPEDOBJECT_PACKAGE_PREFIX.length()).indexOf('.') == -1) + return super.loadClass(name, resolve); + + String className = name.replace('.', '/'); + + if (MappedObjectTransformer.PRINT_ACTIVITY) + System.out.println(MappedObjectClassLoader.class.getSimpleName() + ": " + className); + + byte[] bytecode = readStream(this.getResourceAsStream(className.concat(".class"))); + + long t0 = System.nanoTime(); + bytecode = MappedObjectTransformer.transformFieldAccess(className, bytecode); + long t1 = System.nanoTime(); + total_time_transforming += (t1 - t0); + + if (MappedObjectTransformer.PRINT_TIMING) + System.out.println("transforming " + className + " took " + (t1 - t0) / 1000 + " micros (total: " + (total_time_transforming / 1000 / 1000) + "ms)"); + + Class< ? > clazz = super.defineClass(name, bytecode, 0, bytecode.length); + if (resolve) + resolveClass(clazz); + return clazz; + } + + static byte[] readStream(InputStream in) + { + byte[] bytecode = new byte[256]; + int len = 0; + try + { + while (true) + { + if (bytecode.length == len) + bytecode = Arrays.copyOf(bytecode, len * 2); + int got = in.read(bytecode, len, bytecode.length - len); + if (got == -1) + break; + len += got; + } + } + catch (IOException exc) + { + // stop! + } + finally + { + try + { + in.close(); + } + catch (IOException exc) + { + // ignore... + } + } + return Arrays.copyOf(bytecode, len); + } +} diff --git a/src/java/org/lwjgl/util/mapped/MappedObjectTransformer.java b/src/java/org/lwjgl/util/mapped/MappedObjectTransformer.java new file mode 100644 index 00000000..9ff1f0cb --- /dev/null +++ b/src/java/org/lwjgl/util/mapped/MappedObjectTransformer.java @@ -0,0 +1,588 @@ +/* + * Created on Jun 23, 2011 + */ + +package org.lwjgl.util.mapped; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.Map; + +import org.objectweb.asm.*; +import org.objectweb.asm.util.TraceClassVisitor; +import static org.objectweb.asm.Opcodes.*; + +/** + * @author Riven + */ + +public class MappedObjectTransformer +{ + static final boolean PRINT_TIMING = true; + static final boolean PRINT_ACTIVITY = false; + static final boolean PRINT_BYTECODE = false; + static final Map className_to_subtype; + + static + { + className_to_subtype = new HashMap(); + + { + // 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 + // + MappedSubtypeInfo info = new MappedSubtypeInfo(jvmClassName(MappedObject.class), -1, -1); + className_to_subtype.put(info.className, info); + } + + 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."); + } + } + + public static void register(Class< ? > type) + { + if (MappedObjectClassLoader.FORKED) + return; + + MappedType mapped = type.getAnnotation(MappedType.class); + if (mapped == null) + throw new InternalError("missing " + MappedType.class.getName() + " annotation"); + + String className = jvmClassName(type); + + MappedSubtypeInfo mappedType = new MappedSubtypeInfo(className, mapped.sizeof(), mapped.align()); + + int advancingOffset = 0; + + for (Field field : type.getDeclaredFields()) + { + // static fields are never mapped + if ((field.getModifiers() & Modifier.STATIC) != 0) + continue; + + // 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()); + + MappedField meta = field.getAnnotation(MappedField.class); + if (meta == null && !mapped.autoGenerateOffsets()) + throw new InternalError("field '" + className + "." + field.getName() + "' missing annotation " + MappedField.class.getName() + ": " + className); + + // quick hack + long byteOffset = meta == null ? advancingOffset : meta.byteOffset(); + long byteLength; + if (field.getType() == long.class || 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(); + else + throw new IllegalStateException(field.getType().getName()); + + if (field.getType() != ByteBuffer.class) + if ((advancingOffset % byteLength) != 0) + throw new IllegalStateException("misaligned mapped type: " + className + "." + field.getName()); + + if (PRINT_ACTIVITY) + System.out.println(MappedObjectTransformer.class.getSimpleName() + ": " + className + "." + field.getName() + " [type=" + field.getType().getSimpleName() + ", offset=" + byteOffset + "]"); + + mappedType.fieldToOffset.put(field.getName(), Long.valueOf(byteOffset)); + mappedType.fieldToLength.put(field.getName(), Long.valueOf(byteLength)); + + advancingOffset += byteLength; + } + + if (className_to_subtype.put(className, mappedType) != null) + { + throw new InternalError("duplicate mapped type: " + className); + } + } + + public static final String view_constructor_method = "_construct_view_"; + + public static byte[] transformFieldAccess(final String className, byte[] bytecode) + { + int flags = ClassWriter.COMPUTE_FRAMES; + + ClassWriter writer = new ClassWriter(flags); + + ClassAdapter adapter = new ClassAdapter(writer) + { + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) + { + { + MappedSubtypeInfo mappedSubtype = className_to_subtype.get(className); + + if (mappedSubtype != null && !mappedSubtype.className.equals(jvmClassName(MappedObject.class))) + { + if (name.equals("")) + { + if (!desc.equals("()V")) + throw new IllegalStateException(className + " can only have a default constructor, found: " + desc); + + MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); + mv.visitCode(); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKESPECIAL, jvmClassName(MappedObject.class), "", "()V"); + mv.visitInsn(RETURN); + mv.visitMaxs(1, 1); + mv.visitEnd(); + + // put the method body in another method + name = view_constructor_method; + } + } + } + + return new MappedInstanceMethodAdapter(className, name, desc, super.visitMethod(access, name, desc, signature, exceptions)); + } + + @Override + public FieldVisitor visitField(int access, String fieldName, String typeName, String dunno, Object value) + { + // remove redirected fields + MappedSubtypeInfo mappedSubtype = className_to_subtype.get(className); + + if (mappedSubtype != null && mappedSubtype.fieldToOffset.containsKey(fieldName)) + { + if (PRINT_ACTIVITY) + System.out.println(MappedObjectTransformer.class.getSimpleName() + ": discarding field: " + className + "." + fieldName + ":" + typeName); + return null; + } + + return super.visitField(access, fieldName, typeName, dunno, value); + } + }; + + new ClassReader(bytecode).accept(adapter, 0); + bytecode = writer.toByteArray(); + + if (PRINT_BYTECODE) + printBytecode(bytecode, adapter); + + return bytecode; + } + + static void printBytecode(byte[] bytecode, ClassAdapter adapter) + { + StringWriter sw = new StringWriter(); + ClassVisitor tracer = new TraceClassVisitor(adapter, new PrintWriter(sw)); + new ClassReader(bytecode).accept(tracer, 0); + String dump = sw.toString(); + + System.out.println(dump); + } + + static void throwException0(MethodVisitor visitor, Class< ? > type, String msg) + { + String errorType = jvmClassName(type); + + visitor.visitTypeInsn(NEW, errorType); + visitor.visitInsn(DUP); + visitor.visitLdcInsn(msg); + visitor.visitMethodInsn(INVOKESPECIAL, errorType, "", "(Ljava/lang/String;)V"); + visitor.visitInsn(ATHROW); + } + + static class MappedInstanceMethodAdapter extends MethodAdapter + { + private final String className; + private final String methodName; + private final String descr; + + public MappedInstanceMethodAdapter(String className, String methodName, String descr, MethodVisitor backing) + { + super(backing); + this.className = className; + this.methodName = methodName; + this.descr = descr; + } + + @Override + public void visitTypeInsn(int opcode, String typeName) + { + if (opcode == NEW && className_to_subtype.containsKey(typeName)) + { + throw new IllegalAccessError("must not manually create instances of " + typeName + " in method " + this.className + "." + this.methodName + this.descr); + } + + super.visitTypeInsn(opcode, typeName); + } + + @Override + public void visitMethodInsn(int opcode, String className, String methodName, String signature) + { + if (opcode == INVOKESPECIAL && className.equals(jvmClassName(MappedObject.class)) && methodName.equals("") && signature.equals("()V")) + { + // stack: instance + visitInsn(POP); + // stack: - + return; + } + + for (MappedSubtypeInfo mappedType : className_to_subtype.values()) + { + boolean isMapDirectMethod = (opcode == INVOKESTATIC && methodName.equals("map") && className.equals(mappedType.className) && signature.equals("(JI)L" + jvmClassName(MappedObject.class) + ";")); + boolean isMapBufferMethod = (opcode == INVOKESTATIC && methodName.equals("map") && className.equals(mappedType.className) && signature.equals("(Ljava/nio/ByteBuffer;)L" + jvmClassName(MappedObject.class) + ";")); + boolean isMallocMethod = (opcode == INVOKESTATIC && methodName.equals("malloc") && className.equals(mappedType.className) && signature.equals("(I)L" + jvmClassName(MappedObject.class) + ";")); + + if ((isMapDirectMethod || isMapBufferMethod) || isMallocMethod) + { + if (isMallocMethod) + { + // stack: count + pushInt(super.mv, mappedType.sizeof); + // stack: sizeof, count + super.visitInsn(IMUL); + // stack: bytes + super.visitMethodInsn(INVOKESTATIC, jvmClassName(ByteBuffer.class), "allocateDirect", "(I)L" + jvmClassName(ByteBuffer.class) + ";"); + // stack: buffer + } + else if (isMapDirectMethod) + { + // stack: capacity, address + super.visitMethodInsn(INVOKESTATIC, jvmClassName(MappedHelper.class), "newBuffer", "(JI)L" + jvmClassName(ByteBuffer.class) + ";"); + // stack: buffer + } + + // stack: buffer + super.visitTypeInsn(NEW, className); + // stack: new, buffer + super.visitInsn(DUP); + // stack: new, new, buffer + super.visitMethodInsn(INVOKESPECIAL, className, "", "()V"); + // stack: new, buffer + super.visitInsn(DUP_X1); + // stack: new, buffer, new + super.visitInsn(SWAP); + // stack: buffer, new, new + pushInt(super.mv, mappedType.align); + // stack: int, buffer, new, new + pushInt(super.mv, mappedType.sizeof); + // stack: int, int, buffer, new, new + super.visitMethodInsn(INVOKESTATIC, jvmClassName(MappedHelper.class), "setup", "(L" + jvmClassName(MappedObject.class) + ";Ljava/nio/ByteBuffer;II)V"); + // stack: new + return; + } + + if (opcode == INVOKEVIRTUAL && methodName.equals("dup") && className.equals(mappedType.className) && signature.equals("()L" + jvmClassName(MappedObject.class) + ";")) + { + // stack: this + super.visitTypeInsn(NEW, className); + // stack: new, this + super.visitInsn(DUP); + // stack: new, new, this + super.visitMethodInsn(INVOKESPECIAL, className, "", "()V"); + // stack: new, this + super.visitMethodInsn(INVOKESTATIC, jvmClassName(MappedHelper.class), "dup", "(L" + jvmClassName(MappedObject.class) + ";L" + jvmClassName(MappedObject.class) + ";)L" + jvmClassName(MappedObject.class) + ";"); + // stack: new + return; + } + + if (opcode == INVOKEVIRTUAL && methodName.equals("slice") && className.equals(mappedType.className) && signature.equals("()L" + jvmClassName(MappedObject.class) + ";")) + { + // stack: this + super.visitTypeInsn(NEW, className); + // stack: new, this + super.visitInsn(DUP); + // stack: new, new, this + super.visitMethodInsn(INVOKESPECIAL, className, "", "()V"); + // stack: new, this + super.visitMethodInsn(INVOKESTATIC, jvmClassName(MappedHelper.class), "slice", "(L" + jvmClassName(MappedObject.class) + ";L" + jvmClassName(MappedObject.class) + ";)L" + jvmClassName(MappedObject.class) + ";"); + // stack: new + return; + } + + // + + if (opcode == INVOKEVIRTUAL && methodName.equals("runViewConstructor") && className.equals(mappedType.className) && signature.equals("()V")) + { + // stack: this + super.visitInsn(DUP); + // stack: this, this + super.visitMethodInsn(INVOKEVIRTUAL, className, view_constructor_method, "()V"); + // stack: this + return; + } + + // + + if (opcode == INVOKEVIRTUAL && methodName.equals("copyTo") && className.equals(mappedType.className) && signature.equals("(L" + jvmClassName(MappedObject.class) + ";)V")) + { + // stack: target, this + pushInt(super.mv, mappedType.sizeof); + // stack: sizeof, target, this + super.visitMethodInsn(INVOKESTATIC, jvmClassName(MappedHelper.class), "copy", "(L" + jvmClassName(MappedObject.class) + ";L" + jvmClassName(MappedObject.class) + ";I)V"); + // stack: - + return; + } + + if (opcode == INVOKEVIRTUAL && methodName.equals("copyRange") && className.equals(mappedType.className) && signature.equals("(L" + jvmClassName(MappedObject.class) + ";I)V")) + { + // stack: instances, target, this + pushInt(super.mv, mappedType.sizeof); + // stack: sizeof, instances, target, this + super.visitInsn(IMUL); + // stack: bytes, target, this + super.visitMethodInsn(INVOKESTATIC, jvmClassName(MappedHelper.class), "copy", "(L" + jvmClassName(MappedObject.class) + ";L" + jvmClassName(MappedObject.class) + ";I)V"); + // stack: - + return; + } + } + + super.visitMethodInsn(opcode, className, methodName, signature); + } + + static void throwAccessErrorOnReadOnlyField(String className, String fieldName) + { + throw new IllegalAccessError("field '" + className + "." + fieldName + "' is final"); + } + + @Override + public void visitFieldInsn(int opcode, String className, String fieldName, String typeName) + { + MappedSubtypeInfo mappedSubtype = className_to_subtype.get(className); + if (mappedSubtype == null) + { + String mappedSetPrefix = jvmClassName(MappedSet.class); + + outer: if (fieldName.equals("view") && className.startsWith(mappedSetPrefix)) + { + if (opcode == GETFIELD) + throwAccessErrorOnReadOnlyField(className, fieldName); + if (opcode != PUTFIELD) + break outer; + + // stack: index, this + if (false) + break outer; + else if (className.equals(jvmClassName(MappedSet2.class))) + super.visitMethodInsn(INVOKESTATIC, jvmClassName(MappedHelper.class), "put_views", "(L" + jvmClassName(MappedSet2.class) + ";I)V"); + else if (className.equals(jvmClassName(MappedSet3.class))) + super.visitMethodInsn(INVOKESTATIC, jvmClassName(MappedHelper.class), "put_views", "(L" + jvmClassName(MappedSet3.class) + ";I)V"); + else if (className.equals(jvmClassName(MappedSet4.class))) + super.visitMethodInsn(INVOKESTATIC, jvmClassName(MappedHelper.class), "put_views", "(L" + jvmClassName(MappedSet4.class) + ";I)V"); + else + break outer; + // stack: - + return; + } + + // early out + super.visitFieldInsn(opcode, className, fieldName, typeName); + return; + } + + if (fieldName.equals("SIZEOF")) + { + if (!typeName.equals("I")) + throw new IllegalStateException(); + + if (opcode == GETSTATIC) + { + pushInt(super.mv, mappedSubtype.sizeof); + return; + } + if (opcode == PUTSTATIC) + { + throwAccessErrorOnReadOnlyField(className, fieldName); + } + } + + if (fieldName.equals("view")) + { + if (!typeName.equals("I")) + throw new IllegalStateException(); + + if (opcode == GETFIELD) + { + // stack: instance + super.visitMethodInsn(INVOKESTATIC, jvmClassName(MappedHelper.class), "get_view", "(L" + jvmClassName(MappedObject.class) + ";)I"); + return; + } + if (opcode == PUTFIELD) + { + // stack: int, instance + super.visitMethodInsn(INVOKESTATIC, jvmClassName(MappedHelper.class), "put_view", "(L" + jvmClassName(MappedObject.class) + ";I)V"); + return; + } + } + + if (fieldName.equals("align")) + { + if (!typeName.equals("I")) + throw new IllegalStateException(); + + if (opcode == GETFIELD) + { + // stack: instance + super.visitInsn(POP); + // stack: - + pushInt(super.mv, mappedSubtype.align); + // stack: int + return; + } + if (opcode == PUTFIELD) + { + throwAccessErrorOnReadOnlyField(className, fieldName); + } + } + + if (fieldName.equals("stride")) + { + if (!typeName.equals("I")) + throw new IllegalStateException(); + + if (opcode == GETFIELD) + { + // do not change a thing + } + if (opcode == PUTFIELD) + { + throwAccessErrorOnReadOnlyField(className, fieldName); + } + } + + if (fieldName.equals("baseAddress") || fieldName.equals("viewAddress")) + { + if (!typeName.equals("J")) + throw new IllegalStateException(); + + if (opcode == GETFIELD) + { + // do not change a thing + } + if (opcode == PUTFIELD) + { + throwAccessErrorOnReadOnlyField(className, fieldName); + } + } + + Long fieldOffset = mappedSubtype.fieldToOffset.get(fieldName); + if (fieldOffset == null) + { + // early out + super.visitFieldInsn(opcode, className, fieldName, typeName); + return; + } + + if (typeName.equals("L" + jvmClassName(ByteBuffer.class) + ";")) + { + if (opcode == PUTFIELD) + { + throwAccessErrorOnReadOnlyField(className, fieldName); + } + if (opcode == GETFIELD) + { + Long fieldLength = mappedSubtype.fieldToLength.get(fieldName); + + super.visitFieldInsn(GETFIELD, mappedSubtype.className, "viewAddress", "J"); + super.visitLdcInsn(fieldOffset); + super.visitInsn(LADD); + super.visitLdcInsn(fieldLength); + super.visitInsn(L2I); + super.visitMethodInsn(INVOKESTATIC, jvmClassName(MappedHelper.class), "newBuffer", "(JI)L" + jvmClassName(ByteBuffer.class) + ";"); + + return; + } + } + + if (opcode == PUTFIELD) + { + super.visitInsn(SWAP); + super.visitFieldInsn(GETFIELD, mappedSubtype.className, "viewAddress", "J"); + super.visitLdcInsn(fieldOffset); + super.visitInsn(LADD); + super.visitMethodInsn(INVOKESTATIC, jvmClassName(MappedHelper.class), typeName.toLowerCase() + "put", "(" + typeName + "J)V"); + return; + } + if (opcode == GETFIELD) + { + super.visitFieldInsn(GETFIELD, mappedSubtype.className, "viewAddress", "J"); + super.visitLdcInsn(fieldOffset); + super.visitInsn(LADD); + super.visitMethodInsn(INVOKESTATIC, jvmClassName(MappedHelper.class), typeName.toLowerCase() + "get", "(J)" + typeName); + return; + } + + // original field access + super.visitFieldInsn(opcode, className, fieldName, typeName); + return; + } + } + + static void pushInt(MethodVisitor mv, int value) + { + if (value == -1) + mv.visitInsn(ICONST_M1); + else if (value == 0) + mv.visitInsn(ICONST_0); + else if (value == 1) + mv.visitInsn(ICONST_1); + else if (value == 2) + mv.visitInsn(ICONST_2); + else if (value == 3) + mv.visitInsn(ICONST_3); + else if (value == 4) + mv.visitInsn(ICONST_4); + else if (value == 5) + mv.visitInsn(ICONST_5); + 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(Integer.valueOf(value)); + } + + static String jvmClassName(Class< ? > type) + { + return type.getName().replace('.', '/'); + } + + static class MappedSubtypeInfo + { + public final String className; + + public int sizeof; + public int align; + + public Map fieldToOffset; + public Map fieldToLength; + + public MappedSubtypeInfo(String className, int sizeof, int align) + { + this.className = className; + + this.sizeof = sizeof; + this.align = align; + + this.fieldToOffset = new HashMap(); + this.fieldToLength = new HashMap(); + } + } +} \ No newline at end of file diff --git a/src/java/org/lwjgl/util/mapped/MappedObjectUnsafe.java b/src/java/org/lwjgl/util/mapped/MappedObjectUnsafe.java new file mode 100644 index 00000000..3cb4cca1 --- /dev/null +++ b/src/java/org/lwjgl/util/mapped/MappedObjectUnsafe.java @@ -0,0 +1,83 @@ +/* + * Created on Jun 24, 2011 + */ + +package org.lwjgl.util.mapped; + +import java.lang.reflect.Field; +import java.nio.Buffer; +import java.nio.ByteBuffer; + +import sun.misc.Unsafe; + +/** + * @author Riven + */ + +public class MappedObjectUnsafe +{ + public static final Unsafe INSTANCE = getUnsafeInstance(); + public static final int ADDRESS_SIZE = INSTANCE.addressSize(); + private static final Object[] ARRAY = new Object[1]; + private static final long ARRAY_ELEMENT_OFFSET = INSTANCE.arrayBaseOffset(ARRAY.getClass()); + + private static final long BUFFER_ADDRESS_OFFSET = getObjectFieldOffset(ByteBuffer.class, "address"); + private static final long BUFFER_CAPACITY_OFFSET = getObjectFieldOffset(ByteBuffer.class, "capacity"); + + // + + public static long getBufferBaseAddress(Buffer buffer) + { + return INSTANCE.getLong(buffer, BUFFER_ADDRESS_OFFSET); + } + + private static final ByteBuffer global = ByteBuffer.allocateDirect(4 * 1024); + + public static ByteBuffer newBuffer(long address, int capacity) + { + if (address <= 0L || capacity < 0) + throw new IllegalStateException("you almost crashed the jvm"); + + ByteBuffer buffer = global.duplicate(); + INSTANCE.putLong(buffer, BUFFER_ADDRESS_OFFSET, address); + INSTANCE.putInt(buffer, BUFFER_CAPACITY_OFFSET, capacity); + buffer.position(0); + buffer.limit(capacity); + return buffer; + } + + + + private static long getObjectFieldOffset(Class< ? > type, String fieldName) + { + while (type != null) + { + try + { + return INSTANCE.objectFieldOffset(type.getDeclaredField(fieldName)); + } + catch (Throwable t) + { + type = type.getSuperclass(); + } + } + throw new InternalError(); + } + + private static Unsafe getUnsafeInstance() + { + try + { + ByteBuffer buffer = ByteBuffer.allocateDirect(1); + Field unsafeField = buffer.getClass().getDeclaredField("unsafe"); + unsafeField.setAccessible(true); + Unsafe instance = (Unsafe) unsafeField.get(buffer); + buffer.flip(); // prevented 'buffer' from being gc'ed + return instance; + } + catch (Exception exc) + { + throw new InternalError(); + } + } +} diff --git a/src/java/org/lwjgl/util/mapped/MappedSet.java b/src/java/org/lwjgl/util/mapped/MappedSet.java new file mode 100644 index 00000000..4210f74e --- /dev/null +++ b/src/java/org/lwjgl/util/mapped/MappedSet.java @@ -0,0 +1,23 @@ +/* + * Created on Jul 11, 2011 + */ + +package org.lwjgl.util.mapped; + +public class MappedSet +{ + public static MappedSet2 create(MappedObject a, MappedObject b) + { + return new MappedSet2(a, b); + } + + public static MappedSet3 create(MappedObject a, MappedObject b, MappedObject c) + { + return new MappedSet3(a, b, c); + } + + public static MappedSet4 create(MappedObject a, MappedObject b, MappedObject c, MappedObject d) + { + return new MappedSet4(a, b, c, d); + } +} diff --git a/src/java/org/lwjgl/util/mapped/MappedSet2.java b/src/java/org/lwjgl/util/mapped/MappedSet2.java new file mode 100644 index 00000000..2a15080a --- /dev/null +++ b/src/java/org/lwjgl/util/mapped/MappedSet2.java @@ -0,0 +1,30 @@ +/* + * Created on Jul 11, 2011 + */ + +package org.lwjgl.util.mapped; + +public class MappedSet2 +{ + private final MappedObject a, b; + + MappedSet2(MappedObject a, MappedObject b) + { + this.a = a; + this.b = b; + } + + public int view; + + void view(int view) + { + this.a.viewAddress = this.a.baseAddress + this.a.stride * view; + this.b.viewAddress = this.b.baseAddress + this.b.stride * view; + } + + public void next() + { + this.a.viewAddress += this.a.stride; + this.b.viewAddress += this.b.stride; + } +} diff --git a/src/java/org/lwjgl/util/mapped/MappedSet3.java b/src/java/org/lwjgl/util/mapped/MappedSet3.java new file mode 100644 index 00000000..6adac0cc --- /dev/null +++ b/src/java/org/lwjgl/util/mapped/MappedSet3.java @@ -0,0 +1,33 @@ +/* + * Created on Jul 11, 2011 + */ + +package org.lwjgl.util.mapped; + +public class MappedSet3 +{ + private final MappedObject a, b, c; + + MappedSet3(MappedObject a, MappedObject b, MappedObject c) + { + this.a = a; + this.b = b; + this.c = c; + } + + public int view; + + void view(int view) + { + this.a.viewAddress = this.a.baseAddress + this.a.stride * view; + this.b.viewAddress = this.b.baseAddress + this.b.stride * view; + this.c.viewAddress = this.c.baseAddress + this.c.stride * view; + } + + public void next() + { + this.a.viewAddress += this.a.stride; + this.b.viewAddress += this.b.stride; + this.c.viewAddress += this.c.stride; + } +} diff --git a/src/java/org/lwjgl/util/mapped/MappedSet4.java b/src/java/org/lwjgl/util/mapped/MappedSet4.java new file mode 100644 index 00000000..a8a98eab --- /dev/null +++ b/src/java/org/lwjgl/util/mapped/MappedSet4.java @@ -0,0 +1,36 @@ +/* + * Created on Jul 11, 2011 + */ + +package org.lwjgl.util.mapped; + +public class MappedSet4 +{ + private final MappedObject a, b, c, d; + + MappedSet4(MappedObject a, MappedObject b, MappedObject c, MappedObject d) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + } + + public int view; + + void view(int view) + { + this.a.viewAddress = this.a.baseAddress + this.a.stride * view; + this.b.viewAddress = this.b.baseAddress + this.b.stride * view; + this.c.viewAddress = this.c.baseAddress + this.c.stride * view; + this.d.viewAddress = this.d.baseAddress + this.d.stride * view; + } + + public void next() + { + this.a.viewAddress += this.a.stride; + this.b.viewAddress += this.b.stride; + this.c.viewAddress += this.c.stride; + this.d.viewAddress += this.d.stride; + } +} diff --git a/src/java/org/lwjgl/util/mapped/MappedType.java b/src/java/org/lwjgl/util/mapped/MappedType.java new file mode 100644 index 00000000..e655d066 --- /dev/null +++ b/src/java/org/lwjgl/util/mapped/MappedType.java @@ -0,0 +1,21 @@ +/* + * Created on Jun 24, 2011 + */ + +package org.lwjgl.util.mapped; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * @author Riven + */ + +@Retention(RetentionPolicy.RUNTIME) +public @interface MappedType { + int sizeof(); + + int align() default 4; + + boolean autoGenerateOffsets() default true; +}