| // Copyright (c) 2021, 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.desugar.records; |
| |
| import com.android.tools.r8.graph.AppView; |
| import com.android.tools.r8.graph.DexClass; |
| import com.android.tools.r8.graph.DexField; |
| import com.android.tools.r8.graph.DexMethod; |
| import com.android.tools.r8.graph.FieldResolutionResult; |
| import com.android.tools.r8.graph.ProgramMethod; |
| import com.android.tools.r8.ir.code.ArrayPut; |
| import com.android.tools.r8.ir.code.BasicBlock; |
| import com.android.tools.r8.ir.code.ConstNumber; |
| import com.android.tools.r8.ir.code.IRCode; |
| import com.android.tools.r8.ir.code.Instruction; |
| import com.android.tools.r8.ir.code.InstructionListIterator; |
| import com.android.tools.r8.ir.code.MemberType; |
| import com.android.tools.r8.ir.code.NewArrayEmpty; |
| import com.android.tools.r8.ir.code.Position; |
| import com.android.tools.r8.ir.code.RecordFieldValues; |
| import com.android.tools.r8.ir.code.Value; |
| import com.android.tools.r8.ir.conversion.IRConverter; |
| import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore; |
| import com.android.tools.r8.shaking.AppInfoWithLiveness; |
| import com.android.tools.r8.utils.Timing; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.ListIterator; |
| |
| /** Used to shrink record field arrays in dex compilations */ |
| public class RecordFieldValuesRewriter { |
| |
| private final AppView<AppInfoWithLiveness> appView; |
| private final IRConverter irConverter; |
| |
| public static RecordFieldValuesRewriter create(AppView<AppInfoWithLiveness> appView) { |
| if (appView.enableWholeProgramOptimizations() |
| && appView.options().isGeneratingDex() |
| && appView.options().testing.enableRecordModeling) { |
| return new RecordFieldValuesRewriter(appView); |
| } |
| return null; |
| } |
| |
| private RecordFieldValuesRewriter(AppView<AppInfoWithLiveness> appView) { |
| this.appView = appView; |
| irConverter = new IRConverter(appView, Timing.empty()); |
| } |
| |
| // Called after final tree shaking, prune and minify field names and field values. |
| // At least one instruction is a newRecordFieldArray. |
| public void rewriteRecordFieldValues() { |
| for (DexMethod recordFieldValuesReference : appView.appInfo().recordFieldValuesReferences) { |
| DexClass dexClass = |
| appView.contextIndependentDefinitionFor(recordFieldValuesReference.getHolderType()); |
| assert dexClass.isProgramClass(); |
| ProgramMethod programMethod = |
| dexClass.asProgramClass().lookupProgramMethod(recordFieldValuesReference); |
| assert programMethod != null; |
| rewriteRecordFieldValues(programMethod); |
| } |
| } |
| |
| public void rewriteRecordFieldValues(ProgramMethod programMethod) { |
| IRCode irCode = |
| programMethod |
| .getDefinition() |
| .getCode() |
| .buildIR(programMethod, appView, programMethod.getOrigin()); |
| boolean done = false; |
| ListIterator<BasicBlock> blockIterator = irCode.listIterator(); |
| while (blockIterator.hasNext()) { |
| BasicBlock block = blockIterator.next(); |
| InstructionListIterator iterator = block.listIterator(irCode); |
| while (iterator.hasNext()) { |
| Instruction instruction = iterator.next(); |
| if (instruction.isRecordFieldValues()) { |
| rewriteRecordFieldArray( |
| instruction.asRecordFieldValues(), irCode, blockIterator, iterator); |
| done = true; |
| } |
| } |
| } |
| assert done; |
| irConverter.removeDeadCodeAndFinalizeIR( |
| irCode, OptimizationFeedbackIgnore.getInstance(), Timing.empty()); |
| } |
| |
| public void rewriteRecordFieldArray( |
| RecordFieldValues recordFieldArray, |
| IRCode code, |
| ListIterator<BasicBlock> blockIterator, |
| InstructionListIterator iterator) { |
| List<Value> newInValues = computePresentFields(recordFieldArray, code.context()); |
| ConstNumber arrayLengthIntConstant = code.createIntConstant(newInValues.size()); |
| Position constantPosition = |
| appView.options().debug ? Position.none() : recordFieldArray.getPosition(); |
| arrayLengthIntConstant.setPosition(constantPosition); |
| iterator.previous(); |
| iterator.add(arrayLengthIntConstant); |
| iterator.next(); |
| NewArrayEmpty newArrayEmpty = |
| new NewArrayEmpty( |
| recordFieldArray.outValue(), |
| arrayLengthIntConstant.outValue(), |
| appView.dexItemFactory().objectArrayType); |
| newArrayEmpty.setPosition(recordFieldArray.getPosition()); |
| iterator.replaceCurrentInstruction(newArrayEmpty); |
| for (int i = 0; i < newInValues.size(); i++) { |
| ConstNumber intConstantI = code.createIntConstant(i); |
| intConstantI.setPosition(constantPosition); |
| iterator.add(intConstantI); |
| ArrayPut arrayPut = |
| new ArrayPut( |
| MemberType.OBJECT, |
| newArrayEmpty.outValue(), |
| intConstantI.outValue(), |
| newInValues.get(i)); |
| iterator.add(arrayPut); |
| arrayPut.setPosition(recordFieldArray.getPosition()); |
| } |
| if (newArrayEmpty.getBlock().hasCatchHandlers()) { |
| splitIfCatchHandlers(code, newArrayEmpty.getBlock(), blockIterator); |
| } |
| } |
| |
| private void splitIfCatchHandlers( |
| IRCode code, |
| BasicBlock blockWithIncorrectThrowingInstructions, |
| ListIterator<BasicBlock> blockIterator) { |
| InstructionListIterator instructionsIterator = |
| blockWithIncorrectThrowingInstructions.listIterator(code); |
| BasicBlock currentBlock = blockWithIncorrectThrowingInstructions; |
| while (currentBlock != null && instructionsIterator.hasNext()) { |
| Instruction throwingInstruction = |
| instructionsIterator.nextUntil(Instruction::instructionTypeCanThrow); |
| BasicBlock nextBlock; |
| if (throwingInstruction != null) { |
| nextBlock = instructionsIterator.split(code, blockIterator); |
| // Back up to before the split before inserting catch handlers. |
| blockIterator.previous(); |
| nextBlock.copyCatchHandlers(code, blockIterator, currentBlock, appView.options()); |
| BasicBlock b = blockIterator.next(); |
| assert b == nextBlock; |
| // Switch iteration to the split block. |
| instructionsIterator = nextBlock.listIterator(code); |
| currentBlock = nextBlock; |
| } else { |
| assert !instructionsIterator.hasNext(); |
| instructionsIterator = null; |
| currentBlock = null; |
| } |
| } |
| } |
| |
| private List<Value> computePresentFields( |
| RecordFieldValues recordFieldValues, ProgramMethod context) { |
| List<Value> inValues = recordFieldValues.inValues(); |
| DexField[] fields = recordFieldValues.getFields(); |
| assert inValues.size() == fields.length; |
| List<Value> newInValues = new ArrayList<>(); |
| for (int index = 0; index < fields.length; index++) { |
| FieldResolutionResult resolution = |
| appView |
| .appInfo() |
| .resolveField(appView.graphLens().getRenamedFieldSignature(fields[index]), context); |
| if (resolution.isSingleFieldResolutionResult()) { |
| newInValues.add(inValues.get(index)); |
| } |
| } |
| return newInValues; |
| } |
| } |