All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.calcite.linq4j.tree.BlockBuilder Maven / Gradle / Ivy

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to you under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.calcite.linq4j.tree;

import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.checker.nullness.qual.PolyNull;

import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static java.util.Objects.requireNonNull;

/**
 * Builder for {@link BlockStatement}.
 *
 * 

Has methods that help ensure that variable names are unique.

*/ public class BlockBuilder { final List statements = new ArrayList<>(); final Set variables = new HashSet<>(); /** Contains final-fine-to-reuse-declarations. * An entry to this map is added when adding final declaration of a * statement with optimize=true parameter. */ final Map expressionForReuse = new HashMap<>(); private final boolean optimizing; private final @Nullable BlockBuilder parent; private final boolean removeUnused; private static final Shuttle OPTIMIZE_SHUTTLE = new OptimizeShuttle(); /** Private constructor. */ private BlockBuilder(boolean optimizing, @Nullable BlockBuilder parent, boolean removeUnused) { this.optimizing = optimizing; this.parent = parent; this.removeUnused = removeUnused; } /** * Creates a non-optimizing BlockBuilder. */ public BlockBuilder() { this(true); } /** * Creates a BlockBuilder. * * @param optimizing Whether to eliminate common sub-expressions */ public BlockBuilder(boolean optimizing) { this(optimizing, null); } /** * Creates a BlockBuilder. * * @param optimizing Whether to eliminate common sub-expressions */ public BlockBuilder(boolean optimizing, @Nullable BlockBuilder parent) { this(optimizing, parent, true); } /** * Clears this BlockBuilder. */ public void clear() { statements.clear(); variables.clear(); expressionForReuse.clear(); } /** * Appends a block to a list of statements and returns an expression * (possibly a variable) that represents the result of the newly added * block. */ public Expression append(String name, BlockStatement block) { return append(name, block, true); } /** * Appends an expression to a list of statements, optionally optimizing it * to a variable if it is used more than once. * * @param name Suggested variable name * @param block Expression * @param optimize Whether to try to optimize by assigning the expression to * a variable. Do not do this if the expression has * side-effects or a time-dependent value. */ public Expression append(String name, BlockStatement block, boolean optimize) { if (statements.size() > 0) { Statement lastStatement = statements.get(statements.size() - 1); if (lastStatement instanceof GotoStatement) { // convert "return expr;" into "expr;" statements.set(statements.size() - 1, Expressions.statement(((GotoStatement) lastStatement).expression)); } } Expression result = null; final IdentityHashMap replacements = new IdentityHashMap<>(); final Shuttle shuttle = new SubstituteVariableVisitor(replacements); for (int i = 0; i < block.statements.size(); i++) { Statement statement = block.statements.get(i); if (!replacements.isEmpty()) { // Save effort, and only substitute variables if there are some. statement = statement.accept(shuttle); } if (statement instanceof DeclarationStatement) { DeclarationStatement declaration = (DeclarationStatement) statement; if (!variables.contains(declaration.parameter.name)) { add(statement); } else { String newName = newName(declaration.parameter.name, optimize); Expression x; // When initializer is null, append(name, initializer) can't deduce expression type if (declaration.initializer != null && isSafeForReuse(declaration)) { x = append(newName, declaration.initializer); } else { ParameterExpression pe = Expressions.parameter( declaration.parameter.type, newName); DeclarationStatement newDeclaration = Expressions.declare( declaration.modifiers, pe, declaration.initializer); x = pe; add(newDeclaration); } statement = null; result = x; if (declaration.parameter != x) { // declaration.parameter can be equal to x if exactly the same // declaration was present in BlockBuilder replacements.put(declaration.parameter, x); } } } else { add(statement); } if (i == block.statements.size() - 1) { if (statement instanceof DeclarationStatement) { result = ((DeclarationStatement) statement).parameter; } else if (statement instanceof GotoStatement) { statements.remove(statements.size() - 1); result = append_(name, requireNonNull(((GotoStatement) statement).expression, "expression"), optimize); if (isSimpleExpression(result)) { // already simple; no need to declare a variable or // even to evaluate the expression } else { DeclarationStatement declare = Expressions.declare(Modifier.FINAL, newName(name, optimize), result); add(declare); result = declare.parameter; } } else { // not an expression -- result remains null } } } return requireNonNull(result, () -> "empty result when appending name=" + name + ", " + block); } /** * Appends an expression to a list of statements, and returns an expression * (possibly a variable) that represents the result of the newly added * block. */ public Expression append(String name, Expression expression) { return append(name, expression, true); } /** * Appends an expression to a list of statements if it is not null, * and returns the expression. */ public @PolyNull Expression appendIfNotNull(String name, @PolyNull Expression expression) { if (expression == null) { return null; } return append(name, expression, true); } /** * Appends an expression to a list of statements, optionally optimizing if * the expression is used more than once. */ public Expression append(String name, Expression expression, boolean optimize) { if (statements.size() > 0) { Statement lastStatement = statements.get(statements.size() - 1); if (lastStatement instanceof GotoStatement) { // convert "return expr;" into "expr;" statements.set(statements.size() - 1, Expressions.statement(((GotoStatement) lastStatement).expression)); } } return append_(name, expression, optimize); } private Expression append_(String name, Expression expression, boolean optimize) { if (isSimpleExpression(expression)) { // already simple; no need to declare a variable or // even to evaluate the expression return expression; } if (optimizing && optimize) { DeclarationStatement decl = getComputedExpression(expression); if (decl != null) { return decl.parameter; } } DeclarationStatement declare = Expressions.declare(Modifier.FINAL, newName(name, optimize), expression); add(declare); return declare.parameter; } /** * Checks if expression is simple enough to always inline at zero cost. * * @param expr expression to test * @return true when given expression is safe to always inline */ protected boolean isSimpleExpression(@Nullable Expression expr) { if (expr instanceof ParameterExpression || expr instanceof ConstantExpression) { return true; } if (expr instanceof UnaryExpression) { UnaryExpression una = (UnaryExpression) expr; return una.getNodeType() == ExpressionType.Convert && isSimpleExpression(una.expression); } return false; } protected boolean isSafeForReuse(DeclarationStatement decl) { return (decl.modifiers & Modifier.FINAL) != 0 && !decl.parameter.name.startsWith("_"); } protected void addExpressionForReuse(DeclarationStatement decl) { if (isSafeForReuse(decl)) { Expression expr = normalizeDeclaration(decl); expressionForReuse.put(expr, decl); } } private static boolean isCostly(DeclarationStatement decl) { return decl.initializer instanceof NewExpression; } /** * Prepares declaration for inlining, adds cast. * * @param decl inlining candidate * @return normalized expression */ private static Expression normalizeDeclaration(DeclarationStatement decl) { Expression expr = decl.initializer; Type declType = decl.parameter.getType(); if (expr == null) { expr = Expressions.constant(null, declType); } else if (expr.getType() != declType) { expr = Expressions.convert_(expr, declType); } return expr; } /** * Returns the reference to ParameterExpression if given expression was * already computed and stored to local variable. * * @param expr expression to test * @return existing ParameterExpression or null */ public @Nullable DeclarationStatement getComputedExpression(Expression expr) { if (parent != null) { DeclarationStatement decl = parent.getComputedExpression(expr); if (decl != null) { return decl; } } return optimizing ? expressionForReuse.get(expr) : null; } public void add(Statement statement) { statements.add(statement); if (statement instanceof DeclarationStatement) { DeclarationStatement decl = (DeclarationStatement) statement; String name = decl.parameter.name; if (!variables.add(name)) { throw new AssertionError("duplicate variable " + name); } addExpressionForReuse(decl); } } public void add(Expression expression) { add(Expressions.return_(null, expression)); } /** * Returns a block consisting of the current list of statements. */ public BlockStatement toBlock() { if (optimizing && removeUnused) { // We put an artificial limit of 10 iterations just to prevent an endless // loop. Optimize should not loop forever, however it is hard to prove if // it always finishes in reasonable time. for (int i = 0; i < 10; i++) { if (!optimize(createOptimizeShuttle(), true)) { break; } } optimize(createFinishingOptimizeShuttle(), false); } return Expressions.block(statements); } /** * Optimizes the list of statements. If an expression is used only once, * it is inlined. * * @return whether any optimizations were made */ private boolean optimize(Shuttle optimizer, boolean performInline) { int optimizeCount = 0; final UseCounter useCounter = new UseCounter(); for (Statement statement : statements) { if (statement instanceof DeclarationStatement && performInline) { DeclarationStatement decl = (DeclarationStatement) statement; useCounter.map.put(decl.parameter, new Slot()); } // We are added only counters up to current statement. // It is fine to count usages as the latter declarations cannot be used // in more recent statements. if (!useCounter.map.isEmpty()) { statement.accept(useCounter); } } final IdentityHashMap subMap = new IdentityHashMap<>(useCounter.map.size()); final Shuttle visitor = new InlineVariableVisitor( subMap); final ArrayList oldStatements = new ArrayList<>(statements); statements.clear(); for (Statement oldStatement : oldStatements) { if (oldStatement instanceof DeclarationStatement) { DeclarationStatement statement = (DeclarationStatement) oldStatement; final Slot slot = useCounter.map.get(statement.parameter); int count = slot == null ? Integer.MAX_VALUE - 10 : slot.count; if (count > 1 && isSimpleExpression(statement.initializer)) { // Inline simple final constants count = 1; } if (!isSafeForReuse(statement)) { // Don't inline variables that are not final. They might be assigned // more than once. count = 100; } if (isCostly(statement)) { // Don't inline variables that are costly, such as "new MyFunction()". // Later we will make their declarations static. count = 100; } if (statement.parameter.name.startsWith("_")) { // Don't inline variables whose name begins with "_". This // is a hacky way to prevent inlining. E.g. // final int _count = collection.size(); // foo(collection); // return collection.size() - _count; count = Integer.MAX_VALUE; } if (statement.initializer instanceof NewExpression && ((NewExpression) statement.initializer).memberDeclarations != null) { // Don't inline anonymous inner classes. Janino gets // confused referencing variables from deeply nested // anonymous classes. count = Integer.MAX_VALUE; } Expression normalized = normalizeDeclaration(statement); expressionForReuse.remove(normalized); switch (count) { case 0: // Only declared, never used. Throw away declaration. break; case 1: // declared, used once. inline it. subMap.put(statement.parameter, normalized); break; default: Statement beforeOptimize = oldStatement; if (!subMap.isEmpty()) { oldStatement = oldStatement.accept(visitor); // remap } oldStatement = oldStatement.accept(optimizer); if (beforeOptimize != oldStatement) { ++optimizeCount; if (count != Integer.MAX_VALUE && oldStatement instanceof DeclarationStatement && isSafeForReuse((DeclarationStatement) oldStatement) && isSimpleExpression( ((DeclarationStatement) oldStatement).initializer)) { // Allow to inline the expression that became simple after // optimizations. DeclarationStatement newDecl = (DeclarationStatement) oldStatement; subMap.put(newDecl.parameter, normalizeDeclaration(newDecl)); oldStatement = OptimizeShuttle.EMPTY_STATEMENT; } } if (oldStatement != OptimizeShuttle.EMPTY_STATEMENT) { if (oldStatement instanceof DeclarationStatement) { addExpressionForReuse((DeclarationStatement) oldStatement); } statements.add(oldStatement); } break; } } else { Statement beforeOptimize = oldStatement; if (!subMap.isEmpty()) { oldStatement = oldStatement.accept(visitor); // remap } oldStatement = oldStatement.accept(optimizer); if (beforeOptimize != oldStatement) { ++optimizeCount; } if (oldStatement != OptimizeShuttle.EMPTY_STATEMENT) { statements.add(oldStatement); } } } return optimizeCount > 0; } /** * Creates a shuttle that will be used during block optimization. * Sub-classes might provide more specific optimizations (e.g. partial * evaluation). * * @return shuttle used to optimize the statements when converting to block */ protected Shuttle createOptimizeShuttle() { return OPTIMIZE_SHUTTLE; } /** * Creates a final optimization shuttle. * Typically, the visitor will factor out constant expressions. * * @return shuttle that is used to finalize the optimization */ protected Shuttle createFinishingOptimizeShuttle() { return ClassDeclarationFinder.create(); } /** * Creates a name for a new variable, unique within this block, controlling * whether the variable can be inlined later. */ private String newName(String suggestion, boolean optimize) { if (!optimize && !suggestion.startsWith("_")) { // "_" prefix reminds us not to consider the variable for inlining suggestion = '_' + suggestion; } return newName(suggestion); } /** * Creates a name for a new variable, unique within this block. */ public String newName(String suggestion) { int i = 0; String candidate = suggestion; while (hasVariable(candidate)) { candidate = suggestion + i++; } return candidate; } public boolean hasVariable(String name) { return variables.contains(name) || (parent != null && parent.hasVariable(name)); } public BlockBuilder append(Expression expression) { add(expression); return this; } public BlockBuilder withRemoveUnused(boolean removeUnused) { return new BlockBuilder(optimizing, parent, removeUnused); } /** Substitute Variable Visitor. */ private static class SubstituteVariableVisitor extends Shuttle { protected final Map map; private final IdentityHashMap actives = new IdentityHashMap<>(); SubstituteVariableVisitor(Map map) { this.map = map; } @Override public Expression visit(ParameterExpression parameterExpression) { Expression e = map.get(parameterExpression); if (e != null) { try { final Boolean put = actives.put(parameterExpression, true); if (put != null) { throw new AssertionError( "recursive expansion of " + parameterExpression + " in " + actives.keySet()); } // recursively substitute return e.accept(this); } finally { actives.remove(parameterExpression); } } return super.visit(parameterExpression); } } /** Inline Variable Visitor. */ private static class InlineVariableVisitor extends SubstituteVariableVisitor { InlineVariableVisitor( Map map) { super(map); } @Override public Expression visit(UnaryExpression unaryExpression, Expression expression) { if (unaryExpression.getNodeType().modifiesLvalue) { expression = unaryExpression.expression; // avoid substitution if (expression instanceof ParameterExpression) { // avoid "optimization of" int t=1; t++; to 1++ return unaryExpression; } } return super.visit(unaryExpression, expression); } @Override public Expression visit(BinaryExpression binaryExpression, Expression expression0, Expression expression1) { if (binaryExpression.getNodeType().modifiesLvalue) { expression0 = binaryExpression.expression0; // avoid substitution if (expression0 instanceof ParameterExpression) { // If t is a declaration used only once, replace // int t; // int v = (t = 1) != a ? c : d; // with // int v = 1 != a ? c : d; if (map.containsKey(expression0)) { return expression1.accept(this); } } } return super.visit(binaryExpression, expression0, expression1); } } /** Use counter. */ private static class UseCounter extends VisitorImpl { private final IdentityHashMap map = new IdentityHashMap<>(); @Override public Void visit(ParameterExpression parameter) { final Slot slot = map.get(parameter); if (slot != null) { // Count use of parameter, if it's registered. It's OK if // parameter is not registered. It might be beyond the control // of this block. slot.count++; } return super.visit(parameter); } @Override public Void visit(DeclarationStatement declarationStatement) { // Unlike base class, do not visit declarationStatement.parameter. if (declarationStatement.initializer != null) { declarationStatement.initializer.accept(this); } return null; } } /** * Holds the number of times a declaration was used. */ private static class Slot { private int count; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy