Commit ecc3cd25 authored by Camillo Bruni's avatar Camillo Bruni Committed by V8 LUCI CQ

[tools] Improve gcmole part II

Prepare gcmole.cc for the next update:
- Print possible GC locations when discovering stale/dead variables
- Make error messages less confusing for the modern V8 engineer
- Prepare gcmole to read suspects.allowlist instead of .whitelist
- Use more readable variable names
- Only log non-found types with --verbose
- Change the currently unusued gccauses format in gcmole.py and
  support loading it back in gcmole.cc
- Implemented first basic gc call-chain printing (disabled by default)

GCmole packaging:
- Add debug mode to bootstrap.sh build script
- Update gcmole.py run instructions in bootstrap.sh and package.sh

Bug: v8:10009
Change-Id: I369d48baa2980455d2e8f57e7a803d0384fe83f1
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3480095Reviewed-by: 's avatarJakob Kummerow <jkummerow@chromium.org>
Reviewed-by: 's avatarMaya Lekova <mslekova@chromium.org>
Commit-Queue: Camillo Bruni <cbruni@chromium.org>
Cr-Commit-Position: refs/heads/main@{#79357}
parent 8e18ea39
...@@ -2293,12 +2293,20 @@ struct borrowed_vec { ...@@ -2293,12 +2293,20 @@ struct borrowed_vec {
// Vectors // Vectors
#ifdef V8_GC_MOLE
#define ASSERT_VEC_BASE_SIZE(name, Name, vec, ptr_or_none)
#else
#define ASSERT_VEC_BASE_SIZE(name, Name, vec, ptr_or_none) \
static_assert(sizeof(wasm_##name##_vec_t) == sizeof(vec<Name>), \
"C/C++ incompatibility"); \
static_assert( \
sizeof(wasm_##name##_t ptr_or_none) == sizeof(vec<Name>::elem_type), \
"C/C++ incompatibility");
#endif
#define WASM_DEFINE_VEC_BASE(name, Name, vec, ptr_or_none) \ #define WASM_DEFINE_VEC_BASE(name, Name, vec, ptr_or_none) \
static_assert(sizeof(wasm_##name##_vec_t) == sizeof(vec<Name>), \ ASSERT_VEC_BASE_SIZE(name, Name, vec, ptr_or_none) \
"C/C++ incompatibility"); \
static_assert( \
sizeof(wasm_##name##_t ptr_or_none) == sizeof(vec<Name>::elem_type), \
"C/C++ incompatibility"); \
extern "C++" inline auto hide_##name##_vec(vec<Name>& v) \ extern "C++" inline auto hide_##name##_vec(vec<Name>& v) \
->wasm_##name##_vec_t* { \ ->wasm_##name##_vec_t* { \
return reinterpret_cast<wasm_##name##_vec_t*>(&v); \ return reinterpret_cast<wasm_##name##_vec_t*>(&v); \
......
...@@ -495,7 +495,9 @@ class V8_EXPORT_PRIVATE WasmCode final { ...@@ -495,7 +495,9 @@ class V8_EXPORT_PRIVATE WasmCode final {
// often for rather small functions. // often for rather small functions.
// Increase the limit if needed, but first check if the size increase is // Increase the limit if needed, but first check if the size increase is
// justified. // justified.
#ifndef V8_GC_MOLE
STATIC_ASSERT(sizeof(WasmCode) <= 88); STATIC_ASSERT(sizeof(WasmCode) <= 88);
#endif
WasmCode::Kind GetCodeKind(const WasmCompilationResult& result); WasmCode::Kind GetCodeKind(const WasmCompilationResult& result);
......
...@@ -32,11 +32,18 @@ LLVM_BUILD_INCLUDE:=$(BUILD_ROOT)/include ...@@ -32,11 +32,18 @@ LLVM_BUILD_INCLUDE:=$(BUILD_ROOT)/include
CLANG_SRC_INCLUDE:=$(CLANG_SRC_ROOT)/include CLANG_SRC_INCLUDE:=$(CLANG_SRC_ROOT)/include
CLANG_BUILD_INCLUDE:=$(BUILD_ROOT)/tools/clang/include CLANG_BUILD_INCLUDE:=$(BUILD_ROOT)/tools/clang/include
CXXFLAGS = -O3 -g3
all: libgcmole.so
Release: libgcmole.so
Debug: CXXFLAGS = -O1 -DDEBUG -g
Debug: libgcmole.so
libgcmole.so: gcmole.cc libgcmole.so: gcmole.cc
$(CXX) -I$(LLVM_BUILD_INCLUDE) -I$(LLVM_SRC_INCLUDE) \ $(CXX) -I$(LLVM_BUILD_INCLUDE) -I$(LLVM_SRC_INCLUDE) \
-I$(CLANG_BUILD_INCLUDE) -I$(CLANG_SRC_INCLUDE) -I. -D_DEBUG \ -I$(CLANG_BUILD_INCLUDE) -I$(CLANG_SRC_INCLUDE) -I. ${CXXFLAGS} \
-D_GNU_SOURCE -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS \ -D_GNU_SOURCE -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS \
-D__STDC_LIMIT_MACROS -O3 -fomit-frame-pointer -fno-exceptions \ -D__STDC_LIMIT_MACROS -fomit-frame-pointer -fno-exceptions \
-fno-rtti -fPIC -Woverloaded-virtual -Wcast-qual -fno-strict-aliasing \ -fno-rtti -fPIC -Woverloaded-virtual -Wcast-qual -fno-strict-aliasing \
-pedantic -Wno-long-long -Wall -W -Wno-unused-parameter \ -pedantic -Wno-long-long -Wall -W -Wno-unused-parameter \
-Wwrite-strings -static-libstdc++ -std=c++0x -shared -o libgcmole.so \ -Wwrite-strings -static-libstdc++ -std=c++0x -shared -o libgcmole.so \
......
...@@ -109,7 +109,7 @@ script "bootstrap.sh" mentioned above). ...@@ -109,7 +109,7 @@ script "bootstrap.sh" mentioned above).
TROUBLESHOOTING --------------------------------------------------------------- TROUBLESHOOTING ---------------------------------------------------------------
gcmole is tighly coupled with the AST structure that Clang produces. Therefore gcmole is tightly coupled with the AST structure that Clang produces. Therefore
when upgrading to a newer Clang version, it might start producing bogus output when upgrading to a newer Clang version, it might start producing bogus output
or completely stop outputting warnings. In such occasion, one might start the or completely stop outputting warnings. In such occasion, one might start the
debugging process by checking weather a new AST node type is introduced which debugging process by checking weather a new AST node type is introduced which
......
...@@ -35,6 +35,8 @@ ...@@ -35,6 +35,8 @@
LLVM_RELEASE=9.0.1 LLVM_RELEASE=9.0.1
BUILD_TYPE="Release"
# BUILD_TYPE="Debug"
THIS_DIR="$(readlink -f "$(dirname "${0}")")" THIS_DIR="$(readlink -f "$(dirname "${0}")")"
LLVM_PROJECT_DIR="${THIS_DIR}/bootstrap/llvm" LLVM_PROJECT_DIR="${THIS_DIR}/bootstrap/llvm"
BUILD_DIR="${THIS_DIR}/bootstrap/build" BUILD_DIR="${THIS_DIR}/bootstrap/build"
...@@ -99,29 +101,35 @@ if [ ! -e "${BUILD_DIR}" ]; then ...@@ -99,29 +101,35 @@ if [ ! -e "${BUILD_DIR}" ]; then
fi fi
cd "${BUILD_DIR}" cd "${BUILD_DIR}"
cmake -GNinja -DCMAKE_CXX_FLAGS="-static-libstdc++" -DLLVM_ENABLE_TERMINFO=OFF \ cmake -GNinja -DCMAKE_CXX_FLAGS="-static-libstdc++" -DLLVM_ENABLE_TERMINFO=OFF \
-DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_PROJECTS=clang \ -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DLLVM_ENABLE_PROJECTS=clang \
-DLLVM_ENABLE_Z3_SOLVER=OFF "${LLVM_PROJECT_DIR}/llvm" -DLLVM_ENABLE_Z3_SOLVER=OFF "${LLVM_PROJECT_DIR}/llvm"
MACOSX_DEPLOYMENT_TARGET=10.5 ninja -j"${NUM_JOBS}" MACOSX_DEPLOYMENT_TARGET=10.5 ninja -j"${NUM_JOBS}" clang
# Strip the clang binary. if [[ "${BUILD_TYPE}" = "Release" ]]; then
STRIP_FLAGS= # Strip the clang binary.
if [ "${OS}" = "Darwin" ]; then STRIP_FLAGS=
# See http://crbug.com/256342 if [ "${OS}" = "Darwin" ]; then
STRIP_FLAGS=-x # See http://crbug.com/256342
STRIP_FLAGS=-x
fi
strip ${STRIP_FLAGS} bin/clang
fi fi
strip ${STRIP_FLAGS} bin/clang
cd - cd -
# Build libgcmole.so # Build libgcmole.so
make -C "${THIS_DIR}" clean make -C "${THIS_DIR}" clean
make -C "${THIS_DIR}" LLVM_SRC_ROOT="${LLVM_PROJECT_DIR}/llvm" \ make -C "${THIS_DIR}" LLVM_SRC_ROOT="${LLVM_PROJECT_DIR}/llvm" \
CLANG_SRC_ROOT="${LLVM_PROJECT_DIR}/clang" \ CLANG_SRC_ROOT="${LLVM_PROJECT_DIR}/clang" \
BUILD_ROOT="${BUILD_DIR}" libgcmole.so BUILD_ROOT="${BUILD_DIR}" $BUILD_TYPE
set +x set +x
echo echo '#########################################################################'
echo You can now run gcmole using this command: echo 'Congratulations you compiled clang and libgcmole.so'
echo echo
echo CLANG_BIN=\"tools/gcmole/gcmole-tools/bin\" python tools/gcmole/gcmole.py echo '# You can now run gcmole:'
echo 'tools/gcmole/gcmole.py \'
echo ' --clang-bin-dir="tools/gcmole/bootstrap/build/bin" \'
echo ' --clang-plugins-dir="tools/gcmole" \'
echo ' --v8-target-cpu=$CPU'
echo echo
...@@ -48,6 +48,8 @@ namespace { ...@@ -48,6 +48,8 @@ namespace {
bool g_tracing_enabled = false; bool g_tracing_enabled = false;
bool g_dead_vars_analysis = false; bool g_dead_vars_analysis = false;
bool g_verbose = false;
bool g_print_gc_call_chain = false;
#define TRACE(str) \ #define TRACE(str) \
do { \ do { \
...@@ -80,16 +82,13 @@ typedef std::map<MangledName, MangledName> CalleesMap; ...@@ -80,16 +82,13 @@ typedef std::map<MangledName, MangledName> CalleesMap;
static bool GetMangledName(clang::MangleContext* ctx, static bool GetMangledName(clang::MangleContext* ctx,
const clang::NamedDecl* decl, const clang::NamedDecl* decl,
MangledName* result) { MangledName* result) {
if (!llvm::isa<clang::CXXConstructorDecl>(decl) && if (llvm::isa<clang::CXXConstructorDecl>(decl)) return false;
!llvm::isa<clang::CXXDestructorDecl>(decl)) { if (llvm::isa<clang::CXXDestructorDecl>(decl)) return false;
llvm::SmallVector<char, 512> output; llvm::SmallVector<char, 512> output;
llvm::raw_svector_ostream out(output); llvm::raw_svector_ostream out(output);
ctx->mangleName(decl, out); ctx->mangleName(decl, out);
*result = out.str().str(); *result = out.str().str();
return true; return true;
}
return false;
} }
...@@ -217,8 +216,7 @@ struct Resolver { ...@@ -217,8 +216,7 @@ struct Resolver {
class CalleesPrinter : public clang::RecursiveASTVisitor<CalleesPrinter> { class CalleesPrinter : public clang::RecursiveASTVisitor<CalleesPrinter> {
public: public:
explicit CalleesPrinter(clang::MangleContext* ctx) : ctx_(ctx) { explicit CalleesPrinter(clang::MangleContext* ctx) : ctx_(ctx) {}
}
virtual bool VisitCallExpr(clang::CallExpr* expr) { virtual bool VisitCallExpr(clang::CallExpr* expr) {
const clang::FunctionDecl* callee = expr->getDirectCallee(); const clang::FunctionDecl* callee = expr->getDirectCallee();
...@@ -236,17 +234,17 @@ class CalleesPrinter : public clang::RecursiveASTVisitor<CalleesPrinter> { ...@@ -236,17 +234,17 @@ class CalleesPrinter : public clang::RecursiveASTVisitor<CalleesPrinter> {
} }
void AnalyzeFunction(const clang::FunctionDecl* f) { void AnalyzeFunction(const clang::FunctionDecl* f) {
if (!InV8Namespace(f)) return;
MangledName name; MangledName name;
if (InV8Namespace(f) && GetMangledName(ctx_, f, &name)) { if (!GetMangledName(ctx_, f, &name)) return;
const std::string& function = f->getNameAsString(); const std::string& function = f->getNameAsString();
AddCallee(name, function); AddCallee(name, function);
const clang::FunctionDecl* body = NULL; const clang::FunctionDecl* body = NULL;
if (f->hasBody(body) && !Analyzed(name)) { if (f->hasBody(body) && !Analyzed(name)) {
EnterScope(name); EnterScope(name);
TraverseStmt(body->getBody()); TraverseStmt(body->getBody());
LeaveScope(); LeaveScope();
}
} }
} }
...@@ -303,17 +301,18 @@ class FunctionDeclarationFinder ...@@ -303,17 +301,18 @@ class FunctionDeclarationFinder
: public clang::ASTConsumer, : public clang::ASTConsumer,
public clang::RecursiveASTVisitor<FunctionDeclarationFinder> { public clang::RecursiveASTVisitor<FunctionDeclarationFinder> {
public: public:
explicit FunctionDeclarationFinder(clang::DiagnosticsEngine& d, explicit FunctionDeclarationFinder(
clang::SourceManager& sm, clang::DiagnosticsEngine& diagnostics_engine,
const std::vector<std::string>& args) clang::SourceManager& source_manager,
: d_(d), sm_(sm) {} const std::vector<std::string>& args)
: diagnostics_engine_(diagnostics_engine),
source_manager_(source_manager) {}
virtual void HandleTranslationUnit(clang::ASTContext &ctx) { virtual void HandleTranslationUnit(clang::ASTContext &ctx) {
mangle_context_ = clang::ItaniumMangleContext::create(ctx, d_); mangle_context_ =
clang::ItaniumMangleContext::create(ctx, diagnostics_engine_);
callees_printer_ = new CalleesPrinter(mangle_context_); callees_printer_ = new CalleesPrinter(mangle_context_);
TraverseDecl(ctx.getTranslationUnitDecl()); TraverseDecl(ctx.getTranslationUnitDecl());
callees_printer_->PrintCallGraph(); callees_printer_->PrintCallGraph();
} }
...@@ -323,8 +322,8 @@ class FunctionDeclarationFinder ...@@ -323,8 +322,8 @@ class FunctionDeclarationFinder
} }
private: private:
clang::DiagnosticsEngine& d_; clang::DiagnosticsEngine& diagnostics_engine_;
clang::SourceManager& sm_; clang::SourceManager& source_manager_;
clang::MangleContext* mangle_context_; clang::MangleContext* mangle_context_;
CalleesPrinter* callees_printer_; CalleesPrinter* callees_printer_;
...@@ -333,8 +332,39 @@ class FunctionDeclarationFinder ...@@ -333,8 +332,39 @@ class FunctionDeclarationFinder
static bool gc_suspects_loaded = false; static bool gc_suspects_loaded = false;
static CalleesSet gc_suspects; static CalleesSet gc_suspects;
static CalleesSet gc_functions; static CalleesSet gc_functions;
static bool whitelist_loaded = false;
static CalleesSet suspects_whitelist; static bool allowlist_loaded = false;
static CalleesSet suspects_allowlist;
static bool gc_causes_loaded = false;
static std::map<MangledName, std::vector<MangledName>> gc_causes;
static void LoadGCCauses() {
if (gc_causes_loaded) return;
std::ifstream fin("gccauses");
std::string mangled, function;
while (!fin.eof()) {
std::getline(fin, mangled, ',');
std::getline(fin, function);
if (mangled.empty()) break;
std::string parent = mangled;
// start,nested
std::getline(fin, mangled, ',');
assert(mangled.compare("start") == 0);
std::getline(fin, function);
assert(function.compare("nested") == 0);
while (true) {
std::getline(fin, mangled, ',');
std::getline(fin, function);
if (mangled.compare("end") == 0) {
assert(function.compare("nested") == 0);
break;
}
gc_causes[parent].push_back(mangled);
}
}
gc_causes_loaded = true;
}
static void LoadGCSuspects() { static void LoadGCSuspects() {
if (gc_suspects_loaded) return; if (gc_suspects_loaded) return;
...@@ -352,55 +382,51 @@ static void LoadGCSuspects() { ...@@ -352,55 +382,51 @@ static void LoadGCSuspects() {
gc_suspects_loaded = true; gc_suspects_loaded = true;
} }
static void LoadSuspectsWhitelist() { static void LoadSuspectsAllowList() {
if (whitelist_loaded) return; if (allowlist_loaded) return;
std::ifstream fin("tools/gcmole/suspects.whitelist"); // TODO(cbruni): clean up once fully migrated
std::ifstream fin("tools/gcmole/suspects.allowlist");
if (!fin.is_open()) {
fin = std::ifstream("tools/gcmole/suspects.whitelist");
}
std::string s; std::string s;
while (fin >> s) suspects_whitelist.insert(s); while (fin >> s) suspects_allowlist.insert(s);
whitelist_loaded = true; allowlist_loaded = true;
} }
// Looks for exact match of the mangled name. // Looks for exact match of the mangled name.
static bool KnownToCauseGC(clang::MangleContext* ctx, static bool IsKnownToCauseGC(clang::MangleContext* ctx,
const clang::FunctionDecl* decl) { const clang::FunctionDecl* decl) {
LoadGCSuspects(); LoadGCSuspects();
if (!InV8Namespace(decl)) return false; if (!InV8Namespace(decl)) return false;
if (suspects_allowlist.find(decl->getNameAsString()) !=
if (suspects_whitelist.find(decl->getNameAsString()) != suspects_allowlist.end()) {
suspects_whitelist.end()) {
return false; return false;
} }
MangledName name; MangledName name;
if (GetMangledName(ctx, decl, &name)) { if (GetMangledName(ctx, decl, &name)) {
return gc_suspects.find(name) != gc_suspects.end(); return gc_suspects.find(name) != gc_suspects.end();
} }
return false; return false;
} }
// Looks for partial match of only the function name. // Looks for partial match of only the function name.
static bool SuspectedToCauseGC(clang::MangleContext* ctx, static bool IsSuspectedToCauseGC(clang::MangleContext* ctx,
const clang::FunctionDecl* decl) { const clang::FunctionDecl* decl) {
LoadGCSuspects(); LoadGCSuspects();
if (!InV8Namespace(decl)) return false; if (!InV8Namespace(decl)) return false;
LoadSuspectsAllowList();
LoadSuspectsWhitelist(); if (suspects_allowlist.find(decl->getNameAsString()) !=
if (suspects_whitelist.find(decl->getNameAsString()) != suspects_allowlist.end()) {
suspects_whitelist.end()) {
return false; return false;
} }
if (gc_functions.find(decl->getNameAsString()) != gc_functions.end()) { if (gc_functions.find(decl->getNameAsString()) != gc_functions.end()) {
TRACE_LLVM_DECL("Suspected by ", decl); TRACE_LLVM_DECL("Suspected by ", decl);
return true; return true;
} }
return false; return false;
} }
...@@ -449,10 +475,9 @@ class ExprEffect { ...@@ -449,10 +475,9 @@ class ExprEffect {
intptr_t effect_; intptr_t effect_;
}; };
const std::string BAD_EXPR_MSG(
const std::string BAD_EXPR_MSG("Possible problem with evaluation order."); "Possible problem with evaluation order with interleaved GCs.");
const std::string DEAD_VAR_MSG("Possibly dead variable."); const std::string DEAD_VAR_MSG("Possibly stale variable due to GCs.");
class Environment { class Environment {
public: public:
...@@ -612,22 +637,16 @@ class CallProps { ...@@ -612,22 +637,16 @@ class CallProps {
ExprEffect ComputeCumulativeEffect(bool result_is_raw) { ExprEffect ComputeCumulativeEffect(bool result_is_raw) {
ExprEffect out = ExprEffect::NoneWithEnv(env_); ExprEffect out = ExprEffect::NoneWithEnv(env_);
if (gc_.any()) { if (gc_.any()) out.setGC();
out.setGC();
}
if (raw_use_.any()) out.setRawUse(); if (raw_use_.any()) out.setRawUse();
if (result_is_raw) out.setRawDef(); if (result_is_raw) out.setRawDef();
return out; return out;
} }
bool IsSafe() { bool IsSafe() {
if (!gc_.any()) { if (!gc_.any()) return true;
return true;
}
std::bitset<kMaxNumberOfArguments> raw = (raw_def_ | raw_use_); std::bitset<kMaxNumberOfArguments> raw = (raw_def_ | raw_use_);
if (!raw.any()) { if (!raw.any()) return true;
return true;
}
bool result = gc_.count() == 1 && !((raw ^ gc_).any()); bool result = gc_.count() == 1 && !((raw ^ gc_).any());
return result; return result;
} }
...@@ -950,13 +969,10 @@ class FunctionAnalyzer { ...@@ -950,13 +969,10 @@ class FunctionAnalyzer {
ExprEffect Parallel(clang::Expr* parent, int n, clang::Expr** exprs, ExprEffect Parallel(clang::Expr* parent, int n, clang::Expr** exprs,
const Environment& env) { const Environment& env) {
CallProps props; CallProps props;
for (int i = 0; i < n; ++i) { for (int i = 0; i < n; ++i) {
props.SetEffect(i, VisitExpr(exprs[i], env)); props.SetEffect(i, VisitExpr(exprs[i], env));
} }
if (!props.IsSafe()) ReportUnsafe(parent, BAD_EXPR_MSG); if (!props.IsSafe()) ReportUnsafe(parent, BAD_EXPR_MSG);
return props.ComputeCumulativeEffect( return props.ComputeCumulativeEffect(
RepresentsRawPointerType(parent->getType())); RepresentsRawPointerType(parent->getType()));
} }
...@@ -984,27 +1000,24 @@ class FunctionAnalyzer { ...@@ -984,27 +1000,24 @@ class FunctionAnalyzer {
const clang::QualType& var_type, const clang::QualType& var_type,
const std::string& var_name, const std::string& var_name,
const Environment& env) { const Environment& env) {
if (RepresentsRawPointerType(var_type)) { if (!g_dead_vars_analysis) return ExprEffect::None();
// We currently care only about our internal pointer types and not about if (!RepresentsRawPointerType(var_type)) return ExprEffect::None();
// raw C++ pointers, because normally special care is taken when storing // We currently care only about our internal pointer types and not about
// raw pointers to the managed heap. Furthermore, checking for raw // raw C++ pointers, because normally special care is taken when storing
// pointers produces too many false positives in the dead variable // raw pointers to the managed heap. Furthermore, checking for raw
// analysis. // pointers produces too many false positives in the dead variable
if (IsInternalPointerType(var_type) && !env.IsAlive(var_name) && // analysis.
!HasActiveGuard() && g_dead_vars_analysis) { if (!IsInternalPointerType(var_type)) return ExprEffect::None();
ReportUnsafe(parent, DEAD_VAR_MSG); if (env.IsAlive(var_name)) return ExprEffect::None();
} if (HasActiveGuard()) return ExprEffect::None();
return ExprEffect::RawUse(); ReportUnsafe(parent, DEAD_VAR_MSG);
} return ExprEffect::RawUse();
return ExprEffect::None();
} }
ExprEffect Use(const clang::Expr* parent, ExprEffect Use(const clang::Expr* parent,
const clang::ValueDecl* var, const clang::ValueDecl* var,
const Environment& env) { const Environment& env) {
if (IsExternalVMState(var)) { if (IsExternalVMState(var)) return ExprEffect::GC();
return ExprEffect::GC();
}
return Use(parent, var->getType(), var->getNameAsString(), env); return Use(parent, var->getType(), var->getNameAsString(), env);
} }
...@@ -1062,43 +1075,40 @@ class FunctionAnalyzer { ...@@ -1062,43 +1075,40 @@ class FunctionAnalyzer {
RepresentsRawPointerType(call->getType())); RepresentsRawPointerType(call->getType()));
clang::FunctionDecl* callee = call->getDirectCallee(); clang::FunctionDecl* callee = call->getDirectCallee();
if (callee != NULL) { if (callee == NULL) return out;
if (KnownToCauseGC(ctx_, callee)) {
if (IsKnownToCauseGC(ctx_, callee)) {
out.setGC();
scopes_.back().SetGCCauseLocation(
clang::FullSourceLoc(call->getExprLoc(), sm_), callee);
}
// Support for virtual methods that might be GC suspects.
if (memcall == NULL) return out;
clang::CXXMethodDecl* method =
llvm::dyn_cast_or_null<clang::CXXMethodDecl>(callee);
if (method == NULL) return out;
if (!method->isVirtual()) return out;
clang::CXXMethodDecl* target = method->getDevirtualizedMethod(
memcall->getImplicitObjectArgument(), false);
if (target != NULL) {
if (IsKnownToCauseGC(ctx_, target)) {
out.setGC(); out.setGC();
scopes_.back().SetGCCauseLocation( scopes_.back().SetGCCauseLocation(
clang::FullSourceLoc(call->getExprLoc(), sm_)); clang::FullSourceLoc(call->getExprLoc(), sm_), target);
} }
} else {
// Support for virtual methods that might be GC suspects. // According to the documentation, {getDevirtualizedMethod} might
clang::CXXMethodDecl* method = // return NULL, in which case we still want to use the partial
llvm::dyn_cast_or_null<clang::CXXMethodDecl>(callee); // match of the {method}'s name against the GC suspects in order
if (method != NULL && method->isVirtual()) { // to increase coverage.
clang::CXXMemberCallExpr* memcall = if (IsSuspectedToCauseGC(ctx_, method)) {
llvm::dyn_cast_or_null<clang::CXXMemberCallExpr>(call); out.setGC();
if (memcall != NULL) { scopes_.back().SetGCCauseLocation(
clang::CXXMethodDecl* target = method->getDevirtualizedMethod( clang::FullSourceLoc(call->getExprLoc(), sm_), method);
memcall->getImplicitObjectArgument(), false);
if (target != NULL) {
if (KnownToCauseGC(ctx_, target)) {
out.setGC();
scopes_.back().SetGCCauseLocation(
clang::FullSourceLoc(call->getExprLoc(), sm_));
}
} else {
// According to the documentation, {getDevirtualizedMethod} might
// return NULL, in which case we still want to use the partial
// match of the {method}'s name against the GC suspects in order
// to increase coverage.
if (SuspectedToCauseGC(ctx_, method)) {
out.setGC();
scopes_.back().SetGCCauseLocation(
clang::FullSourceLoc(call->getExprLoc(), sm_));
}
}
}
} }
} }
return out; return out;
} }
...@@ -1185,11 +1195,9 @@ class FunctionAnalyzer { ...@@ -1185,11 +1195,9 @@ class FunctionAnalyzer {
} }
bool changed() { bool changed() {
if (changed_) { if (!changed_) return false;
changed_ = false; changed_ = false;
return true; return true;
}
return false;
} }
const Environment& in() { const Environment& in() {
...@@ -1455,7 +1463,7 @@ class FunctionAnalyzer { ...@@ -1455,7 +1463,7 @@ class FunctionAnalyzer {
} }
bool HasActiveGuard() { bool HasActiveGuard() {
for (auto s : scopes_) { for (const auto s : scopes_) {
if (s.IsBeforeGCCause()) return true; if (s.IsBeforeGCCause()) return true;
} }
return false; return false;
...@@ -1466,6 +1474,36 @@ class FunctionAnalyzer { ...@@ -1466,6 +1474,36 @@ class FunctionAnalyzer {
d_.Report(clang::FullSourceLoc(expr->getExprLoc(), sm_), d_.Report(clang::FullSourceLoc(expr->getExprLoc(), sm_),
d_.getCustomDiagID(clang::DiagnosticsEngine::Warning, "%0")) d_.getCustomDiagID(clang::DiagnosticsEngine::Warning, "%0"))
<< msg; << msg;
if (scopes_.empty()) return;
GCScope scope = scopes_[0];
if (!scope.gccause_location.isValid()) return;
d_.Report(scope.gccause_location,
d_.getCustomDiagID(clang::DiagnosticsEngine::Note,
"Call might cause unexpected GC."));
clang::FunctionDecl* gccause_decl = scope.gccause_decl;
d_.Report(
clang::FullSourceLoc(gccause_decl->getBeginLoc(), sm_),
d_.getCustomDiagID(clang::DiagnosticsEngine::Note, "GC call here."));
if (!g_print_gc_call_chain) return;
// TODO(cbruni, v8::10009): print call-chain to gc with proper source
// positions.
LoadGCCauses();
MangledName name;
if (!GetMangledName(ctx_, gccause_decl, &name)) return;
std::cout << "Potential GC call chain:\n";
std::set<MangledName> stack;
while (true) {
if (!stack.insert(name).second) break;
std::cout << "\t" << name << "\n";
auto next = gc_causes.find(name);
if (next == gc_causes.end()) break;
std::vector<MangledName> calls = next->second;
for (MangledName call : calls) {
name = call;
if (stack.find(call) != stack.end()) break;
}
}
} }
...@@ -1484,10 +1522,11 @@ class FunctionAnalyzer { ...@@ -1484,10 +1522,11 @@ class FunctionAnalyzer {
struct GCScope { struct GCScope {
clang::FullSourceLoc guard_location; clang::FullSourceLoc guard_location;
clang::FullSourceLoc gccause_location; clang::FullSourceLoc gccause_location;
clang::FunctionDecl* gccause_decl;
// We're only interested in guards that are declared before any further GC // We're only interested in guards that are declared before any further GC
// causing calls (see TestGuardedDeadVarAnalysisMidFunction for example). // causing calls (see TestGuardedDeadVarAnalysisMidFunction for example).
bool IsBeforeGCCause() { bool IsBeforeGCCause() const {
if (!guard_location.isValid()) return false; if (!guard_location.isValid()) return false;
if (!gccause_location.isValid()) return true; if (!gccause_location.isValid()) return true;
return guard_location.isBeforeInTranslationUnitThan(gccause_location); return guard_location.isBeforeInTranslationUnitThan(gccause_location);
...@@ -1495,9 +1534,11 @@ class FunctionAnalyzer { ...@@ -1495,9 +1534,11 @@ class FunctionAnalyzer {
// After we set the first GC cause in the scope, we don't need the later // After we set the first GC cause in the scope, we don't need the later
// ones. // ones.
void SetGCCauseLocation(clang::FullSourceLoc gccause_location_) { void SetGCCauseLocation(clang::FullSourceLoc gccause_location_,
clang::FunctionDecl* decl) {
if (gccause_location.isValid()) return; if (gccause_location.isValid()) return;
gccause_location = gccause_location_; gccause_location = gccause_location_;
gccause_decl = decl;
} }
}; };
std::vector<GCScope> scopes_; std::vector<GCScope> scopes_;
...@@ -1513,9 +1554,8 @@ class ProblemsFinder : public clang::ASTConsumer, ...@@ -1513,9 +1554,8 @@ class ProblemsFinder : public clang::ASTConsumer,
if (args[i] == "--dead-vars") { if (args[i] == "--dead-vars") {
g_dead_vars_analysis = true; g_dead_vars_analysis = true;
} }
if (args[i] == "--verbose") { if (args[i] == "--verbose-trace") g_tracing_enabled = true;
g_tracing_enabled = true; if (args[i] == "--verbose") g_verbose = true;
}
} }
} }
...@@ -1571,7 +1611,7 @@ class ProblemsFinder : public clang::ASTConsumer, ...@@ -1571,7 +1611,7 @@ class ProblemsFinder : public clang::ASTConsumer,
clang::ItaniumMangleContext::create(ctx, d_), object_decl, clang::ItaniumMangleContext::create(ctx, d_), object_decl,
maybe_object_decl, smi_decl, no_gc_mole_decl, d_, sm_); maybe_object_decl, smi_decl, no_gc_mole_decl, d_, sm_);
TraverseDecl(ctx.getTranslationUnitDecl()); TraverseDecl(ctx.getTranslationUnitDecl());
} else { } else if (g_verbose) {
if (object_decl == NULL) { if (object_decl == NULL) {
llvm::errs() << "Failed to resolve v8::internal::Object\n"; llvm::errs() << "Failed to resolve v8::internal::Object\n";
} }
...@@ -1609,7 +1649,6 @@ class ProblemsFinder : public clang::ASTConsumer, ...@@ -1609,7 +1649,6 @@ class ProblemsFinder : public clang::ASTConsumer,
FunctionAnalyzer* function_analyzer_; FunctionAnalyzer* function_analyzer_;
}; };
template<typename ConsumerType> template<typename ConsumerType>
class Action : public clang::PluginASTAction { class Action : public clang::PluginASTAction {
protected: protected:
......
...@@ -6,7 +6,6 @@ ...@@ -6,7 +6,6 @@
# This is main driver for gcmole tool. See README for more details. # This is main driver for gcmole tool. See README for more details.
# Usage: CLANG_BIN=clang-bin-dir python tools/gcmole/gcmole.py [arm|arm64|ia32|x64] # Usage: CLANG_BIN=clang-bin-dir python tools/gcmole/gcmole.py [arm|arm64|ia32|x64]
# for py2/py3 compatibility # for py2/py3 compatibility
from __future__ import print_function from __future__ import print_function
...@@ -14,13 +13,13 @@ from multiprocessing import cpu_count ...@@ -14,13 +13,13 @@ from multiprocessing import cpu_count
import collections import collections
import difflib import difflib
import json
import optparse import optparse
import os import os
import re import re
import subprocess import subprocess
import sys import sys
import threading import threading
import json
if sys.version_info.major > 2: if sys.version_info.major > 2:
from pathlib import Path from pathlib import Path
...@@ -82,6 +81,15 @@ else: ...@@ -82,6 +81,15 @@ else:
ArchCfg = collections.namedtuple( ArchCfg = collections.namedtuple(
"ArchCfg", ["name", "cpu", "triple", "arch_define", "arch_options"]) "ArchCfg", ["name", "cpu", "triple", "arch_define", "arch_options"])
# TODO(cbruni): use gn desc by default for platform-specific settings
OPTIONS_64BIT = [
"-DV8_COMPRESS_POINTERS",
"-DV8_COMPRESS_POINTERS_IN_SHARED_CAGE",
"-DV8_EXTERNAL_CODE_SPACE",
"-DV8_SHORT_BUILTIN_CALLS",
"-DV8_SHARED_RO_HEAP",
]
ARCHITECTURES = { ARCHITECTURES = {
"ia32": "ia32":
ArchCfg( ArchCfg(
...@@ -99,14 +107,15 @@ ARCHITECTURES = { ...@@ -99,14 +107,15 @@ ARCHITECTURES = {
arch_define="V8_TARGET_ARCH_ARM", arch_define="V8_TARGET_ARCH_ARM",
arch_options=["-m32"], arch_options=["-m32"],
), ),
# TODO(cbruni): Use detailed settings:
# arch_options = OPTIONS_64BIT + [ "-DV8_WIN64_UNWINDING_INFO" ]
"x64": "x64":
ArchCfg( ArchCfg(
name="x64", name="x64",
cpu="x64", cpu="x64",
triple="x86_64-unknown-linux", triple="x86_64-unknown-linux",
arch_define="V8_TARGET_ARCH_X64", arch_define="V8_TARGET_ARCH_X64",
arch_options=[], arch_options=[]),
),
"arm64": "arm64":
ArchCfg( ArchCfg(
name="arm64", name="arm64",
...@@ -148,7 +157,7 @@ def make_clang_command_line(plugin, plugin_args, options): ...@@ -148,7 +157,7 @@ def make_clang_command_line(plugin, plugin_args, options):
icu_src_dir = options.v8_root_dir / 'third_party/icu/source' icu_src_dir = options.v8_root_dir / 'third_party/icu/source'
return ([ return ([
options.clang_bin_dir / "clang++", options.clang_bin_dir / "clang++",
"-std=c++14", "-std=c++17",
"-c", "-c",
"-Xclang", "-Xclang",
"-load", "-load",
...@@ -164,11 +173,13 @@ def make_clang_command_line(plugin, plugin_args, options): ...@@ -164,11 +173,13 @@ def make_clang_command_line(plugin, plugin_args, options):
"-Xclang", "-Xclang",
arch_cfg.triple, arch_cfg.triple,
"-fno-exceptions", "-fno-exceptions",
"-Wno-everything",
"-D", "-D",
arch_cfg.arch_define, arch_cfg.arch_define,
"-DENABLE_DEBUGGER_SUPPORT", "-DENABLE_DEBUGGER_SUPPORT",
"-DV8_INTL_SUPPORT",
"-DV8_ENABLE_WEBASSEMBLY", "-DV8_ENABLE_WEBASSEMBLY",
"-DV8_GC_MOLE",
"-DV8_INTL_SUPPORT",
"-I{}".format(options.v8_root_dir), "-I{}".format(options.v8_root_dir),
"-I{}".format(options.v8_root_dir / 'include'), "-I{}".format(options.v8_root_dir / 'include'),
"-I{}".format(options.v8_build_dir / 'gen'), "-I{}".format(options.v8_build_dir / 'gen'),
...@@ -253,7 +264,7 @@ def invoke_clang_plugin_for_each_file(filenames, plugin, plugin_args, options): ...@@ -253,7 +264,7 @@ def invoke_clang_plugin_for_each_file(filenames, plugin, plugin_args, options):
else: else:
break break
filename, returncode, stdout, stderr = output filename, returncode, stdout, stderr = output
log(filename, level=1) log(filename, level=2)
if returncode != 0: if returncode != 0:
sys.stderr.write(stderr) sys.stderr.write(stderr)
sys.exit(returncode) sys.exit(returncode)
...@@ -439,25 +450,44 @@ def generate_gc_suspects(files, options): ...@@ -439,25 +450,44 @@ def generate_gc_suspects(files, options):
collector.parse(stdout.splitlines()) collector.parse(stdout.splitlines())
collector.propagate() collector.propagate()
# TODO(cbruni): remove once gcmole.cc is migrated # TODO(cbruni): remove once gcmole.cc is migrated
write_gc_suspects(collector, options.v8_root_dir) write_gcmole_results(collector, options, options.v8_root_dir)
write_gc_suspects(collector, options.out_dir) write_gcmole_results(collector, options, options.out_dir)
log("GCSuspects generated for {}", options.v8_target_cpu)
def write_gcmole_results(collector, options, dst):
def write_gc_suspects(collector, dst): # gcsuspects contains a list("mangled_full_name,name") of all functions that
# could cause a gc (directly or indirectly).
#
# EXAMPLE
# _ZN2v88internal4Heap16CreateApiObjectsEv,CreateApiObjects
# _ZN2v88internal4Heap17CreateInitialMapsEv,CreateInitialMaps
# ...
with open(dst / "gcsuspects", "w") as out: with open(dst / "gcsuspects", "w") as out:
for name, value in collector.gc.items(): for name, value in list(collector.gc.items()):
if value: if value:
out.write(name + "\n") out.write(name + "\n")
# gccauses contains a map["mangled_full_name,name"] => list(inner gcsuspects)
# Where the inner gcsuspects are functions directly called in the outer
# function that can cause a gc. The format is encoded for simplified
# deserialization in gcmole.cc.
#
# EXAMPLE:
# _ZN2v88internal4Heap17CreateHeapObjectsEv,CreateHeapObjects
# start,nested
# _ZN2v88internal4Heap16CreateApiObjectsEv,CreateApiObjects
# _ZN2v88internal4Heap17CreateInitialMapsEv,CreateInitialMaps
# ...
# end,nested
# ...
with open(dst / "gccauses", "w") as out: with open(dst / "gccauses", "w") as out:
out.write("GC = {\n") for name, causes in list(collector.gc_caused.items()):
for name, causes in collector.gc_caused.items(): out.write("{}\n".format(name))
out.write(" '{}': [\n".format(name)) out.write("start,nested\n")
for cause in causes: for cause in causes:
out.write(" '{}',\n".format(cause)) out.write("{}\n".format(cause))
out.write(" ],\n") out.write("end,nested\n")
out.write("}\n") log("GCSuspects and gccauses generated for {} in '{}'", options.v8_target_cpu,
dst)
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
...@@ -498,7 +528,7 @@ def check_correctness_for_arch(options, for_test): ...@@ -498,7 +528,7 @@ def check_correctness_for_arch(options, for_test):
sys.stdout.write(stderr) sys.stdout.write(stderr)
log("Done processing {} files.", processed_files) log("Done processing {} files.", processed_files)
log("Errors found" if errors_found else "## No errors found") log("Errors found" if errors_found else "No errors found")
return errors_found, output return errors_found, output
...@@ -598,7 +628,7 @@ def main(args): ...@@ -598,7 +628,7 @@ def main(args):
parser.add_option( parser.add_option(
"--out-dir", "--out-dir",
metavar="DIR", metavar="DIR",
help="Output location for gcsuspect and gcauses file." help="Output location for the gcsuspect and gcauses file."
"Default: BUILD_DIR/gen/tools/gcmole") "Default: BUILD_DIR/gen/tools/gcmole")
parser.add_option( parser.add_option(
"--is-bot", "--is-bot",
...@@ -639,9 +669,9 @@ def main(args): ...@@ -639,9 +669,9 @@ def main(args):
action="store_true", action="store_true",
default=True, default=True,
dest="allowlist", dest="allowlist",
help="""When building gcsuspects allowlist certain functions as if they can be help="When building gcsuspects allowlist certain functions as if they can be "
causing GC. Currently used to reduce number of false positives in dead "causing GC. Currently used to reduce number of false positives in dead "
variables analysis. See TODO for ALLOWLIST in gcmole.py""") "variables analysis. See TODO for ALLOWLIST in gcmole.py")
group.add_option( group.add_option(
"--test-run", "--test-run",
action="store_true", action="store_true",
......
...@@ -14,6 +14,7 @@ PACKAGE_DIR="${THIS_DIR}/gcmole-tools" ...@@ -14,6 +14,7 @@ PACKAGE_DIR="${THIS_DIR}/gcmole-tools"
PACKAGE_FILE="${THIS_DIR}/gcmole-tools.tar.gz" PACKAGE_FILE="${THIS_DIR}/gcmole-tools.tar.gz"
PACKAGE_SUM="${THIS_DIR}/gcmole-tools.tar.gz.sha1" PACKAGE_SUM="${THIS_DIR}/gcmole-tools.tar.gz.sha1"
BUILD_DIR="${THIS_DIR}/bootstrap/build" BUILD_DIR="${THIS_DIR}/bootstrap/build"
V8_ROOT_DIR= `realpath "${THIS_DIR}/../.."`
# Echo all commands # Echo all commands
set -x set -x
...@@ -72,5 +73,8 @@ echo "sudo chroot \$CHROOT_DIR bash -c 'PATH=/docs/depot_tools:\$PATH; /docs/v8/ ...@@ -72,5 +73,8 @@ echo "sudo chroot \$CHROOT_DIR bash -c 'PATH=/docs/depot_tools:\$PATH; /docs/v8/
echo echo
echo You can now run gcmole using this command: echo You can now run gcmole using this command:
echo echo
echo CLANG_BIN=\"tools/gcmole/gcmole-tools/bin\" python tools/gcmole/gcmole.py echo 'tools/gcmole/gcmole.py \'
echo ' --clang-bin-dir="tools/gcmole/gcmole-tools/bin" \'
echo ' --clang-plugins-dir="tools/gcmole/gcmole-tools" \'
echo ' --v8-target-cpu=$CPU'
echo echo
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment