/* ***************************************************************************** * Copyright 2018 Dynamic Analysis Group, Università della Svizzera Italiana (USI) * Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved. * * 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 ch.usi.inf.nodeprof.analysis; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Map.Entry; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.instrumentation.EventContext; import com.oracle.truffle.api.instrumentation.ExecutionEventNode; import com.oracle.truffle.api.instrumentation.ExecutionEventNodeFactory; import com.oracle.truffle.api.instrumentation.InstrumentableNode; import com.oracle.truffle.api.instrumentation.Instrumenter; import com.oracle.truffle.api.instrumentation.SourceSectionFilter; import com.oracle.truffle.api.instrumentation.SourceSectionFilter.SourcePredicate; import com.oracle.truffle.api.instrumentation.StandardTags; import com.oracle.truffle.api.instrumentation.Tag; import com.oracle.truffle.api.instrumentation.TruffleInstrument.Env; import com.oracle.truffle.api.source.Source; import com.oracle.truffle.js.nodes.instrumentation.JSTags; import com.oracle.truffle.js.nodes.instrumentation.JSTags.InputNodeTag; import ch.usi.inf.nodeprof.ProfiledTagEnum; import ch.usi.inf.nodeprof.analysis.AnalysisFilterSourceList.ScopeEnum; import ch.usi.inf.nodeprof.handlers.BaseEventHandlerNode; import ch.usi.inf.nodeprof.handlers.MultiEventHandler; import ch.usi.inf.nodeprof.jalangi.NodeProfJalangi; import ch.usi.inf.nodeprof.utils.GlobalConfiguration; import ch.usi.inf.nodeprof.utils.Logger; public abstract class NodeProfAnalysis { private final Env env; private final Instrumenter instrumenter; private final String name; @TruffleBoundary public NodeProfAnalysis(String name, Instrumenter instrumenter, Env env) { this.name = name; this.instrumenter = instrumenter; this.env = env; this.handlers = new HashMap<>(); } public Instrumenter getInstrumenter() { return this.instrumenter; } public Env getEnv() { return this.env; } public String getName() { return name; } private static final int maxAnalyses = 100; private static int numAnalysis = 0; private static final NodeProfAnalysis[] enabled = new NodeProfAnalysis[maxAnalyses]; public static NodeProfAnalysis[] getEnabledAnalyses() { return enabled; } @TruffleBoundary public static NodeProfAnalysis loadAnalysisFromClass(String analysisClass, Instrumenter instrumenter, Env env) { /** * create the NodeProfAnalysis using reflection */ try { NodeProfAnalysis newAnalysis = (NodeProfAnalysis) Class.forName(analysisClass).getConstructor(Instrumenter.class, Env.class).newInstance(instrumenter, env); return newAnalysis; } catch (Exception e) { e.printStackTrace(); System.exit(-1); } return null; } /** * executed after constructor, could throw an exception */ public Object onLoad() throws Exception { return null; } public static void addAnalysis(NodeProfAnalysis analysis) { enabled[numAnalysis++] = analysis; } @TruffleBoundary public static void enableAnalysis(Instrumenter instrumenter, Env env, String analysisClass) { if (!analysisClass.isEmpty()) { NodeProfAnalysis analysis = null; if (analysisClass.contains("NodeProfJalangi")) { analysis = new NodeProfJalangi(instrumenter, env); try { analysis.onLoad(); } catch (Exception e) { Logger.error("error happens in loading the analysis " + analysisClass); e.printStackTrace(); } } else { HashMap<String, NodeProfAnalysis> instances = new HashMap<>(); for (Entry<String, NodeProfAnalysis> entry : instances.entrySet()) { if (analysisClass.equals(entry.getKey())) { analysis = entry.getValue(); break; } } if (analysis == null) { analysis = loadAnalysisFromClass(analysisClass, instrumenter, env); } try { analysis.onLoad(); } catch (Exception e) { Logger.error("error happens in loading the analysis " + analysisClass); e.printStackTrace(); System.exit(-1); } analysis.initCallbacks(); analysis.analysisReady(); } addAnalysis(analysis); } } public AnalysisFilterSourceList getFilter() { return AnalysisFilterSourceList.getDefault(); } /** * register callbacks when constructing the analysis if they are pre-known */ public abstract void initCallbacks(); /** * Clear all states of an analysis. For example, when we run the analysis for many rounds, we * need to reset all states so that the performance and results of the analysis won't get * affected. */ public abstract void onClear(); /** * Result dump. */ public abstract void printResult(); /** * Callback at the end of the execution for the analysis. * * first print the result and then clean the states */ @TruffleBoundary public void onDispose() { printResult(); onClear(); } /** * * @param result the result to be compared * @return true if no error found during the analysis */ public boolean checkResult(Object result) { return true; } /** * We cache the callbacks registered via NodeProfAnalysis.onCallback(), and enable them together * with onReady(). When multiple callbacks together are enabled together, the ordering is * guaranteed. */ private HashMap<ProfiledTagEnum, ArrayList<AnalysisFactory<BaseEventHandlerNode>>> handlers; @TruffleBoundary public void onCallback(ProfiledTagEnum e, AnalysisFactory<BaseEventHandlerNode> factory) { if (GlobalConfiguration.DEBUG) { Logger.debug("adding callback for tag " + e.getTag().getSimpleName()); } if (!handlers.containsKey(e)) { handlers.put(e, new ArrayList<AnalysisFactory<BaseEventHandlerNode>>()); } handlers.get(e).add(factory); return; } /** * enable all the callbacks not yet enabled using the default analysis filter. */ @TruffleBoundary public void analysisReady() { assert (getFilter() != null); analysisReady(getFilter(), handlers); } /** * enable all the callbacks not yet enabled using the provided source filter */ @TruffleBoundary public void analysisReady(AnalysisFilterBase filter) { analysisReady(filter, handlers); } // tags that require a separate factory for instrumentation private static final ProfiledTagEnum[] separateFactoryTags = { ProfiledTagEnum.BUILTIN, ProfiledTagEnum.STATEMENT, ProfiledTagEnum.EXPRESSION, ProfiledTagEnum.CF_BRANCH, ProfiledTagEnum.ROOT, ProfiledTagEnum.DECLARE }; @TruffleBoundary private void analysisReady(AnalysisFilterBase sourceFilter, HashMap<ProfiledTagEnum, ArrayList<AnalysisFactory<BaseEventHandlerNode>>> handlerMapping) { // check if any new callback is registered if (handlerMapping.size() > 0) { ArrayList<Class<? extends Tag>> definedTags = new ArrayList<>(); for (Entry<ProfiledTagEnum, ArrayList<AnalysisFactory<BaseEventHandlerNode>>> entry : handlerMapping.entrySet()) { entry.getKey().usedAnalysis++; if (!Arrays.asList(separateFactoryTags).contains(entry.getKey())) { definedTags.add(entry.getKey().getTag()); } } for (ProfiledTagEnum tag : separateFactoryTags) { if (handlerMapping.containsKey(tag)) { SourcePredicate sourcePredicate = tag.equals(ProfiledTagEnum.BUILTIN) ? AnalysisFilterSourceList.getFilter(ScopeEnum.builtin) : sourceFilter; SourceSectionFilter filter = SourceSectionFilter.newBuilder().tagIs(tag.getTag()).sourceIs(sourcePredicate).build(); getInstrumenter().attachExecutionEventFactory( filter, tag.getExpectedNumInputs() == 0 ? null : SourceSectionFilter.newBuilder().tagIs(StandardTags.ExpressionTag.class, InputNodeTag.class).build(), new ExecutionEventNodeFactory() { @TruffleBoundary public ExecutionEventNode create(EventContext context) { InstrumentableNode instrumentedNode = (InstrumentableNode) context.getInstrumentedNode(); if (instrumentedNode.hasTag(tag.getTag())) { return createAndSimplifyExecutionEventNode(context, tag, handlerMapping.get(tag)); } else { return new ExecutionEventNode() { }; } } }); } } if (definedTags.size() > 0) { Class<?>[] eventTags = new Class<?>[definedTags.size()]; definedTags.toArray(eventTags); SourceSectionFilter eventFilter = SourceSectionFilter.newBuilder().tagIs(eventTags).sourceIs(sourceFilter).build(); SourceSectionFilter inputFilter = SourceSectionFilter.newBuilder().tagIs(StandardTags.ExpressionTag.class, InputNodeTag.class).build(); getInstrumenter().attachExecutionEventFactory( eventFilter, inputFilter, new ExecutionEventNodeFactory() { @Override @TruffleBoundary public ExecutionEventNode create(EventContext context) { int count = 0; InstrumentableNode instrumentedNode = (InstrumentableNode) context.getInstrumentedNode(); for (Entry<ProfiledTagEnum, ArrayList<AnalysisFactory<BaseEventHandlerNode>>> entry : handlerMapping.entrySet()) { if (instrumentedNode.hasTag(entry.getKey().getTag()) && !Arrays.asList(separateFactoryTags).contains(entry.getKey())) { count += 1; } } // a node should never have two tags the same time(except // for the built-in) if (count > 1) { Logger.error("a node has more than 1 profiling tags!!"); String tags = ""; for (Entry<ProfiledTagEnum, ArrayList<AnalysisFactory<BaseEventHandlerNode>>> entry : handlerMapping.entrySet()) { if (instrumentedNode.hasTag(entry.getKey().getTag()) && !Arrays.asList(separateFactoryTags).contains(entry.getKey())) { tags += entry.getKey().getTag().getSimpleName() + " "; } } Logger.error(context.getInstrumentedSourceSection(), context.getInstrumentedNode().getClass().getName() + " has tags: " + tags); } assert (count <= 1); for (Entry<ProfiledTagEnum, ArrayList<AnalysisFactory<BaseEventHandlerNode>>> entry : handlerMapping.entrySet()) { try { ProfiledTagEnum key = entry.getKey(); Source source = context.getInstrumentedSourceSection().getSource(); if (instrumentedNode.hasTag(key.getTag()) && !Arrays.asList(separateFactoryTags).contains(entry.getKey()) && sourceFilter.testTag(source, key)) { return createAndSimplifyExecutionEventNode(context, entry.getKey(), entry.getValue()); } } catch (Exception exception) { exception.printStackTrace(); } } // if there is no handler for this node, return an empty // ExecutionEventNode which should bring zero overhead after // compilation return new ExecutionEventNode() { }; } }); } } this.handlers = new HashMap<>(); } /** * create the ExecutionEventNode with the proper handler * * @param context * @param key * @param listOfHandlers * @return a ProfilerExecutionEventNode or an empty ExecutionEventNode */ @TruffleBoundary private static ExecutionEventNode createAndSimplifyExecutionEventNode(EventContext context, ProfiledTagEnum key, ArrayList<AnalysisFactory<BaseEventHandlerNode>> listOfHandlers) { BaseEventHandlerNode handler = null; if (listOfHandlers.size() == 1) { // create the handler using the only factory handler = listOfHandlers.get(0).create(context); } else { // create the handlers one by one BaseEventHandlerNode[] components = new BaseEventHandlerNode[listOfHandlers.size()]; int numNonEmpty = 0; BaseEventHandlerNode last = null; for (int i = 0; i < components.length; i++) { components[i] = listOfHandlers.get(i).create(context); if (components[i] != null) { numNonEmpty++; last = components[i]; } } if (numNonEmpty == 0) { handler = null; } else if (numNonEmpty == 1) { handler = last; } else { BaseEventHandlerNode[] handlers = new BaseEventHandlerNode[numNonEmpty]; int cnt = 0; for (int i = 0; i < components.length; i++) { if (components[i] != null) { handlers[cnt++] = components[i]; } } handler = MultiEventHandler.create(key, handlers); } } if (handler != null) { return new ProfilerExecutionEventNode(key, context, handler); } else { return new ExecutionEventNode() { }; } } /** * a quick way to create instrumentatino for all events via a simple factory * * @param factory */ @TruffleBoundary public void onAllCallback(ExecutionEventNodeFactory factory) { onAllCallback(factory, getFilter()); } @TruffleBoundary public void onSingleTagCallback(Class<? extends Tag> tag, ExecutionEventNodeFactory factory) { onSingleTagCallback(tag, factory, getFilter()); } @TruffleBoundary public void onAllCallback(ExecutionEventNodeFactory factory, SourcePredicate sourcePredicate) { getInstrumenter().attachExecutionEventFactory( SourceSectionFilter.newBuilder().tagIs(ProfiledTagEnum.getTags()).sourceIs(sourcePredicate).build(), SourceSectionFilter.newBuilder().tagIs(StandardTags.ExpressionTag.class, JSTags.InputNodeTag.class).build(), factory); } @TruffleBoundary public void onSingleTagCallback(Class<? extends Tag> tag, ExecutionEventNodeFactory factory, SourcePredicate sourcePredicate) { getInstrumenter().attachExecutionEventFactory( SourceSectionFilter.newBuilder().tagIs(tag).sourceIs(sourcePredicate).build(), SourceSectionFilter.newBuilder().tagIs(StandardTags.ExpressionTag.class, JSTags.InputNodeTag.class).build(), factory); } }