| // Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| package com.android.tools.r8.ir.synthetic; |
| |
| import com.android.tools.r8.cf.code.CfConstNull; |
| import com.android.tools.r8.cf.code.CfConstNumber; |
| import com.android.tools.r8.cf.code.CfConstString; |
| import com.android.tools.r8.cf.code.CfFrame; |
| import com.android.tools.r8.cf.code.CfIf; |
| import com.android.tools.r8.cf.code.CfInstruction; |
| import com.android.tools.r8.cf.code.CfInvoke; |
| import com.android.tools.r8.cf.code.CfLabel; |
| import com.android.tools.r8.cf.code.CfLoad; |
| import com.android.tools.r8.cf.code.CfNew; |
| import com.android.tools.r8.cf.code.CfReturn; |
| import com.android.tools.r8.cf.code.CfReturnVoid; |
| import com.android.tools.r8.cf.code.CfStackInstruction; |
| import com.android.tools.r8.cf.code.CfStackInstruction.Opcode; |
| import com.android.tools.r8.cf.code.CfSwitch; |
| import com.android.tools.r8.cf.code.CfSwitch.Kind; |
| import com.android.tools.r8.cf.code.CfThrow; |
| import com.android.tools.r8.cf.code.frame.FrameType; |
| import com.android.tools.r8.errors.Unreachable; |
| import com.android.tools.r8.graph.AppView; |
| import com.android.tools.r8.graph.CfCode; |
| import com.android.tools.r8.graph.CfCodeWithLens; |
| import com.android.tools.r8.graph.DexField; |
| import com.android.tools.r8.graph.DexItemFactory; |
| import com.android.tools.r8.graph.DexMethod; |
| import com.android.tools.r8.graph.DexType; |
| import com.android.tools.r8.ir.analysis.value.AbstractValue; |
| import com.android.tools.r8.ir.code.IfType; |
| import com.android.tools.r8.ir.code.ValueType; |
| import com.android.tools.r8.ir.optimize.enums.EnumDataMap.EnumData; |
| import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldMappingData; |
| import com.android.tools.r8.utils.ArrayUtils; |
| import com.android.tools.r8.utils.BooleanUtils; |
| import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.function.BiConsumer; |
| import org.objectweb.asm.Opcodes; |
| |
| public abstract class EnumUnboxingCfCodeProvider extends SyntheticCfCodeProvider { |
| |
| EnumUnboxingCfCodeProvider(AppView<?> appView, DexType holder) { |
| super(appView, holder); |
| } |
| |
| @SuppressWarnings("ReferenceEquality") |
| void addCfInstructionsForAbstractValue( |
| List<CfInstruction> instructions, AbstractValue value, DexType returnType) { |
| // TODO(b/155368026): Support fields and const class fields. |
| // Move this to something similar than SingleValue#createMaterializingInstruction |
| if (value.isNull()) { |
| assert returnType.isReferenceType(); |
| instructions.add(new CfConstNull()); |
| } else if (value.isSingleStringValue()) { |
| assert returnType == appView.dexItemFactory().stringType; |
| instructions.add(new CfConstString(value.asSingleStringValue().getDexString())); |
| } else if (value.isSingleNumberValue()) { |
| assert returnType.isPrimitiveType(); |
| instructions.add( |
| new CfConstNumber( |
| value.asSingleNumberValue().getValue(), ValueType.fromDexType(returnType))); |
| } else { |
| throw new Unreachable("Unsupported value: " + value); |
| } |
| } |
| |
| <T> void addCfSwitch( |
| List<CfInstruction> instructions, |
| BiConsumer<List<CfInstruction>, T> generateCase, |
| Int2ReferenceSortedMap<T> cases, |
| T defaultCase, |
| CfFrame.Builder frameBuilder, |
| boolean defaultThrows) { |
| // The switch is *always* going to be converted to IR then either to dex or back to cf. The IR |
| // representation does not differentiate table and look-up switches, and generates the most |
| // appropriate one in the back-end. |
| // The keys should however be sorted in natural order for packing to table switch to be |
| // generated, which should be implicitly the case with the Int2ObjectSortedMap. |
| assert defaultCase == null || !defaultThrows; |
| boolean hasDefaultCase = defaultCase != null || defaultThrows; |
| assert cases.size() + BooleanUtils.intValue(hasDefaultCase) >= 2; |
| int[] keys = new int[cases.size() - BooleanUtils.intValue(!hasDefaultCase)]; |
| List<CfLabel> targets = new ArrayList<>(keys.length); |
| int index = 0; |
| for (int key : cases.keySet()) { |
| if (index < keys.length) { |
| keys[index++] = key; |
| targets.add(new CfLabel()); |
| } |
| } |
| CfLabel defaultLabel = new CfLabel(); |
| T actualDefaultCase = hasDefaultCase ? defaultCase : cases.get(cases.lastIntKey()); |
| assert ArrayUtils.isSorted(keys); |
| assert keys.length == targets.size(); |
| // We expect the value to switch on to be in local slot 0. |
| instructions.add(new CfLoad(ValueType.fromDexType(appView.dexItemFactory().intType), 0)); |
| instructions.add(new CfSwitch(Kind.LOOKUP, defaultLabel, keys, targets)); |
| for (int i = 0; i < keys.length; i++) { |
| instructions.add(targets.get(i)); |
| instructions.add(frameBuilder.build()); |
| generateCase.accept(instructions, cases.get(keys[i])); |
| assert instructions.get(instructions.size() - 1).isReturn(); |
| } |
| instructions.add(defaultLabel); |
| instructions.add(frameBuilder.build()); |
| if (defaultThrows) { |
| // default: throw null; |
| instructions.add(new CfConstNull()); |
| instructions.add(new CfThrow()); |
| } else { |
| generateCase.accept(instructions, actualDefaultCase); |
| assert instructions.get(instructions.size() - 1).isReturn(); |
| } |
| } |
| |
| public static class EnumUnboxingMethodDispatchCfCodeProvider extends EnumUnboxingCfCodeProvider { |
| |
| private final DexMethod superEnumMethod; |
| private final Int2ReferenceSortedMap<DexMethod> methodMap; |
| |
| public EnumUnboxingMethodDispatchCfCodeProvider( |
| AppView<?> appView, |
| DexType holder, |
| DexMethod superEnumMethod, |
| Int2ReferenceSortedMap<DexMethod> methodMap) { |
| super(appView, holder); |
| this.superEnumMethod = superEnumMethod; |
| this.methodMap = methodMap; |
| } |
| |
| @Override |
| public CfCodeWithLens generateCfCode() { |
| assert !methodMap.isEmpty(); |
| List<CfInstruction> instructions = new ArrayList<>(); |
| DexMethod representative = methodMap.values().iterator().next(); |
| CfFrame.Builder frameBuilder = CfFrame.builder(); |
| int paramRegisterSize = 0; |
| for (DexType parameter : representative.getParameters()) { |
| frameBuilder.appendLocal(FrameType.initialized(parameter)); |
| paramRegisterSize += parameter.getRequiredRegisters(); |
| } |
| addCfSwitch( |
| instructions, this::addReturnInvoke, methodMap, superEnumMethod, frameBuilder, false); |
| // We need to get an estimate of the stack and local with is greater than the actual number, |
| // IR processing will compute the exact number. There are at most 255 parameters, so this is |
| // always within unsigned 16 bits bounds. |
| assert paramRegisterSize < 256; |
| int maxStack = 2 * paramRegisterSize + 16; |
| int maxLocals = paramRegisterSize + 16; |
| return new CfCodeWithLens(null, getHolder(), maxStack, maxLocals, instructions); |
| } |
| |
| private void addReturnInvoke(List<CfInstruction> instructions, DexMethod method) { |
| int localIndex = 0; |
| for (DexType parameterType : method.getParameters()) { |
| instructions.add(new CfLoad(ValueType.fromDexType(parameterType), localIndex)); |
| localIndex += parameterType.getRequiredRegisters(); |
| } |
| instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, method, false)); |
| instructions.add( |
| method.getReturnType().isVoidType() |
| ? new CfReturnVoid() |
| : new CfReturn(ValueType.fromDexType(method.getReturnType()))); |
| } |
| } |
| |
| public static class EnumUnboxingInstanceFieldCfCodeProvider extends EnumUnboxingCfCodeProvider { |
| |
| private final DexType returnType; |
| private final EnumInstanceFieldMappingData fieldDataMap; |
| private final AbstractValue nullValue; |
| |
| public EnumUnboxingInstanceFieldCfCodeProvider( |
| AppView<?> appView, DexType holder, EnumData data, DexField field) { |
| this(appView, holder, data, field, null); |
| } |
| |
| public EnumUnboxingInstanceFieldCfCodeProvider( |
| AppView<?> appView, |
| DexType holder, |
| EnumData data, |
| DexField field, |
| AbstractValue nullValue) { |
| super(appView, holder); |
| this.returnType = field.getType(); |
| this.fieldDataMap = data.getInstanceFieldData(field).asEnumFieldMappingData(); |
| this.nullValue = nullValue; |
| } |
| |
| @Override |
| public CfCode generateCfCode() { |
| // Generated static method, for class com.x.MyEnum {A(10),B(20);} would look like: |
| // String UtilityClass#com.x.MyEnum_toString(int i) { |
| // switch (i) { |
| // case 1: return 10; |
| // case 2: return 20; |
| // default: throw null; // or throw default value. |
| // } |
| // } |
| DexItemFactory factory = appView.dexItemFactory(); |
| List<CfInstruction> instructions = new ArrayList<>(); |
| CfFrame.Builder frameBuilder = |
| CfFrame.builder().appendLocal(FrameType.initialized(factory.intType)); |
| addCfSwitch( |
| instructions, |
| this::addReturnValue, |
| fieldDataMap.getMapping(), |
| nullValue, |
| frameBuilder, |
| nullValue == null); |
| return standardCfCodeFromInstructions(instructions); |
| } |
| |
| private void addReturnValue(List<CfInstruction> instructions, AbstractValue value) { |
| addCfInstructionsForAbstractValue(instructions, value, returnType); |
| instructions.add(new CfReturn(ValueType.fromDexType(returnType))); |
| } |
| } |
| |
| public static class EnumUnboxingValueOfCfCodeProvider extends EnumUnboxingCfCodeProvider { |
| |
| private final DexType enumType; |
| private final EnumInstanceFieldMappingData fieldDataMap; |
| |
| public EnumUnboxingValueOfCfCodeProvider( |
| AppView<?> appView, |
| DexType holder, |
| DexType enumType, |
| EnumInstanceFieldMappingData fieldDataMap) { |
| super(appView, holder); |
| this.enumType = enumType; |
| this.fieldDataMap = fieldDataMap; |
| } |
| |
| @Override |
| public CfCode generateCfCode() { |
| // Generated static method, for class com.x.MyEnum {A,B} would look like: |
| // int UtilityClass#com.x.MyEnum_valueOf(String s) { |
| // if (s == null) { throw npe("Name is null"); } |
| // if (s.equals("A")) { return 1;} |
| // if (s.equals("B")) { return 2;} |
| // throw new IllegalArgumentException( |
| // "No enum constant com.x.MyEnum." + s); |
| DexItemFactory factory = appView.dexItemFactory(); |
| List<CfInstruction> instructions = new ArrayList<>(); |
| |
| CfFrame frame = |
| CfFrame.builder().appendLocal(FrameType.initialized(factory.stringType)).build(); |
| |
| // if (s == null) { throw npe("Name is null"); } |
| CfLabel nullDest = new CfLabel(); |
| instructions.add(new CfLoad(ValueType.fromDexType(factory.stringType), 0)); |
| instructions.add(new CfIf(IfType.NE, ValueType.OBJECT, nullDest)); |
| instructions.add(new CfNew(factory.npeType)); |
| instructions.add(new CfStackInstruction(Opcode.Dup)); |
| instructions.add(new CfConstString(appView.dexItemFactory().createString("Name is null"))); |
| instructions.add( |
| new CfInvoke(Opcodes.INVOKESPECIAL, factory.npeMethods.initWithMessage, false)); |
| instructions.add(new CfThrow()); |
| instructions.add(nullDest); |
| instructions.add(frame); |
| |
| // if (s.equals("A")) { return 1;} |
| // if (s.equals("B")) { return 2;} |
| fieldDataMap.forEach( |
| (unboxedEnumValue, value) -> { |
| CfLabel dest = new CfLabel(); |
| instructions.add(new CfLoad(ValueType.fromDexType(factory.stringType), 0)); |
| addCfInstructionsForAbstractValue(instructions, value, factory.stringType); |
| instructions.add( |
| new CfInvoke(Opcodes.INVOKEVIRTUAL, factory.stringMembers.equals, false)); |
| instructions.add(new CfIf(IfType.EQ, ValueType.INT, dest)); |
| instructions.add(new CfConstNumber(unboxedEnumValue, ValueType.INT)); |
| instructions.add(new CfReturn(ValueType.INT)); |
| instructions.add(dest); |
| instructions.add(frame.clone()); |
| }); |
| |
| // throw new IllegalArgumentException("No enum constant com.x.MyEnum." + s); |
| instructions.add(new CfNew(factory.illegalArgumentExceptionType)); |
| instructions.add(new CfStackInstruction(Opcode.Dup)); |
| instructions.add( |
| new CfConstString( |
| appView |
| .dexItemFactory() |
| .createString( |
| "No enum constant " + enumType.toSourceString().replace('$', '.') + "."))); |
| instructions.add(new CfLoad(ValueType.fromDexType(factory.stringType), 0)); |
| instructions.add(new CfInvoke(Opcodes.INVOKEVIRTUAL, factory.stringMembers.concat, false)); |
| instructions.add( |
| new CfInvoke( |
| Opcodes.INVOKESPECIAL, |
| factory.illegalArgumentExceptionMethods.initWithMessage, |
| false)); |
| instructions.add(new CfThrow()); |
| return standardCfCodeFromInstructions(instructions); |
| } |
| } |
| } |