| /* |
| * 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.ast; |
| |
| import com.android.jack.Jack; |
| import com.android.jack.Options; |
| import com.android.jack.ir.ast.JAsgOperation; |
| import com.android.jack.ir.ast.JBlock; |
| import com.android.jack.ir.ast.JCatchBlock; |
| import com.android.jack.ir.ast.JClass; |
| import com.android.jack.ir.ast.JClassLiteral; |
| import com.android.jack.ir.ast.JDefinedClass; |
| import com.android.jack.ir.ast.JExpression; |
| import com.android.jack.ir.ast.JExpressionStatement; |
| import com.android.jack.ir.ast.JLocal; |
| import com.android.jack.ir.ast.JLocalRef; |
| import com.android.jack.ir.ast.JLock; |
| import com.android.jack.ir.ast.JMethod; |
| import com.android.jack.ir.ast.JMethodBody; |
| import com.android.jack.ir.ast.JSession; |
| import com.android.jack.ir.ast.JStatement; |
| import com.android.jack.ir.ast.JSynchronizedBlock; |
| import com.android.jack.ir.ast.JThisRef; |
| import com.android.jack.ir.ast.JTryStatement; |
| import com.android.jack.ir.ast.JType; |
| import com.android.jack.ir.ast.JUnlock; |
| import com.android.jack.ir.ast.JVariable; |
| import com.android.jack.ir.ast.JVisitor; |
| import com.android.jack.ir.sourceinfo.SourceInfo; |
| import com.android.jack.library.DumpInLibrary; |
| import com.android.jack.library.PrebuiltCompatibility; |
| import com.android.jack.lookup.CommonTypes; |
| import com.android.jack.scheduling.filter.TypeWithoutPrebuiltFilter; |
| import com.android.jack.transformations.LocalVarCreator; |
| import com.android.jack.transformations.request.AppendBefore; |
| import com.android.jack.transformations.request.Replace; |
| import com.android.jack.transformations.request.TransformationRequest; |
| import com.android.jack.transformations.threeaddresscode.ThreeAddressCodeForm; |
| import com.android.sched.item.Description; |
| import com.android.sched.item.Name; |
| import com.android.sched.schedulable.Constraint; |
| import com.android.sched.schedulable.Filter; |
| 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.id.BooleanPropertyId; |
| |
| import java.util.Collections; |
| |
| import javax.annotation.Nonnull; |
| |
| /** |
| * Transform {@link JSynchronizedBlock} and synchronized flag of method into try/finally with |
| * {@link JLock} and {@link JUnlock} statement. |
| */ |
| @Description("Transform synchronization into try/finally with jlock/junlock statement.") |
| @Name("SynchronizeTransformer") |
| @Constraint(need = {NoImplicitBlock.class}) |
| @Transform(remove = {JSynchronizedBlock.class, ThreeAddressCodeForm.class}, add = {JBlock.class, |
| JTryStatement.class, |
| JTryStatement.FinallyBlock.class, |
| JLock.class, |
| JUnlock.class, |
| JLocalRef.class, |
| JAsgOperation.NonReusedAsg.class, |
| JClassLiteral.class, |
| JThisRef.class, |
| JExpressionStatement.class}) |
| @Use(LocalVarCreator.class) |
| @HasKeyId |
| @Filter(TypeWithoutPrebuiltFilter.class) |
| public class SynchronizeTransformer implements RunnableSchedulable<JMethod> { |
| |
| @Nonnull |
| private final com.android.jack.util.filter.Filter<JMethod> filter = |
| ThreadConfig.get(Options.METHOD_FILTER); |
| |
| @Nonnull |
| public static final BooleanPropertyId REUSE_SYNC_VARIABLE = BooleanPropertyId.create( |
| "jack.transformation.reusesyncvariable", |
| "Reduce the 'get class' usage in static synchronized methods by reusing a local variable") |
| .addDefaultValue(Boolean.TRUE) |
| .addCategory(DumpInLibrary.class) |
| .addCategory(PrebuiltCompatibility.class); |
| |
| private final boolean reuseSyncVariable = ThreadConfig.get(REUSE_SYNC_VARIABLE).booleanValue(); |
| |
| @Nonnull |
| private final JSession session = Jack.getSession(); |
| |
| private class Visitor extends JVisitor { |
| |
| @Nonnull |
| private final TransformationRequest tr; |
| @Nonnull |
| private final LocalVarCreator lvCreator; |
| |
| public Visitor(@Nonnull TransformationRequest tr, @Nonnull LocalVarCreator lvCreator) { |
| this.tr = tr; |
| this.lvCreator = lvCreator; |
| } |
| |
| @Override |
| public boolean visit(@Nonnull JMethodBody methodBody) { |
| JMethod enclosingMethod = methodBody.getMethod(); |
| |
| if (enclosingMethod.isSynchronized()) { |
| JBlock bodyBlock = methodBody.getBlock(); |
| |
| JTryStatement tryStmt = getTryFinally(SourceInfo.UNKNOWN, bodyBlock); |
| |
| JType enclosingType = enclosingMethod.getEnclosingType(); |
| JExpression lockExpr = null; |
| JExpression unlockExpr = null; |
| |
| JBlock newBodyBlock = new JBlock(methodBody.getSourceInfo()); |
| |
| if (reuseSyncVariable && enclosingMethod.isStatic()) { |
| JLocal syncVar = lvCreator.createTempLocal(enclosingType, SourceInfo.UNKNOWN, tr); |
| JExpression syncVarValue = new JClassLiteral(SourceInfo.UNKNOWN, enclosingType); |
| JAsgOperation asg = new JAsgOperation(SourceInfo.UNKNOWN, |
| syncVar.makeRef(SourceInfo.UNKNOWN), syncVarValue); |
| newBodyBlock.addStmt(asg.makeStatement()); |
| lockExpr = syncVar.makeRef(SourceInfo.UNKNOWN); |
| unlockExpr = syncVar.makeRef(SourceInfo.UNKNOWN); |
| } else { |
| if (enclosingMethod.isStatic()) { |
| lockExpr = new JClassLiteral(SourceInfo.UNKNOWN, enclosingType); |
| unlockExpr = new JClassLiteral(SourceInfo.UNKNOWN, enclosingType); |
| } else { |
| assert enclosingType instanceof JDefinedClass; |
| JVariable thisVar = enclosingMethod.getThis(); |
| assert thisVar != null; |
| lockExpr = thisVar.makeRef(SourceInfo.UNKNOWN); |
| unlockExpr = thisVar.makeRef(SourceInfo.UNKNOWN); |
| } |
| } |
| |
| newBodyBlock.addStmt(new JLock(SourceInfo.UNKNOWN, lockExpr)); |
| newBodyBlock.addStmt(tryStmt); |
| |
| JBlock finallyBlock = tryStmt.getFinallyBlock(); |
| assert finallyBlock != null; |
| finallyBlock.addStmt(new JUnlock(SourceInfo.UNKNOWN, unlockExpr)); |
| |
| tr.append(new Replace(bodyBlock, newBodyBlock)); |
| } |
| |
| return super.visit(methodBody); |
| } |
| |
| @Override |
| public boolean visit(@Nonnull JSynchronizedBlock syncBlock) { |
| SourceInfo srcInfo = syncBlock.getSourceInfo(); |
| JBlock bodyBlock = syncBlock.getSynchronizedBlock(); |
| |
| JTryStatement tryStmt = getTryFinally(srcInfo, bodyBlock); |
| JExpression lockExpr = syncBlock.getLockExpr(); |
| JType lockExprType = lockExpr.getType(); |
| |
| // $sync0 = lockExpr |
| JLocal syncVar = lvCreator.createTempLocal(lockExprType, srcInfo, tr); |
| JLocalRef asgLhs = syncVar.makeRef(srcInfo); |
| JAsgOperation asg = new JAsgOperation(srcInfo, asgLhs, lockExpr); |
| |
| JBlock finallyBlock = tryStmt.getFinallyBlock(); |
| assert finallyBlock != null; |
| finallyBlock.addStmt(new JUnlock(SourceInfo.UNKNOWN, syncVar.makeRef(srcInfo))); |
| |
| tr.append(new AppendBefore(syncBlock, asg.makeStatement())); |
| tr.append(new AppendBefore(syncBlock, new JLock(srcInfo, syncVar.makeRef(srcInfo)))); |
| tr.append(new Replace(syncBlock, tryStmt)); |
| |
| return super.visit(syncBlock); |
| } |
| |
| @Nonnull |
| private JTryStatement getTryFinally(@Nonnull SourceInfo mthSrcInfo, @Nonnull JBlock bodyBlock) { |
| JBlock finallyBlock = new JBlock(mthSrcInfo); |
| JTryStatement tryStmt = new JTryStatement(mthSrcInfo, |
| Collections.<JStatement>emptyList(), |
| bodyBlock, |
| Collections.<JCatchBlock>emptyList(), |
| finallyBlock); |
| return tryStmt; |
| } |
| |
| @Nonnull |
| private JClass getJLClass() { |
| return session.getPhantomLookup().getClass(CommonTypes.JAVA_LANG_CLASS); |
| } |
| } |
| |
| @Override |
| public void run(@Nonnull JMethod method) { |
| if (method.isNative() || method.isAbstract() || !filter.accept(this.getClass(), method)) { |
| return; |
| } |
| |
| TransformationRequest tr = new TransformationRequest(method); |
| LocalVarCreator lvCreator = new LocalVarCreator(method, "sync"); |
| Visitor visitor = new Visitor(tr, lvCreator); |
| visitor.accept(method); |
| tr.commit(); |
| } |
| |
| } |