/* * Copyright 2013 Guidewire Software, Inc. */ package gw.internal.gosu.ir.compiler.bytecode.expression; import gw.internal.ext.org.objectweb.asm.Label; import gw.internal.ext.org.objectweb.asm.MethodVisitor; import gw.internal.ext.org.objectweb.asm.Opcodes; import gw.internal.gosu.ir.compiler.bytecode.AbstractBytecodeCompiler; import gw.internal.gosu.ir.compiler.bytecode.IRBytecodeCompiler; import gw.internal.gosu.ir.compiler.bytecode.IRBytecodeContext; import gw.internal.gosu.ir.nodes.JavaClassIRType; import gw.lang.ir.IRExpression; import gw.lang.ir.IRType; import gw.lang.ir.IRTypeConstants; import gw.lang.ir.expression.IRMethodCallExpression; import java.lang.reflect.Constructor; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import static gw.internal.ext.org.objectweb.asm.Opcodes.*; public class IRMethodCallExpressionCompiler extends AbstractBytecodeCompiler { public static void compile( IRMethodCallExpression expression, IRBytecodeContext context ) { IRType type = null; if (expression.getRoot() != null) { IRBytecodeCompiler.compileIRExpression( expression.getRoot(), context ); type = maybeProxyStructuralCallRoot( expression, context ); } for (IRExpression arg : expression.getArgs()) { IRBytecodeCompiler.compileIRExpression( arg, context ); } int opCode; if( type != null ) { // the type is structural, the root expr is now wrapped in a proxy of the structural interface assert type.isStructural(); opCode = Opcodes.INVOKEINTERFACE; } else if (expression.getRoot() == null) { // If the root is null, use INVOKESTATIC, regardless of the types or the "special" flag type = expression.getOwnersType(); opCode = Opcodes.INVOKESTATIC; } else if (expression.isSpecial()) { type = expression.getOwnersType(); opCode = Opcodes.INVOKESPECIAL; } else if (isObjectMethod(expression)) { // Methods on Object always need to be invoked with INVOKEVIRTUAL and Object as the root. Why? // Because it's legal to invoke an Object method like toString() on a root expression that's an interface type, // but using INVOKEINTERFACE to invoke an Object method not defined directly on the interface will result in an // IncompatibleClassChangeException at runtime in the IBM VM, and using INVOKEVIRTUAL with an interface type // as the root will result in such an exception at runtime in both the Sun and IBM VMs type = IRTypeConstants.OBJECT(); opCode = Opcodes.INVOKEVIRTUAL; } else { type = expression.getRoot().getType(); if (type.isInterface()) { opCode = Opcodes.INVOKEINTERFACE; } else { opCode = Opcodes.INVOKEVIRTUAL; } } StringBuilder descriptor = new StringBuilder(); descriptor.append("("); for (IRType param : expression.getParameterTypes()) { descriptor.append(param.getDescriptor()); } descriptor.append(")"); descriptor.append(expression.getReturnType().getDescriptor()); context.getMv().visitMethodInsn( opCode, type.isArray() ? JavaClassIRType.get( Object.class ).getSlashName() : type.getSlashName(), expression.getName(), descriptor.toString() ); } private static IRType maybeProxyStructuralCallRoot( IRMethodCallExpression expression, IRBytecodeContext context ) { IRType ownersType = expression.getOwnersType(); if( ownersType.isStructural() ) { // Generate the following: (note rootObject is top on the stack from the caller of this method) // if( rootObject instanceof <structure-iface> ) { // CheckCast and return existing instance // } // else { // Return proxy instance // } String structureName = ownersType.getName(); MethodVisitor mv = context.getMv(); mv.visitInsn( Opcodes.DUP ); // dup the root value mv.visitTypeInsn( INSTANCEOF, ownersType.getSlashName() ); Label labelProxy = new Label(); mv.visitJumpInsn( IFEQ, labelProxy ); mv.visitTypeInsn( CHECKCAST, ownersType.getSlashName() ); Label labelEnd = new Label(); mv.visitJumpInsn( GOTO, labelEnd ); mv.visitLabel( labelProxy ); mv.visitLdcInsn( structureName ); mv.visitMethodInsn( Opcodes.INVOKESTATIC, IRMethodCallExpressionCompiler.class.getName().replace( '.', '/' ), "constructProxy", "(Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object;" ); mv.visitLabel( labelEnd ); return ownersType; } return null; } @SuppressWarnings("UnusedDeclaration") public static Object constructProxy( Object root, String iface ) { // return findCachedProxy( root, iface ); // this is only beneficial when structural invocation happens in a loop, otherwise too costly return createNewProxy( root, iface ); } // private static Map<String, Map<Object, Object>> PROXY_INSTANCE_CACHE = new ConcurrentHashMap<String, Map<Object, Object>>(); // private static Object findCachedProxy( Object root, String iface ) { // Map<Object, Object> proxyInstanceByInstance = PROXY_INSTANCE_CACHE.get( iface ); // if( proxyInstanceByInstance == null ) { // PROXY_INSTANCE_CACHE.put( iface, proxyInstanceByInstance = Collections.synchronizedMap( new WeakHashMap<Object, Object>() ) ); // } // Object proxyInstance = proxyInstanceByInstance.get( root ); // if( proxyInstance == null ) { // proxyInstanceByInstance.put( root, proxyInstance = createNewProxy( root, iface ) ); // } // return proxyInstance; // } private static Map<String, Map<Class, Constructor>> PROXY_CACHE = new ConcurrentHashMap<String, Map<Class, Constructor>>(); private static Object createNewProxy( Object root, String iface ) { Map<Class, Constructor> proxyByClass = PROXY_CACHE.get( iface ); if( proxyByClass == null ) { PROXY_CACHE.put( iface, proxyByClass = new ConcurrentHashMap<Class, Constructor>() ); } Class rootClass = root.getClass(); Constructor proxyClassCtor = proxyByClass.get( rootClass ); if( proxyClassCtor == null ) { Class proxyClass = createProxy( iface, rootClass ); proxyByClass.put( rootClass, proxyClassCtor = proxyClass.getConstructors()[0] ); } try { return proxyClassCtor.newInstance( root ); } catch( Exception e ) { throw new RuntimeException( e ); } } private static Class createProxy( String iface, Class rootClass ) { String relativeProxyName = rootClass.getSimpleName() + "_structuralproxy_" + iface.replace( '.', '_' ); return StructuralTypeProxyGenerator.makeProxy( iface, rootClass, relativeProxyName ); } /** * Determines whether or not the method in question is a method directly on Object, which thus * needs to be invoked using INVOKEVIRTUAL regardless of the expression's root type. */ private static boolean isObjectMethod(IRMethodCallExpression expression) { return expression.getOwnersType().getName().equals("java.lang.Object"); } }