diff --git a/community/codegen/src/main/java/org/neo4j/codegen/CatchClause.java b/community/codegen/src/main/java/org/neo4j/codegen/CatchClause.java new file mode 100644 index 000000000000..d37fa2d070e0 --- /dev/null +++ b/community/codegen/src/main/java/org/neo4j/codegen/CatchClause.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.codegen; + +import java.util.List; +import java.util.function.Consumer; + +public class CatchClause +{ + private final Parameter exception; + private final List> actions; + + public CatchClause( Parameter exception, List> actions ) + { + this.exception = exception; + this.actions = actions; + } + + public Parameter exception() + { + return exception; + } + + public List> actions() + { + return actions; + } + +} diff --git a/community/codegen/src/main/java/org/neo4j/codegen/CodeBlock.java b/community/codegen/src/main/java/org/neo4j/codegen/CodeBlock.java index 17996fa55f77..f6f8226f0849 100644 --- a/community/codegen/src/main/java/org/neo4j/codegen/CodeBlock.java +++ b/community/codegen/src/main/java/org/neo4j/codegen/CodeBlock.java @@ -19,20 +19,21 @@ */ package org.neo4j.codegen; -import java.util.HashMap; -import java.util.Map; +import java.util.function.Consumer; +import static org.neo4j.codegen.LocalVariables.copy; import static org.neo4j.codegen.Resource.withResource; import static org.neo4j.codegen.TypeReference.typeReference; public class CodeBlock implements AutoCloseable { + final ClassGenerator clazz; private MethodEmitter emitter; private final CodeBlock parent; private boolean done; - private Map localVariables = new HashMap<>( ); - private int varCount = 0; + + protected LocalVariables localVariables = new LocalVariables(); CodeBlock( CodeBlock parent ) { @@ -40,7 +41,8 @@ public class CodeBlock implements AutoCloseable this.emitter = parent.emitter; parent.emitter = InvalidState.IN_SUB_BLOCK; this.parent = parent; - this.localVariables = parent.localVariables; + //copy over local variables from parent + this.localVariables = copy(parent.localVariables); } CodeBlock( ClassGenerator clazz, MethodEmitter emitter, Parameter...parameters ) @@ -48,10 +50,10 @@ public class CodeBlock implements AutoCloseable this.clazz = clazz; this.emitter = emitter; this.parent = null; - localVariables.put("this", localVariable( clazz.handle(), "this" ) ); + localVariables.createNew( clazz.handle(), "this" ); for ( Parameter parameter : parameters ) { - localVariables.put( parameter.name(), localVariable( parameter.type(), parameter.name() ) ); + localVariables.createNew( parameter.type(), parameter.name() ); } } @@ -75,7 +77,7 @@ public void close() this.emitter = InvalidState.BLOCK_CLOSED; } - private void endBlock() + protected void endBlock() { if ( !done ) { @@ -84,9 +86,14 @@ private void endBlock() } } + protected void emit( Consumer emitFunction ) + { + emitFunction.accept( emitter ); + } + public void expression( Expression expression ) { - emitter.expression( expression ); + emit( ( e ) -> e.expression( expression ) ); } LocalVariable local( String name ) @@ -96,15 +103,14 @@ LocalVariable local( String name ) public LocalVariable declare( TypeReference type, String name ) { - LocalVariable local = localVariable( type, name ); - localVariables.put(name, local); - emitter.declare( local ); + LocalVariable local = localVariables.createNew( type, name ); + emit( e -> e.declare( local ) ); return local; } public void assign( LocalVariable local, Expression value ) { - emitter.assignVariableInScope( local, value ); + emit( e -> e.assignVariableInScope( local, value ) ); } public void assign( Class type, String name, Expression value ) @@ -114,14 +120,13 @@ public void assign( Class type, String name, Expression value ) public void assign( TypeReference type, String name, Expression value ) { - LocalVariable variable = localVariable( type, name ); - localVariables.put(name, variable ); - emitter.assign( variable, value ); + LocalVariable variable = localVariables.createNew( type, name ); + emit( e -> e.assign( variable, value ) ); } public void put( Expression target, FieldReference field, Expression value ) { - emitter.put( target, field, value ); + emit( e -> e.put( target, field, value ) ); } public Expression self() @@ -136,65 +141,50 @@ public Expression load( String name ) public CodeBlock forEach( Parameter local, Expression iterable ) { - emitter.beginForEach( local, iterable ); + emit( e -> e.beginForEach( local, iterable ) ); return new CodeBlock( this ); } public CodeBlock whileLoop( Expression test ) { - emitter.beginWhile( test ); + emit( e -> e.beginWhile( test ) ); return new CodeBlock( this ); } public CodeBlock ifStatement( Expression test ) { - emitter.beginIf( test ); + emit( e -> e.beginIf( test ) ); return new CodeBlock( this ); } - CodeBlock emitCatch( Parameter exception ) - { - endBlock(); - emitter.beginCatch( exception ); - return new CodeBlock( this ); - } - - CodeBlock emitFinally() - { - endBlock(); - emitter.beginFinally(); - return new CodeBlock( this ); - } - - public CodeBlock tryBlock( Class resourceType, String resourceName, Expression resource ) + public TryBlock tryBlock( Class resourceType, String resourceName, Expression resource ) { return tryBlock( withResource( resourceType, resourceName, resource ) ); } - public CodeBlock tryBlock( TypeReference resourceType, String resourceName, Expression resource ) + public TryBlock tryBlock( TypeReference resourceType, String resourceName, Expression resource ) { return tryBlock( withResource( resourceType, resourceName, resource ) ); } public TryBlock tryBlock( Resource... resources ) { - emitter.beginTry( resources ); - return new TryBlock( this ); + return new TryBlock( this, resources ); } public void returns() { - emitter.returns(); + emit( MethodEmitter::returns ); } public void returns( Expression value ) { - emitter.returns( value ); + emit( e -> e.returns( value ) ); } public void throwException( Expression exception ) { - emitter.throwException( exception ); + emit( e -> e.throwException( exception ) ); } public TypeReference owner() @@ -202,8 +192,4 @@ public TypeReference owner() return clazz.handle(); } - private LocalVariable localVariable( TypeReference type, String name ) - { - return new LocalVariable( type, name, varCount++ ); - } } diff --git a/community/codegen/src/main/java/org/neo4j/codegen/Expression.java b/community/codegen/src/main/java/org/neo4j/codegen/Expression.java index bdea9e3bd0c1..49f377a1abc6 100644 --- a/community/codegen/src/main/java/org/neo4j/codegen/Expression.java +++ b/community/codegen/src/main/java/org/neo4j/codegen/Expression.java @@ -70,7 +70,7 @@ public void accept( ExpressionVisitor visitor ) }; } - static Expression load( final LocalVariable variable) + public static Expression load( final LocalVariable variable) { return new Expression() { diff --git a/community/codegen/src/main/java/org/neo4j/codegen/InvalidState.java b/community/codegen/src/main/java/org/neo4j/codegen/InvalidState.java index 69fe7cadf9aa..fb1e4149f258 100644 --- a/community/codegen/src/main/java/org/neo4j/codegen/InvalidState.java +++ b/community/codegen/src/main/java/org/neo4j/codegen/InvalidState.java @@ -19,6 +19,9 @@ */ package org.neo4j.codegen; +import java.util.List; +import java.util.function.Consumer; + class InvalidState implements MethodEmitter { public static final ClassEmitter CLASS_DONE = new ClassEmitter() @@ -98,12 +101,6 @@ public void beginIf( Expression test ) throw new IllegalStateException( reason ); } - @Override - public void beginFinally() - { - throw new IllegalStateException( reason ); - } - @Override public void endBlock() { @@ -111,7 +108,8 @@ public void endBlock() } @Override - public void beginTry( Resource... resources ) + public void tryCatchBlock( List> body, List catchClauses, + List> finalClauses, LocalVariables localVariables, Resource... resources ) { throw new IllegalStateException( reason ); } @@ -122,12 +120,6 @@ public void throwException( Expression exception ) throw new IllegalStateException( reason ); } - @Override - public void beginCatch( Parameter exception ) - { - throw new IllegalStateException( reason ); - } - @Override public void declare( LocalVariable local ) { diff --git a/community/codegen/src/main/java/org/neo4j/codegen/LocalVariables.java b/community/codegen/src/main/java/org/neo4j/codegen/LocalVariables.java new file mode 100644 index 000000000000..5ccffec4d454 --- /dev/null +++ b/community/codegen/src/main/java/org/neo4j/codegen/LocalVariables.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.codegen; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Repository of local variables. + */ +public class LocalVariables +{ + private final AtomicInteger counter = new AtomicInteger( 0 ); + private final Map localVariables = new HashMap<>(); + + public LocalVariable createNew( TypeReference type, String name ) + { + LocalVariable localVariable = new LocalVariable( type, name, counter.getAndIncrement() ); + localVariables.put( name, localVariable ); + return localVariable; + } + + public LocalVariable get( String name ) + { + return localVariables.get( name ); + } + + public static LocalVariables copy( LocalVariables original ) + { + LocalVariables variables = new LocalVariables(); + variables.counter.set( original.counter.get() ); + original.localVariables.forEach( variables.localVariables::put ); + return variables; + } + + public int nextIndex() + { + return counter.getAndIncrement(); + } +} diff --git a/community/codegen/src/main/java/org/neo4j/codegen/MethodEmitter.java b/community/codegen/src/main/java/org/neo4j/codegen/MethodEmitter.java index 9e4e6f8d7443..543bae50c8b5 100644 --- a/community/codegen/src/main/java/org/neo4j/codegen/MethodEmitter.java +++ b/community/codegen/src/main/java/org/neo4j/codegen/MethodEmitter.java @@ -19,6 +19,9 @@ */ package org.neo4j.codegen; +import java.util.List; +import java.util.function.Consumer; + public interface MethodEmitter { void done(); @@ -37,16 +40,15 @@ public interface MethodEmitter void beginIf( Expression test ); - void beginFinally(); - void endBlock(); - void beginTry( Resource... resources ); + void tryCatchBlock( List> body, + List catchClauses, + List> finalClauses, + LocalVariables localVariables, Resource... resources ); void throwException( Expression exception ); - void beginCatch( Parameter exception ); - void declare( LocalVariable local ); void assignVariableInScope( LocalVariable local, Expression value ); diff --git a/community/codegen/src/main/java/org/neo4j/codegen/MethodReference.java b/community/codegen/src/main/java/org/neo4j/codegen/MethodReference.java index 86eb65e1b426..592f2315c9d9 100644 --- a/community/codegen/src/main/java/org/neo4j/codegen/MethodReference.java +++ b/community/codegen/src/main/java/org/neo4j/codegen/MethodReference.java @@ -46,6 +46,12 @@ private static MethodReference methodReference( Class owner, TypeReference re return methodReference( typeReference( owner ), returns, name, modifiers, parameters ); } + public static MethodReference methodReference( TypeReference owner, TypeReference returns, String name, + TypeReference... parameters ) + { + return new MethodReference( owner, name, returns, Modifier.PUBLIC, parameters ); + } + public static MethodReference methodReference( TypeReference owner, TypeReference returns, String name, int modifiers, TypeReference... parameters ) { diff --git a/community/codegen/src/main/java/org/neo4j/codegen/TryBlock.java b/community/codegen/src/main/java/org/neo4j/codegen/TryBlock.java index cf1843fb8be2..18ed9b35365a 100644 --- a/community/codegen/src/main/java/org/neo4j/codegen/TryBlock.java +++ b/community/codegen/src/main/java/org/neo4j/codegen/TryBlock.java @@ -19,20 +19,165 @@ */ package org.neo4j.codegen; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +/** + * TryBlock are not really block but tries to look like it from an API standpoint. + * Keeps a state-machine to figure out which state we're in (try, catch, finally). + * It is not until we call the last "try block" we actually call close on the actual + * code block and generates code for the entire try-catch block at once. + */ public class TryBlock extends CodeBlock { - TryBlock( CodeBlock parent ) + private final Resource[] resources; + private final State tryState = newState( null ); + private final List catchStates = new LinkedList<>(); + private State finallyState = null; + + private State currentState = tryState; + + TryBlock( CodeBlock parent, Resource... resources ) { super( parent ); + this.resources = resources; } public CodeBlock catchBlock(Parameter exception) { - return emitCatch(exception); + CatchState catchState = new CatchState( exception, currentState ); + this.currentState = catchState; + catchStates.add( catchState ); + return this; } public CodeBlock finallyBlock() { - return emitFinally(); + if ( finallyState != null ) + { + throw new IllegalStateException( "Cannot have more than one finally block" ); + } + finallyState = newState( currentState ); + currentState = finallyState; + return this; + } + + @Override + protected void emit( Consumer emitFunction ) + { + currentState.addAction( emitFunction ); + } + + @Override + public void close() + { + State nextState = currentState.nextState(); + if ( nextState == null ) + { + super.emit( ( e ) -> e.tryCatchBlock( tryActions(), catchClauses(), finallyActions(), localVariables, resources ) ); + super.close(); + } + currentState = nextState; + + } + + @Override + protected void endBlock() + { + //do nothing + } + + private List> tryActions() + { + return tryState.actions(); + } + + private List catchClauses() + { + return catchStates.stream().map( c -> new CatchClause( c.exception, c.actions) ) + .collect( Collectors.toList() ); + } + + private List> finallyActions() + { + return finallyState == null ? Collections.emptyList() : finallyState.actions(); + } + + /** + * A Try block can be in different states depending on if we're in try, catch or finally. + * When nextState returns null it means we have no more blocks left and should close + * the underlying code block. + */ + private interface State + { + + void addAction( Consumer action ); + + List> actions(); + + State nextState(); + } + + /** + * Catch states are special states that stores what kind of exception we are catching. + */ + private static class CatchState implements State + { + + private final List> actions = new LinkedList<>(); + private final Parameter exception; + private final State parent; + + CatchState( Parameter exception, State parent ) + { + this.exception = exception; + this.parent = parent; + } + + @Override + public void addAction( Consumer action ) + { + actions.add( action ); + } + + @Override + public List> actions() + { + return actions; + } + + @Override + public State nextState() + { + return parent; + } + } + private State newState( State parent ) + { + return new State() + { + private final List> actions = new LinkedList<>(); + + @Override + public void addAction( Consumer action ) + { + actions.add( action ); + } + + @Override + public List> actions() + { + return actions; + } + + @Override + public State nextState() + { + return parent; + } + }; } } diff --git a/community/codegen/src/main/java/org/neo4j/codegen/bytecode/ByteCodeExpressionVisitor.java b/community/codegen/src/main/java/org/neo4j/codegen/bytecode/ByteCodeExpressionVisitor.java index 0d44158326c0..a429061f2979 100644 --- a/community/codegen/src/main/java/org/neo4j/codegen/bytecode/ByteCodeExpressionVisitor.java +++ b/community/codegen/src/main/java/org/neo4j/codegen/bytecode/ByteCodeExpressionVisitor.java @@ -196,6 +196,7 @@ public void or( Expression lhs, Expression rhs ) methodVisitor.visitInsn( ICONST_1 ); Label l2 = new Label(); methodVisitor.visitJumpInsn( GOTO, l2 ); + methodVisitor.visitLabel( l1 ); methodVisitor.visitInsn( ICONST_0 ); methodVisitor.visitLabel( l2 ); diff --git a/community/codegen/src/main/java/org/neo4j/codegen/bytecode/If.java b/community/codegen/src/main/java/org/neo4j/codegen/bytecode/If.java index 9efa81a7911d..6d1dbf593923 100644 --- a/community/codegen/src/main/java/org/neo4j/codegen/bytecode/If.java +++ b/community/codegen/src/main/java/org/neo4j/codegen/bytecode/If.java @@ -38,6 +38,5 @@ public If( MethodVisitor methodVisitor, Label l0) public void endBlock() { methodVisitor.visitLabel( l0 ); - methodVisitor.visitFrame( F_SAME, 0, null, 0, null ); } } diff --git a/community/codegen/src/main/java/org/neo4j/codegen/bytecode/MethodByteCodeEmitter.java b/community/codegen/src/main/java/org/neo4j/codegen/bytecode/MethodByteCodeEmitter.java index 041cfc6824b7..5776de79ca65 100644 --- a/community/codegen/src/main/java/org/neo4j/codegen/bytecode/MethodByteCodeEmitter.java +++ b/community/codegen/src/main/java/org/neo4j/codegen/bytecode/MethodByteCodeEmitter.java @@ -24,13 +24,18 @@ import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; +import java.util.ArrayList; import java.util.Deque; import java.util.LinkedList; +import java.util.List; +import java.util.function.Consumer; import org.neo4j.codegen.BaseExpressionVisitor; +import org.neo4j.codegen.CatchClause; import org.neo4j.codegen.Expression; import org.neo4j.codegen.FieldReference; import org.neo4j.codegen.LocalVariable; +import org.neo4j.codegen.LocalVariables; import org.neo4j.codegen.MethodDeclaration; import org.neo4j.codegen.MethodEmitter; import org.neo4j.codegen.MethodReference; @@ -44,6 +49,7 @@ import static org.neo4j.codegen.ByteCodeUtils.outerName; import static org.neo4j.codegen.ByteCodeUtils.signature; import static org.neo4j.codegen.ByteCodeUtils.typeName; +import static org.neo4j.codegen.MethodReference.methodReference; class MethodByteCodeEmitter implements MethodEmitter, Opcodes { @@ -121,6 +127,7 @@ public void returns( Expression value ) case "byte": case "short": case "char": + case "boolean": methodVisitor.visitInsn( IRETURN ); break; case "long": @@ -162,7 +169,6 @@ public void assign( LocalVariable variable, Expression value ) default: methodVisitor.visitVarInsn( ASTORE, variable.index() ); } - } @Override @@ -190,11 +196,6 @@ public void beginIf( Expression test ) stateStack.push(new If(methodVisitor, l0)); } - @Override - public void beginFinally() - { - callSuperIfNecessary(); - } @Override public void endBlock() @@ -209,19 +210,153 @@ public void endBlock() } @Override - public void beginTry( Resource... resources ) + public void tryCatchBlock( List> body, List catchClauses, + List> finalClauses, LocalVariables localVariables, Resource... resources ) { callSuperIfNecessary(); + //Note we are not giving the same gurantee as built in try-with-resources + //we are essentially rewriting to: + //Resource resource = ... + //try... + //finally { ... resource.close} + List> updatedFinalClauses = resources.length == 0 ? + finalClauses : new ArrayList<>( finalClauses ); + for ( Resource resource : resources ) + { + LocalVariable variable = localVariables.createNew( resource.type(), resource.name() ); + assign( variable, resource.producer() ); + + MethodReference close = methodReference( resource.type(), TypeReference.VOID, "close" ); + updatedFinalClauses.add( ( e ) -> e.expression( + Expression.invoke( Expression.load( variable ), close ) ) ); + } + + if ( !catchClauses.isEmpty() ) + { + tryCatchFinally( body, catchClauses, updatedFinalClauses, localVariables ); + } + else + { + tryFinally( body, updatedFinalClauses, localVariables ); + } } - @Override - public void throwException( Expression exception ) + private void tryFinally(List> body, + List> finalClauses, LocalVariables localVariables) { - callSuperIfNecessary(); + Label startLabel = new Label(); + Label endLabel = new Label(); + Label continueLabel = new Label(); + //if we have a simple try {} finally {} we only need to run finally after body + //and make sure we catch any errors and then run finally and rethrow + Label handlerLabel = new Label(); + methodVisitor.visitTryCatchBlock( startLabel, endLabel, handlerLabel, null ); + methodVisitor.visitLabel( startLabel ); + body.forEach( e -> e.accept( this ) ); + methodVisitor.visitLabel( endLabel ); + //run finally on success + finalClauses.forEach( e -> e.accept( this ) ); + + int indexToStoreException = localVariables.nextIndex(); + //catch error and run finally + methodVisitor.visitJumpInsn( GOTO, continueLabel ); + methodVisitor.visitLabel( handlerLabel ); + //store error + methodVisitor.visitVarInsn( ASTORE, indexToStoreException ); + finalClauses.forEach( e -> e.accept( this ) ); + //catch and rethrow + methodVisitor.visitVarInsn( ALOAD, indexToStoreException ); + methodVisitor.visitInsn( ATHROW ); + methodVisitor.visitLabel( continueLabel ); + } + + private void tryCatchFinally(List> body, List catchClauses, + List> finalClauses, LocalVariables localVariables) + { + Label startLabel = new Label(); + Label endLabel = new Label(); + Label continueLabel = new Label(); + //if we have catch clauses we need to catch error, run body create labels for each catch clauses + //we also need to make sure that the final clauses run both after body and after each catch block + //and also make sure if any of the catch clauses fail we still run finally + + //generate try-catch block for each catch clause + List