[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:
parent
8e18ea3913
commit
ecc3cd256a
@ -2293,12 +2293,20 @@ struct borrowed_vec {
|
||||
|
||||
// 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>), \
|
||||
"C/C++ incompatibility"); \
|
||||
static_assert( \
|
||||
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) \
|
||||
->wasm_##name##_vec_t* { \
|
||||
return reinterpret_cast<wasm_##name##_vec_t*>(&v); \
|
||||
|
@ -495,7 +495,9 @@ class V8_EXPORT_PRIVATE WasmCode final {
|
||||
// often for rather small functions.
|
||||
// Increase the limit if needed, but first check if the size increase is
|
||||
// justified.
|
||||
#ifndef V8_GC_MOLE
|
||||
STATIC_ASSERT(sizeof(WasmCode) <= 88);
|
||||
#endif
|
||||
|
||||
WasmCode::Kind GetCodeKind(const WasmCompilationResult& result);
|
||||
|
||||
|
@ -32,11 +32,18 @@ LLVM_BUILD_INCLUDE:=$(BUILD_ROOT)/include
|
||||
CLANG_SRC_INCLUDE:=$(CLANG_SRC_ROOT)/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
|
||||
$(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__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 \
|
||||
-pedantic -Wno-long-long -Wall -W -Wno-unused-parameter \
|
||||
-Wwrite-strings -static-libstdc++ -std=c++0x -shared -o libgcmole.so \
|
||||
|
@ -109,7 +109,7 @@ script "bootstrap.sh" mentioned above).
|
||||
|
||||
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
|
||||
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
|
||||
|
@ -35,6 +35,8 @@
|
||||
|
||||
LLVM_RELEASE=9.0.1
|
||||
|
||||
BUILD_TYPE="Release"
|
||||
# BUILD_TYPE="Debug"
|
||||
THIS_DIR="$(readlink -f "$(dirname "${0}")")"
|
||||
LLVM_PROJECT_DIR="${THIS_DIR}/bootstrap/llvm"
|
||||
BUILD_DIR="${THIS_DIR}/bootstrap/build"
|
||||
@ -99,10 +101,11 @@ if [ ! -e "${BUILD_DIR}" ]; then
|
||||
fi
|
||||
cd "${BUILD_DIR}"
|
||||
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"
|
||||
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_FLAGS=
|
||||
if [ "${OS}" = "Darwin" ]; then
|
||||
@ -110,18 +113,23 @@ if [ "${OS}" = "Darwin" ]; then
|
||||
STRIP_FLAGS=-x
|
||||
fi
|
||||
strip ${STRIP_FLAGS} bin/clang
|
||||
fi
|
||||
cd -
|
||||
|
||||
# Build libgcmole.so
|
||||
make -C "${THIS_DIR}" clean
|
||||
make -C "${THIS_DIR}" LLVM_SRC_ROOT="${LLVM_PROJECT_DIR}/llvm" \
|
||||
CLANG_SRC_ROOT="${LLVM_PROJECT_DIR}/clang" \
|
||||
BUILD_ROOT="${BUILD_DIR}" libgcmole.so
|
||||
BUILD_ROOT="${BUILD_DIR}" $BUILD_TYPE
|
||||
|
||||
set +x
|
||||
|
||||
echo '#########################################################################'
|
||||
echo 'Congratulations you compiled clang and libgcmole.so'
|
||||
echo
|
||||
echo You can now run gcmole using this command:
|
||||
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
|
||||
|
@ -48,6 +48,8 @@ namespace {
|
||||
|
||||
bool g_tracing_enabled = false;
|
||||
bool g_dead_vars_analysis = false;
|
||||
bool g_verbose = false;
|
||||
bool g_print_gc_call_chain = false;
|
||||
|
||||
#define TRACE(str) \
|
||||
do { \
|
||||
@ -80,8 +82,8 @@ typedef std::map<MangledName, MangledName> CalleesMap;
|
||||
static bool GetMangledName(clang::MangleContext* ctx,
|
||||
const clang::NamedDecl* decl,
|
||||
MangledName* result) {
|
||||
if (!llvm::isa<clang::CXXConstructorDecl>(decl) &&
|
||||
!llvm::isa<clang::CXXDestructorDecl>(decl)) {
|
||||
if (llvm::isa<clang::CXXConstructorDecl>(decl)) return false;
|
||||
if (llvm::isa<clang::CXXDestructorDecl>(decl)) return false;
|
||||
llvm::SmallVector<char, 512> output;
|
||||
llvm::raw_svector_ostream out(output);
|
||||
ctx->mangleName(decl, out);
|
||||
@ -89,9 +91,6 @@ static bool GetMangledName(clang::MangleContext* ctx,
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
static bool InV8Namespace(const clang::NamedDecl* decl) {
|
||||
return decl->getQualifiedNameAsString().compare(0, 4, "v8::") == 0;
|
||||
@ -217,8 +216,7 @@ struct Resolver {
|
||||
|
||||
class CalleesPrinter : public clang::RecursiveASTVisitor<CalleesPrinter> {
|
||||
public:
|
||||
explicit CalleesPrinter(clang::MangleContext* ctx) : ctx_(ctx) {
|
||||
}
|
||||
explicit CalleesPrinter(clang::MangleContext* ctx) : ctx_(ctx) {}
|
||||
|
||||
virtual bool VisitCallExpr(clang::CallExpr* expr) {
|
||||
const clang::FunctionDecl* callee = expr->getDirectCallee();
|
||||
@ -236,8 +234,9 @@ class CalleesPrinter : public clang::RecursiveASTVisitor<CalleesPrinter> {
|
||||
}
|
||||
|
||||
void AnalyzeFunction(const clang::FunctionDecl* f) {
|
||||
if (!InV8Namespace(f)) return;
|
||||
MangledName name;
|
||||
if (InV8Namespace(f) && GetMangledName(ctx_, f, &name)) {
|
||||
if (!GetMangledName(ctx_, f, &name)) return;
|
||||
const std::string& function = f->getNameAsString();
|
||||
AddCallee(name, function);
|
||||
|
||||
@ -248,7 +247,6 @@ class CalleesPrinter : public clang::RecursiveASTVisitor<CalleesPrinter> {
|
||||
LeaveScope();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typedef std::map<MangledName, CalleesSet* > Callgraph;
|
||||
|
||||
@ -303,17 +301,18 @@ class FunctionDeclarationFinder
|
||||
: public clang::ASTConsumer,
|
||||
public clang::RecursiveASTVisitor<FunctionDeclarationFinder> {
|
||||
public:
|
||||
explicit FunctionDeclarationFinder(clang::DiagnosticsEngine& d,
|
||||
clang::SourceManager& sm,
|
||||
explicit FunctionDeclarationFinder(
|
||||
clang::DiagnosticsEngine& diagnostics_engine,
|
||||
clang::SourceManager& source_manager,
|
||||
const std::vector<std::string>& args)
|
||||
: d_(d), sm_(sm) {}
|
||||
: diagnostics_engine_(diagnostics_engine),
|
||||
source_manager_(source_manager) {}
|
||||
|
||||
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_);
|
||||
|
||||
TraverseDecl(ctx.getTranslationUnitDecl());
|
||||
|
||||
callees_printer_->PrintCallGraph();
|
||||
}
|
||||
|
||||
@ -323,8 +322,8 @@ class FunctionDeclarationFinder
|
||||
}
|
||||
|
||||
private:
|
||||
clang::DiagnosticsEngine& d_;
|
||||
clang::SourceManager& sm_;
|
||||
clang::DiagnosticsEngine& diagnostics_engine_;
|
||||
clang::SourceManager& source_manager_;
|
||||
clang::MangleContext* mangle_context_;
|
||||
|
||||
CalleesPrinter* callees_printer_;
|
||||
@ -333,8 +332,39 @@ class FunctionDeclarationFinder
|
||||
static bool gc_suspects_loaded = false;
|
||||
static CalleesSet gc_suspects;
|
||||
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() {
|
||||
if (gc_suspects_loaded) return;
|
||||
@ -352,55 +382,51 @@ static void LoadGCSuspects() {
|
||||
gc_suspects_loaded = true;
|
||||
}
|
||||
|
||||
static void LoadSuspectsWhitelist() {
|
||||
if (whitelist_loaded) return;
|
||||
static void LoadSuspectsAllowList() {
|
||||
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;
|
||||
|
||||
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.
|
||||
static bool KnownToCauseGC(clang::MangleContext* ctx,
|
||||
static bool IsKnownToCauseGC(clang::MangleContext* ctx,
|
||||
const clang::FunctionDecl* decl) {
|
||||
LoadGCSuspects();
|
||||
|
||||
if (!InV8Namespace(decl)) return false;
|
||||
|
||||
if (suspects_whitelist.find(decl->getNameAsString()) !=
|
||||
suspects_whitelist.end()) {
|
||||
if (suspects_allowlist.find(decl->getNameAsString()) !=
|
||||
suspects_allowlist.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
MangledName name;
|
||||
if (GetMangledName(ctx, decl, &name)) {
|
||||
return gc_suspects.find(name) != gc_suspects.end();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
LoadGCSuspects();
|
||||
|
||||
if (!InV8Namespace(decl)) return false;
|
||||
|
||||
LoadSuspectsWhitelist();
|
||||
if (suspects_whitelist.find(decl->getNameAsString()) !=
|
||||
suspects_whitelist.end()) {
|
||||
LoadSuspectsAllowList();
|
||||
if (suspects_allowlist.find(decl->getNameAsString()) !=
|
||||
suspects_allowlist.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (gc_functions.find(decl->getNameAsString()) != gc_functions.end()) {
|
||||
TRACE_LLVM_DECL("Suspected by ", decl);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -449,10 +475,9 @@ class ExprEffect {
|
||||
intptr_t effect_;
|
||||
};
|
||||
|
||||
|
||||
const std::string BAD_EXPR_MSG("Possible problem with evaluation order.");
|
||||
const std::string DEAD_VAR_MSG("Possibly dead variable.");
|
||||
|
||||
const std::string BAD_EXPR_MSG(
|
||||
"Possible problem with evaluation order with interleaved GCs.");
|
||||
const std::string DEAD_VAR_MSG("Possibly stale variable due to GCs.");
|
||||
|
||||
class Environment {
|
||||
public:
|
||||
@ -612,22 +637,16 @@ class CallProps {
|
||||
|
||||
ExprEffect ComputeCumulativeEffect(bool result_is_raw) {
|
||||
ExprEffect out = ExprEffect::NoneWithEnv(env_);
|
||||
if (gc_.any()) {
|
||||
out.setGC();
|
||||
}
|
||||
if (gc_.any()) out.setGC();
|
||||
if (raw_use_.any()) out.setRawUse();
|
||||
if (result_is_raw) out.setRawDef();
|
||||
return out;
|
||||
}
|
||||
|
||||
bool IsSafe() {
|
||||
if (!gc_.any()) {
|
||||
return true;
|
||||
}
|
||||
if (!gc_.any()) return true;
|
||||
std::bitset<kMaxNumberOfArguments> raw = (raw_def_ | raw_use_);
|
||||
if (!raw.any()) {
|
||||
return true;
|
||||
}
|
||||
if (!raw.any()) return true;
|
||||
bool result = gc_.count() == 1 && !((raw ^ gc_).any());
|
||||
return result;
|
||||
}
|
||||
@ -950,13 +969,10 @@ class FunctionAnalyzer {
|
||||
ExprEffect Parallel(clang::Expr* parent, int n, clang::Expr** exprs,
|
||||
const Environment& env) {
|
||||
CallProps props;
|
||||
|
||||
for (int i = 0; i < n; ++i) {
|
||||
props.SetEffect(i, VisitExpr(exprs[i], env));
|
||||
}
|
||||
|
||||
if (!props.IsSafe()) ReportUnsafe(parent, BAD_EXPR_MSG);
|
||||
|
||||
return props.ComputeCumulativeEffect(
|
||||
RepresentsRawPointerType(parent->getType()));
|
||||
}
|
||||
@ -984,27 +1000,24 @@ class FunctionAnalyzer {
|
||||
const clang::QualType& var_type,
|
||||
const std::string& var_name,
|
||||
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
|
||||
// raw C++ pointers, because normally special care is taken when storing
|
||||
// raw pointers to the managed heap. Furthermore, checking for raw
|
||||
// pointers produces too many false positives in the dead variable
|
||||
// analysis.
|
||||
if (IsInternalPointerType(var_type) && !env.IsAlive(var_name) &&
|
||||
!HasActiveGuard() && g_dead_vars_analysis) {
|
||||
if (!IsInternalPointerType(var_type)) return ExprEffect::None();
|
||||
if (env.IsAlive(var_name)) return ExprEffect::None();
|
||||
if (HasActiveGuard()) return ExprEffect::None();
|
||||
ReportUnsafe(parent, DEAD_VAR_MSG);
|
||||
}
|
||||
return ExprEffect::RawUse();
|
||||
}
|
||||
return ExprEffect::None();
|
||||
}
|
||||
|
||||
ExprEffect Use(const clang::Expr* parent,
|
||||
const clang::ValueDecl* var,
|
||||
const Environment& env) {
|
||||
if (IsExternalVMState(var)) {
|
||||
return ExprEffect::GC();
|
||||
}
|
||||
if (IsExternalVMState(var)) return ExprEffect::GC();
|
||||
return Use(parent, var->getType(), var->getNameAsString(), env);
|
||||
}
|
||||
|
||||
@ -1062,43 +1075,40 @@ class FunctionAnalyzer {
|
||||
RepresentsRawPointerType(call->getType()));
|
||||
|
||||
clang::FunctionDecl* callee = call->getDirectCallee();
|
||||
if (callee != NULL) {
|
||||
if (KnownToCauseGC(ctx_, callee)) {
|
||||
if (callee == NULL) return out;
|
||||
|
||||
if (IsKnownToCauseGC(ctx_, callee)) {
|
||||
out.setGC();
|
||||
scopes_.back().SetGCCauseLocation(
|
||||
clang::FullSourceLoc(call->getExprLoc(), sm_));
|
||||
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 && method->isVirtual()) {
|
||||
clang::CXXMemberCallExpr* memcall =
|
||||
llvm::dyn_cast_or_null<clang::CXXMemberCallExpr>(call);
|
||||
if (memcall != NULL) {
|
||||
if (method == NULL) return out;
|
||||
if (!method->isVirtual()) return out;
|
||||
|
||||
clang::CXXMethodDecl* target = method->getDevirtualizedMethod(
|
||||
memcall->getImplicitObjectArgument(), false);
|
||||
if (target != NULL) {
|
||||
if (KnownToCauseGC(ctx_, target)) {
|
||||
if (IsKnownToCauseGC(ctx_, target)) {
|
||||
out.setGC();
|
||||
scopes_.back().SetGCCauseLocation(
|
||||
clang::FullSourceLoc(call->getExprLoc(), sm_));
|
||||
clang::FullSourceLoc(call->getExprLoc(), sm_), target);
|
||||
}
|
||||
} 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)) {
|
||||
if (IsSuspectedToCauseGC(ctx_, method)) {
|
||||
out.setGC();
|
||||
scopes_.back().SetGCCauseLocation(
|
||||
clang::FullSourceLoc(call->getExprLoc(), sm_));
|
||||
clang::FullSourceLoc(call->getExprLoc(), sm_), method);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
@ -1185,12 +1195,10 @@ class FunctionAnalyzer {
|
||||
}
|
||||
|
||||
bool changed() {
|
||||
if (changed_) {
|
||||
if (!changed_) return false;
|
||||
changed_ = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const Environment& in() {
|
||||
return in_;
|
||||
@ -1455,7 +1463,7 @@ class FunctionAnalyzer {
|
||||
}
|
||||
|
||||
bool HasActiveGuard() {
|
||||
for (auto s : scopes_) {
|
||||
for (const auto s : scopes_) {
|
||||
if (s.IsBeforeGCCause()) return true;
|
||||
}
|
||||
return false;
|
||||
@ -1466,6 +1474,36 @@ class FunctionAnalyzer {
|
||||
d_.Report(clang::FullSourceLoc(expr->getExprLoc(), sm_),
|
||||
d_.getCustomDiagID(clang::DiagnosticsEngine::Warning, "%0"))
|
||||
<< 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 {
|
||||
clang::FullSourceLoc guard_location;
|
||||
clang::FullSourceLoc gccause_location;
|
||||
clang::FunctionDecl* gccause_decl;
|
||||
|
||||
// We're only interested in guards that are declared before any further GC
|
||||
// causing calls (see TestGuardedDeadVarAnalysisMidFunction for example).
|
||||
bool IsBeforeGCCause() {
|
||||
bool IsBeforeGCCause() const {
|
||||
if (!guard_location.isValid()) return false;
|
||||
if (!gccause_location.isValid()) return true;
|
||||
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
|
||||
// ones.
|
||||
void SetGCCauseLocation(clang::FullSourceLoc gccause_location_) {
|
||||
void SetGCCauseLocation(clang::FullSourceLoc gccause_location_,
|
||||
clang::FunctionDecl* decl) {
|
||||
if (gccause_location.isValid()) return;
|
||||
gccause_location = gccause_location_;
|
||||
gccause_decl = decl;
|
||||
}
|
||||
};
|
||||
std::vector<GCScope> scopes_;
|
||||
@ -1513,9 +1554,8 @@ class ProblemsFinder : public clang::ASTConsumer,
|
||||
if (args[i] == "--dead-vars") {
|
||||
g_dead_vars_analysis = true;
|
||||
}
|
||||
if (args[i] == "--verbose") {
|
||||
g_tracing_enabled = true;
|
||||
}
|
||||
if (args[i] == "--verbose-trace") 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,
|
||||
maybe_object_decl, smi_decl, no_gc_mole_decl, d_, sm_);
|
||||
TraverseDecl(ctx.getTranslationUnitDecl());
|
||||
} else {
|
||||
} else if (g_verbose) {
|
||||
if (object_decl == NULL) {
|
||||
llvm::errs() << "Failed to resolve v8::internal::Object\n";
|
||||
}
|
||||
@ -1609,7 +1649,6 @@ class ProblemsFinder : public clang::ASTConsumer,
|
||||
FunctionAnalyzer* function_analyzer_;
|
||||
};
|
||||
|
||||
|
||||
template<typename ConsumerType>
|
||||
class Action : public clang::PluginASTAction {
|
||||
protected:
|
||||
|
@ -6,7 +6,6 @@
|
||||
# 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]
|
||||
|
||||
|
||||
# for py2/py3 compatibility
|
||||
from __future__ import print_function
|
||||
|
||||
@ -14,13 +13,13 @@ from multiprocessing import cpu_count
|
||||
|
||||
import collections
|
||||
import difflib
|
||||
import json
|
||||
import optparse
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
import json
|
||||
|
||||
if sys.version_info.major > 2:
|
||||
from pathlib import Path
|
||||
@ -82,6 +81,15 @@ else:
|
||||
ArchCfg = collections.namedtuple(
|
||||
"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 = {
|
||||
"ia32":
|
||||
ArchCfg(
|
||||
@ -99,14 +107,15 @@ ARCHITECTURES = {
|
||||
arch_define="V8_TARGET_ARCH_ARM",
|
||||
arch_options=["-m32"],
|
||||
),
|
||||
# TODO(cbruni): Use detailed settings:
|
||||
# arch_options = OPTIONS_64BIT + [ "-DV8_WIN64_UNWINDING_INFO" ]
|
||||
"x64":
|
||||
ArchCfg(
|
||||
name="x64",
|
||||
cpu="x64",
|
||||
triple="x86_64-unknown-linux",
|
||||
arch_define="V8_TARGET_ARCH_X64",
|
||||
arch_options=[],
|
||||
),
|
||||
arch_options=[]),
|
||||
"arm64":
|
||||
ArchCfg(
|
||||
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'
|
||||
return ([
|
||||
options.clang_bin_dir / "clang++",
|
||||
"-std=c++14",
|
||||
"-std=c++17",
|
||||
"-c",
|
||||
"-Xclang",
|
||||
"-load",
|
||||
@ -164,11 +173,13 @@ def make_clang_command_line(plugin, plugin_args, options):
|
||||
"-Xclang",
|
||||
arch_cfg.triple,
|
||||
"-fno-exceptions",
|
||||
"-Wno-everything",
|
||||
"-D",
|
||||
arch_cfg.arch_define,
|
||||
"-DENABLE_DEBUGGER_SUPPORT",
|
||||
"-DV8_INTL_SUPPORT",
|
||||
"-DV8_ENABLE_WEBASSEMBLY",
|
||||
"-DV8_GC_MOLE",
|
||||
"-DV8_INTL_SUPPORT",
|
||||
"-I{}".format(options.v8_root_dir),
|
||||
"-I{}".format(options.v8_root_dir / 'include'),
|
||||
"-I{}".format(options.v8_build_dir / 'gen'),
|
||||
@ -253,7 +264,7 @@ def invoke_clang_plugin_for_each_file(filenames, plugin, plugin_args, options):
|
||||
else:
|
||||
break
|
||||
filename, returncode, stdout, stderr = output
|
||||
log(filename, level=1)
|
||||
log(filename, level=2)
|
||||
if returncode != 0:
|
||||
sys.stderr.write(stderr)
|
||||
sys.exit(returncode)
|
||||
@ -439,25 +450,44 @@ def generate_gc_suspects(files, options):
|
||||
collector.parse(stdout.splitlines())
|
||||
collector.propagate()
|
||||
# TODO(cbruni): remove once gcmole.cc is migrated
|
||||
write_gc_suspects(collector, options.v8_root_dir)
|
||||
write_gc_suspects(collector, options.out_dir)
|
||||
log("GCSuspects generated for {}", options.v8_target_cpu)
|
||||
write_gcmole_results(collector, options, options.v8_root_dir)
|
||||
write_gcmole_results(collector, options, options.out_dir)
|
||||
|
||||
|
||||
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:
|
||||
for name, value in collector.gc.items():
|
||||
for name, value in list(collector.gc.items()):
|
||||
if value:
|
||||
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:
|
||||
out.write("GC = {\n")
|
||||
for name, causes in collector.gc_caused.items():
|
||||
out.write(" '{}': [\n".format(name))
|
||||
for name, causes in list(collector.gc_caused.items()):
|
||||
out.write("{}\n".format(name))
|
||||
out.write("start,nested\n")
|
||||
for cause in causes:
|
||||
out.write(" '{}',\n".format(cause))
|
||||
out.write(" ],\n")
|
||||
out.write("}\n")
|
||||
out.write("{}\n".format(cause))
|
||||
out.write("end,nested\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)
|
||||
|
||||
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
|
||||
|
||||
@ -598,7 +628,7 @@ def main(args):
|
||||
parser.add_option(
|
||||
"--out-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")
|
||||
parser.add_option(
|
||||
"--is-bot",
|
||||
@ -639,9 +669,9 @@ def main(args):
|
||||
action="store_true",
|
||||
default=True,
|
||||
dest="allowlist",
|
||||
help="""When building gcsuspects allowlist certain functions as if they can be
|
||||
causing GC. Currently used to reduce number of false positives in dead
|
||||
variables analysis. See TODO for ALLOWLIST in gcmole.py""")
|
||||
help="When building gcsuspects allowlist certain functions as if they can be "
|
||||
"causing GC. Currently used to reduce number of false positives in dead "
|
||||
"variables analysis. See TODO for ALLOWLIST in gcmole.py")
|
||||
group.add_option(
|
||||
"--test-run",
|
||||
action="store_true",
|
||||
|
@ -14,6 +14,7 @@ PACKAGE_DIR="${THIS_DIR}/gcmole-tools"
|
||||
PACKAGE_FILE="${THIS_DIR}/gcmole-tools.tar.gz"
|
||||
PACKAGE_SUM="${THIS_DIR}/gcmole-tools.tar.gz.sha1"
|
||||
BUILD_DIR="${THIS_DIR}/bootstrap/build"
|
||||
V8_ROOT_DIR= `realpath "${THIS_DIR}/../.."`
|
||||
|
||||
# Echo all commands
|
||||
set -x
|
||||
@ -72,5 +73,8 @@ echo "sudo chroot \$CHROOT_DIR bash -c 'PATH=/docs/depot_tools:\$PATH; /docs/v8/
|
||||
echo
|
||||
echo You can now run gcmole using this command:
|
||||
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
|
||||
|
Loading…
Reference in New Issue
Block a user