Java tutorial
/* * Copyright (c) 2016 Yahoo Inc. * Licensed under the terms of the Apache version 2.0 license. * See LICENSE file for terms. */ package com.yahoo.yqlplus.engine.internal.compiler; import com.google.common.base.Preconditions; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Lists; import com.google.common.collect.Multimap; import com.yahoo.yqlplus.api.types.YQLBaseType; import com.yahoo.yqlplus.api.types.YQLCoreType; import com.yahoo.yqlplus.engine.internal.bytecode.UnitGenerator; import com.yahoo.yqlplus.engine.internal.plan.types.*; import com.yahoo.yqlplus.engine.internal.plan.types.base.AnyTypeWidget; import com.yahoo.yqlplus.engine.internal.plan.types.base.BaseTypeAdapter; import com.yahoo.yqlplus.engine.internal.plan.types.base.NotNullableTypeWidget; import com.yahoo.yqlplus.engine.internal.plan.types.base.NullCheckedEvaluatedExpression; import com.yahoo.yqlplus.language.parser.ProgramCompileException; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Map; import static org.objectweb.asm.Opcodes.*; public final class CodeEmitter implements VariableEnvironment { UnitGenerator unit; LocalFrame locals; MethodVisitor methodVisitor; final CodeEmitter parent; int sym; // TODO: keep track of if child emitters have had endScope called before permitting any further use of parent public CodeEmitter(UnitGenerator unit, LocalFrame arguments, MethodVisitor methodVisitor) { this.unit = unit; this.locals = new LocalFrame(arguments); this.methodVisitor = methodVisitor; this.parent = null; startScope(); } private CodeEmitter(CodeEmitter parent) { this.unit = parent.unit; this.locals = new LocalFrame(parent.locals); this.methodVisitor = parent.methodVisitor; this.parent = parent; } public CodeEmitter createScope() { CodeEmitter out = new CodeEmitter(this); out.startScope(); return out; } public CodeEmitter createScope(LocalFrame locals) { CodeEmitter out = new CodeEmitter(this); out.locals.replaceFrom(locals); out.startScope(); return out; } public void checkFrame(LocalValue localValue) { locals.check(localValue); } public void startScope() { locals.startFrame(this); } public void endScope() { locals.endFrame(this); } public void emitIntConstant(int constant) { switch (constant) { case -1: methodVisitor.visitInsn(Opcodes.ICONST_M1); break; case 0: methodVisitor.visitInsn(Opcodes.ICONST_0); break; case 1: methodVisitor.visitInsn(Opcodes.ICONST_1); break; case 2: methodVisitor.visitInsn(Opcodes.ICONST_2); break; case 3: methodVisitor.visitInsn(Opcodes.ICONST_3); break; case 4: methodVisitor.visitInsn(Opcodes.ICONST_4); break; case 5: methodVisitor.visitInsn(Opcodes.ICONST_5); break; default: methodVisitor.visitLdcInsn(constant); } } public void emitBooleanConstant(boolean constant) { if (constant) { methodVisitor.visitInsn(ICONST_1); } else { methodVisitor.visitInsn(ICONST_0); } } public Label getStart() { return locals.startFrame; } public Label getEnd() { return locals.endFrame; } public void gotoExitScope() { methodVisitor.visitJumpInsn(Opcodes.GOTO, getEnd()); } public void inc(AssignableValue offsetExpr, int val) { Preconditions.checkArgument(offsetExpr.getType().getJVMType().getSort() == Type.INT, "inc applies only to INT types"); if (offsetExpr instanceof LocalValue) { LocalValue localValue = (LocalValue) offsetExpr; methodVisitor.visitIincInsn(localValue.start, val); } else { exec(offsetExpr.read()); emitIntConstant(val); methodVisitor.visitInsn(Opcodes.IADD); exec(offsetExpr.write(offsetExpr.getType())); } } public LocalValue getLocal(String name) { return locals.get(name); } public LocalValue allocate(TypeWidget type, String name) { return locals.allocate(name, type); } public BytecodeExpression evaluateOnce(BytecodeExpression target) { if (target instanceof EvaluatedExpression) { return target; } return allocate(target).read(); } @Override public AssignableValue allocate(TypeWidget type) { return locals.allocate(gensym("local"), type); } @Override public AssignableValue evaluate(BytecodeExpression expr) { AssignableValue out = allocate(expr.getType()); exec(out.write(expr)); return out; } @Override public AssignableValue allocate(String name, TypeWidget type) { return locals.allocate(name, type); } @Override public AssignableValue evaluate(String name, BytecodeExpression expr) { AssignableValue out = allocate(name, expr.getType()); exec(out.write(expr)); return out; } @Override public void alias(String from, String to) { locals.alias(from, to); } public MethodVisitor getMethodVisitor() { return methodVisitor; } public void unbox(TypeWidget type) { cast(type.unboxed(), type); } public void box(TypeWidget type) { cast(type.boxed(), NotNullableTypeWidget.create(type)); } public void emitStringConstant(String constant) { methodVisitor.visitLdcInsn(constant); } public String gensym(String prefix) { return prefix + (++sym); } public TypeWidget adapt(Class<?> clazz) { return unit.getEnvironment().getValueTypeAdapter().adaptInternal(clazz); } public TypeWidget adapt(java.lang.reflect.Type clazz) { return unit.getEnvironment().getValueTypeAdapter().adaptInternal(clazz); } private TypeWidget selectMax(TypeWidget left, TypeWidget right) { if (left.getValueCoreType().ordinal() > right.getValueCoreType().ordinal()) { return left; } else { return right; } } public boolean isNumeric(TypeWidget type) { return isInteger(type) || isFloat(type); } public boolean isInteger(TypeWidget type) { return YQLBaseType.INTEGERS.contains(type.getValueCoreType()); } public boolean isFloat(TypeWidget type) { return YQLBaseType.FLOATS.contains(type.getValueCoreType()); } public Unification unifiedEmit(BytecodeExpression left, BytecodeExpression right, Label leftNull, Label rightNull, Label bothNull) { // unify left and right (widening as needed); check for nulls and emit hasNull if either side is null // return the unified type that matches operands // result is a stack with both operands (L, R) and neither is null OR a jump to hasNull // primitive types are unboxed after their null check // if either side is any, they are unified to not-null ANY (boxed if needed) TypeWidget leftType = left.getType(); TypeWidget rightType = right.getType(); if ((isInteger(leftType) && isInteger(rightType)) || (isFloat(leftType) && isFloat(rightType))) { return unifyAs(selectMax(leftType, rightType).unboxed(), left, right, leftNull, rightNull, bothNull); } else if (isNumeric(leftType) && isFloat(rightType)) { // fine, just coerce them both to FLOAT64 return unifyAs(BaseTypeAdapter.FLOAT64, left, right, leftNull, rightNull, bothNull); } else if (isNumeric(rightType) && isFloat(leftType)) { // fine, just coerce them both to FLOAT64 return unifyAs(BaseTypeAdapter.FLOAT64, left, right, leftNull, rightNull, bothNull); } else if (leftType.getValueCoreType() == YQLCoreType.BOOLEAN && rightType.getValueCoreType() == YQLCoreType.BOOLEAN) { // both boolean! return unifyAs(BaseTypeAdapter.BOOLEAN, left, right, leftNull, rightNull, bothNull); } else if (leftType.getJVMType().getDescriptor().equals(rightType.getJVMType().getDescriptor())) { return unifyAs(leftType, left, right, leftNull, rightNull, bothNull); } else { return unifyAs(AnyTypeWidget.getInstance(), left, right, leftNull, rightNull, bothNull); } } public Unification unifyAs(TypeWidget typeWidget, BytecodeExpression left, BytecodeExpression right, Label leftNull, Label rightNull, Label bothNull) { left.generate(this); boolean distinct = (leftNull != bothNull) && right.getType().isNullable(); Label leftIsNull = distinct ? new Label() : leftNull; boolean nullpossible = cast(typeWidget, left.getType(), leftIsNull); if (nullpossible && distinct) { Label skip = new Label(); methodVisitor.visitJumpInsn(Opcodes.GOTO, skip); methodVisitor.visitLabel(leftIsNull); right.generate(this); methodVisitor.visitJumpInsn(Opcodes.IFNULL, bothNull); methodVisitor.visitJumpInsn(Opcodes.GOTO, leftNull); methodVisitor.visitLabel(skip); } Label pop = new Label(); right.generate(this); if (cast(typeWidget, right.getType(), pop)) { Label done = new Label(); nullpossible = true; methodVisitor.visitJumpInsn(Opcodes.GOTO, done); methodVisitor.visitLabel(pop); // pop the left-value pop(typeWidget); methodVisitor.visitJumpInsn(Opcodes.GOTO, rightNull); methodVisitor.visitLabel(done); } return new Unification(typeWidget, nullpossible); } public BinaryCoercion binaryCoercion(BytecodeExpression left, Class<?> leftClazz, BytecodeExpression right, Class<?> rightClazz, Label leftNull, Label rightNull, Label bothNull) { TypeWidget leftTarget = adapt(leftClazz); TypeWidget rightTarget = adapt(rightClazz); left.generate(this); boolean distinct = (leftNull != bothNull) && right.getType().isNullable(); Label leftIsNull = distinct ? new Label() : leftNull; boolean leftMaybeNull = cast(leftTarget, left.getType(), leftIsNull); if (leftMaybeNull && distinct) { Label skip = new Label(); methodVisitor.visitJumpInsn(Opcodes.GOTO, skip); methodVisitor.visitLabel(leftIsNull); right.generate(this); methodVisitor.visitJumpInsn(Opcodes.IFNULL, bothNull); methodVisitor.visitJumpInsn(Opcodes.GOTO, leftNull); methodVisitor.visitLabel(skip); } Label pop = new Label(); right.generate(this); boolean rightMaybeNull = false; if (cast(rightTarget, right.getType(), pop)) { Label done = new Label(); rightMaybeNull = true; methodVisitor.visitJumpInsn(Opcodes.GOTO, done); methodVisitor.visitLabel(pop); // pop the left-value pop(leftTarget); methodVisitor.visitJumpInsn(Opcodes.GOTO, rightNull); methodVisitor.visitLabel(done); } return new BinaryCoercion(leftMaybeNull, rightMaybeNull); } public AssignableValue allocate(Class<?> clazz) { return allocate(adapt(clazz)); } public void exec(BytecodeSequence sequence) { sequence.generate(this); } public AssignableValue allocate(BytecodeExpression initializer) { AssignableValue val = allocate(initializer.getType()); val.write(initializer).generate(this); return val; } public void emitNewArray(TypeWidget elementType, BytecodeExpression e) { MethodVisitor mv = getMethodVisitor(); exec(e); cast(BaseTypeAdapter.INT32, e.getType()); switch (elementType.getJVMType().getSort()) { case Type.BYTE: mv.visitIntInsn(Opcodes.NEWARRAY, Opcodes.T_BYTE); break; case Type.BOOLEAN: mv.visitIntInsn(Opcodes.NEWARRAY, Opcodes.T_BOOLEAN); break; case Type.SHORT: mv.visitIntInsn(Opcodes.NEWARRAY, Opcodes.T_SHORT); break; case Type.INT: mv.visitIntInsn(Opcodes.NEWARRAY, Opcodes.T_INT); break; case Type.CHAR: mv.visitIntInsn(Opcodes.NEWARRAY, Opcodes.T_CHAR); break; case Type.FLOAT: mv.visitIntInsn(Opcodes.NEWARRAY, Opcodes.T_FLOAT); break; case Type.LONG: mv.visitIntInsn(Opcodes.NEWARRAY, Opcodes.T_LONG); break; case Type.DOUBLE: mv.visitIntInsn(Opcodes.NEWARRAY, Opcodes.T_DOUBLE); break; case Type.OBJECT: mv.visitTypeInsn(Opcodes.ANEWARRAY, elementType.getJVMType().getInternalName()); break; default: throw new UnsupportedOperationException("unknown sort for newArray" + elementType.getJVMType()); } } public Unification unifiedEmit(BytecodeExpression left, BytecodeExpression right, Label hasNull) { return unifiedEmit(left, right, hasNull, hasNull, hasNull); } public BytecodeExpression nullChecked(BytecodeExpression expr, Label isNull) { if (expr.getType().isNullable()) { BytecodeExpression input = evaluateOnce(expr); gotoIfNull(input, isNull); return new NullCheckedEvaluatedExpression(input); } return expr; } public boolean gotoIfNull(BytecodeExpression expr, Label isNull) { if (expr.getType().isNullable()) { exec(expr); methodVisitor.visitJumpInsn(Opcodes.IFNULL, isNull); return true; } return false; } public boolean nullTest(TypeWidget typeWidget, Label isNull) { if (typeWidget.isNullable()) { Label done = new Label(); dup(typeWidget); getMethodVisitor().visitJumpInsn(Opcodes.IFNONNULL, done); pop(typeWidget); getMethodVisitor().visitJumpInsn(Opcodes.GOTO, isNull); getMethodVisitor().visitLabel(done); return true; } return false; } public boolean nullTestLeaveNull(TypeWidget typeWidget, Label isNull) { if (typeWidget.isNullable()) { Label done = new Label(); dup(typeWidget); getMethodVisitor().visitJumpInsn(Opcodes.IFNONNULL, done); getMethodVisitor().visitJumpInsn(Opcodes.GOTO, isNull); getMethodVisitor().visitLabel(done); return true; } return false; } public void notNullTest(TypeWidget typeWidget, Label isNotNull) { if (typeWidget.isNullable()) { dup(typeWidget); getMethodVisitor().visitJumpInsn(Opcodes.IFNONNULL, isNotNull); pop(typeWidget); } else { getMethodVisitor().visitJumpInsn(Opcodes.GOTO, isNotNull); } } public void emitInstanceOf(TypeWidget targetType, Class<?> clazz, Label isNotInstance) { Preconditions.checkArgument(!clazz.isPrimitive()); Preconditions.checkArgument(!targetType.isPrimitive()); if (targetType.getJVMType().getDescriptor().equals(Type.getDescriptor(clazz))) { // trivial -- we already know this to be true! // we don't need to emit any code here } else { // naively emit the instanceof -- be nice to statically determine if it's even possible for targetType to be an instance of clazz // --- and also to know if we don't need a cast to do so dup(targetType); getMethodVisitor().visitTypeInsn(Opcodes.INSTANCEOF, Type.getInternalName(clazz)); getMethodVisitor().visitJumpInsn(Opcodes.IFEQ, isNotInstance); getMethodVisitor().visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(clazz)); } } // like emitInstanceOf but only leave the value of it IS public void emitInstanceCheck(TypeWidget targetType, Class<?> clazz, Label isNotInstance) { Label is = new Label(); Label not = new Label(); nullTest(targetType, isNotInstance); emitInstanceOf(targetType, clazz, not); getMethodVisitor().visitJumpInsn(Opcodes.GOTO, is); getMethodVisitor().visitLabel(not); pop(targetType); getMethodVisitor().visitJumpInsn(Opcodes.GOTO, isNotInstance); getMethodVisitor().visitLabel(is); } public void emitIntegerSwitch(Map<Integer, Label> labels, Label defaultCase) { // TODO: we should see if the labels are dense, and if so use TABLESWITCH MethodVisitor mv = getMethodVisitor(); List<Integer> codes = Lists.newArrayList(labels.keySet()); Collections.sort(codes); int[] k = new int[codes.size()]; Label[] kl = new Label[codes.size()]; for (int i = 0; i < k.length; ++i) { k[i] = codes.get(i); kl[i] = labels.get(i); } mv.visitLookupSwitchInsn(defaultCase, k, kl); mv.visitJumpInsn(GOTO, defaultCase); } public void emitStringSwitch(Map<String, Label> labels, Label defaultCase, boolean caseInsensitive) { MethodVisitor mv = getMethodVisitor(); if (caseInsensitive) { mv.visitFieldInsn(GETSTATIC, "java/util/Locale", "ENGLISH", "Ljava/util/Locale;"); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "toUpperCase", "(Ljava/util/Locale;)Ljava/lang/String;", false); } mv.visitInsn(DUP); AssignableValue tmp = allocate(String.class); tmp.write(BaseTypeAdapter.STRING).generate(this); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "hashCode", "()I", false); Multimap<Integer, SwitchLabel> keys = ArrayListMultimap.create(); for (Map.Entry<String, Label> e : labels.entrySet()) { String name = e.getKey(); if (caseInsensitive) { name = name.toUpperCase(Locale.ENGLISH); } keys.put(name.hashCode(), new SwitchLabel(name, e.getValue())); } List<Integer> codes = Lists.newArrayList(keys.keySet()); Collections.sort(codes); int[] k = new int[codes.size()]; Label[] kl = new Label[codes.size()]; for (int i = 0; i < k.length; ++i) { k[i] = codes.get(i); kl[i] = new Label(); } mv.visitLookupSwitchInsn(defaultCase, k, kl); for (int i = 0; i < k.length; ++i) { mv.visitLabel(kl[i]); for (SwitchLabel switchLabel : keys.get(k[i])) { mv.visitLdcInsn(switchLabel.name); tmp.read().generate(this); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "equals", "(Ljava/lang/Object;)Z", false); mv.visitJumpInsn(IFNE, switchLabel.label); } mv.visitJumpInsn(GOTO, defaultCase); } } public void castIfNecessary(TypeWidget targetType, TypeWidget sourceType) { if (targetType != null && sourceType != null && sourceType.isAssignableFrom(targetType) && !sourceType.getJVMType().equals(targetType.getJVMType())) { cast(targetType, sourceType); } } public void cast(TypeWidget targetType, TypeWidget sourceType) { if (targetType.equals(sourceType)) { return; } if (targetType.getJVMType().getDescriptor().equals(sourceType.getJVMType().getDescriptor())) { return; } if (targetType.getJVMType().getDescriptor() .equals(AnyTypeWidget.getInstance().getJVMType().getDescriptor())) { box(sourceType); return; } BytecodeSequence code = Conversions.getConversion(sourceType.getJVMType(), targetType.getJVMType()); if (code != null) { code.generate(this); } else if (targetType.isPrimitive() && !sourceType.isPrimitive()) { Preconditions.checkState(!targetType.boxed().isPrimitive()); cast(targetType.boxed(), sourceType); cast(targetType, targetType.boxed()); } else if (targetType.isPrimitive() || sourceType.isPrimitive()) { throw new ProgramCompileException("Cannot cast " + targetType + " from " + sourceType); } else { // should check for castability getMethodVisitor().visitTypeInsn(Opcodes.CHECKCAST, targetType.getJVMType().getInternalName()); } } public boolean cast(TypeWidget targetType, TypeWidget sourceType, Label isNull) { if (targetType.equals(sourceType)) { return nullTest(sourceType, isNull); } if (targetType.getJVMType().getDescriptor().equals(sourceType.getJVMType().getDescriptor())) { return nullTest(sourceType, isNull); } if (targetType.getJVMType().getDescriptor() .equals(AnyTypeWidget.getInstance().getJVMType().getDescriptor())) { if (sourceType.isPrimitive()) { box(sourceType); return false; } else { return nullTest(sourceType, isNull); } } BytecodeSequence code = Conversions.getConversion(sourceType.getJVMType(), targetType.getJVMType()); if (code != null) { boolean r = false; if (sourceType.isNullable()) { r = nullTest(sourceType, isNull); } code.generate(this); return r; } else if (targetType.isPrimitive() && !sourceType.isPrimitive()) { boolean r = cast(targetType.boxed(), sourceType, isNull); unbox(NotNullableTypeWidget.create(targetType.boxed())); return r; } else if (targetType.isPrimitive() || sourceType.isPrimitive()) { throw new ProgramCompileException("Cannot cast " + targetType + " from " + sourceType); } else { boolean r = nullTest(sourceType, isNull); // should check for castability getMethodVisitor().visitTypeInsn(Opcodes.CHECKCAST, targetType.getJVMType().getInternalName()); return r; } } public void pop(TypeWidget typeWidget) { switch (typeWidget.getJVMType().getSize()) { case 0: return; case 1: getMethodVisitor().visitInsn(Opcodes.POP); return; case 2: getMethodVisitor().visitInsn(Opcodes.POP2); return; default: throw new UnsupportedOperationException("Unexpected JVM type width: " + typeWidget.getJVMType()); } } public void dup(TypeWidget typeWidget) { switch (typeWidget.getJVMType().getSize()) { case 0: throw new UnsupportedOperationException(); case 1: getMethodVisitor().visitInsn(Opcodes.DUP); return; case 2: getMethodVisitor().visitInsn(Opcodes.DUP2); return; default: throw new UnsupportedOperationException("Unexpected JVM type width: " + typeWidget.getJVMType()); } } public void swap(TypeWidget top, TypeWidget previous) { MethodVisitor mv = getMethodVisitor(); switch (top.getJVMType().getSize()) { case 1: switch (previous.getJVMType().getSize()) { case 1: // 1, 1 mv.visitInsn(Opcodes.SWAP); return; case 2: // 2, 1 // {value3, value2}, value1 value1, {value3, value2}, value1 mv.visitInsn(Opcodes.DUP_X2); // value1, {value3, value2} mv.visitInsn(Opcodes.POP); return; } break; case 2: switch (previous.getJVMType().getSize()) { case 1: // 1, 2 // value3, {value2, value1} {value2, value1}, value3, {value2, value1} mv.visitInsn(Opcodes.DUP2_X1); // {value2, value1}, value3 mv.visitInsn(Opcodes.POP2); return; case 2: // {value4, value3}, {value2, value1} {value2, value1}, {value4, value3}, {value2, value1} mv.visitInsn(Opcodes.DUP2_X2); // {value2, value1}, {value4, value3} mv.visitInsn(Opcodes.POP2); return; } break; } throw new UnsupportedOperationException(); } public void emitThrow(Class<? extends Throwable> exceptionType, BytecodeExpression... constructorArguments) { emitNew(Type.getInternalName(exceptionType), constructorArguments); methodVisitor.visitInsn(Opcodes.ATHROW); } public void emitNew(String typeInternalName, BytecodeExpression... constructorArguments) { methodVisitor.visitTypeInsn(Opcodes.NEW, typeInternalName); methodVisitor.visitInsn(Opcodes.DUP); String desc = Type.getMethodDescriptor(Type.VOID_TYPE); if (constructorArguments != null && constructorArguments.length > 0) { Type[] argumentTypes = new Type[constructorArguments.length]; for (int i = 0; i < constructorArguments.length; ++i) { argumentTypes[i] = constructorArguments[i].getType().getJVMType(); } desc = Type.getMethodDescriptor(Type.VOID_TYPE, argumentTypes); } methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, typeInternalName, "<init>", desc, false); } public static class Unification { public final TypeWidget type; public final boolean nullPossible; public Unification(TypeWidget type, boolean nullPossible) { this.type = type; this.nullPossible = nullPossible; } } public static class BinaryCoercion { public final boolean leftNullable; public final boolean rightNullable; public BinaryCoercion(boolean leftNullable, boolean rightNullable) { this.leftNullable = leftNullable; this.rightNullable = rightNullable; } } private static class SwitchLabel { String name; Label label; private SwitchLabel(String name, Label label) { this.name = name; this.label = label; } } }