[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/+/3480095
Reviewed-by: Jakob Kummerow <jkummerow@chromium.org>
Reviewed-by: Maya Lekova <mslekova@chromium.org>
Commit-Queue: Camillo Bruni <cbruni@chromium.org>
Cr-Commit-Position: refs/heads/main@{#79357}
This commit is contained in:
Camillo Bruni 2022-03-03 22:38:39 +01:00 committed by V8 LUCI CQ
parent 8e18ea3913
commit ecc3cd256a
8 changed files with 277 additions and 179 deletions

View File

@ -2293,12 +2293,20 @@ struct borrowed_vec {
// Vectors // Vectors
#define WASM_DEFINE_VEC_BASE(name, Name, vec, ptr_or_none) \ #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>), \ static_assert(sizeof(wasm_##name##_vec_t) == sizeof(vec<Name>), \
"C/C++ incompatibility"); \ "C/C++ incompatibility"); \
static_assert( \ static_assert( \
sizeof(wasm_##name##_t ptr_or_none) == sizeof(vec<Name>::elem_type), \ sizeof(wasm_##name##_t ptr_or_none) == sizeof(vec<Name>::elem_type), \
"C/C++ incompatibility"); \ "C/C++ incompatibility");
#endif
#define WASM_DEFINE_VEC_BASE(name, Name, vec, ptr_or_none) \
ASSERT_VEC_BASE_SIZE(name, Name, vec, ptr_or_none) \
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); \

View File

@ -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);

View File

@ -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 \

View File

@ -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

View File

@ -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,10 +101,11 @@ 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
if [[ "${BUILD_TYPE}" = "Release" ]]; then
# Strip the clang binary. # Strip the clang binary.
STRIP_FLAGS= STRIP_FLAGS=
if [ "${OS}" = "Darwin" ]; then if [ "${OS}" = "Darwin" ]; then
@ -110,18 +113,23 @@ if [ "${OS}" = "Darwin" ]; then
STRIP_FLAGS=-x STRIP_FLAGS=-x
fi fi
strip ${STRIP_FLAGS} bin/clang strip ${STRIP_FLAGS} bin/clang
fi
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 'Congratulations you compiled clang and libgcmole.so'
echo echo
echo You can now run gcmole using this command: echo '# You can now run gcmole:'
echo echo 'tools/gcmole/gcmole.py \'
echo CLANG_BIN=\"tools/gcmole/gcmole-tools/bin\" python 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

View File

@ -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,8 +82,8 @@ 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);
@ -89,9 +91,6 @@ static bool GetMangledName(clang::MangleContext* ctx,
return true; return true;
} }
return false;
}
static bool InV8Namespace(const clang::NamedDecl* decl) { static bool InV8Namespace(const clang::NamedDecl* decl) {
return decl->getQualifiedNameAsString().compare(0, 4, "v8::") == 0; return decl->getQualifiedNameAsString().compare(0, 4, "v8::") == 0;
@ -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,8 +234,9 @@ 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);
@ -248,7 +247,6 @@ class CalleesPrinter : public clang::RecursiveASTVisitor<CalleesPrinter> {
LeaveScope(); LeaveScope();
} }
} }
}
typedef std::map<MangledName, CalleesSet* > Callgraph; typedef std::map<MangledName, CalleesSet* > Callgraph;
@ -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,
clang::SourceManager& source_manager,
const std::vector<std::string>& args) const std::vector<std::string>& args)
: d_(d), sm_(sm) {} : 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
} }
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
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() {
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 {
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 {
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 {
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 {
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();
if (!RepresentsRawPointerType(var_type)) return ExprEffect::None();
// We currently care only about our internal pointer types and not about // We currently care only about our internal pointer types and not about
// raw C++ pointers, because normally special care is taken when storing // raw C++ pointers, because normally special care is taken when storing
// raw pointers to the managed heap. Furthermore, checking for raw // raw pointers to the managed heap. Furthermore, checking for raw
// pointers produces too many false positives in the dead variable // pointers produces too many false positives in the dead variable
// analysis. // analysis.
if (IsInternalPointerType(var_type) && !env.IsAlive(var_name) && if (!IsInternalPointerType(var_type)) return ExprEffect::None();
!HasActiveGuard() && g_dead_vars_analysis) { if (env.IsAlive(var_name)) return ExprEffect::None();
if (HasActiveGuard()) return ExprEffect::None();
ReportUnsafe(parent, DEAD_VAR_MSG); ReportUnsafe(parent, DEAD_VAR_MSG);
}
return ExprEffect::RawUse(); 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 {
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(); out.setGC();
scopes_.back().SetGCCauseLocation( scopes_.back().SetGCCauseLocation(
clang::FullSourceLoc(call->getExprLoc(), sm_)); clang::FullSourceLoc(call->getExprLoc(), sm_), callee);
} }
// Support for virtual methods that might be GC suspects. // Support for virtual methods that might be GC suspects.
if (memcall == NULL) return out;
clang::CXXMethodDecl* method = clang::CXXMethodDecl* method =
llvm::dyn_cast_or_null<clang::CXXMethodDecl>(callee); llvm::dyn_cast_or_null<clang::CXXMethodDecl>(callee);
if (method != NULL && method->isVirtual()) { if (method == NULL) return out;
clang::CXXMemberCallExpr* memcall = if (!method->isVirtual()) return out;
llvm::dyn_cast_or_null<clang::CXXMemberCallExpr>(call);
if (memcall != NULL) {
clang::CXXMethodDecl* target = method->getDevirtualizedMethod( clang::CXXMethodDecl* target = method->getDevirtualizedMethod(
memcall->getImplicitObjectArgument(), false); memcall->getImplicitObjectArgument(), false);
if (target != NULL) { if (target != NULL) {
if (KnownToCauseGC(ctx_, target)) { 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 { } else {
// According to the documentation, {getDevirtualizedMethod} might // According to the documentation, {getDevirtualizedMethod} might
// return NULL, in which case we still want to use the partial // return NULL, in which case we still want to use the partial
// match of the {method}'s name against the GC suspects in order // match of the {method}'s name against the GC suspects in order
// to increase coverage. // to increase coverage.
if (SuspectedToCauseGC(ctx_, method)) { if (IsSuspectedToCauseGC(ctx_, method)) {
out.setGC(); out.setGC();
scopes_.back().SetGCCauseLocation( scopes_.back().SetGCCauseLocation(
clang::FullSourceLoc(call->getExprLoc(), sm_)); clang::FullSourceLoc(call->getExprLoc(), sm_), method);
} }
} }
}
}
}
return out; return out;
} }
@ -1185,12 +1195,10 @@ 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() {
return in_; return in_;
@ -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 {
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 {
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 {
// 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,
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,
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,
FunctionAnalyzer* function_analyzer_; FunctionAnalyzer* function_analyzer_;
}; };
template<typename ConsumerType> template<typename ConsumerType>
class Action : public clang::PluginASTAction { class Action : public clang::PluginASTAction {
protected: protected:

View File

@ -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
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:
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 = {
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):
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):
"-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):
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):
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_gc_suspects(collector, dst): def write_gcmole_results(collector, options, 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):
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):
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):
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",

View File

@ -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/
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