/** * Copyright (C) 2010 Hal Hildebrand. All rights reserved. * * This file is part of the Prime Mover Event Driven Simulation Framework. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.hellblazer.primeMover.soot; import static com.hellblazer.primeMover.soot.util.Utils.markTransformed; import static com.hellblazer.primeMover.soot.util.Utils.willContinue; import static java.util.Arrays.asList; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.UUID; import java.util.logging.Logger; import soot.ArrayType; import soot.Body; import soot.BodyTransformer; import soot.BooleanType; import soot.ByteType; import soot.CharType; import soot.DoubleType; import soot.FloatType; import soot.IntType; import soot.Local; import soot.LongType; import soot.Modifier; import soot.PatchingChain; import soot.RefType; import soot.Scene; import soot.ShortType; import soot.SootClass; import soot.SootField; import soot.SootMethod; import soot.Type; import soot.Unit; import soot.UnitBox; import soot.Value; import soot.VoidType; import soot.jimple.AssignStmt; import soot.jimple.DoubleConstant; import soot.jimple.FloatConstant; import soot.jimple.IdentityStmt; import soot.jimple.InstanceFieldRef; import soot.jimple.IntConstant; import soot.jimple.InvokeStmt; import soot.jimple.Jimple; import soot.jimple.JimpleBody; import soot.jimple.LongConstant; import soot.jimple.NullConstant; import soot.jimple.Stmt; import soot.jimple.ThisRef; import soot.toolkits.graph.ExceptionalUnitGraph; import soot.toolkits.scalar.SimpleLiveLocals; import soot.toolkits.scalar.UnusedLocalEliminator; import com.hellblazer.primeMover.soot.util.MethodHelper; /** * Transform the method body, creating continuations at blocking or continuing * method call sites. * * @author <a href="mailto:hal.hildebrand@gmail.com">Hal Hildebrand</a> * */ public class ContinuationTransformer extends BodyTransformer { public class ContinuationSite { public final int location; public final Unit continuedCall; public final SootClass frameClass; public final List<Local> locals; private final UnitBox callSite = Jimple.v().newStmtBox(null); private final Type returnType; private Local frame; private ContinuationSite(String id, int s, Unit u, List<Local> l, SootClass definingClass, Type t) { location = s; continuedCall = u; locals = l; frameClass = generateFrame(id, definingClass); returnType = t; } private Unit defaultReturn(Type returnType) { if (returnType == VoidType.v()) { return Jimple.v().newReturnVoidStmt(); } if (returnType instanceof RefType || returnType instanceof ArrayType) { return Jimple.v().newReturnStmt(NullConstant.v()); } if (returnType instanceof BooleanType) { return Jimple.v().newReturnStmt(IntConstant.v(0)); } if (returnType instanceof ByteType) { return Jimple.v().newReturnStmt(IntConstant.v(0)); } if (returnType instanceof CharType) { return Jimple.v().newReturnStmt(IntConstant.v(0)); } if (returnType instanceof DoubleType) { return Jimple.v().newReturnStmt(DoubleConstant.v(0)); } if (returnType instanceof FloatType) { return Jimple.v().newReturnStmt(FloatConstant.v(0)); } if (returnType instanceof IntType) { return Jimple.v().newReturnStmt(IntConstant.v(0)); } if (returnType instanceof LongType) { return Jimple.v().newReturnStmt(LongConstant.v(0)); } if (returnType instanceof ShortType) { return Jimple.v().newReturnStmt(IntConstant.v(0)); } throw new VerifyError(String.format("Invalid return type: %1s", returnType)); } private SootClass generateFrame(String id, SootClass definingClass) { if (locals.size() == 0) { return emptyContinuationFrame; } StringBuffer generatedName = new StringBuffer(100); generatedName.append(definingClass.getName()).append('$').append(id).append('$').append(location).append("$gen"); SootClass frame = new SootClass(generatedName.toString(), Modifier.PUBLIC); frame.setSuperclass(continuationFrame); int i = 0; for (Local local : locals) { frame.addField(new SootField(LOCAL_PREFIX + i++, local.getType(), Modifier.PUBLIC)); } SootMethod constructor = continuationFrame.getMethod(NO_ARG_CONSTRUCTOR_SIGNATURE); @SuppressWarnings("unchecked") MethodHelper helper = new MethodHelper( frame, constructor.getName(), constructor.getParameterTypes(), VoidType.v(), constructor.getModifiers()); helper.invoke(Jimple.v().newSpecialInvokeExpr(helper.thisLocal(), constructor.makeRef())); helper.returnVoid(); frame.addMethod(helper.getMethod()); Scene.v().addClass(frame); generated.add(frame); return frame; } private List<Unit> popFrame(Body body, Local poppedFrame) { ArrayList<Unit> pop = new ArrayList<Unit>(); Jimple jimple = Jimple.v(); pop.add(jimple.newAssignStmt(frame, jimple.newCastExpr(poppedFrame, frameClass.getType()))); int index = 0; for (Local l : locals) { pop.add(jimple.newAssignStmt(l, jimple.newInstanceFieldRef(frame, frameClass.getFieldByName(LOCAL_PREFIX + index++).makeRef()))); } pop.add(jimple.newGotoStmt(callSite)); return pop; } private List<Unit> pushFrame(Body body, Local frame) { ArrayList<Unit> push = new ArrayList<Unit>(); Jimple jimple = Jimple.v(); // <FrameClass> frame = new <FrameClass>(); push.add(jimple.newAssignStmt(frame, jimple.newNewExpr(frameClass.getType()))); push.add(jimple.newInvokeStmt(jimple.newSpecialInvokeExpr(frame, frameClass.getMethod(NO_ARG_CONSTRUCTOR_SIGNATURE).makeRef()))); InstanceFieldRef ref = jimple.newInstanceFieldRef(frame, frameClass.getSuperclass().getFieldByName(CONTINUATON_LOCATION_FIELD).makeRef()); push.add(jimple.newAssignStmt(ref, IntConstant.v(location))); // Load the live locals into the frame instance variables int index = 0; for (Local l : locals) { String instVar = LOCAL_PREFIX + index++; ref = jimple.newInstanceFieldRef(frame, frameClass.getFieldByName(instVar).makeRef()); push.add(jimple.newAssignStmt(ref, l)); } return push; } private void transformContinuation(Body body, Local poppedFrame) { PatchingChain<Unit> units = body.getUnits(); Jimple jimple = Jimple.v(); Local saveFrame = newLocal(SAVE_FRAME_LOCAL, BooleanType.v(), body); frame = newLocal(FRAME_LOCAL + location, frameClass.getType(), body); UnitBox next = jimple.newStmtBox(null); ArrayList<Unit> post = new ArrayList<Unit>(); post.add(jimple.newAssignStmt(saveFrame, jimple.newStaticInvokeExpr(framework.getMethod(FRAMEWORK_SAVE_FRAME_SIGNATURE).makeRef()))); post.add(jimple.newIfStmt(jimple.newEqExpr(saveFrame, IntConstant.v(0)), next)); post.add(jimple.newInvokeStmt(jimple.newStaticInvokeExpr(framework.getMethod(PUSH_FRAME_SIGNATURE).makeRef(), asList(frame)))); post.add(defaultReturn(returnType)); Unit nextUnit = units.getSuccOf(continuedCall); units.insertBefore(pushFrame(body, frame), continuedCall); units.insertAfter(post, continuedCall); callSite.setUnit(continuedCall); next.setUnit(nextUnit); } } private static final String SAVE_FRAME_LOCAL = "saveFrame"; private static final String FRAME_LOCAL = "frame"; private static final String FRAMEWORK_SAVE_FRAME_SIGNATURE = "boolean saveFrame()"; private static final String CONTINUATION_FRAME_LOCAL = "continuationFrame"; private static final String INVALID_LOCATION_LOCAL = "invalidLocation"; private static final String LOCATION_LOCAL = "location"; private static final String RESTORE_FRAME_LOCAL = "restoreFrame"; private static final String NO_ARG_CONSTRUCTOR_SIGNATURE = "void <init>()"; private static final String FRAMEWORK_RESTORE_FRAME_SIGNATURE = "boolean restoreFrame()"; private static final String PUSH_FRAME_SIGNATURE = "void pushFrame(com.hellblazer.primeMover.runtime.ContinuationFrame)"; private static final String POP_FRAME_SIGNATURE = "com.hellblazer.primeMover.runtime.ContinuationFrame popFrame()"; private static final String LOCAL_PREFIX = "l_"; private static final String CONTINUATON_LOCATION_FIELD = LOCATION_LOCAL; private static final Logger log = Logger.getLogger(ContinuationTransformer.class.getCanonicalName()); private static Local newLocal(String name, Type t, Body b) { Local l = Jimple.v().newLocal(name, t); b.getLocals().add(l); return l; } private final SootClass framework = Scene.v().loadClassAndSupport("com.hellblazer.primeMover.runtime.Framework"); private final SootClass continuationFrame = Scene.v().loadClassAndSupport("com.hellblazer.primeMover.runtime.ContinuationFrame"); private final SootClass illegalStateException = Scene.v().loadClassAndSupport(IllegalStateException.class.getCanonicalName()); private final SootClass emptyContinuationFrame = Scene.v().loadClassAndSupport("com.hellblazer.primeMover.runtime.EmptyContinuationFrame"); private final boolean validate; private final List<SootClass> generated; public ContinuationTransformer(List<SootClass> generated) { this(generated, false); } public ContinuationTransformer(List<SootClass> generated, boolean validate) { this.validate = validate; this.generated = generated; } private List<Unit> generateContinuableReentry(Body body, Local poppedFrame, List<ContinuationSite> continuations) { ArrayList<Unit> reentry = new ArrayList<Unit>(); Jimple jimple = Jimple.v(); UnitBox normalEntry = jimple.newStmtBox(jimple.newNopStmt()); Local restoreFrame = newLocal(RESTORE_FRAME_LOCAL, BooleanType.v(), body); // boolean restoreFramew = Framework.restoreFrame(); reentry.add(jimple.newAssignStmt(restoreFrame, jimple.newStaticInvokeExpr(framework.getMethod(FRAMEWORK_RESTORE_FRAME_SIGNATURE).makeRef()))); // if (restoreFrame) { reentry.add(jimple.newIfStmt(jimple.newEqExpr(restoreFrame, IntConstant.v(0)), normalEntry)); reentry.add(jimple.newAssignStmt(poppedFrame, jimple.newStaticInvokeExpr(framework.getMethod(POP_FRAME_SIGNATURE).makeRef()))); Local location = newLocal(LOCATION_LOCAL, IntType.v(), body); InstanceFieldRef ref = jimple.newInstanceFieldRef(poppedFrame, continuationFrame.getFieldByName(CONTINUATON_LOCATION_FIELD).makeRef()); reentry.add(jimple.newAssignStmt(location, ref)); UnitBox reentryBranch = jimple.newStmtBox(null); reentry.add(jimple.newGotoStmt(reentryBranch)); ArrayList<Value> lookupValues = new ArrayList<Value>(); ArrayList<Unit> targets = new ArrayList<Unit>(); for (ContinuationSite site : continuations) { List<Unit> popFrame = site.popFrame(body, poppedFrame); reentry.addAll(popFrame); lookupValues.add(IntConstant.v(site.location)); targets.add(popFrame.get(0)); } Local ise = newLocal(INVALID_LOCATION_LOCAL, illegalStateException.getType(), body); Unit defaultTarget = jimple.newAssignStmt(ise, jimple.newNewExpr(illegalStateException.getType())); reentry.add(defaultTarget); reentry.add(jimple.newInvokeStmt(jimple.newSpecialInvokeExpr(ise, illegalStateException.getMethod(NO_ARG_CONSTRUCTOR_SIGNATURE).makeRef()))); reentry.add(jimple.newThrowStmt(ise)); reentryBranch.setUnit(jimple.newLookupSwitchStmt(location, lookupValues, targets, defaultTarget)); reentry.add(reentryBranch.getUnit()); reentry.add(normalEntry.getUnit()); return reentry; } @SuppressWarnings("unchecked") List<ContinuationSite> generateContinuationSites(Body body, Type returnType) { String id = UUID.randomUUID().toString().replace('-', '_'); UnusedLocalEliminator.v().transform(body); List<ContinuationSite> continuedCalls = new ArrayList<ContinuationSite>(); SimpleLiveLocals liveLocals = new SimpleLiveLocals( new ExceptionalUnitGraph( body)); int callSite = 0; @SuppressWarnings("rawtypes") Iterator statements = body.getUnits().snapshotIterator(); Local thisLocal = body.getMethod().isStatic() ? null : body.getThisLocal(); while (statements.hasNext()) { Stmt stmt = (Stmt) statements.next(); SootClass declaringClass = body.getMethod().getDeclaringClass(); List<Local> locals = liveLocals.getLiveLocalsBefore(stmt); if (thisLocal != null && locals.contains(thisLocal)) { locals = new ArrayList<Local>(locals); locals.remove(thisLocal); locals = Collections.unmodifiableList(locals); } if (stmt instanceof InvokeStmt) { InvokeStmt invoke = (InvokeStmt) stmt; if (willContinue(invoke.getInvokeExpr().getMethod())) { ContinuationSite site = new ContinuationSite( id, callSite++, stmt, locals, declaringClass, returnType); continuedCalls.add(site); } } else if (stmt instanceof AssignStmt) { AssignStmt assign = (AssignStmt) stmt; if (assign.containsInvokeExpr() && willContinue(assign.getInvokeExpr().getMethod())) { ContinuationSite site = new ContinuationSite( id, callSite++, stmt, locals, declaringClass, returnType); continuedCalls.add(site); } } else if (stmt.containsInvokeExpr()) { String errorMessage = String.format("Found an expression which contains a method invocation that is not an assignment nor a stand alone invocation: %1s", stmt); log.severe(errorMessage); throw new VerifyError(errorMessage); } } return continuedCalls; } public Local getThisLocal(Body body) { for (Unit unit : body.getUnits()) { if (unit instanceof IdentityStmt && ((IdentityStmt) unit).getRightOp() instanceof ThisRef) { return (Local) ((IdentityStmt) unit).getLeftOp(); } } return null; } @Override protected void internalTransform(Body body, String phaseName, @SuppressWarnings("rawtypes") Map options) { if (!willContinue(body.getMethod())) { return; } List<ContinuationSite> continuedCalls = generateContinuationSites(body, body.getMethod().getReturnType()); if (continuedCalls.size() != 0) { Local poppedFrame = newLocal(CONTINUATION_FRAME_LOCAL, continuationFrame.getType(), body); for (ContinuationSite site : continuedCalls) { site.transformContinuation(body, poppedFrame); } body.getUnits().insertBefore(generateContinuableReentry(body, poppedFrame, continuedCalls), ((JimpleBody) body).getFirstNonIdentityStmt()); markTransformed(body.getMethod(), this, "Transformed continuations"); if (validate) { body.validate(); } } } }