blob: a2f7dc97884a61442b7231f493c0e1eeb2a0bc3e [file] [log] [blame]
/*
* Copyright (C) 2012 The Android Open Source Project
*
* 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.android.jack.transformations.enums;
import com.android.jack.Jack;
import com.android.jack.Options;
import com.android.jack.ir.ast.JArrayLength;
import com.android.jack.ir.ast.JArrayRef;
import com.android.jack.ir.ast.JArrayType;
import com.android.jack.ir.ast.JAsgOperation;
import com.android.jack.ir.ast.JBinaryOperation;
import com.android.jack.ir.ast.JBinaryOperator;
import com.android.jack.ir.ast.JBlock;
import com.android.jack.ir.ast.JCaseStatement;
import com.android.jack.ir.ast.JCatchBlock;
import com.android.jack.ir.ast.JClass;
import com.android.jack.ir.ast.JDefinedClass;
import com.android.jack.ir.ast.JDefinedClassOrInterface;
import com.android.jack.ir.ast.JDefinedEnum;
import com.android.jack.ir.ast.JDefinedInterface;
import com.android.jack.ir.ast.JEnumField;
import com.android.jack.ir.ast.JEnumLiteral;
import com.android.jack.ir.ast.JExpression;
import com.android.jack.ir.ast.JExpressionStatement;
import com.android.jack.ir.ast.JField;
import com.android.jack.ir.ast.JFieldId;
import com.android.jack.ir.ast.JFieldRef;
import com.android.jack.ir.ast.JIfStatement;
import com.android.jack.ir.ast.JIntLiteral;
import com.android.jack.ir.ast.JLiteral;
import com.android.jack.ir.ast.JLocal;
import com.android.jack.ir.ast.JLocalRef;
import com.android.jack.ir.ast.JMethod;
import com.android.jack.ir.ast.JMethodBody;
import com.android.jack.ir.ast.JMethodCall;
import com.android.jack.ir.ast.JMethodId;
import com.android.jack.ir.ast.JModifier;
import com.android.jack.ir.ast.JNeqOperation;
import com.android.jack.ir.ast.JNewArray;
import com.android.jack.ir.ast.JNullLiteral;
import com.android.jack.ir.ast.JPrimitiveType.JPrimitiveTypeEnum;
import com.android.jack.ir.ast.JReturnStatement;
import com.android.jack.ir.ast.JStatement;
import com.android.jack.ir.ast.JSwitchStatement;
import com.android.jack.ir.ast.JTryStatement;
import com.android.jack.ir.ast.JType;
import com.android.jack.ir.ast.JVisitor;
import com.android.jack.ir.ast.MethodKind;
import com.android.jack.ir.formatter.BinaryQualifiedNameFormatter;
import com.android.jack.ir.sourceinfo.SourceInfo;
import com.android.jack.library.DumpInLibrary;
import com.android.jack.lookup.JLookup;
import com.android.jack.lookup.JMethodLookupException;
import com.android.jack.shrob.obfuscation.OriginalNames;
import com.android.jack.transformations.LocalVarCreator;
import com.android.jack.transformations.ast.inner.InnerAccessorGeneratorSchedulingSeparator;
import com.android.jack.transformations.exceptions.TryStatementSchedulingSeparator;
import com.android.jack.transformations.request.AppendField;
import com.android.jack.transformations.request.AppendMethod;
import com.android.jack.transformations.request.Replace;
import com.android.jack.transformations.request.TransformationRequest;
import com.android.jack.transformations.threeaddresscode.ThreeAddressCodeForm;
import com.android.jack.util.NamingTools;
import com.android.jack.util.filter.Filter;
import com.android.sched.item.Description;
import com.android.sched.item.Name;
import com.android.sched.item.Synchronized;
import com.android.sched.marker.Marker;
import com.android.sched.marker.ValidOn;
import com.android.sched.schedulable.Constraint;
import com.android.sched.schedulable.RunnableSchedulable;
import com.android.sched.schedulable.Transform;
import com.android.sched.schedulable.Use;
import com.android.sched.util.config.HasKeyId;
import com.android.sched.util.config.ThreadConfig;
import com.android.sched.util.config.category.Private;
import com.android.sched.util.config.id.BooleanPropertyId;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
/**
* Add support for partial recompilation for enum used into switches.
*/
@Description("Add support for partial recompilation for enum used into switches.")
@Name("SwitchEnumSupport")
@Synchronized
@Constraint(need = {JSwitchStatement.class, JEnumField.class, JEnumLiteral.class,
SwitchEnumSupport.UsedEnumField.class, OriginalNames.class})
@Transform(modify = JSwitchStatement.class, add = {EnumMappingMarker.class, JNewArray.class,
JAsgOperation.NonReusedAsg.class, JMethodCall.class, JArrayRef.class, JArrayLength.class,
JLocalRef.class, JField.class, JMethod.class, JMethodBody.class, JFieldRef.class,
JNullLiteral.class, JLocal.class, JIfStatement.class, JReturnStatement.class, JBlock.class,
JTryStatement.class, JIntLiteral.class, JExpressionStatement.class, JNeqOperation.class,
TryStatementSchedulingSeparator.SeparatorTag.class,
EnumMappingSchedulingSeparator.SeparatorTag.class,
InnerAccessorGeneratorSchedulingSeparator.SeparatorSwitchEnumSupportTag.class},
remove = {JSwitchStatement.SwitchWithEnum.class, ThreeAddressCodeForm.class})
@Use(value = {LocalVarCreator.class})
@HasKeyId
public class SwitchEnumSupport implements RunnableSchedulable<JMethod> {
@Nonnull
public static final BooleanPropertyId SORT_ENUM_FIELD = BooleanPropertyId.create(
"jack.internal.switch-enumfield.sort",
"Generate determinist code to initialize constant array indexed by ordinal "
+ "value of enum field")
.addDefaultValue(Boolean.TRUE).addCategory(Private.class).addCategory(DumpInLibrary.class);
private final JType noSuchFieldErrorType =
Jack.getSession().getPhantomLookup().getType("Ljava/lang/NoSuchFieldError;");
private final boolean sortEnumField = ThreadConfig.get(SORT_ENUM_FIELD).booleanValue();
@Nonnull
private final JLookup lookup = Jack.getSession().getPhantomLookup();
/**
* Enum fields used into switch.
*/
@Description("Enum fields used into switch")
@ValidOn({JDefinedClass.class, JDefinedInterface.class})
public static class UsedEnumField implements Marker {
@Nonnull
private final Set<JFieldId> enumFields;
public UsedEnumField(@Nonnull Set<JFieldId> enumFields) {
this.enumFields = enumFields;
}
@Nonnull
public Set<JFieldId> getEnumFields() {
return enumFields;
}
@Override
public Marker cloneIfNeeded() {
return this;
}
}
@Nonnull
private final Filter<JMethod> filter = ThreadConfig.get(Options.METHOD_FILTER);
private class Visitor extends JVisitor {
private static final String ORDINAL = "ordinal";
@Nonnull
private final TransformationRequest tr;
@Nonnull
private final JDefinedClassOrInterface currentClOrI;
@CheckForNull
private Set<JFieldId> usedEnumFields;
public Visitor(@Nonnull TransformationRequest tr, @Nonnull JDefinedClassOrInterface clOrI) {
this.tr = tr;
this.currentClOrI = clOrI;
}
@Override
public boolean visit(@Nonnull JMethod method) {
UsedEnumField uef = method.getEnclosingType().getMarker(UsedEnumField.class);
assert uef != null;
usedEnumFields = uef.getEnumFields();
return super.visit(method);
}
@Override
public boolean visit(@Nonnull JSwitchStatement switchStmt) {
JExpression expr = switchStmt.getExpr();
JType exprType = expr.getType();
if (exprType instanceof JDefinedEnum) {
JDefinedEnum enumType = (JDefinedEnum) exprType;
JMethod getEnumSwitchValues = getSwitchValuesMethod(enumType);
// Replace enum access by $SwitchesValues[x.ordinal()]
JMethodId methodId = getEnumSwitchValues.getMethodId();
JExpression callSwitchValues = new JMethodCall(
switchStmt.getSourceInfo(), null, getEnumSwitchValues.getEnclosingType(),
methodId, getEnumSwitchValues.getType(),
methodId.canBeVirtual());
JMethodId ordinalMethodId = enumType.getOrCreateMethodId(
ORDINAL, Collections.<JType>emptyList(), MethodKind.INSTANCE_VIRTUAL);
tr.append(new Replace(expr, new JArrayRef(SourceInfo.UNKNOWN, callSwitchValues,
new JMethodCall(SourceInfo.UNKNOWN, expr, enumType, ordinalMethodId,
JPrimitiveTypeEnum.INT.getType(), ordinalMethodId.canBeVirtual()))));
}
return super.visit(switchStmt);
}
@Override
public boolean visit(@Nonnull JCaseStatement caseStmt) {
JLiteral caseExpr = caseStmt.getExpr();
if (caseExpr != null && caseExpr instanceof JEnumLiteral) {
JEnumLiteral literal = (JEnumLiteral) caseExpr;
JMethod getEnumSwitchValues =
getSwitchValuesMethod((JDefinedEnum) literal.getType());
EnumMappingMarker emm = getEnumSwitchValues.getMarker(EnumMappingMarker.class);
assert emm != null;
Integer enumSwitchValue = emm.getMapping().get(literal.getFieldId());
assert enumSwitchValue != null;
tr.append(new Replace(caseExpr,
new JIntLiteral(caseStmt.getSourceInfo(), enumSwitchValue.intValue())));
}
return super.visit(caseStmt);
}
@Nonnull
private JMethod getSwitchValuesMethod(@Nonnull JDefinedEnum enumType) {
SourceInfo dbgInfo = SourceInfo.UNKNOWN;
String enumName = BinaryQualifiedNameFormatter.getFormatter().getName(enumType);
String methodName =
NamingTools.getStrictNonSourceConflictingName("get" + enumName + "SwitchesValues");
String fieldName =
NamingTools.getStrictNonSourceConflictingName(enumName + "SwitchesValues");
JArrayType switchValuesArrayType = JPrimitiveTypeEnum.INT.getType().getArray();
JArrayType enumArrayType = enumType.getArray();
JMethod getEnumSwitchValues;
try {
getEnumSwitchValues = currentClOrI.getMethod(methodName, switchValuesArrayType);
} catch (JMethodLookupException e) {
TransformationRequest localTr = new TransformationRequest(currentClOrI);
// Create $[EnumName]switchesValues field
// STOPSHIP: the field must not be public, uses an inner class to avoid the public field ?
JField enumSwitchValues =
new JField(dbgInfo, fieldName, currentClOrI, switchValuesArrayType,
(currentClOrI instanceof JDefinedInterface ? JModifier.PUBLIC : JModifier.PRIVATE)
| JModifier.FINAL | JModifier.STATIC | JModifier.SYNTHETIC);
localTr.append(new AppendField(currentClOrI, enumSwitchValues));
// Create method $getEnumSwitchesValues
getEnumSwitchValues =
new JMethod(dbgInfo, new JMethodId(methodName, MethodKind.STATIC),
currentClOrI, switchValuesArrayType,
JModifier.PRIVATE | JModifier.STATIC | JModifier.SYNTHETIC);
localTr.append(new AppendMethod(currentClOrI, getEnumSwitchValues));
JBlock bodyBlock = new JBlock(dbgInfo);
JMethodBody body = new JMethodBody(dbgInfo, bodyBlock);
getEnumSwitchValues.setBody(body);
LocalVarCreator lvc = new LocalVarCreator(getEnumSwitchValues, "es");
// if ($switchesValues != null) return ($switchesValues);
JFieldId enumSwitchValuesId = enumSwitchValues.getId();
JExpression checkNull =
JBinaryOperation.create(dbgInfo, JBinaryOperator.NEQ, new JFieldRef(dbgInfo,
null /* instance */, enumSwitchValuesId, currentClOrI),
new JNullLiteral(dbgInfo));
JBlock thenBlock = new JBlock(dbgInfo);
thenBlock.addStmt(new JReturnStatement(dbgInfo,
new JFieldRef(dbgInfo, null /* instance */, enumSwitchValuesId, currentClOrI)));
bodyBlock.addStmt(new JIfStatement(dbgInfo, checkNull, thenBlock, null /* elseStmt */));
JLocal arrayVar = lvc.createTempLocal(switchValuesArrayType, dbgInfo, localTr);
JMethod valuesMethod;
try {
valuesMethod = enumType.getMethod("values", enumArrayType);
} catch (JMethodLookupException e1) {
// A valid enum must have a values() method
throw new AssertionError(e1);
}
// int[] array = new int[enum.values().length]
JMethodId valuesId = valuesMethod.getMethodId();
JExpression valuesLength = new JArrayLength(dbgInfo, new JMethodCall(dbgInfo,
null /* instance */, enumType, valuesId, valuesMethod.getType(),
valuesId.canBeVirtual()));
List<JExpression> dimensions = new ArrayList<JExpression>();
dimensions.add(valuesLength);
bodyBlock.addStmt(new JAsgOperation(dbgInfo, arrayVar.makeRef(dbgInfo),
JNewArray.createWithDims(dbgInfo, switchValuesArrayType, dimensions)).makeStatement());
// 0 is used to represent unknown value.
int usedEnumFieldCstValue = 1;
assert usedEnumFields != null;
// +1 due to the fact that numbering of used fields does not start to 0 but 1, otherwise
// the numbering of used fields and unused fields could be overlap.
int unusedEnumFieldCstValue = usedEnumFields.size() + 1;
EnumMappingMarker emm = new EnumMappingMarker();
List<JField> enumFields = enumType.getFields();
if (sortEnumField) {
Collections.sort(enumFields, new Comparator<JField>() {
@Override
public int compare(JField o1, JField o2) {
return o1.getName().compareTo(o2.getName());
}
});
}
for (JField enumField : enumFields) {
if (!(enumField instanceof JEnumField)) {
continue;
}
// try { array[enumField.ordinal()] = constant; } catch (NoSuchFieldError ex) {}
JBlock tryBlock = new JBlock(dbgInfo);
JLocal exVar = new JLocal(dbgInfo, "ex", noSuchFieldErrorType, JModifier.SYNTHETIC, body);
List<JCatchBlock> catchBlock = new ArrayList<JCatchBlock>(1);
catchBlock.add(new JCatchBlock(dbgInfo, Collections
.singletonList((JClass) noSuchFieldErrorType), exVar));
bodyBlock.addStmt(new JTryStatement(dbgInfo,
Collections.<JStatement>emptyList(),
tryBlock,
catchBlock,
null /* finallyBlock */));
JFieldId enumFieldId = enumField.getId();
JExpression enumFieldAccess =
new JFieldRef(dbgInfo, null /* instance */, enumFieldId, enumType);
JMethodId ordinalMethodId = enumType.getOrCreateMethodId(
ORDINAL, Collections.<JType>emptyList(), MethodKind.INSTANCE_VIRTUAL);
JExpression callOrdinal =
new JMethodCall(dbgInfo, enumFieldAccess, enumType, ordinalMethodId,
JPrimitiveTypeEnum.INT.getType(),
ordinalMethodId.canBeVirtual());
int constant;
assert usedEnumFields != null;
if (usedEnumFields.contains(enumFieldId)) {
constant = usedEnumFieldCstValue;
usedEnumFieldCstValue++;
} else {
constant = unusedEnumFieldCstValue;
unusedEnumFieldCstValue++;
}
tryBlock.addStmt(new JAsgOperation(dbgInfo,
new JArrayRef(dbgInfo, arrayVar.makeRef(dbgInfo), callOrdinal),
new JIntLiteral(dbgInfo, constant)).makeStatement());
emm.addMapping(enumFieldId, constant);
}
getEnumSwitchValues.addMarker(emm);
bodyBlock.addStmt(new JAsgOperation(dbgInfo,
new JFieldRef(dbgInfo, null /* instance */, enumSwitchValuesId, currentClOrI),
arrayVar.makeRef(dbgInfo)).makeStatement());
bodyBlock.addStmt(new JReturnStatement(dbgInfo, arrayVar.makeRef(dbgInfo)));
localTr.commit();
}
return (getEnumSwitchValues);
}
}
@Override
public synchronized void run(@Nonnull JMethod method) throws Exception {
JDefinedClassOrInterface enclosingType = method.getEnclosingType();
if (method.isNative() || method.isAbstract() || !filter.accept(this.getClass(), method)) {
return;
}
TransformationRequest tr = new TransformationRequest(enclosingType);
Visitor visitor = new Visitor(tr, enclosingType);
visitor.accept(method);
tr.commit();
}
}