--- /dev/null 2016-05-31 09:42:47.975716356 -0700
+++ new/src/jdk.vm.compiler/share/classes/org.graalvm.compiler.core.test/src/org/graalvm/compiler/core/test/tutorial/StaticAnalysis.java 2016-12-09 00:47:57.090248005 -0800
@@ -0,0 +1,619 @@
+/*
+ * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package org.graalvm.compiler.core.test.tutorial;
+
+import static org.graalvm.compiler.core.common.CompilationIdentifier.INVALID_COMPILATION_ID;
+
+import java.util.ArrayDeque;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.graalvm.compiler.debug.Debug;
+import org.graalvm.compiler.debug.GraalError;
+import org.graalvm.compiler.debug.Debug.Scope;
+import org.graalvm.compiler.graph.Node;
+import org.graalvm.compiler.graph.NodeMap;
+import org.graalvm.compiler.java.GraphBuilderPhase;
+import org.graalvm.compiler.nodes.CallTargetNode.InvokeKind;
+import org.graalvm.compiler.nodes.ConstantNode;
+import org.graalvm.compiler.nodes.FixedNode;
+import org.graalvm.compiler.nodes.Invoke;
+import org.graalvm.compiler.nodes.ParameterNode;
+import org.graalvm.compiler.nodes.ReturnNode;
+import org.graalvm.compiler.nodes.StructuredGraph;
+import org.graalvm.compiler.nodes.StructuredGraph.AllowAssumptions;
+import org.graalvm.compiler.nodes.ValueNode;
+import org.graalvm.compiler.nodes.ValuePhiNode;
+import org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration;
+import org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration.BytecodeExceptionMode;
+import org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration.Plugins;
+import org.graalvm.compiler.nodes.graphbuilderconf.InvocationPlugins;
+import org.graalvm.compiler.nodes.java.LoadFieldNode;
+import org.graalvm.compiler.nodes.java.MethodCallTargetNode;
+import org.graalvm.compiler.nodes.java.NewArrayNode;
+import org.graalvm.compiler.nodes.java.NewInstanceNode;
+import org.graalvm.compiler.nodes.java.StoreFieldNode;
+import org.graalvm.compiler.nodes.spi.StampProvider;
+import org.graalvm.compiler.nodes.util.GraphUtil;
+import org.graalvm.compiler.phases.OptimisticOptimizations;
+import org.graalvm.compiler.phases.graph.StatelessPostOrderNodeIterator;
+
+import jdk.vm.ci.meta.JavaConstant;
+import jdk.vm.ci.meta.JavaKind;
+import jdk.vm.ci.meta.MetaAccessProvider;
+import jdk.vm.ci.meta.ResolvedJavaField;
+import jdk.vm.ci.meta.ResolvedJavaMethod;
+import jdk.vm.ci.meta.ResolvedJavaType;
+
+/**
+ * A simple context-insensitive static analysis based on the Graal API. It is intended for
+ * educational purposes, not for use in production. Only a limited set of Java functionality is
+ * supported to keep the code minimal.
+ *
+ * The analysis builds a directed graph of {@link TypeFlow type flows}. If a type is added to type
+ * flow, it is propagated to all {@link TypeFlow#uses uses} of the type flow. Types are propagated
+ * using a {@link #worklist} of changed type flows until a fixpoint is reached, i.e., until no more
+ * types need to be added to any type state.
+ *
+ * The type flows are constructed from a high-level Graal graph by the {@link TypeFlowBuilder}. All
+ * nodes that operate on {@link JavaKind#Object object} values are converted to the appropriate type
+ * flows. The analysis is context insensitive: every Java field has {@link Results#lookupField one
+ * list} of types assigned to the field; every Java method has {@link Results#lookupMethod one
+ * state} for each {@link MethodState#formalParameters parameter} as well as the
+ * {@link MethodState#formalReturn return value}.
+ */
+public class StaticAnalysis {
+ /** Access to type, method, and fields using the Graal API. */
+ private final MetaAccessProvider metaAccess;
+ /** Access to platform dependent stamps. */
+ private final StampProvider stampProvider;
+ /** The results of the static analysis. */
+ private final Results results;
+ /** Worklist for fixpoint iteration. */
+ private final Deque worklist;
+
+ public StaticAnalysis(MetaAccessProvider metaAccess, StampProvider stampProvider) {
+ this.metaAccess = metaAccess;
+ this.stampProvider = stampProvider;
+ this.results = new Results();
+ this.worklist = new ArrayDeque<>();
+ }
+
+ /**
+ * Adds a root method to the static analysis. The method must be static and must not have any
+ * parameters, because the possible types of the parameters would not be known.
+ */
+ public void addMethod(ResolvedJavaMethod method) {
+ if (!method.isStatic() || method.getSignature().getParameterCount(false) > 0) {
+ error("Entry point method is not static or has parameters: " + method.format("%H.%n(%p)"));
+ }
+ addToWorklist(results.lookupMethod(method));
+ }
+
+ /**
+ * Performs the fixed-point analysis that finds all methods transitively reachable from the
+ * {@link #addMethod root methods}.
+ */
+ public void finish() {
+ while (!worklist.isEmpty()) {
+ worklist.removeFirst().process();
+ }
+ }
+
+ /**
+ * Returns the static analysis results computed by {@link StaticAnalysis#finish}.
+ */
+ public Results getResults() {
+ return results;
+ }
+
+ protected void addToWorklist(WorklistEntry task) {
+ worklist.addLast(task);
+ }
+
+ protected static RuntimeException error(String msg) {
+ throw GraalError.shouldNotReachHere(msg);
+ }
+
+ /**
+ * Base class for all work items that can be {@link #addToWorklist added to the worklist}.
+ */
+ abstract class WorklistEntry {
+ protected abstract void process();
+ }
+
+ /**
+ * The results computed by the static analysis.
+ */
+ public class Results {
+ private final TypeFlow allInstantiatedTypes;
+ private final Map fields;
+ private final Map methods;
+
+ protected Results() {
+ allInstantiatedTypes = new TypeFlow();
+ fields = new HashMap<>();
+ methods = new HashMap<>();
+ }
+
+ /**
+ * All {@link TypeFlow#getTypes() types} that are found to be instantiated, i.e., all types
+ * allocated by the reachable instance and array allocation bytecodes.
+ */
+ public TypeFlow getAllInstantiatedTypes() {
+ return allInstantiatedTypes;
+ }
+
+ /**
+ * All {@link TypeFlow#getTypes() types} that the given field can have, i.e., all types
+ * assigned by the reachable field store bytecodes.
+ */
+ public TypeFlow lookupField(ResolvedJavaField field) {
+ TypeFlow result = fields.get(field);
+ if (result == null) {
+ result = new TypeFlow();
+ fields.put(field, result);
+ }
+ return result;
+ }
+
+ /**
+ * All {@link TypeFlow#getTypes() types} that {@link MethodState#formalParameters
+ * parameters} and {@link MethodState#formalReturn return value} of the given method can
+ * have.
+ */
+ public MethodState lookupMethod(ResolvedJavaMethod method) {
+ MethodState result = methods.get(method);
+ if (result == null) {
+ result = new MethodState(method);
+ methods.put(method, result);
+ }
+ return result;
+ }
+ }
+
+ /**
+ * The {@link TypeFlow#getTypes() types} of the parameters and return value of a method. Also
+ * serves as the worklist element to parse the bytecodes of the method.
+ */
+ public class MethodState extends WorklistEntry {
+ private final ResolvedJavaMethod method;
+ private final TypeFlow[] formalParameters;
+ private final TypeFlow formalReturn;
+ private boolean processed;
+
+ protected MethodState(ResolvedJavaMethod method) {
+ this.method = method;
+
+ formalParameters = new TypeFlow[method.getSignature().getParameterCount(!method.isStatic())];
+ for (int i = 0; i < formalParameters.length; i++) {
+ formalParameters[i] = new TypeFlow();
+ }
+ formalReturn = new TypeFlow();
+ }
+
+ /**
+ * All {@link TypeFlow#getTypes() types} that the parameters of this method can have.
+ */
+ public TypeFlow[] getFormalParameters() {
+ return formalParameters;
+ }
+
+ /**
+ * All {@link TypeFlow#getTypes() types} that the return value of this method can have.
+ */
+ public TypeFlow getFormalReturn() {
+ return formalReturn;
+ }
+
+ @Override
+ @SuppressWarnings("try")
+ protected void process() {
+ if (!processed) {
+ /* We want to process a method only once. */
+ processed = true;
+
+ /*
+ * Build the Graal graph for the method using the bytecode parser provided by Graal.
+ */
+
+ StructuredGraph graph = new StructuredGraph(method, AllowAssumptions.NO, INVALID_COMPILATION_ID);
+ /*
+ * Support for graph dumping, IGV uses this information to show the method name of a
+ * graph.
+ */
+ try (Scope scope = Debug.scope("graph building", graph)) {
+ /*
+ * We want all types to be resolved by the graph builder, i.e., we want classes
+ * referenced by the bytecodes to be loaded and initialized. Since we do not run
+ * the code before static analysis, the classes would otherwise be not loaded
+ * yet and the bytecode parser would only create a graph.
+ */
+ Plugins plugins = new Plugins(new InvocationPlugins(metaAccess));
+ GraphBuilderConfiguration graphBuilderConfig = GraphBuilderConfiguration.getDefault(plugins).withEagerResolving(true);
+ /*
+ * For simplicity, we ignore all exception handling during the static analysis.
+ * This is a constraint of this example code, a real static analysis needs to
+ * handle the Graal nodes for throwing and handling exceptions.
+ */
+ graphBuilderConfig = graphBuilderConfig.withBytecodeExceptionMode(BytecodeExceptionMode.OmitAll);
+ /*
+ * We do not want Graal to perform any speculative optimistic optimizations,
+ * i.e., we do not want to use profiling information. Since we do not run the
+ * code before static analysis, the profiling information is empty and therefore
+ * wrong.
+ */
+ OptimisticOptimizations optimisticOpts = OptimisticOptimizations.NONE;
+
+ GraphBuilderPhase.Instance graphBuilder = new GraphBuilderPhase.Instance(metaAccess, stampProvider, null, null, graphBuilderConfig, optimisticOpts, null);
+ graphBuilder.apply(graph);
+ } catch (Throwable ex) {
+ Debug.handle(ex);
+ }
+
+ /*
+ * Build the type flow graph from the Graal graph, i.e., process all nodes that are
+ * deal with objects.
+ */
+
+ TypeFlowBuilder typeFlowBuilder = new TypeFlowBuilder(graph);
+ typeFlowBuilder.apply();
+ }
+ }
+ }
+
+ /**
+ * The active element during static analysis: types are added until a fixed point is reached.
+ * When a new type is added, it is propagated to all usages by putting this element on the
+ * {@link StaticAnalysis#addToWorklist worklist}.
+ */
+ public class TypeFlow extends WorklistEntry {
+ private final Set types;
+ private final Set uses;
+
+ protected TypeFlow() {
+ types = new HashSet<>();
+ uses = new HashSet<>();
+ }
+
+ /** Returns the types of this element. */
+ public Set getTypes() {
+ return types;
+ }
+
+ /**
+ * Adds new types to this element. If that changes the state of this element, it is added to
+ * the {@link StaticAnalysis#addToWorklist worklist} in order to propagate the added types
+ * to all usages.
+ */
+ protected void addTypes(Set newTypes) {
+ if (types.addAll(newTypes)) {
+ addToWorklist(this);
+ }
+ }
+
+ /**
+ * Adds a new use to this element. All types of this element are propagated to the new
+ * usage.
+ */
+ protected void addUse(TypeFlow use) {
+ if (uses.add(use)) {
+ use.addTypes(types);
+ }
+ }
+
+ /**
+ * Processing of the worklist element: propagate the types to all usages. That in turn can
+ * add the usages to the worklist (if the types of the usage are changed).
+ */
+ @Override
+ protected void process() {
+ for (TypeFlow use : uses) {
+ use.addTypes(types);
+ }
+ }
+ }
+
+ /**
+ * The active element for method invocations. For {@link InvokeKind#Virtual virtual} and
+ * {@link InvokeKind#Interface interface} calls, the {@link TypeFlow#getTypes() types} of this
+ * node are the receiver types. When a new receiver type is added, a new callee might be added.
+ * Adding a new callee means linking the type flow of the actual parameters with the formal
+ * parameters of the callee, and linking the return value of the callee with the return value
+ * state of the invocation.
+ *
+ * Statically bindable methods calls ({@link InvokeKind#Static static} and
+ * {@link InvokeKind#Special special} calls) have only one callee, but use the same code for
+ * simplicity.
+ */
+ class InvokeTypeFlow extends TypeFlow {
+ private final MethodCallTargetNode callTarget;
+ private final TypeFlow[] actualParameters;
+ private final TypeFlow actualReturn;
+ private final Set callees;
+
+ protected InvokeTypeFlow(MethodCallTargetNode callTarget, TypeFlow[] actualParameterFlows, TypeFlow actualReturnFlow) {
+ this.callTarget = callTarget;
+ this.actualParameters = actualParameterFlows;
+ this.actualReturn = actualReturnFlow;
+ this.callees = new HashSet<>();
+ }
+
+ private void linkCallee(ResolvedJavaMethod callee) {
+ if (callees.add(callee)) {
+ /* We have added a new callee. */
+
+ /*
+ * Connect the actual parameters of the invocation with the formal parameters of the
+ * callee.
+ */
+ MethodState calleeState = results.lookupMethod(callee);
+ for (int i = 0; i < actualParameters.length; i++) {
+ if (actualParameters[i] != null) {
+ actualParameters[i].addUse(calleeState.formalParameters[i]);
+ }
+ }
+
+ /*
+ * Connect the formal return value of the callee with the actual return value of the
+ * invocation.
+ */
+ if (actualReturn != null) {
+ calleeState.formalReturn.addUse(actualReturn);
+ }
+ addToWorklist(calleeState);
+ }
+ }
+
+ @Override
+ protected void process() {
+ if (callTarget.invokeKind().isDirect()) {
+ /* Static and special calls: link the statically known callee method. */
+ linkCallee(callTarget.targetMethod());
+ } else {
+ /* Virtual and interface call: Iterate all receiver types. */
+ for (ResolvedJavaType type : getTypes()) {
+ /*
+ * Resolve the method call for one exact receiver type. The method linking
+ * semantics of Java are complicated, but fortunatley we can use the linker of
+ * the hosting Java VM. The Graal API exposes this functionality.
+ */
+ ResolvedJavaMethod method = type.resolveConcreteMethod(callTarget.targetMethod(), callTarget.invoke().getContextType());
+
+ /*
+ * Since the static analysis is conservative, the list of receiver types can
+ * contain types that actually do not provide the method to be called. Ignore
+ * these.
+ */
+ if (method != null && !method.isAbstract()) {
+ linkCallee(method);
+ }
+ }
+ }
+ super.process();
+ }
+ }
+
+ /**
+ * Converts the Graal nodes of a method to a type flow graph. The main part of the algorithm is
+ * a reverse-postorder iteration of the Graal nodes, which is provided by the base class
+ * {@link StatelessPostOrderNodeIterator}.
+ */
+ class TypeFlowBuilder extends StatelessPostOrderNodeIterator {
+ private final StructuredGraph graph;
+ private final MethodState methodState;
+ /**
+ * Mapping from Graal nodes to type flows. This uses an efficient Graal-provided
+ * {@link NodeMap collection class}.
+ */
+ private final NodeMap typeFlows;
+
+ protected TypeFlowBuilder(StructuredGraph graph) {
+ super(graph.start());
+
+ this.graph = graph;
+ this.methodState = results.lookupMethod(graph.method());
+ this.typeFlows = new NodeMap<>(graph);
+ }
+
+ /**
+ * Register the type flow node for a Graal node.
+ */
+ private void registerFlow(ValueNode node, TypeFlow flow) {
+ /*
+ * We ignore intermediate nodes used by Graal that, e.g., add more type information or
+ * encapsulate values flowing out of loops.
+ */
+ ValueNode unproxiedNode = GraphUtil.unproxify(node);
+
+ assert typeFlows.get(unproxiedNode) == null : "overwriting existing value";
+ typeFlows.set(unproxiedNode, flow);
+ }
+
+ /**
+ * Lookup the type flow node for a Graal node.
+ */
+ private TypeFlow lookupFlow(ValueNode node) {
+ ValueNode unproxiedNode = GraphUtil.unproxify(node);
+ TypeFlow result = typeFlows.get(unproxiedNode);
+ if (result == null) {
+ /*
+ * This is only the prototype of a static analysis, the handling of many Graal nodes
+ * (such as array accesses) is not implemented.
+ */
+ throw error("Node is not supported yet by static analysis: " + node.getClass().getName());
+ }
+ return result;
+ }
+
+ private boolean isObject(ValueNode node) {
+ return node.getStackKind() == JavaKind.Object;
+ }
+
+ @Override
+ public void apply() {
+ /*
+ * Before the reverse-postorder iteration of fixed nodes, we handle some classes of
+ * floating nodes.
+ */
+ for (Node n : graph.getNodes()) {
+ if (n instanceof ParameterNode) {
+ /*
+ * Incoming method parameter already have a type flow created by the
+ * MethodState.
+ */
+ ParameterNode node = (ParameterNode) n;
+ if (isObject(node)) {
+ registerFlow(node, methodState.formalParameters[(node.index())]);
+ }
+ } else if (n instanceof ValuePhiNode) {
+ /*
+ * Phi functions for loops are cyclic. We create the type flow here (before
+ * processing any loop nodes), but link the phi values only later (after
+ * processing of all loop nodes.
+ */
+ ValuePhiNode node = (ValuePhiNode) n;
+ if (isObject(node)) {
+ registerFlow(node, new TypeFlow());
+ }
+ } else if (n instanceof ConstantNode) {
+ /* Constants have a known type. */
+ ConstantNode node = (ConstantNode) n;
+ JavaConstant constant = node.asJavaConstant();
+ if (constant.isNull()) {
+ registerFlow(node, new TypeFlow());
+ }
+ }
+ }
+
+ super.apply();
+
+ for (Node n : graph.getNodes()) {
+ if (n instanceof ValuePhiNode) {
+ /*
+ * Post-processing of phi functions. Now the type flow for all input values has
+ * been created, so we can link the type flows together.
+ */
+ ValuePhiNode node = (ValuePhiNode) n;
+ if (isObject(node)) {
+ TypeFlow phiFlow = lookupFlow(node);
+ for (ValueNode value : node.values()) {
+ lookupFlow(value).addUse(phiFlow);
+ }
+ }
+ }
+ }
+ }
+
+ private void allocation(ValueNode node, ResolvedJavaType type) {
+ /*
+ * The type flow of allocation nodes is one exact type. This is the source of the
+ * fixpoint iteration, the types are propagated downwards from these sources.
+ */
+ TypeFlow flow = new TypeFlow();
+ flow.addTypes(Collections.singleton(type));
+ registerFlow(node, flow);
+ flow.addUse(results.getAllInstantiatedTypes());
+ }
+
+ @Override
+ protected void node(FixedNode n) {
+ if (n instanceof NewInstanceNode) {
+ NewInstanceNode node = (NewInstanceNode) n;
+ allocation(node, node.instanceClass());
+ } else if (n instanceof NewArrayNode) {
+ NewArrayNode node = (NewArrayNode) n;
+ allocation(node, node.elementType().getArrayClass());
+
+ } else if (n instanceof LoadFieldNode) {
+ /*
+ * The type flow of a field load is the type flow of the field itself. It
+ * accumulates all types ever stored to the field.
+ */
+ LoadFieldNode node = (LoadFieldNode) n;
+ if (isObject(node)) {
+ registerFlow(node, results.lookupField(node.field()));
+ }
+ } else if (n instanceof StoreFieldNode) {
+ /*
+ * Connect the type flow of the stored value with the type flow of the field.
+ */
+ StoreFieldNode node = (StoreFieldNode) n;
+ if (isObject(node.value())) {
+ TypeFlow fieldFlow = results.lookupField(node.field());
+ lookupFlow(node.value()).addUse(fieldFlow);
+ }
+
+ } else if (n instanceof ReturnNode) {
+ /*
+ * Connect the type flow of the returned value with the formal return type flow of
+ * the MethodState.
+ */
+ ReturnNode node = (ReturnNode) n;
+ if (node.result() != null && isObject(node.result())) {
+ lookupFlow(node.result()).addUse(methodState.formalReturn);
+ }
+
+ } else if (n instanceof Invoke) {
+ /*
+ * Create the InvokeTypeFlow, which performs all the linking of actual and formal
+ * parameter values with all identified callees.
+ */
+ Invoke invoke = (Invoke) n;
+ MethodCallTargetNode callTarget = (MethodCallTargetNode) invoke.callTarget();
+
+ TypeFlow[] actualParameters = new TypeFlow[callTarget.arguments().size()];
+ for (int i = 0; i < actualParameters.length; i++) {
+ ValueNode actualParam = callTarget.arguments().get(i);
+ if (isObject(actualParam)) {
+ actualParameters[i] = lookupFlow(actualParam);
+ }
+ }
+ TypeFlow actualReturn = null;
+ if (isObject(invoke.asNode())) {
+ actualReturn = new TypeFlow();
+ registerFlow(invoke.asNode(), actualReturn);
+ }
+
+ InvokeTypeFlow invokeFlow = new InvokeTypeFlow(callTarget, actualParameters, actualReturn);
+
+ if (callTarget.invokeKind().isIndirect()) {
+ /*
+ * For virtual and interface calls, new receiver types can lead to new callees.
+ * Connect the type flow of the receiver with the invocation flow.
+ */
+ lookupFlow(callTarget.arguments().get(0)).addUse(invokeFlow);
+ }
+ /*
+ * Ensure the invocation is on the worklist at least once, even if it is a static
+ * call with not parameters that does not involve any type flows.
+ */
+ addToWorklist(invokeFlow);
+ }
+ }
+ }
+}