[cfe-commits] [PATCH] AST plugin invocation before default action

MORITA Hajime morrita at gmail.com
Sat Apr 23 23:25:35 PDT 2011


Hi folks,

I read some discussions about AST modification using plugin on
cfe-dev, and read the code around FrontendAction and AST.

Now with some understanding what the limitations are, I think AST
modification will be useful even if with that limitations. So I tried
to write a patch for inserting plugin before the default action like
code generation.

Here is how:

 * Added PluginASTAction::GetASTConsumerOrder() to control the
   invocation order of that plugin.  The default is "BeforeDefault".

 * Changed FrontendAction::CreateWrappedASTConsumer() to recognize the
   order of each plugin. After this change, "BeforeDefault" plugin
   actions are listed before the default action.

   * Extracted PluginASTActionList for simplify the code.

This change also includes simple example called Multiplier, which
multiplies the return value of int value function with specified
number.

My goal is to allow plugins to insert some instrumentation function
calls. And we might need some more hooks for that type of
operation. But I think this is a good small first step for that.

Some other alternative options, like tree cloning (in functional
fashion) and injecting TreeTransform class, seem to need big change.
Thus that will be risky and controversial, at least in this stage.
I prefer a small start.
What do you think?
Any suggestions are appreciated!


Regards,
--
morrita

---------------------------
diff --git a/examples/Makefile b/examples/Makefile
index 8cb431d..3fbcdf1 100644
--- a/examples/Makefile
+++ b/examples/Makefile
@@ -9,6 +9,6 @@

 CLANG_LEVEL := ..

-PARALLEL_DIRS := clang-interpreter PrintFunctionNames
+PARALLEL_DIRS := clang-interpreter PrintFunctionNames Multiplier

 include $(CLANG_LEVEL)/Makefile
diff --git a/examples/Multiplier/CMakeLists.txt
b/examples/Multiplier/CMakeLists.txt
new file mode 100644
index 0000000..2c008fe
--- /dev/null
+++ b/examples/Multiplier/CMakeLists.txt
@@ -0,0 +1,15 @@
+set(MODULE TRUE)
+
+set( LLVM_USED_LIBS
+  clangFrontend
+  clangAST
+  )
+
+set( LLVM_LINK_COMPONENTS support mc)
+
+add_clang_library(Multiple Multiple.cpp)
+
+set_target_properties(Multiple
+  PROPERTIES
+  LINKER_LANGUAGE CXX
+  PREFIX "")
diff --git a/examples/Multiplier/Makefile b/examples/Multiplier/Makefile
new file mode 100644
index 0000000..3cf35a6
--- /dev/null
+++ b/examples/Multiplier/Makefile
@@ -0,0 +1,28 @@
+##===- examples/PrintFunctionNames/Makefile ----------------*-
Makefile -*-===##
+#
+#                     The LLVM Compiler Infrastructure
+#
+# This file is distributed under the University of Illinois Open Source
+# License. See LICENSE.TXT for details.
+#
+##===----------------------------------------------------------------------===##
+
+CLANG_LEVEL := ../..
+LIBRARYNAME = Multiplier
+
+# If we don't need RTTI or EH, there's no reason to export anything
+# from the plugin.
+ifneq ($(REQUIRES_RTTI), 1)
+ifneq ($(REQUIRES_EH), 1)
+EXPORTED_SYMBOL_FILE = $(PROJ_SRC_DIR)/Multiplier.exports
+endif
+endif
+
+LINK_LIBS_IN_SHARED = 0
+SHARED_LIBRARY = 1
+
+include $(CLANG_LEVEL)/Makefile
+
+ifeq ($(OS),Darwin)
+  LDFLAGS=-Wl,-undefined,dynamic_lookup
+endif
diff --git a/examples/Multiplier/Multiplier.cpp
b/examples/Multiplier/Multiplier.cpp
new file mode 100644
index 0000000..41f065c
--- /dev/null
+++ b/examples/Multiplier/Multiplier.cpp
@@ -0,0 +1,131 @@
+//===- PrintFunctionNames.cpp
---------------------------------------------===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//
+// Example clang plugin which simply prints the names of all the
top-level decls
+// in the input file.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Frontend/FrontendPluginRegistry.h"
+#include "clang/AST/ASTConsumer.h"
+#include "clang/AST/AST.h"
+#include "clang/AST/DeclVisitor.h"
+#include "clang/AST/StmtVisitor.h"
+#include "clang/Frontend/CompilerInstance.h"
+#include "llvm/Support/raw_ostream.h"
+using namespace clang;
+
+namespace {
+
+class MultiplierVisitor : public DeclVisitor<MultiplierVisitor>,
+                          public StmtVisitor<MultiplierVisitor>
+{
+private:
+  ASTContext& Context;
+  int Scale;
+
+public:
+  MultiplierVisitor(ASTContext& C, int S) : Context(C), Scale(S) {}
+
+  bool isAcceptable(const QualType& T) const {
+    return T.getTypePtr()->isIntegerType();
+  }
+
+  void VisitFunctionDecl(FunctionDecl *ND) {
+    Stmt* B = ND->getBody();
+    if (B && isAcceptable(ND->getResultType())) {
+      for (Stmt::child_iterator SI = B->child_begin(); SI !=
B->child_end(); ++SI) {
+        static_cast<StmtVisitor<MultiplierVisitor>*>(this)->Visit(*SI);
+      }
+    }
+  }
+
+  void VisitReturnStmt(ReturnStmt *Node) {
+    Expr* OriginalRet = Node->getRetValue();
+    llvm::APInt ScaleValue(32, Scale, 10);
+    Expr* RHS = new (Context) IntegerLiteral(Context, ScaleValue,
OriginalRet->getType(), SourceLocation());
+    BinaryOperator* NewRet =
+      new (Context) BinaryOperator(OriginalRet, RHS, BO_Mul,
+                                   OriginalRet->getType(),
+                                   VK_RValue, OK_Ordinary,
+                                   SourceLocation());
+    Node->setRetValue(NewRet);
+  }
+};
+
+class MultiplierConsumer : public ASTConsumer {
+  ASTContext* Context;
+  int Scale;
+
+public:
+  virtual void Initialize(ASTContext &C) {
+    Context = &C;
+  }
+
+  virtual void HandleTopLevelDecl(DeclGroupRef DG) {
+    MultiplierVisitor V(*Context, Scale);
+    for (DeclGroupRef::iterator i = DG.begin(), e = DG.end(); i != e; ++i) {
+      static_cast<DeclVisitor<MultiplierVisitor>&>(V).Visit(*i);
+    }
+  }
+
+  explicit MultiplierConsumer(int S) : Scale(S) {}
+};
+
+
+class MultiplierAction : public PluginASTAction {
+  int Scale;
+protected:
+  ASTConsumer *CreateASTConsumer(CompilerInstance &CI, llvm::StringRef) {
+    return new MultiplierConsumer(Scale);
+  }
+
+  bool ParseArgs(const CompilerInstance &CI,
+                 const std::vector<std::string>& args) {
+    if (0 == args.size()) {
+      Diagnostic &D = CI.getDiagnostics();
+      unsigned DiagID = D.getCustomDiagID(
+        Diagnostic::Error, "Need scale argument");
+      D.Report(DiagID);
+      return false;
+    }
+
+    if (args[0] == "help") {
+      PrintHelp(llvm::errs());
+      return false;
+    }
+
+    llvm::StringRef FirstArg(args[0]);
+    if (FirstArg.getAsInteger(10, Scale)) {
+      Diagnostic &D = CI.getDiagnostics();
+      unsigned DiagID = D.getCustomDiagID(
+        Diagnostic::Error, "Invalid argument '" + args[0] + "'");
+      D.Report(DiagID);
+      return false;
+    }
+
+    return true;
+  }
+
+  virtual ConsumerOrderKind GetASTConsumerOrder() {
+    return PluginASTAction::BeforeDefault;
+  }
+
+  void PrintHelp(llvm::raw_ostream& ros) {
+    ros << "This plugin explains the most simple AST modification\n";
+  }
+
+public:
+  MultiplierAction() : Scale(0) {}
+};
+
+}
+
+static FrontendPluginRegistry::Add<MultiplierAction>
+X("multiply", "multiply integer return values");
diff --git a/examples/Multiplier/Multiplier.exports
b/examples/Multiplier/Multiplier.exports
new file mode 100644
index 0000000..0ff590d
--- /dev/null
+++ b/examples/Multiplier/Multiplier.exports
@@ -0,0 +1 @@
+_ZN4llvm8Registry*
diff --git a/examples/Multiplier/README.txt b/examples/Multiplier/README.txt
new file mode 100644
index 0000000..7ea0e1a
--- /dev/null
+++ b/examples/Multiplier/README.txt
@@ -0,0 +1,11 @@
+This is another simple example demonstrating how to use clang's facility for
+providing AST consumers using a plugin.
+
+Build the plugin by running `make` in this directory.
+
+Once the plugin is built, you can run it using:
+--
+Linux:
+$ clang -Xclang -load -Xclang
../../Debug+Asserts/lib/libMultiplier.so -Xclang -add-plugin -Xclang
multiply -Xclang -plugin-arg-multiply -Xclang 42
+Mac:
+$ clang -Xclang -load -Xclang
../../Debug+Asserts/lib/libMultiplier.dylib -Xclang -add-plugin
-Xclang multiply -Xclang -plugin-arg-multiply -Xclang 42
diff --git a/include/clang/Frontend/FrontendAction.h
b/include/clang/Frontend/FrontendAction.h
index ee0863a..67f7b65 100644
--- a/include/clang/Frontend/FrontendAction.h
+++ b/include/clang/Frontend/FrontendAction.h
@@ -225,11 +225,16 @@ public:
 };

 class PluginASTAction : public ASTFrontendAction {
-protected:
+public:
+  /// ConsumerOrderKind - Specifies the invocation order of the ASTConsumer.
+  enum ConsumerOrderKind {
+    BeforeDefault, ///< The consumer is invoked before the default consumer.
+    AfterDefault   ///< The consumer is invoked before the default consumer.
+  };
+
   virtual ASTConsumer *CreateASTConsumer(CompilerInstance &CI,
                                          llvm::StringRef InFile) = 0;

-public:
   /// ParseArgs - Parse the given plugin command line arguments.
   ///
   /// \param CI - The compiler instance, for use in reporting diagnostics.
@@ -238,6 +243,18 @@ public:
   /// CompilerInstance's Diagnostic object to report errors.
   virtual bool ParseArgs(const CompilerInstance &CI,
                          const std::vector<std::string> &arg) = 0;
+
+  /// GetASTConsumerOrder - Determines the invocation order for a
consumer which
+  /// is created by CreateASTConsumer() method.  ASTConsumers which are invoked
+  /// earlier can affect AST observed by later one.
+  ///
+  /// Note that ASTConsumer invocation is one-pass, thus there are limitations
+  /// for the AST modification: You can make some kind of local modification
+  /// like modifying statements inside the given function. But you cannot make
+  /// global changes including global function addition or removal.
+  /// Also note that Type object modification is not possible because Type
+  /// objects are immutable.
+  virtual ConsumerOrderKind GetASTConsumerOrder() { return AfterDefault; }
 };

 /// PreprocessorFrontendAction - Abstract base class to use for preprocessor
diff --git a/lib/Frontend/FrontendAction.cpp b/lib/Frontend/FrontendAction.cpp
index 42da44c..206e754 100644
--- a/lib/Frontend/FrontendAction.cpp
+++ b/lib/Frontend/FrontendAction.cpp
@@ -76,6 +76,56 @@ public:
     }
 };

+/// \Manages the lifetime of list of PluginASTAction instances
+class PluginASTActionList {
+  typedef std::vector<PluginASTAction*> ActionList;
+  typedef std::vector<ASTConsumer*> ConsumerList;
+
+  ActionList Actions;
+  CompilerInstance& CI;
+  llvm::StringRef InFile;
+
+public:
+  PluginASTActionList(FrontendPluginRegistry::iterator begin,
+                      FrontendPluginRegistry::iterator end,
+                      CompilerInstance &CI,
+                      llvm::StringRef InFile)
+    : CI(CI), InFile(InFile) {
+    for (size_t i = 0, e = CI.getFrontendOpts().AddPluginActions.size();
+         i != e; ++i) {
+      for (FrontendPluginRegistry::iterator it = begin;
+           it != end; ++it) {
+        if (it->getName() == CI.getFrontendOpts().AddPluginActions[i]) {
+          llvm::OwningPtr<PluginASTAction> P(it->instantiate());
+          if (P->ParseArgs(CI, CI.getFrontendOpts().AddPluginArgs[i]))
+            Actions.push_back(P.take());
+        }
+      }
+    }
+  }
+
+  ~PluginASTActionList() {
+    for (size_t i = 0; i < Actions.size(); ++i)
+      delete Actions[i];
+  }
+
+  ConsumerList CreateASTConsumers(ASTConsumer* DefaultConsumer) {
+    ConsumerList Consumers;
+
+    for (ActionList::iterator it = Actions.begin(), ie = Actions.end();
+         it != ie; ++it) {
+      PluginASTAction& A = (**it);
+        if (A.GetASTConsumerOrder() == PluginASTAction::BeforeDefault)
+          Consumers.push_back(A.CreateASTConsumer(CI, InFile));
+        Consumers.push_back(DefaultConsumer);
+        if (A.GetASTConsumerOrder() == PluginASTAction::AfterDefault)
+          Consumers.push_back(A.CreateASTConsumer(CI, InFile));
+    }
+
+    return Consumers;
+  }
+};
+
 } // end anonymous namespace

 FrontendAction::FrontendAction() : Instance(0) {}
@@ -98,28 +148,10 @@ ASTConsumer*
FrontendAction::CreateWrappedASTConsumer(CompilerInstance &CI,
   if (CI.getFrontendOpts().AddPluginActions.size() == 0)
     return Consumer;

-  // Make sure the non-plugin consumer is first, so that plugins can't
-  // modifiy the AST.
-  std::vector<ASTConsumer*> Consumers(1, Consumer);
-
-  for (size_t i = 0, e = CI.getFrontendOpts().AddPluginActions.size();
-       i != e; ++i) {
-    // This is O(|plugins| * |add_plugins|), but since both numbers are
-    // way below 50 in practice, that's ok.
-    for (FrontendPluginRegistry::iterator
-        it = FrontendPluginRegistry::begin(),
-        ie = FrontendPluginRegistry::end();
-        it != ie; ++it) {
-      if (it->getName() == CI.getFrontendOpts().AddPluginActions[i]) {
-        llvm::OwningPtr<PluginASTAction> P(it->instantiate());
-        FrontendAction* c = P.get();
-        if (P->ParseArgs(CI, CI.getFrontendOpts().AddPluginArgs[i]))
-          Consumers.push_back(c->CreateASTConsumer(CI, InFile));
-      }
-    }
-  }
-
-  return new MultiplexConsumer(Consumers);
+  PluginASTActionList L(FrontendPluginRegistry::begin(),
+                        FrontendPluginRegistry::end(),
+                        CI, InFile);
+  return new MultiplexConsumer(L.CreateASTConsumers(Consumer));
 }

 bool FrontendAction::BeginSourceFile(CompilerInstance &CI,



More information about the cfe-commits mailing list