/* * Licensed 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 com.facebook.presto.sql.gen; import com.facebook.presto.bytecode.BytecodeBlock; import com.facebook.presto.bytecode.BytecodeNode; import com.facebook.presto.bytecode.Scope; import com.facebook.presto.metadata.FunctionRegistry; import com.facebook.presto.sql.relational.CallExpression; import com.facebook.presto.sql.relational.ConstantExpression; import com.facebook.presto.sql.relational.InputReferenceExpression; import com.facebook.presto.sql.relational.LambdaDefinitionExpression; import com.facebook.presto.sql.relational.RowExpressionVisitor; import com.facebook.presto.sql.relational.VariableReferenceExpression; import static com.facebook.presto.bytecode.expression.BytecodeExpressions.constantTrue; import static com.facebook.presto.bytecode.instruction.Constant.loadBoolean; import static com.facebook.presto.bytecode.instruction.Constant.loadDouble; import static com.facebook.presto.bytecode.instruction.Constant.loadFloat; import static com.facebook.presto.bytecode.instruction.Constant.loadInt; import static com.facebook.presto.bytecode.instruction.Constant.loadLong; import static com.facebook.presto.bytecode.instruction.Constant.loadString; import static com.facebook.presto.sql.gen.BytecodeUtils.loadConstant; import static com.facebook.presto.sql.relational.Signatures.BIND; import static com.facebook.presto.sql.relational.Signatures.CAST; import static com.facebook.presto.sql.relational.Signatures.COALESCE; import static com.facebook.presto.sql.relational.Signatures.DEREFERENCE; import static com.facebook.presto.sql.relational.Signatures.IF; import static com.facebook.presto.sql.relational.Signatures.IN; import static com.facebook.presto.sql.relational.Signatures.IS_NULL; import static com.facebook.presto.sql.relational.Signatures.NULL_IF; import static com.facebook.presto.sql.relational.Signatures.ROW_CONSTRUCTOR; import static com.facebook.presto.sql.relational.Signatures.SWITCH; import static com.facebook.presto.sql.relational.Signatures.TRY; import static com.google.common.base.Preconditions.checkState; public class BytecodeExpressionVisitor implements RowExpressionVisitor<Scope, BytecodeNode> { private final CallSiteBinder callSiteBinder; private final CachedInstanceBinder cachedInstanceBinder; private final RowExpressionVisitor<Scope, BytecodeNode> fieldReferenceCompiler; private final FunctionRegistry registry; private final PreGeneratedExpressions preGeneratedExpressions; public BytecodeExpressionVisitor( CallSiteBinder callSiteBinder, CachedInstanceBinder cachedInstanceBinder, RowExpressionVisitor<Scope, BytecodeNode> fieldReferenceCompiler, FunctionRegistry registry, PreGeneratedExpressions preGeneratedExpressions) { this.callSiteBinder = callSiteBinder; this.cachedInstanceBinder = cachedInstanceBinder; this.fieldReferenceCompiler = fieldReferenceCompiler; this.registry = registry; this.preGeneratedExpressions = preGeneratedExpressions; } @Override public BytecodeNode visitCall(CallExpression call, final Scope scope) { BytecodeGenerator generator; // special-cased in function registry if (call.getSignature().getName().equals(CAST)) { generator = new CastCodeGenerator(); } else { switch (call.getSignature().getName()) { // lazy evaluation case IF: generator = new IfCodeGenerator(); break; case NULL_IF: generator = new NullIfCodeGenerator(); break; case SWITCH: // (SWITCH <expr> (WHEN <expr> <expr>) (WHEN <expr> <expr>) <expr>) generator = new SwitchCodeGenerator(); break; case TRY: generator = new TryCodeGenerator(preGeneratedExpressions.getTryMethodMap()); break; // functions that take null as input case IS_NULL: generator = new IsNullCodeGenerator(); break; case COALESCE: generator = new CoalesceCodeGenerator(); break; // functions that require varargs and/or complex types (e.g., lists) case IN: generator = new InCodeGenerator(registry); break; // optimized implementations (shortcircuiting behavior) case "AND": generator = new AndCodeGenerator(); break; case "OR": generator = new OrCodeGenerator(); break; case DEREFERENCE: generator = new DereferenceCodeGenerator(); break; case ROW_CONSTRUCTOR: generator = new RowConstructorCodeGenerator(); break; case BIND: generator = new BindCodeGenerator(); break; default: generator = new FunctionCallCodeGenerator(); } } BytecodeGeneratorContext generatorContext = new BytecodeGeneratorContext( this, scope, callSiteBinder, cachedInstanceBinder, registry); return generator.generateExpression(call.getSignature(), generatorContext, call.getType(), call.getArguments()); } @Override public BytecodeNode visitConstant(ConstantExpression constant, Scope scope) { Object value = constant.getValue(); Class<?> javaType = constant.getType().getJavaType(); BytecodeBlock block = new BytecodeBlock(); if (value == null) { return block.comment("constant null") .append(scope.getVariable("wasNull").set(constantTrue())) .pushJavaDefault(javaType); } // use LDC for primitives (boolean, short, int, long, float, double) block.comment("constant " + constant.getType().getTypeSignature()); if (javaType == boolean.class) { return block.append(loadBoolean((Boolean) value)); } if (javaType == byte.class || javaType == short.class || javaType == int.class) { return block.append(loadInt(((Number) value).intValue())); } if (javaType == long.class) { return block.append(loadLong((Long) value)); } if (javaType == float.class) { return block.append(loadFloat((Float) value)); } if (javaType == double.class) { return block.append(loadDouble((Double) value)); } if (javaType == String.class) { return block.append(loadString((String) value)); } if (javaType == void.class) { return block; } // bind constant object directly into the call-site using invoke dynamic Binding binding = callSiteBinder.bind(value, constant.getType().getJavaType()); return new BytecodeBlock() .setDescription("constant " + constant.getType()) .comment(constant.toString()) .append(loadConstant(binding)); } @Override public BytecodeNode visitInputReference(InputReferenceExpression node, Scope scope) { return fieldReferenceCompiler.visitInputReference(node, scope); } @Override public BytecodeNode visitLambda(LambdaDefinitionExpression lambda, Scope scope) { checkState(preGeneratedExpressions.getLambdaFieldMap().containsKey(lambda), "lambda expressions map does not contain this lambda definition"); return scope.getThis().getField(preGeneratedExpressions.getLambdaFieldMap().get(lambda).getInstanceField()); } @Override public BytecodeNode visitVariableReference(VariableReferenceExpression reference, Scope scope) { return fieldReferenceCompiler.visitVariableReference(reference, scope); } }