Upload Kmake

This commit is contained in:
Gorochu
2026-05-26 23:36:42 -07:00
parent ba051b2f74
commit 555ec72358
41615 changed files with 13344630 additions and 1 deletions

76
deps/v8/tools/gcmole/BUILD.gn vendored Normal file
View File

@ -0,0 +1,76 @@
# Copyright 2018 the V8 project authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import("//build/config/sysroot.gni")
import("../../gni/v8.gni")
if (v8_gcmole) {
group("v8_gcmole_files") {
testonly = true
data_deps = [
":v8_gcmole_args",
"../../:v8_dump_build_config",
"../../:v8_generated_cc_files",
]
data = [
"gcmole.py",
"gcmole-test.cc",
"gcmole-tools/",
"run-gcmole.py",
"suspects.allowlist",
"ignored_files",
"test-expectations.txt",
# The following contains all relevant source and build files.
"../debug_helper/debug-helper.h",
"../../BUILD.gn",
"../../buildtools/third_party/libc++/",
"../../include/",
"../../src/",
"../../test/cctest/",
"../../test/common/",
"../../testing/gtest/include/gtest/gtest_prod.h",
"../../third_party/abseil-cpp/",
"../../third_party/fast_float/",
"../../third_party/fp16/",
"../../third_party/glibc/",
"../../third_party/googletest/src/googletest/include/gtest/gtest_prod.h",
"../../third_party/highway/",
"../../third_party/icu/source/common/",
"../../third_party/icu/source/i18n/",
"../../third_party/inspector_protocol/",
"../../third_party/libc++/src/",
"../../third_party/rapidhash-v8/",
"../../third_party/simdutf/",
"../../third_party/siphash/",
"../../third_party/utf8-decoder/",
"../../third_party/v8/codegen/",
"../../third_party/valgrind/",
"../../third_party/wasm-api/wasm.h",
"../../third_party/wasm-api/wasm.hh",
"../../third_party/zlib/",
"$target_gen_dir/../../",
"$target_gen_dir/../../torque-generated/",
# This assumes gcmole tools have been fetched by a hook
# into v8/tools/gcmole/gcmole_tools.
"gcmole-tools/",
# We use the bundled sysroot with gcmole.
sysroot,
]
}
# Only prepare gcmole args if gcmole is activated by a gn arg.
action("v8_gcmole_args") {
script = "gcmole_args.py"
outputs = [ "$root_out_dir/v8_gcmole.args" ]
args = [ sysroot ]
# We use an arbitrary v8 target as proxy for calculating globally used
# gcmole plugin arguments. Should be a target that's early in the chain
# and that enables icu.
deps = [ "../../:v8_base_without_compiler" ]
}
}

53
deps/v8/tools/gcmole/Makefile vendored Normal file
View File

@ -0,0 +1,53 @@
# Copyright 2011 the V8 project authors. All rights reserved.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided
# with the distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# This is Makefile for clang plugin part of gcmole tool. See README.
LLVM_SRC_INCLUDE:=$(LLVM_SRC_ROOT)/include
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. ${CXXFLAGS} \
-D_GNU_SOURCE -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS \
-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++17 -shared -o libgcmole.so \
gcmole.cc
clean:
$(RM) libgcmole.so

3
deps/v8/tools/gcmole/OWNERS vendored Normal file
View File

@ -0,0 +1,3 @@
clemensb@chromium.org
leszeks@chromium.org
machenbach@chromium.org

32
deps/v8/tools/gcmole/PRESUBMIT.py vendored Normal file
View File

@ -0,0 +1,32 @@
# Copyright 2022 the V8 project authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
USE_PYTHON3 = True
def _RunTests(input_api, output_api):
return input_api.RunTests(
input_api.canned_checks.GetUnitTestsInDirectory(
input_api,
output_api,
'.',
files_to_check=[r'.+_test\.py$'],
run_on_python2=False))
def _CommonChecks(input_api, output_api):
"""Checks common to both upload and commit."""
checks = [
_RunTests,
]
return sum([check(input_api, output_api) for check in checks], [])
def CheckChangeOnCommit(input_api, output_api):
return _CommonChecks(input_api, output_api)
def CheckChangeOnUpload(input_api, output_api):
return _CommonChecks(input_api, output_api)

132
deps/v8/tools/gcmole/README vendored Normal file
View File

@ -0,0 +1,132 @@
DESCRIPTION -------------------------------------------------------------------
gcmole is a simple static analysis tool used to find possible evaluation order
dependent GC-unsafe places in the V8 codebase and "stale" pointers to the heap
(ones whose addresses got invalidated by the GC).
For example the following code is GC-unsafe:
Handle<Object> Foo(); // Assume Foo can trigger a GC.
void Bar(Object, Object);
Handle<Object> baz;
baz->Qux(*Foo()); // (a)
Bar(*Foo(), *baz); // (b)
Both in cases (a) and (b) compiler is free to evaluate call arguments (that
includes receiver) in any order. That means it can dereference baz before
calling to Foo and save a raw pointer to a heap object in the register or
on the stack.
In terms of the AST analysis that gcmole does, it warns about places in the
code which result in 2 subtrees, the order of execution of which is undefined
by C++, one of which causes a GC and the other dereferences a Handle to a raw
Object (or its subclasses).
The following code triggers a stale variable warning (assuming that the Foo
function was detected as potentially allocating, as in the previous example):
JSObject raw_obj = ...;
Foo();
Print(raw_obj);
Since Foo can trigger a GC, it might have moved the raw_obj. The solution is
simply to store it as a Handle.
PREREQUISITES -----------------------------------------------------------------
(1) Install Python
$ sudo apt-get install python
(2) Get LLVM 8.0 and Clang 8.0 sources and build them.
Follow the instructions on http://clang.llvm.org/get_started.html.
Make sure to pass -DCMAKE_BUILD_TYPE=Release to cmake to get Release build
instead of a Debug one.
(3) Build gcmole Clang plugin (libgcmole.so)
In the tools/gcmole directory execute the following command:
$ BUILD_ROOT=<path> LLVM_SRC_ROOT=<path> CLANG_SRC_ROOT=<path> make
(*) Note that steps (2) and (3) can also be achieved by just using the included
bootstrapping script in this directory:
$ ./tools/gcmole/bootstrap.sh
This will use "third_party/llvm+clang-build" as a build directory and checkout
required sources in the "third_party" directory.
USING GCMOLE ------------------------------------------------------------------
gcmole consists of driver script written in Python and Clang plugin that does
C++ AST processing. Plugin (libgcmole.so) is expected to be in the same
folder as driver (gcmole.py).
To start analysis cd into the root of v8 checkout and execute the following
command:
CLANG_BIN=<path-to-clang-bin-folder> python tools/gcmole/gcmole.py [<arch>]
where arch should be one of architectures supported by V8 (arm, ia32, x64).
Analysis will be performed in 2 stages:
- on the first stage driver will parse all files and build a global callgraph
approximation to find all functions that might potentially cause GC, list
of this functions will be written into gcsuspects file.
- on the second stage driver will parse all files again and will locate all
callsites that might be GC-unsafe based on the list of functions causing GC.
Such places are marked with a "Possible problem with evaluation order."
warning. Messages "Failed to resolve v8::internal::Object" are benign and
can be ignored.
If any errors were found driver exits with non-zero status.
TESTING -----------------------------------------------------------------------
Tests are automatically run by the main python runner. Expectations are in
test-expectations.txt and need to be updated whenever the sources of the tests
in gcmole-test.cc are modified (line numbers also count).
PACKAGING ---------------------------------------------------------------------
gcmole is deployed on V8's buildbot infrastructure to run it as part of the
continuous integration. A pre-built package of gcmole together with Clang is
hosted on Google Cloud Storage for this purpose. To update this package to a
newer version, use the provided packaging script:
$ ./tools/gcmole/package.sh
This will create a new "tools/gcmole/gcmole-tools.tar.gz" package with the
corresponding SHA1 sum suitable to be used for this purpose. It assumes that
Clang was built in "third_party/llvm+clang-build" (e.g. by the bootstrapping
script "bootstrap.sh" mentioned above).
TROUBLESHOOTING ---------------------------------------------------------------
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
is currently not supported by gcmole. Insert the following code at the end of
the FunctionAnalyzer::VisitExpr method to see the unsupported AST class(es)
and the source position which generates them:
if (expr) {
clang::Stmt::StmtClass stmtClass = expr->getStmtClass();
d_.Report(clang::FullSourceLoc(expr->getExprLoc(), sm_),
d_.getCustomDiagID(clang::DiagnosticsEngine::Remark, "%0")) << stmtClass;
}
For instance, gcmole currently doesn't support AtomicExprClass statements
introduced for atomic operations.
A convenient way to observe the AST generated by Clang is to pass the following
flags when invoking clang++
-Xclang -ast-dump -fsyntax-only

135
deps/v8/tools/gcmole/bootstrap.sh vendored Executable file
View File

@ -0,0 +1,135 @@
#!/usr/bin/env bash
# Copyright 2013 the V8 project authors. All rights reserved.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided
# with the distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# This script will build libgcmole.so as well as a corresponding recent
# version of Clang and LLVM. The Clang will be built with the locally
# installed compiler and statically link against the local libstdc++ so
# that the resulting binary is easier transferable between different
# environments.
LLVM_RELEASE=19.1.3
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"
# Die if any command dies.
set -e
OS="$(uname -s)"
# Xcode and clang don't get along when predictive compilation is enabled.
# http://crbug.com/96315
if [[ "${OS}" = "Darwin" ]] && xcodebuild -version | grep -q 'Xcode 3.2' ; then
XCONF=com.apple.Xcode
if [[ "${GYP_GENERATORS}" != "make" ]] && \
[ "$(defaults read "${XCONF}" EnablePredictiveCompilation)" != "0" ]; then
echo
echo " HEARKEN!"
echo "You're using Xcode3 and you have 'Predictive Compilation' enabled."
echo "This does not work well with clang (http://crbug.com/96315)."
echo "Disable it in Preferences->Building (lower right), or run"
echo " defaults write ${XCONF} EnablePredictiveCompilation -boolean NO"
echo "while Xcode is not running."
echo
fi
SUB_VERSION=$(xcodebuild -version | sed -Ene 's/Xcode 3\.2\.([0-9]+)/\1/p')
if [[ "${SUB_VERSION}" < 6 ]]; then
echo
echo " YOUR LD IS BUGGY!"
echo "Please upgrade Xcode to at least 3.2.6."
echo
fi
fi
echo Getting LLVM release "${LLVM_RELEASE}" in "${LLVM_PROJECT_DIR}"
if ! [ -d "${LLVM_PROJECT_DIR}" ] || ! git -C "${LLVM_PROJECT_DIR}" remote get-url origin | grep -q -F "https://github.com/llvm/llvm-project.git" ; then
rm -rf "${LLVM_PROJECT_DIR}"
git clone --depth=1 --branch "llvmorg-${LLVM_RELEASE}" "https://github.com/llvm/llvm-project.git" "${LLVM_PROJECT_DIR}"
else
git -C "${LLVM_PROJECT_DIR}" fetch --depth=1 origin "llvmorg-${LLVM_RELEASE}"
git -C "${LLVM_PROJECT_DIR}" checkout FETCH_HEAD
fi
# Echo all commands
set -x
NUM_JOBS=3
if [[ "${OS}" = "Linux" ]]; then
if [[ -e "/proc/cpuinfo" ]]; then
NUM_JOBS="$(grep -c "^processor" /proc/cpuinfo)"
else
# Hack when running in chroot
NUM_JOBS="32"
fi
elif [ "${OS}" = "Darwin" ]; then
NUM_JOBS="$(sysctl -n hw.ncpu)"
fi
# Build clang.
if [ ! -e "${BUILD_DIR}" ]; then
mkdir "${BUILD_DIR}"
fi
cd "${BUILD_DIR}"
cmake -GNinja -DCMAKE_CXX_FLAGS="-static-libstdc++" -DLLVM_ENABLE_TERMINFO=OFF \
-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}" clang
if [[ "${BUILD_TYPE}" = "Release" ]]; then
# Strip the clang binary.
STRIP_FLAGS=
if [ "${OS}" = "Darwin" ]; then
# See http://crbug.com/256342
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}" $BUILD_TYPE
set +x
echo '#########################################################################'
echo 'Congratulations you compiled clang and libgcmole.so'
echo
echo '# You can now run gcmole:'
echo 'tools/gcmole/gcmole.py full \'
echo ' --clang-bin-dir="tools/gcmole/bootstrap/build/bin" \'
echo ' --clang-plugins-dir="tools/gcmole" \'
echo ' --v8-target-cpu=$CPU'
echo

365
deps/v8/tools/gcmole/gcmole-test.cc vendored Normal file
View File

@ -0,0 +1,365 @@
// Copyright 2019 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/execution/isolate.h"
#include "src/handles/handles-inl.h"
#include "src/handles/handles.h"
#include "src/heap/local-heap.h"
#include "src/objects/foreign-inl.h"
#include "src/objects/managed.h"
#include "src/objects/maybe-object.h"
#include "src/objects/object-macros.h"
namespace v8 {
namespace internal {
// ------- Test simple argument evaluation order problems ---------
void Safepoint() { LocalHeap::Current()->Safepoint(); }
Handle<Object> CauseGC(Handle<Object> obj, Isolate* isolate) {
isolate->heap()->CollectGarbage(OLD_SPACE, GarbageCollectionReason::kTesting);
return obj;
}
Tagged<Object> CauseGCRaw(Tagged<Object> obj, Isolate* isolate) {
isolate->heap()->CollectGarbage(OLD_SPACE, GarbageCollectionReason::kTesting);
return obj;
}
Tagged<Managed<int>> CauseGCManaged(int i, Isolate* isolate) {
isolate->heap()->CollectGarbage(OLD_SPACE, GarbageCollectionReason::kTesting);
return Cast<Managed<int>>(Smi::FromInt(i));
}
void TwoArgumentsFunction(Tagged<Object> a, Tagged<Object> b) {
Print(a);
Print(b);
}
void TestTwoArguments(Isolate* isolate) {
Handle<JSObject> obj1 = isolate->factory()->NewJSObjectWithNullProto();
Handle<JSObject> obj2 = isolate->factory()->NewJSObjectWithNullProto();
// Should cause warning.
TwoArgumentsFunction(*CauseGC(obj1, isolate), *CauseGC(obj2, isolate));
}
void TwoSizeTArgumentsFunction(size_t a, size_t b) {
USE(a);
USE(b);
}
void TestTwoSizeTArguments(Isolate* isolate) {
Handle<JSObject> obj1 = isolate->factory()->NewJSObjectWithNullProto();
Handle<JSObject> obj2 = isolate->factory()->NewJSObjectWithNullProto();
// Should cause warning.
TwoSizeTArgumentsFunction(sizeof(*CauseGC(obj1, isolate)),
sizeof(*CauseGC(obj2, isolate)));
}
// --------- Test problFems with method arguments ----------
class SomeObject : public HeapObject {
public:
void Method(Tagged<Object> a) { Print(a); }
OBJECT_CONSTRUCTORS(SomeObject, HeapObject);
};
void TestMethodCall(Isolate* isolate) {
Tagged<SomeObject> obj;
Handle<SomeObject> so = handle(obj, isolate);
Handle<JSObject> obj1 = isolate->factory()->NewJSObjectWithNullProto();
// Should cause warning.
so->Method(*CauseGC(obj1, isolate));
// Should cause warning.
so->Method(CauseGCRaw(*obj1, isolate));
}
void TestOperatorCall(Isolate* isolate) {
Tagged<SomeObject> obj;
Handle<JSObject> obj1 = isolate->factory()->NewJSObjectWithNullProto();
// Should not cause warning.
obj = UncheckedCast<SomeObject>(*CauseGC(obj1, isolate));
}
// --------- Test for templated sub-classes of Object ----------
void TestFollowingTemplates(Isolate* isolate) {
// Should cause warning.
CauseGCManaged(42, isolate);
}
// --------- Test for correctly resolving virtual methods ----------
class BaseObject {
public:
virtual Handle<Object> VirtualCauseGC(Handle<Object> obj, Isolate* isolate) {
return obj;
}
};
class DerivedObject : public BaseObject {
public:
Handle<Object> VirtualCauseGC(Handle<Object> obj, Isolate* isolate) override {
isolate->heap()->CollectGarbage(OLD_SPACE,
GarbageCollectionReason::kTesting);
return obj;
}
};
void TestFollowingVirtualFunctions(Isolate* isolate) {
DerivedObject derived;
BaseObject* base = &derived;
Handle<JSObject> obj1 = isolate->factory()->NewJSObjectWithNullProto();
Tagged<SomeObject> so;
Handle<SomeObject> so_handle = handle(so, isolate);
// Should cause warning.
so_handle->Method(*derived.VirtualCauseGC(obj1, isolate));
// Should cause warning.
so_handle->Method(*base->VirtualCauseGC(obj1, isolate));
}
// --------- Test for correctly resolving static methods ----------
class SomeClass {
public:
static Handle<Object> StaticCauseGC(Handle<Object> obj, Isolate* isolate) {
isolate->heap()->CollectGarbage(OLD_SPACE,
GarbageCollectionReason::kTesting);
return obj;
}
};
void TestFollowingStaticFunctions(Isolate* isolate) {
Tagged<SomeObject> so;
Handle<SomeObject> so_handle = handle(so, isolate);
Handle<JSObject> obj1 = isolate->factory()->NewJSObjectWithNullProto();
// Should cause warning.
so_handle->Method(*SomeClass::StaticCauseGC(obj1, isolate));
}
// --------- Test basic dead variable analysis ----------
void TestDeadVarAnalysis(Isolate* isolate) {
Tagged<JSObject> raw_obj = *isolate->factory()->NewJSObjectWithNullProto();
CauseGCRaw(raw_obj, isolate);
// Should cause warning.
Print(raw_obj);
}
void TestDeadVarBecauseOfSafepointAnalysis(Isolate* isolate) {
Tagged<JSObject> raw_obj = *isolate->factory()->NewJSObjectWithNullProto();
Safepoint();
// Should cause warning.
Print(raw_obj);
}
void TestGuardedDeadVarAnalysis(Isolate* isolate) {
Tagged<JSObject> raw_obj = *isolate->factory()->NewJSObjectWithNullProto();
// Note: having DisableGCMole with the same function as CauseGC
// normally doesn't make sense, but we want to test whether the guards
// are recognized by GCMole.
DisableGCMole no_gc_mole;
CauseGCRaw(raw_obj, isolate);
// Shouldn't cause warning.
Print(raw_obj);
}
void TestGuardedDeadVarAnalysis2(Isolate* isolate) {
Tagged<JSObject> raw_obj = *isolate->factory()->NewJSObjectWithNullProto();
// Note: having DisallowGarbageCollection with the same function as CauseGC
// normally doesn't make sense, but we want to test whether the guards
// are recognized by GCMole.
DisallowGarbageCollection no_gc;
CauseGCRaw(raw_obj, isolate);
// Should cause warning.
Print(raw_obj);
}
void TestGuardedAgainstSafepointDeadVarAnalysis(Isolate* isolate) {
Tagged<JSObject> raw_obj = *isolate->factory()->NewJSObjectWithNullProto();
// Note: having DisableGCMole with the same function as CauseGC
// normally doesn't make sense, but we want to test whether the guards
// are recognized by GCMole.
DisableGCMole no_gc_mole;
Safepoint();
// Shouldn't cause warning.
Print(raw_obj);
}
void TestGuardedAgainstSafepointDeadVarAnalysis2(Isolate* isolate) {
Tagged<JSObject> raw_obj = *isolate->factory()->NewJSObjectWithNullProto();
// Note: having DisallowGarbageCollection with the same function as CauseGC
// normally doesn't make sense, but we want to test whether the guards
// are recognized by GCMole.
DisallowGarbageCollection no_gc;
Safepoint();
// Should cause warning.
Print(raw_obj);
}
void TestGuardedAgainstSafepointDeadVarAnalysis3(Isolate* isolate) {
Tagged<JSObject> raw_obj = *isolate->factory()->NewJSObjectWithNullProto();
// Note: having DisallowGarbageCollection with the same function as CauseGC
// normally doesn't make sense, but we want to test whether the guards
// are recognized by GCMole.
DisallowGarbageCollection no_gc;
Safepoint();
// Should cause warning.
Print(raw_obj);
{
DisableGCMole no_gc_mole;
// Shouldn't cause warning.
Print(raw_obj);
}
// Should cause warning.
Print(raw_obj);
}
void TestOnlyHeapGuardedDeadVarAnalysisInCompound(Isolate* isolate) {
Tagged<JSObject> raw_obj = *isolate->factory()->NewJSObjectWithNullProto();
// {DisallowHeapAccess} has a {DisallowHeapAllocation}, but no
// {DisallowSafepoints}, so it could see objects move due to safepoints.
DisallowHeapAccess no_gc;
CauseGCRaw(raw_obj, isolate);
// Should cause warning.
Print(raw_obj);
}
void TestOnlyHeapGuardedDeadVarAnalysisInCompound2(Isolate* isolate) {
Tagged<JSObject> raw_obj = *isolate->factory()->NewJSObjectWithNullProto();
// {DisallowHeapAccess} has a {DisallowHeapAllocation}, but no
// {DisallowSafepoints}, so it could see objects move due to safepoints.
DisallowHeapAccess no_gc;
CauseGCRaw(raw_obj, isolate);
// Should cause warning.
Print(raw_obj);
DisableGCMole no_gc_mole;
// Should cause warning.
Print(raw_obj);
}
void TestGuardedDeadVarAnalysisNested(Tagged<JSObject> raw_obj,
Isolate* isolate) {
CauseGCRaw(raw_obj, isolate);
// Should cause warning.
Print(raw_obj);
}
void TestGuardedDeadVarAnalysisCaller(Isolate* isolate) {
DisableGCMole no_gc_mole;
Tagged<JSObject> raw_obj = *isolate->factory()->NewJSObjectWithNullProto();
TestGuardedDeadVarAnalysisNested(raw_obj, isolate);
// Shouldn't cause warning.
Print(raw_obj);
}
void TestGuardedDeadVarAnalysisCaller2(Isolate* isolate) {
DisallowGarbageCollection no_gc;
Tagged<JSObject> raw_obj = *isolate->factory()->NewJSObjectWithNullProto();
TestGuardedDeadVarAnalysisNested(raw_obj, isolate);
// Should cause warning.
Print(raw_obj);
}
void TestGuardedDeadVarAnalysisCaller3(Isolate* isolate) {
DisallowHeapAccess no_gc;
Tagged<JSObject> raw_obj = *isolate->factory()->NewJSObjectWithNullProto();
TestGuardedDeadVarAnalysisNested(raw_obj, isolate);
// Should cause warning.
Print(raw_obj);
}
void TestGuardedDeadVarAnalysisCaller4(Isolate* isolate) {
Tagged<JSObject> raw_obj = *isolate->factory()->NewJSObjectWithNullProto();
TestGuardedDeadVarAnalysisNested(raw_obj, isolate);
// Should cause warning.
Print(raw_obj);
}
Tagged<JSObject> GuardedAllocation(Isolate* isolate) {
DisallowGarbageCollection no_gc;
return *isolate->factory()->NewJSObjectWithNullProto();
}
Tagged<JSObject> GuardedAllocation2(Isolate* isolate) {
DisableGCMole no_gc_mole;
return *isolate->factory()->NewJSObjectWithNullProto();
}
void TestNestedDeadVarAnalysis(Isolate* isolate) {
Tagged<JSObject> raw_obj = GuardedAllocation(isolate);
CauseGCRaw(raw_obj, isolate);
// Should cause warning.
Print(raw_obj);
}
void TestNestedDeadVarAnalysis2(Isolate* isolate) {
DisableGCMole no_gc_mole;
Tagged<JSObject> raw_obj = GuardedAllocation(isolate);
CauseGCRaw(raw_obj, isolate);
// Shouldn't cause warning.
Print(raw_obj);
}
// Test that putting a guard in the middle of the function doesn't
// mistakenly cover the whole scope of the raw variable.
void TestGuardedDeadVarAnalysisMidFunction(Isolate* isolate) {
Tagged<JSObject> raw_obj = *isolate->factory()->NewJSObjectWithNullProto();
CauseGCRaw(raw_obj, isolate);
// Guarding the rest of the function from triggering a GC.
DisallowGarbageCollection no_gc;
// Should cause warning.
Print(raw_obj);
}
// Test that putting a guard in the middle of the function doesn't
// mistakenly cover the whole scope of the raw variable.
void TestGuardedDeadVarAnalysisMidFunction2(Isolate* isolate) {
Tagged<JSObject> raw_obj = *isolate->factory()->NewJSObjectWithNullProto();
CauseGCRaw(raw_obj, isolate);
// Guarding the rest of the function from triggering a GC.
DisableGCMole no_gc_mole;
// Should cause warning.
Print(raw_obj);
}
void TestGuardedDeadVarAnalysisMultipleSafepoints(Isolate* isolate) {
// TODO(https://crbug.com/v8/13536): The analysis points to this safepoint,
// while it should point to the one below.
Safepoint();
Tagged<JSObject> raw_obj = *isolate->factory()->NewJSObjectWithNullProto();
DisallowGarbageCollection no_gc;
Safepoint();
Print(raw_obj);
}
void TestVariableScopeInsideIf(Isolate* isolate) {
Safepoint();
Tagged<SomeObject> raw_obj;
if (Tagged<Map> raw_map = raw_obj->map(); !raw_map.is_null()) {
Print(raw_map);
}
}
} // namespace internal
} // namespace v8

View File

@ -0,0 +1 @@
c6c13bce4bf6f92e64236d91a82beb84ea2c8433

1684
deps/v8/tools/gcmole/gcmole.cc vendored Normal file

File diff suppressed because it is too large Load Diff

882
deps/v8/tools/gcmole/gcmole.py vendored Executable file
View File

@ -0,0 +1,882 @@
#!/usr/bin/env python3
# Copyright 2020 the V8 project authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# 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]
from contextlib import contextmanager
from contextlib import redirect_stderr
from multiprocessing import cpu_count
from pathlib import Path
import argparse
import collections
import difflib
import io
import json
import os
import pickle
import re
import subprocess
import sys
import threading
import queue
ArchCfg = collections.namedtuple("ArchCfg",
["name", "cpu", "triple", "arch_options"])
ARCHITECTURES = {
"ia32":
ArchCfg(
name="ia32",
cpu="x86",
triple="i586-unknown-linux",
arch_options=["-m32"],
),
"arm":
ArchCfg(
name="arm",
cpu="arm",
triple="i586-unknown-linux",
arch_options=["-m32"],
),
"x64":
ArchCfg(
name="x64",
cpu="x64",
triple="x86_64-unknown-linux",
arch_options=[]),
"arm64":
ArchCfg(
name="arm64",
cpu="arm64",
triple="x86_64-unknown-linux",
arch_options=[],
),
}
ARCHITECTURES['x86'] = ARCHITECTURES['ia32']
def log(format, *args, **kwargs):
mark = ("#", "=", "-", ".")[kwargs.get("level", 0)]
print(mark * 2, str(format).format(*list(map(str, args))))
def fatal(format):
log(format)
sys.exit(1)
# -----------------------------------------------------------------------------
# Clang invocation
def make_clang_command_line(plugin, plugin_args, options):
with open(options.v8_build_dir / 'v8_gcmole.args') as f:
generated_args = f.read().strip().split()
arch_cfg = ARCHITECTURES[options.v8_target_cpu]
prefixed_plugin_args = []
if plugin_args:
for arg in plugin_args:
prefixed_plugin_args += [
"-Xclang",
"-plugin-arg-" + plugin,
"-Xclang",
arg,
]
log("Using generated files in {}", options.v8_build_dir / 'gen')
return ([
options.clang_bin_dir / "clang++",
"-std=c++20",
"-c",
"-Xclang",
"-load",
"-Xclang",
options.clang_plugins_dir / "libgcmole.so",
"-Xclang",
"-plugin",
"-Xclang",
plugin,
] + prefixed_plugin_args + [
"-Xclang",
"-triple",
"-Xclang",
arch_cfg.triple,
"-fno-exceptions",
"-Wno-everything",
"-DV8_GC_MOLE",
] + generated_args + arch_cfg.arch_options)
def invoke_clang_plugin_for_file(filename, cmd_line, verbose):
args = cmd_line + [filename]
args = list(map(str, args))
if verbose:
print("popen ", " ".join(args))
p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = p.communicate()
return p.returncode, stdout.decode("utf-8"), stderr.decode("utf-8")
def invoke_clang_plugin_for_files_in_queue(i, input_queue, output_queue,
cancel_event, cmd_line, verbose):
success = False
try:
while not cancel_event.is_set():
filename = input_queue.get_nowait()
ret, stdout, stderr = invoke_clang_plugin_for_file(
filename, cmd_line, verbose)
output_queue.put_nowait((filename, ret, stdout, stderr))
if ret != 0:
break
except KeyboardInterrupt:
log("[{}] Interrupting", i, level=1)
except queue.Empty:
success = True
finally:
# Emit a success bool so that the reader knows that there was either an
# error or all files were processed.
output_queue.put_nowait(success)
def invoke_clang_plugin_for_each_file(filenames, plugin, plugin_args, options):
cmd_line = make_clang_command_line(plugin, plugin_args, options)
verbose = options.verbose
if options.sequential:
log("Sequential execution.")
for filename in filenames:
log(filename, level=1)
returncode, stdout, stderr = invoke_clang_plugin_for_file(
filename, cmd_line, verbose)
if returncode != 0:
sys.stderr.write(stderr)
sys.exit(returncode)
yield filename, stdout, stderr
else:
log("Parallel execution.")
cpus = cpu_count()
input_queue = queue.Queue()
output_queue = queue.Queue()
threads = []
try:
for filename in filenames:
input_queue.put(filename)
cancel_event = threading.Event()
for i in range(min(len(filenames), cpus)):
threads.append(
threading.Thread(
target=invoke_clang_plugin_for_files_in_queue,
args=(i, input_queue, output_queue, cancel_event, cmd_line,
verbose)))
for t in threads:
t.start()
num_finished = 0
while num_finished < len(threads):
output = output_queue.get()
if type(output) == bool:
if output:
num_finished += 1
continue
else:
break
filename, returncode, stdout, stderr = output
log(filename, level=2)
if returncode != 0:
sys.stderr.write(stderr)
sys.exit(returncode)
yield filename, stdout, stderr
finally:
cancel_event.set()
for t in threads:
t.join()
# -----------------------------------------------------------------------------
def build_file_list(options):
"""Calculates the list of source files to be checked with gcmole.
The list comprises all files from marked source sections in the
listed BUILD.gn files. All files preceeded by the following comment and
until the end of the source section are used:
### gcmole(arch) ###
Where arch can either be all (all architectures) or one of the supported V8
architectures.
The structure of these directives is also checked by presubmit via:
tools/v8_presubmit.py::GCMoleProcessor.
Returns: List of file paths (of type Path).
"""
if options.test_run:
return [options.v8_root_dir / "tools/gcmole/gcmole-test.cc"]
result = []
gn_files = [
("BUILD.gn", re.compile('"([^"]*?\.cc)"'), ""),
("test/cctest/BUILD.gn", re.compile('"(test-[^"]*?\.cc)"'),
Path("test/cctest/")),
]
gn_re = re.compile(f"### gcmole\((all|{options.v8_target_cpu})\) ###(.*?)\]",
re.MULTILINE | re.DOTALL)
for filename, file_pattern, prefix in gn_files:
path = options.v8_root_dir / filename
with open(path) as gn_file:
gn = gn_file.read()
for _, sources in gn_re.findall(gn):
for file in file_pattern.findall(sources):
result.append(options.v8_root_dir / prefix / file)
# Filter files of current shard if running on multiple hosts.
def is_in_shard(index):
return (index % options.shard_count) == options.shard_index
return [f for i, f in enumerate(result) if is_in_shard(i)]
# -----------------------------------------------------------------------------
# GCSuspects Generation
# Note that the gcsuspects file lists functions in the form:
# mangled_name,unmangled_function_name
#
# This means that we can match just the function name by matching only
# after a comma.
ALLOWLIST = [
# The following functions call CEntryStub which is always present.
"MacroAssembler.*,CallRuntime",
"CompileCallLoadPropertyWithInterceptor",
"CallIC.*,GenerateMiss",
# DirectCEntryStub is a special stub used on ARM.
# It is pinned and always present.
"DirectCEntryStub.*,GenerateCall",
# TODO GCMole currently is sensitive enough to understand that certain
# functions only cause GC and return Failure simulataneously.
# Callsites of such functions are safe as long as they are properly
# check return value and propagate the Failure to the caller.
# It should be possible to extend GCMole to understand this.
"Heap.*,TryEvacuateObject",
# Ignore all StateTag methods.
"StateTag",
# Ignore printing of elements transition.
"PrintElementsTransition",
# CodeCreateEvent receives AbstractCode (a raw ptr) as an argument.
"CodeCreateEvent",
"WriteField",
]
GC_PATTERN = ",.*Collect.*Garbage"
SAFEPOINT_PATTERN = ",SafepointSlowPath"
ALLOWLIST_PATTERN = "|".join("(?:{})".format(p) for p in ALLOWLIST)
def merge_regexp(pattern_dict):
return re.compile("|".join("(?P<{}>{})".format(key, value)
for (key, value) in list(pattern_dict.items())))
IS_SPECIAL_WITHOUT_ALLOW_LIST = merge_regexp({
"gc": GC_PATTERN,
"safepoint": SAFEPOINT_PATTERN
})
IS_SPECIAL_WITH_ALLOW_LIST = merge_regexp({
"gc": GC_PATTERN,
"safepoint": SAFEPOINT_PATTERN,
"allow": ALLOWLIST_PATTERN
})
class CallGraph:
def __init__(self):
self.funcs = collections.defaultdict(set)
self.current_caller = None
def parse(self, lines):
for funcname in lines:
if not funcname:
continue
if funcname[0] != "\t":
# Always inserting the current caller makes the serialized version
# more compact.
self.funcs[funcname]
self.current_caller = funcname
else:
self.funcs[funcname[1:]].add(self.current_caller)
def to_file(self, file_name):
"""Store call graph in file 'file_name'."""
log(f"Writing serialized callgraph to {file_name}")
with open(file_name, 'wb') as f:
pickle.dump(self, f)
@staticmethod
def from_file(file_name):
"""Restore call graph from file 'file_name'."""
log(f"Reading serialized callgraph from {file_name}")
with open(file_name, 'rb') as f:
return pickle.load(f)
@staticmethod
def from_files(*file_names):
"""Merge multiple call graphs from a list of files."""
callgraph = CallGraph()
for file_name in file_names:
funcs = CallGraph.from_file(file_name).funcs
for callee, callers in funcs.items():
callgraph.funcs[callee].update(callers)
return callgraph
class GCSuspectsCollector:
def __init__(self, options, funcs):
self.gc = {}
self.gc_caused = collections.defaultdict(set)
self.funcs = funcs
if options.allowlist:
self.is_special = IS_SPECIAL_WITH_ALLOW_LIST
else:
self.is_special = IS_SPECIAL_WITHOUT_ALLOW_LIST
def add_cause(self, name, cause):
self.gc_caused[name].add(cause)
def resolve(self, name):
m = self.is_special.search(name)
if not m:
return
if m.group("gc"):
self.gc[name] = True
self.add_cause(name, "<GC>")
elif m.group("safepoint"):
self.gc[name] = True
self.add_cause(name, "<Safepoint>")
elif m.group("allow"):
self.gc[name] = False
def propagate(self):
log("Propagating GC information")
def mark(funcname):
for caller in self.funcs[funcname]:
if caller not in self.gc:
self.gc[caller] = True
mark(caller)
self.add_cause(caller, funcname)
for funcname in self.funcs:
self.resolve(funcname)
for funcname in self.funcs:
if self.gc.get(funcname, False):
mark(funcname)
def generate_callgraph(files, options):
"""Construct a (potentially partial) call graph from a subset of
source files.
"""
callgraph = CallGraph()
log(f"Building call graph for {options.v8_target_cpu}")
for _, stdout, _ in invoke_clang_plugin_for_each_file(
files, "dump-callees", [], options):
callgraph.parse(stdout.splitlines())
return callgraph
def generate_gc_suspects_from_callgraph(callgraph, options):
"""Calculate and store gc-suspect information from a given call graph."""
collector = GCSuspectsCollector(options, callgraph.funcs)
collector.propagate()
# TODO(cbruni): remove once gcmole.cc is migrated
write_gcmole_results(collector, options, options.v8_root_dir)
write_gcmole_results(collector, options, options.out_dir)
def generate_gc_suspects_from_files(options):
"""Generate file list and corresponding gc-suspect information."""
files = build_file_list(options)
call_graph = generate_callgraph(files, options)
generate_gc_suspects_from_callgraph(call_graph, options)
return files
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 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:
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("end,nested\n")
log("GCSuspects and gccauses generated for {} in '{}'", options.v8_target_cpu,
dst)
# ------------------------------------------------------------------------------
# Analysis
def check_correctness_for_arch(files, options):
processed_files = 0
errors_found = False
log("Searching for evaluation order problems " +
("and dead variables " if options.dead_vars else "") + "for " +
options.v8_target_cpu)
plugin_args = []
if options.dead_vars:
plugin_args.append("--dead-vars")
if options.verbose:
plugin_args.append("--verbose")
if options.verbose_trace:
plugin_args.append("--verbose-trace")
for _, _, stderr in invoke_clang_plugin_for_each_file(files, "find-problems",
plugin_args, options):
processed_files = processed_files + 1
if not errors_found:
errors_found = re.search("^[^:]+:\d+:\d+: (warning|error)", stderr,
re.MULTILINE) is not None
sys.stderr.write(stderr)
log("Done processing {} files.", processed_files)
log("Errors found" if errors_found else "No errors found")
return errors_found
def clean_test_output(output):
"""Substitute line number patterns for files except gcmole-test.cc, as
otherwise unrelated code changes require a rebaseline of test expectations.
"""
def clean_block(m):
# For blocks whose filename is gcmole-test.cc, leave the block unchanged.
if m.group("file").endswith("gcmole-test.cc"):
return m.group(0)
# Otherwise, clear the line numbers both from the main message, and from the
# individually printed line numbers from the printed source. That is, change
#
# ./src/heap/heap.h:934:21: note: GC call here.
# 934 | V8_EXPORT_PRIVATE void CollectGarbage(
# | ^
#
# into
#
# ./src/heap/heap.h:<number>:<number>: note: GC call here.
# | V8_EXPORT_PRIVATE void CollectGarbage(
# | ^
block = m.group("block")
def clear_line_number(m):
return f"{m.group(1)}{' ' * len(m.group(2))} |{m.group(3)}"
block = re.sub(r'(\s*)(\d+) \|(.*)', clear_line_number, block)
return f"{m.group('file')}:<number>:<number>:{m.group('msg')}\n{block}"
# Substitute within blocks of the form:
# ./src/heap/heap.h:934:21: note: GC call here.
# 934 | V8_EXPORT_PRIVATE void CollectGarbage(
# | ^
return re.sub(
r'(?P<file>\S+):(?P<pos>\d+:\d+):(?P<msg>.*)\n(?P<block>(\s*\d* \|.*\n)*)',
clean_block, output)
def has_unexpected_errors(options, errors_found, file_io):
"""Returns True if error state isn't as expected, False otherwise.
In test-run mode, we expect certain errors and return False if expectations
are met.
"""
if not options.test_run:
return errors_found
log("Test Run")
output = clean_test_output(file_io.getvalue())
if not errors_found:
log("Test file should produce errors, but none were found. Output:")
print(output)
return True
new_file = options.out_dir / "test-expectations-gen.txt"
with open(new_file, "w") as f:
f.write(output)
log("Wrote test-results: {}", new_file)
expected_file = options.v8_root_dir / "tools/gcmole/test-expectations.txt"
with open(expected_file) as exp_file:
expectations = exp_file.read()
if output != expectations:
diff_file = options.out_dir / "test_output.diff"
print("#" * 79)
log("Output mismatch from running tests.")
log("Please run gcmole manually with --test-run --verbose.")
log(f"Expected: {expected_file}")
log(f"New: {new_file}")
log(f"*Diff:* {diff_file}")
print("#" * 79)
for line in difflib.unified_diff(
expectations.splitlines(),
output.splitlines(),
fromfile=str(new_file),
tofile=str(diff_file),
lineterm="",
):
print(line)
print("#" * 79)
log("Full output")
log(f"Expected: {expected_file}")
log(f"Diff: {diff_file}")
log(f"*New*: {new_file}")
print("#" * 79)
print(output)
print("#" * 79)
return True
log("Tests ran successfully")
return False
# =============================================================================
def relative_parents(path, level=0):
return Path(os.path.relpath(str(path.resolve().parents[level])))
def main(argv):
# Get a clean parent path relative to PWD
default_root_dir = relative_parents(Path(__file__), level=2)
if len(argv) >= 1:
default_gcmole_dir = relative_parents(Path(argv[0]))
if default_gcmole_dir or not default_gcmole_dir.exists():
default_gcmole_dir = default_root_dir / 'tools' / 'gcmole'
default_clang_bin_dir = default_gcmole_dir / 'gcmole-tools/bin'
def add_common_args(parser):
archs = list(ARCHITECTURES.keys())
parser.add_argument(
"--v8-root-dir",
metavar="DIR",
default=default_root_dir,
help="V8 checkout directory. Default: '{}'".format(
default_root_dir.absolute()))
parser.add_argument(
"--v8-target-cpu",
default="x64",
choices=archs,
help="Tested CPU architecture. Choices: {}".format(archs),
metavar="CPU")
parser.add_argument(
"--clang-bin-dir",
metavar="DIR",
help="Build dir of the custom clang version for gcmole." + \
"Default: env['CLANG_DIR'] or '{}'".format(default_clang_bin_dir))
parser.add_argument(
"--clang-plugins-dir",
metavar="DIR",
help="Containing dir for libgcmole.so."
"Default: env['CLANG_PLUGINS'] or '{}'".format(default_gcmole_dir))
parser.add_argument(
"--v8-build-dir",
metavar="BUILD_DIR",
help="GN build dir for v8. Default: 'out/CPU.Release'. "
"Config must match cpu specified by --v8-target-cpu")
parser.add_argument(
"--out-dir",
metavar="DIR",
help="Output location for the gcsuspect and gcauses file."
"Default: BUILD_DIR/gen/tools/gcmole")
parser.add_argument(
"--is-bot",
action="store_true",
default=False,
help="Flag for setting build bot specific settings.")
parser.add_argument(
"--shard-count",
default=1,
type=int,
help="Number of tasks the current action (e.g. collect or check) "
"is distributed to.")
parser.add_argument(
"--shard-index",
default=0,
type=int,
help="Index of the current task (in [0..shard-count-1]) if the "
"overall action is distributed (shard-count > 1).")
group = parser.add_argument_group("GCMOLE options")
group.add_argument(
"--sequential",
action="store_true",
default=False,
help="Don't use parallel python runner.")
group.add_argument(
"--verbose",
action="store_true",
default=False,
help="Print commands to console before executing them.")
group.add_argument(
"--no-dead-vars",
action="store_false",
dest="dead_vars",
default=True,
help="Don't perform dead variable analysis.")
group.add_argument(
"--verbose-trace",
action="store_true",
default=False,
help="Enable verbose tracing from the plugin itself."
"This can be useful to debug finding dead variable.")
group.add_argument(
"--no-allowlist",
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")
group.add_argument(
"--test-run",
action="store_true",
default=False,
help="Test gcmole on tools/gcmole/gcmole-test.cc")
parser = argparse.ArgumentParser()
subps = parser.add_subparsers()
subp = subps.add_parser(
"full", description="Run both gcmole analysis passes.")
add_common_args(subp)
subp.set_defaults(func=full_run)
subp = subps.add_parser(
"collect",
description="Construct call graph from source files. "
"The action can be distributed using --shard-count and "
"--shard-index.")
add_common_args(subp)
subp.set_defaults(func=collect_run)
subp.add_argument(
"--output",
required=True,
help="Path to a file where to store the constructed call graph")
subp = subps.add_parser(
"merge",
description="Merge partial call graphs and propagate gc suspects.")
add_common_args(subp)
subp.set_defaults(func=merge_run)
subp.add_argument(
"--input",
action='append',
required=True,
help="Path to a file containing a partial call graph stored by "
"'collect'. Repeat for multiple files.")
subp = subps.add_parser(
"check",
description="Check for problems using previously collected gc-suspect "
"information. The action can be distributed using "
"--shard-count and --shard-index.")
add_common_args(subp)
subp.set_defaults(func=check_run)
options = parser.parse_args(argv[1:])
verify_and_convert_dirs(parser, options, default_gcmole_dir,
default_clang_bin_dir)
verify_clang_plugin(parser, options)
prepare_gcmole_files(options)
verify_build_config(parser, options)
override_env_options(options)
options.func(options)
@contextmanager
def maybe_redirect_stderr(options):
file_io = io.StringIO() if options.test_run else sys.stderr
with redirect_stderr(file_io) as f:
yield f
def check_files(options, files):
with maybe_redirect_stderr(options) as file_io:
errors_found = check_correctness_for_arch(files, options)
sys.exit(has_unexpected_errors(options, errors_found, file_io))
def full_run(options):
check_files(options, generate_gc_suspects_from_files(options))
def collect_run(options):
files = build_file_list(options)
callgraph = generate_callgraph(files, options)
callgraph.to_file(options.output)
def merge_run(options):
generate_gc_suspects_from_callgraph(
CallGraph.from_files(*options.input), options)
def check_run(options):
check_files(options, build_file_list(options))
def verify_and_convert_dirs(parser, options, default_tools_gcmole_dir,
default_clang_bin_dir):
# Verify options for setting directors and convert the input strings to Path
# objects.
options.v8_root_dir = Path(options.v8_root_dir)
if not options.clang_bin_dir:
# Backwards compatibility
if os.getenv("CLANG_BIN"):
options.clang_bin_dir = Path(os.getenv("CLANG_BIN"))
options.is_bot = True
else:
options.clang_bin_dir = default_clang_bin_dir
if not (options.clang_bin_dir / 'clang++').exists():
options.clang_bin_dir = Path(options.v8_root_dir,
"tools/gcmole/bootstrap/build/bin")
log("Using --clang-bin-dir={}", options.clang_bin_dir)
else:
options.clang_bin_dir = Path(options.clang_bin_dir)
if not options.clang_plugins_dir:
# Backwards compatibility
if os.getenv("CLANG_PLUGINS"):
options.clang_plugins_dir = Path(os.getenv("CLANG_PLUGINS"))
else:
options.clang_plugins_dir = default_tools_gcmole_dir.resolve()
log("Using --clang-plugins-dir={}", options.clang_plugins_dir)
else:
options.clang_plugins_dir = Path(options.clang_plugins_dir)
if not options.v8_build_dir:
config = ARCHITECTURES[options.v8_target_cpu]
options.v8_build_dir = options.v8_root_dir / ('out/%s.release' %
config.name)
# Fallback for build bots.
if not options.v8_build_dir.exists() and os.getenv("CLANG_BIN"):
options.v8_build_dir = options.v8_root_dir / 'out/build'
log("Using --v8-build-dir={}", options.v8_build_dir)
else:
options.v8_build_dir = Path(options.v8_build_dir)
if not options.out_dir:
options.out_dir = options.v8_build_dir / 'gen/tools/gcmole'
if options.v8_build_dir.exists():
options.out_dir.mkdir(parents=True, exist_ok=True)
else:
options.out_dir = Path(options.out_dir)
for flag, path in [
("--v8-root-dir", options.v8_root_dir),
("--v8-build-dir", options.v8_build_dir),
("--clang-bin-dir", options.clang_bin_dir),
("--clang-plugins-dir", options.clang_plugins_dir),
("--out-dir", options.out_dir),
]:
if not path.is_dir():
parser.error(f"{flag}='{path}' does not exist!")
def verify_clang_plugin(parser, options):
libgcmole_path = options.clang_plugins_dir / "libgcmole.so"
if not libgcmole_path.is_file():
parser.error("'{}' does not exist. Please build gcmole first.".format(
libgcmole_path))
clang_path = options.clang_bin_dir / "clang++"
if not clang_path.is_file():
parser.error(
"'{}' does not exist. Please build gcmole first.".format(clang_path))
def prepare_gcmole_files(options):
cmd = [
"autoninja", "-C", options.v8_build_dir, "v8_gcmole_files",
"v8_dump_build_config"
]
cmd = list(map(str, cmd))
log("Preparing files: {}", " ".join(cmd))
try:
subprocess.check_call(cmd)
except:
# Ignore ninja task errors on the bots
if options.is_bot:
log("Ninja command failed, ignoring errors.")
else:
raise
def verify_build_config(parser, options):
if options.is_bot:
#TODO(cbruni): Fix, currently not supported on the bots
return
config_file = options.v8_build_dir / 'v8_build_config.json'
with open(config_file) as f:
config = json.load(f)
found_cpu = None
for key in ('v8_target_cpu', 'target_cpu', 'current_cpu'):
found_cpu = config.get('v8_target_cpu')
if found_cpu == options.v8_target_cpu:
return
parser.error("Build dir '{}' config doesn't match request cpu. {}: {}".format(
options.v8_build_dir, options.v8_target_cpu, found_cpu))
def override_env_options(options):
"""Set shard options if passed as gtest environment vars on bots."""
options.shard_count = int(
os.environ.get('GTEST_TOTAL_SHARDS', options.shard_count))
options.shard_index = int(
os.environ.get('GTEST_SHARD_INDEX', options.shard_index))
if __name__ == "__main__":
main(sys.argv)

80
deps/v8/tools/gcmole/gcmole_args.py vendored Normal file
View File

@ -0,0 +1,80 @@
# Copyright 2023 the V8 project authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Calculate arguments for the gcmole plugin based on flags passed to the
compiler for a typical target in V8.
"""
from pathlib import Path
import os
import re
import sys
DEFINES_RE = re.compile(r'^defines = (.*)$', re.M)
INCLUDES_RE = re.compile(r'^include_dirs = (.*)$', re.M)
CFLAGS_CC_RE = re.compile(r'^cflags_cc = (.*)$', re.M)
BASE_DIR = Path(__file__).resolve().parents[2].absolute()
# This script is always called relative to the build directory root
# by ninja.
BUILD_DIR_ABS = Path.cwd()
BUILD_DIR_REL = BUILD_DIR_ABS.relative_to(BASE_DIR)
def search_flags(regexp, ninja_config):
match = regexp.search(ninja_config)
assert match
result = match.group(1)
assert result
return result
def main():
assert len(sys.argv) == 2, 'Expecting sysroot arg'
gn_sysroot_var = sys.argv[1]
assert gn_sysroot_var.startswith('//'), 'Expecting root-dir gn path'
rel_sysroot = gn_sysroot_var[len('//'):]
assert BUILD_DIR_ABS.exists()
ninja_file = BUILD_DIR_ABS / 'obj' / 'v8_base_without_compiler.ninja'
assert ninja_file.exists()
with ninja_file.open() as f:
ninja_config = f.read()
defines = search_flags(DEFINES_RE, ninja_config)
# ninja files escape special characters (e.g. '"', '\', '(', etc).
defines = defines.replace('\\', '')
includes = search_flags(INCLUDES_RE, ninja_config)
cflags_cc = search_flags(CFLAGS_CC_RE, ninja_config)
flags = []
flags += defines.strip().split()
# Include flags are relative to the build root. Make them relative to the
# base directory for gcmole.
# E.g. BUILD_DIR_REL = out/build and -I../../include gives -Iinclude.
for flag in includes.strip().split():
prefix, suffix = flag[:2], flag[2:]
assert prefix == '-I'
flags.append(prefix + os.path.normpath(BUILD_DIR_REL / suffix))
# System include flags are in the ccflags
for flag in cflags_cc.strip().split():
if flag == "-nostdinc++":
flags.append(flag)
elif flag.startswith('-isystem'):
prefix, suffix = flag[:len('-isystem')], flag[len('-isystem'):]
assert prefix == '-isystem'
flags.append(prefix + os.path.normpath(BUILD_DIR_REL / suffix))
flags.append(f'--sysroot={rel_sysroot}')
with open('v8_gcmole.args', 'w') as f:
f.write(' '.join(flags))
if __name__ == '__main__':
main()

441
deps/v8/tools/gcmole/gcmole_test.py vendored Normal file
View File

@ -0,0 +1,441 @@
#!/usr/bin/env python3
# Copyright 2022 the V8 project authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from pathlib import Path
import collections
import os
import re
import shutil
import subprocess
import sys
import tempfile
import textwrap
import unittest
import gcmole
GCMOLE_PATH = Path(__file__).parent.absolute()
TESTDATA_PATH = GCMOLE_PATH / 'testdata' / 'v8'
Options = collections.namedtuple(
'Options', ['v8_root_dir', 'v8_target_cpu', 'shard_count', 'shard_index',
'test_run'])
def abs_test_file(f):
return TESTDATA_PATH / f
class FilesTest(unittest.TestCase):
def testFileList_for_testing(self):
options = Options(TESTDATA_PATH, 'x64', 1, 0, True)
self.assertEqual(
gcmole.build_file_list(options),
list(map(abs_test_file, ['tools/gcmole/gcmole-test.cc'])))
def testFileList_x64(self):
options = Options(TESTDATA_PATH, 'x64', 1, 0, False)
expected = [
'file1.cc',
'file2.cc',
'x64/file1.cc',
'x64/file2.cc',
'file3.cc',
'file4.cc',
'test/cctest/test-x64-file1.cc',
'test/cctest/test-x64-file2.cc',
]
self.assertEqual(
gcmole.build_file_list(options),
list(map(abs_test_file, expected)))
def testFileList_x64_shard0(self):
options = Options(TESTDATA_PATH, 'x64', 2, 0, False)
expected = [
'file1.cc',
'x64/file1.cc',
'file3.cc',
'test/cctest/test-x64-file1.cc',
]
self.assertEqual(
gcmole.build_file_list(options),
list(map(abs_test_file, expected)))
def testFileList_x64_shard1(self):
options = Options(TESTDATA_PATH, 'x64', 2, 1, False)
expected = [
'file2.cc',
'x64/file2.cc',
'file4.cc',
'test/cctest/test-x64-file2.cc',
]
self.assertEqual(
gcmole.build_file_list(options),
list(map(abs_test_file, expected)))
def testFileList_arm(self):
options = Options(TESTDATA_PATH, 'arm', 1, 0, False)
expected = [
'file1.cc',
'file2.cc',
'file3.cc',
'file4.cc',
'arm/file1.cc',
'arm/file2.cc',
]
self.assertEqual(
gcmole.build_file_list(options),
list(map(abs_test_file, expected)))
GC = 'Foo,NowCollectAllTheGarbage'
SP = 'Bar,SafepointSlowPath'
WF = 'Baz,WriteField'
class OutputLines:
CALLERS_RE = re.compile(r'([\w,]+)\s*→\s*(.*)')
def __init__(self, *callee_list):
"""Construct a test data placeholder for output lines of one invocation of
the GCMole plugin.
Args:
callee_list: Strings, each containing a caller/calle relationship
formatted as "A → B C", meaning A calls B and C. For GC,
Safepoint and a allow-listed function use GC, SP and WF
constants above respectivly.
Methods not calling anything are formatted as "A →".
"""
self.callee_list = callee_list
def lines(self):
result = []
for str_rep in self.callee_list:
match = self.CALLERS_RE.match(str_rep)
assert match
result.append(match.group(1))
for callee in (match.group(2) or '').split():
result.append('\t' + callee)
return result
class SuspectCollectorTest(unittest.TestCase):
def create_callgraph(self, *outputs):
call_graph = gcmole.CallGraph()
for output in outputs:
call_graph.parse(output.lines())
return call_graph
def testCallGraph(self):
call_graph = self.create_callgraph(OutputLines())
self.assertDictEqual(call_graph.funcs, {})
call_graph = self.create_callgraph(OutputLines('A →'))
self.assertDictEqual(call_graph.funcs, {'A': set()})
call_graph = self.create_callgraph(OutputLines('A → B'))
self.assertDictEqual(call_graph.funcs, {'A': set(), 'B': set('A')})
call_graph = self.create_callgraph(
OutputLines('A → B C', 'B → C D', 'D →'))
self.assertDictEqual(
call_graph.funcs,
{'A': set(), 'B': set('A'), 'C': set(['A', 'B']), 'D': set('B')})
call_graph = self.create_callgraph(
OutputLines('B → C D', 'D →'), OutputLines('A → B C'))
self.assertDictEqual(
call_graph.funcs,
{'A': set(), 'B': set('A'), 'C': set(['A', 'B']), 'D': set('B')})
def testCallGraphMerge(self):
"""Test serializing, deserializing and merging call graphs."""
temp_dir = Path(tempfile.mkdtemp('gcmole_test'))
call_graph1 = self.create_callgraph(
OutputLines('B → C D E', 'D →'), OutputLines('A → B C'))
self.assertDictEqual(
call_graph1.funcs,
{'A': set(), 'B': set('A'), 'C': set(['A', 'B']), 'D': set('B'),
'E': set('B')})
call_graph2 = self.create_callgraph(
OutputLines('E → A'), OutputLines('C → D F'))
self.assertDictEqual(
call_graph2.funcs,
{'A': set('E'), 'C': set(), 'D': set('C'), 'E': set(), 'F': set('C')})
file1 = temp_dir / 'file1.bin'
file2 = temp_dir / 'file2.bin'
call_graph1.to_file(file1)
call_graph2.to_file(file2)
expected = {'A': set(['E']), 'B': set('A'), 'C': set(['A', 'B']),
'D': set(['B', 'C']), 'E': set(['B']), 'F': set(['C'])}
call_graph = gcmole.CallGraph.from_files(file1, file2)
self.assertDictEqual(call_graph.funcs, expected)
call_graph = gcmole.CallGraph.from_files(file2, file1)
self.assertDictEqual(call_graph.funcs, expected)
call_graph3 = self.create_callgraph(
OutputLines('F → G'), OutputLines('G →'))
self.assertDictEqual(
call_graph3.funcs,
{'G': set('F'), 'F': set()})
file3 = temp_dir / 'file3.bin'
call_graph3.to_file(file3)
call_graph = gcmole.CallGraph.from_files(file1, file2, file3)
self.assertDictEqual(call_graph.funcs, dict(G=set('F'), **expected))
def create_collector(self, outputs):
Options = collections.namedtuple('OptionsForCollector', ['allowlist'])
options = Options(True)
call_graph = self.create_callgraph(*outputs)
collector = gcmole.GCSuspectsCollector(options, call_graph.funcs)
collector.propagate()
return collector
def check(self, outputs, expected_gc, expected_gc_caused):
"""Verify the GCSuspectsCollector propagation and outputs against test
data.
Args:
outputs: List of OutputLines object simulating the lines returned by
the GCMole plugin in drop-callees mode. Each output lines object
represents one plugin invocation.
expected_gc: Mapping as expected by GCSuspectsCollector.gc.
expected_gc_caused: Mapping as expected by GCSuspectsCollector.gc_caused.
"""
collector = self.create_collector(outputs)
self.assertDictEqual(collector.gc, expected_gc)
self.assertDictEqual(collector.gc_caused, expected_gc_caused)
def testNoGC(self):
self.check(
outputs=[OutputLines()],
expected_gc={},
expected_gc_caused={},
)
self.check(
outputs=[OutputLines('A →')],
expected_gc={},
expected_gc_caused={},
)
self.check(
outputs=[OutputLines('A →', 'B →')],
expected_gc={},
expected_gc_caused={},
)
self.check(
outputs=[OutputLines('A → B C')],
expected_gc={},
expected_gc_caused={},
)
self.check(
outputs=[OutputLines('A → B', 'B → C')],
expected_gc={},
expected_gc_caused={},
)
self.check(
outputs=[OutputLines('A → B C', 'B → D', 'D → A', 'C →')],
expected_gc={},
expected_gc_caused={},
)
self.check(
outputs=[OutputLines('A →'), OutputLines('B →')],
expected_gc={},
expected_gc_caused={},
)
self.check(
outputs=[OutputLines('A → B'), OutputLines('B → C')],
expected_gc={},
expected_gc_caused={},
)
self.check(
outputs=[OutputLines('A → B C'),
OutputLines('B → D', 'D → A'),
OutputLines('C →')],
expected_gc={},
expected_gc_caused={},
)
def testGCOneFile(self):
self.check(
outputs=[OutputLines(f'{GC} →')],
expected_gc={GC: True},
expected_gc_caused={GC: {'<GC>'}},
)
self.check(
outputs=[OutputLines(f'A → {GC}')],
expected_gc={GC: True, 'A': True},
expected_gc_caused={'A': {GC}, GC: {'<GC>'}},
)
self.check(
outputs=[OutputLines(f'A → {GC}', 'B → A')],
expected_gc={GC: True, 'A': True, 'B': True},
expected_gc_caused={'B': {'A'}, 'A': {GC}, GC: {'<GC>'}},
)
self.check(
outputs=[OutputLines('B → A', f'A → {GC}')],
expected_gc={GC: True, 'A': True, 'B': True},
expected_gc_caused={'B': {'A'}, 'A': {GC}, GC: {'<GC>'}},
)
self.check(
outputs=[OutputLines(f'A → B {GC}', 'B →', 'C → B A')],
expected_gc={GC: True, 'A': True, 'C': True},
expected_gc_caused={'C': {'A'}, 'A': {GC}, GC: {'<GC>'}},
)
self.check(
outputs=[OutputLines(f'A → {GC}', 'B → A', 'C → A', 'D → B C')],
expected_gc={GC: True, 'A': True, 'B': True, 'C': True, 'D': True},
expected_gc_caused={'C': {'A'}, 'A': {GC}, 'B': {'A'}, 'D': {'B', 'C'},
GC: {'<GC>'}},
)
def testAllowListOneFile(self):
self.check(
outputs=[OutputLines(f'{WF} →')],
expected_gc={WF: False},
expected_gc_caused={},
)
self.check(
outputs=[OutputLines(f'{WF} → {GC}')],
expected_gc={GC: True, WF: False},
expected_gc_caused={WF: {GC}, GC: {'<GC>'}},
)
self.check(
outputs=[OutputLines(f'A → {GC}', f'{WF} → A B', 'D → A B',
f'E → {WF}')],
expected_gc={GC: True, WF: False, 'A': True, 'D': True},
expected_gc_caused={'A': {GC}, WF: {'A'}, 'D': {'A'}, GC: {'<GC>'}},
)
def testSafepointOneFile(self):
self.check(
outputs=[OutputLines(f'{SP} →')],
expected_gc={SP: True},
expected_gc_caused={SP: {'<Safepoint>'}},
)
self.check(
outputs=[OutputLines('B → A', f'A → {SP}')],
expected_gc={SP: True, 'A': True, 'B': True},
expected_gc_caused={'B': {'A'}, 'A': {SP}, SP: {'<Safepoint>'}},
)
def testCombinedOneFile(self):
self.check(
outputs=[OutputLines(f'{GC} →', f'{SP} →')],
expected_gc={SP: True, GC: True},
expected_gc_caused={SP: {'<Safepoint>'}, GC: {'<GC>'}},
)
self.check(
outputs=[OutputLines(f'A → {GC}', f'B → {SP}')],
expected_gc={GC: True, SP: True, 'A': True, 'B': True},
expected_gc_caused={'B': {SP}, 'A': {GC}, SP: {'<Safepoint>'},
GC: {'<GC>'}},
)
self.check(
outputs=[OutputLines(f'A → {GC}', f'B → {SP}', 'C → D A B')],
expected_gc={GC: True, SP: True, 'A': True, 'B': True, 'C': True},
expected_gc_caused={'B': {SP}, 'A': {GC}, 'C': {'A', 'B'},
SP: {'<Safepoint>'}, GC: {'<GC>'}},
)
def testCombinedMoreFiles(self):
self.check(
outputs=[OutputLines(f'A → {GC}'), OutputLines(f'B → {SP}')],
expected_gc={GC: True, SP: True, 'A': True, 'B': True},
expected_gc_caused={'B': {SP}, 'A': {GC}, SP: {'<Safepoint>'},
GC: {'<GC>'}},
)
self.check(
outputs=[OutputLines(f'A → {GC}'), OutputLines(f'B → {SP}'),
OutputLines('C → D A B')],
expected_gc={GC: True, SP: True, 'A': True, 'B': True, 'C': True},
expected_gc_caused={'B': {SP}, 'A': {GC}, 'C': {'A', 'B'},
SP: {'<Safepoint>'}, GC: {'<GC>'}},
)
def testWriteGCMoleResults(self):
temp_dir = Path(tempfile.mkdtemp('gcmole_test'))
Options = collections.namedtuple('OptionsForWriting', ['v8_target_cpu'])
collector = self.create_collector(
[OutputLines(f'A → {GC}'), OutputLines(f'B → {SP}')])
gcmole.write_gcmole_results(collector, Options('x64'), temp_dir)
gcsuspects_expected = textwrap.dedent(f"""\
{GC}
{SP}
A
B
""")
with open(temp_dir / 'gcsuspects') as f:
self.assertEqual(f.read(), gcsuspects_expected)
gccauses_expected = textwrap.dedent(f"""
{GC}
start,nested
<GC>
end,nested
{SP}
start,nested
<Safepoint>
end,nested
A
start,nested
{GC}
end,nested
B
start,nested
{SP}
end,nested
""").strip()
with open(temp_dir / 'gccauses') as f:
self.assertEqual(f.read().strip(), gccauses_expected)
class ArgsTest(unittest.TestCase):
def testArgs(self):
"""Test argument retrieval using a fake v8 file system and build dir."""
with tempfile.TemporaryDirectory('gcmole_args_test') as temp_dir:
temp_dir = Path(temp_dir)
temp_out = temp_dir / 'out'
temp_gcmole = temp_dir / 'tools' / 'gcmole' / 'gcmole_args.py'
shutil.copytree(abs_test_file('out'), temp_out)
os.makedirs(temp_gcmole.parent)
shutil.copy(GCMOLE_PATH / 'gcmole_args.py', temp_gcmole)
# Simulate a ninja call relative to the build dir.
gn_sysroot = '//build/linux/debian_bullseye_amd64-sysroot'
subprocess.check_call(
[sys.executable, temp_gcmole, gn_sysroot], cwd=temp_out)
with open(temp_dir / 'out' / 'v8_gcmole.args') as f:
self.assertEqual(f.read().split(), [
'-DUSE_GLIB=1',
'-DV8_TARGET_ARCH_X64',
'-I.',
'-Iout/gen',
'-Iinclude',
'-Iout/gen/include',
'-isystem../buildtools/third_party/libc++/trunk/include',
'--sysroot=build/linux/debian_bullseye_amd64-sysroot',
])
if __name__ == '__main__':
unittest.main()

2
deps/v8/tools/gcmole/ignored_files vendored Normal file
View File

@ -0,0 +1,2 @@
src/profiler/heap-snapshot-generator.cc
src/execution/isolate.cc

79
deps/v8/tools/gcmole/package.sh vendored Executable file
View File

@ -0,0 +1,79 @@
#!/usr/bin/env bash
# Copyright 2019 the V8 project authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# This script will package a built gcmole plugin together with the
# corresponding clang binary into an archive which can be used on the
# buildbot infrastructure to be run against V8 checkouts.
THIS_DIR="$(readlink -f "$(dirname "${0}")")"
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
# Clean out any old files
if [ -e "${PACKAGE_DIR:?}/bin" ] ; then
rm -rf "${PACKAGE_DIR:?}/bin"
fi
if [ -e "${PACKAGE_DIR:?}/lib" ] ; then
rm -rf "${PACKAGE_DIR:?}/lib"
fi
# Copy all required files
mkdir -p "${PACKAGE_DIR}/bin"
cp "${BUILD_DIR}/bin/clang++" "${PACKAGE_DIR}/bin"
mkdir -p "${PACKAGE_DIR}/lib"
cp -r "${BUILD_DIR}/lib/clang" "${PACKAGE_DIR}/lib"
cp "${THIS_DIR}/libgcmole.so" "${PACKAGE_DIR}"
# Generate the archive. Set some flags on tar to make the output more
# deterministic (e.g. not dependent on timestamps).
cd "$(dirname "${PACKAGE_DIR}")"
tar \
--sort=name \
--owner=root:0 \
--group=root:0 \
--mtime="UTC 1970-01-01" \
--create \
"$(basename "${PACKAGE_DIR}")" | gzip --no-name >"${PACKAGE_FILE}"
# Generate checksum
sha1sum "${PACKAGE_FILE}" | awk '{print $1}' > "${PACKAGE_SUM}"
set +x
echo
echo You can find a packaged version of gcmole here:
echo
echo $(readlink -f "${PACKAGE_FILE}")
echo
echo Upload the update package to the chrome infra:
echo
echo 'gsutil.py cp tools/gcmole/gcmole-tools.tar.gz gs://chrome-v8-gcmole/$(cat tools/gcmole/gcmole-tools.tar.gz.sha1)'
echo
echo Run bootstrap.sh in chroot if glibc versions mismatch with bots:
echo '# Create chroot'
echo 'mkdir -p $CHROOT_DIR'
echo 'sudo debootstrap $DEBIAN_VERSION $CHROOT_DIR'
echo 'sudo chroot $CHROOT_DIR apt install g++ cmake python git'
echo '# Mount ./depot_tools and ./v8 dirs in the chroot:'
echo 'sudo chroot $CHROOT_DIR mkdir /docs'
echo 'sudo mount --bind /path/to/workspace /docs'
echo '# Build gcmole'
echo "sudo chroot \$CHROOT_DIR bash -c 'PATH=/docs/depot_tools:\$PATH; /docs/v8/v8/tools/gcmole/bootstrap.sh'"
echo
echo You can now run gcmole using this command:
echo
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

73
deps/v8/tools/gcmole/run-gcmole.py vendored Executable file
View File

@ -0,0 +1,73 @@
#!/usr/bin/env python3
# Copyright 2016 the V8 project authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import os
import os.path
import signal
import subprocess
import sys
GCMOLE_PATH = os.path.dirname(os.path.abspath(__file__))
CLANG_BIN = os.path.join(GCMOLE_PATH, 'gcmole-tools', 'bin')
CLANG_PLUGINS = os.path.join(GCMOLE_PATH, 'gcmole-tools')
GCMOLE_PY = os.path.join(GCMOLE_PATH, 'gcmole.py')
V8_ROOT_DIR = os.path.dirname(os.path.dirname(GCMOLE_PATH))
def print_help():
print(
"""Usage: ./run-gcmole.py [MODE] V8_TARGET_CPU [gcmole.py OPTION]...
Helper script to run gcmole.py on the bots.""")
args = sys.argv[1:]
if "--help" in args:
print_help()
exit(0)
# Different modes of running gcmole. Optional to stay backwards-compatible.
mode = 'full'
if args and args[0] in ['check', 'collect', 'full', 'merge']:
mode = args[0]
args = args[1:]
if not args:
print("Missing arguments!")
print_help()
exit(1)
if not os.path.isfile("out/build/gen/torque-generated/builtin-definitions.h"):
print("Expected generated headers in out/build/gen.")
print("Either build v8 in out/build or change the 'out/build/gen' location in gcmole.py")
sys.exit(-1)
gcmole_py_options = args[1:]
proc = subprocess.Popen(
[
sys.executable,
GCMOLE_PY,
mode,
"--v8-build-dir=%s" % os.path.join(V8_ROOT_DIR, 'out', 'build'),
"--v8-target-cpu=%s" % args[0],
"--clang-plugins-dir=%s" % CLANG_PLUGINS,
"--clang-bin-dir=%s" % CLANG_BIN,
"--is-bot",
] + gcmole_py_options,
cwd=V8_ROOT_DIR,
)
def handle_sigterm(*args):
try:
proc.kill()
except OSError:
pass
signal.signal(signal.SIGTERM, handle_sigterm)
proc.communicate()
sys.exit(proc.returncode)

View File

@ -0,0 +1,6 @@
IsConstructor
IsEval
IsAsync
IsPromiseAll
IsPromiseAny
VisitRootPointers

View File

@ -0,0 +1,226 @@
tools/gcmole/gcmole-test.cc:30:10: warning: Possibly stale variable due to GCs.
30 | return obj;
| ^
tools/gcmole/gcmole-test.cc:28:20: note: Call might cause unexpected GC.
28 | isolate->heap()->CollectGarbage(OLD_SPACE, GarbageCollectionReason::kTesting);
| ^
./src/heap/heap.h:<number>:<number>: note: GC call here.
| V8_EXPORT_PRIVATE void CollectGarbage(
| ^
tools/gcmole/gcmole-test.cc:48:3: warning: Possible problem with evaluation order with interleaved GCs.
48 | TwoArgumentsFunction(*CauseGC(obj1, isolate), *CauseGC(obj2, isolate));
| ^
tools/gcmole/gcmole-test.cc:48:25: note: Call might cause unexpected GC.
48 | TwoArgumentsFunction(*CauseGC(obj1, isolate), *CauseGC(obj2, isolate));
| ^
tools/gcmole/gcmole-test.cc:21:1: note: GC call here.
21 | Handle<Object> CauseGC(Handle<Object> obj, Isolate* isolate) {
| ^
tools/gcmole/gcmole-test.cc:60:3: warning: Possible problem with evaluation order with interleaved GCs.
60 | TwoSizeTArgumentsFunction(sizeof(*CauseGC(obj1, isolate)),
| ^
tools/gcmole/gcmole-test.cc:60:37: note: Call might cause unexpected GC.
60 | TwoSizeTArgumentsFunction(sizeof(*CauseGC(obj1, isolate)),
| ^
tools/gcmole/gcmole-test.cc:21:1: note: GC call here.
21 | Handle<Object> CauseGC(Handle<Object> obj, Isolate* isolate) {
| ^
tools/gcmole/gcmole-test.cc:78:7: warning: Possible problem with evaluation order with interleaved GCs.
78 | so->Method(*CauseGC(obj1, isolate));
| ^
tools/gcmole/gcmole-test.cc:78:15: note: Call might cause unexpected GC.
78 | so->Method(*CauseGC(obj1, isolate));
| ^
tools/gcmole/gcmole-test.cc:21:1: note: GC call here.
21 | Handle<Object> CauseGC(Handle<Object> obj, Isolate* isolate) {
| ^
tools/gcmole/gcmole-test.cc:80:7: warning: Possible problem with evaluation order with interleaved GCs.
80 | so->Method(CauseGCRaw(*obj1, isolate));
| ^
tools/gcmole/gcmole-test.cc:78:15: note: Call might cause unexpected GC.
78 | so->Method(*CauseGC(obj1, isolate));
| ^
tools/gcmole/gcmole-test.cc:21:1: note: GC call here.
21 | Handle<Object> CauseGC(Handle<Object> obj, Isolate* isolate) {
| ^
tools/gcmole/gcmole-test.cc:124:14: warning: Possible problem with evaluation order with interleaved GCs.
124 | so_handle->Method(*derived.VirtualCauseGC(obj1, isolate));
| ^
tools/gcmole/gcmole-test.cc:124:30: note: Call might cause unexpected GC.
124 | so_handle->Method(*derived.VirtualCauseGC(obj1, isolate));
| ^
tools/gcmole/gcmole-test.cc:108:3: note: GC call here.
108 | Handle<Object> VirtualCauseGC(Handle<Object> obj, Isolate* isolate) override {
| ^
tools/gcmole/gcmole-test.cc:126:14: warning: Possible problem with evaluation order with interleaved GCs.
126 | so_handle->Method(*base->VirtualCauseGC(obj1, isolate));
| ^
tools/gcmole/gcmole-test.cc:124:30: note: Call might cause unexpected GC.
124 | so_handle->Method(*derived.VirtualCauseGC(obj1, isolate));
| ^
tools/gcmole/gcmole-test.cc:108:3: note: GC call here.
108 | Handle<Object> VirtualCauseGC(Handle<Object> obj, Isolate* isolate) override {
| ^
tools/gcmole/gcmole-test.cc:147:14: warning: Possible problem with evaluation order with interleaved GCs.
147 | so_handle->Method(*SomeClass::StaticCauseGC(obj1, isolate));
| ^
tools/gcmole/gcmole-test.cc:147:22: note: Call might cause unexpected GC.
147 | so_handle->Method(*SomeClass::StaticCauseGC(obj1, isolate));
| ^
tools/gcmole/gcmole-test.cc:133:3: note: GC call here.
133 | static Handle<Object> StaticCauseGC(Handle<Object> obj, Isolate* isolate) {
| ^
tools/gcmole/gcmole-test.cc:157:9: warning: Possibly stale variable due to GCs.
157 | Print(raw_obj);
| ^
tools/gcmole/gcmole-test.cc:154:3: note: Call might cause unexpected GC.
154 | CauseGCRaw(raw_obj, isolate);
| ^
tools/gcmole/gcmole-test.cc:27:1: note: GC call here.
27 | Tagged<Object> CauseGCRaw(Tagged<Object> obj, Isolate* isolate) {
| ^
tools/gcmole/gcmole-test.cc:165:9: warning: Possibly stale variable due to GCs.
165 | Print(raw_obj);
| ^
tools/gcmole/gcmole-test.cc:162:3: note: Call might cause unexpected GC.
162 | Safepoint();
| ^
tools/gcmole/gcmole-test.cc:19:1: note: GC call here.
19 | void Safepoint() { LocalHeap::Current()->Safepoint(); }
| ^
tools/gcmole/gcmole-test.cc:191:9: warning: Possibly stale variable due to GCs.
191 | Print(raw_obj);
| ^
tools/gcmole/gcmole-test.cc:188:3: note: Call might cause unexpected GC.
188 | CauseGCRaw(raw_obj, isolate);
| ^
tools/gcmole/gcmole-test.cc:27:1: note: GC call here.
27 | Tagged<Object> CauseGCRaw(Tagged<Object> obj, Isolate* isolate) {
| ^
tools/gcmole/gcmole-test.cc:217:9: warning: Possibly stale variable due to GCs.
217 | Print(raw_obj);
| ^
tools/gcmole/gcmole-test.cc:214:3: note: Call might cause unexpected GC.
214 | Safepoint();
| ^
tools/gcmole/gcmole-test.cc:19:1: note: GC call here.
19 | void Safepoint() { LocalHeap::Current()->Safepoint(); }
| ^
tools/gcmole/gcmole-test.cc:228:9: warning: Possibly stale variable due to GCs.
228 | Print(raw_obj);
| ^
tools/gcmole/gcmole-test.cc:226:3: note: Call might cause unexpected GC.
226 | Safepoint();
| ^
tools/gcmole/gcmole-test.cc:19:1: note: GC call here.
19 | void Safepoint() { LocalHeap::Current()->Safepoint(); }
| ^
tools/gcmole/gcmole-test.cc:235:9: warning: Possibly stale variable due to GCs.
235 | Print(raw_obj);
| ^
tools/gcmole/gcmole-test.cc:226:3: note: Call might cause unexpected GC.
226 | Safepoint();
| ^
tools/gcmole/gcmole-test.cc:19:1: note: GC call here.
19 | void Safepoint() { LocalHeap::Current()->Safepoint(); }
| ^
tools/gcmole/gcmole-test.cc:245:9: warning: Possibly stale variable due to GCs.
245 | Print(raw_obj);
| ^
tools/gcmole/gcmole-test.cc:243:3: note: Call might cause unexpected GC.
243 | CauseGCRaw(raw_obj, isolate);
| ^
tools/gcmole/gcmole-test.cc:27:1: note: GC call here.
27 | Tagged<Object> CauseGCRaw(Tagged<Object> obj, Isolate* isolate) {
| ^
tools/gcmole/gcmole-test.cc:255:9: warning: Possibly stale variable due to GCs.
255 | Print(raw_obj);
| ^
tools/gcmole/gcmole-test.cc:253:3: note: Call might cause unexpected GC.
253 | CauseGCRaw(raw_obj, isolate);
| ^
tools/gcmole/gcmole-test.cc:27:1: note: GC call here.
27 | Tagged<Object> CauseGCRaw(Tagged<Object> obj, Isolate* isolate) {
| ^
tools/gcmole/gcmole-test.cc:258:9: warning: Possibly stale variable due to GCs.
258 | Print(raw_obj);
| ^
tools/gcmole/gcmole-test.cc:253:3: note: Call might cause unexpected GC.
253 | CauseGCRaw(raw_obj, isolate);
| ^
tools/gcmole/gcmole-test.cc:27:1: note: GC call here.
27 | Tagged<Object> CauseGCRaw(Tagged<Object> obj, Isolate* isolate) {
| ^
tools/gcmole/gcmole-test.cc:265:9: warning: Possibly stale variable due to GCs.
265 | Print(raw_obj);
| ^
tools/gcmole/gcmole-test.cc:263:3: note: Call might cause unexpected GC.
263 | CauseGCRaw(raw_obj, isolate);
| ^
tools/gcmole/gcmole-test.cc:27:1: note: GC call here.
27 | Tagged<Object> CauseGCRaw(Tagged<Object> obj, Isolate* isolate) {
| ^
tools/gcmole/gcmole-test.cc:281:9: warning: Possibly stale variable due to GCs.
281 | Print(raw_obj);
| ^
tools/gcmole/gcmole-test.cc:279:3: note: Call might cause unexpected GC.
279 | TestGuardedDeadVarAnalysisNested(raw_obj, isolate);
| ^
tools/gcmole/gcmole-test.cc:261:1: note: GC call here.
261 | void TestGuardedDeadVarAnalysisNested(Tagged<JSObject> raw_obj,
| ^
tools/gcmole/gcmole-test.cc:289:9: warning: Possibly stale variable due to GCs.
289 | Print(raw_obj);
| ^
tools/gcmole/gcmole-test.cc:287:3: note: Call might cause unexpected GC.
287 | TestGuardedDeadVarAnalysisNested(raw_obj, isolate);
| ^
tools/gcmole/gcmole-test.cc:261:1: note: GC call here.
261 | void TestGuardedDeadVarAnalysisNested(Tagged<JSObject> raw_obj,
| ^
tools/gcmole/gcmole-test.cc:296:9: warning: Possibly stale variable due to GCs.
296 | Print(raw_obj);
| ^
tools/gcmole/gcmole-test.cc:294:3: note: Call might cause unexpected GC.
294 | TestGuardedDeadVarAnalysisNested(raw_obj, isolate);
| ^
tools/gcmole/gcmole-test.cc:261:1: note: GC call here.
261 | void TestGuardedDeadVarAnalysisNested(Tagged<JSObject> raw_obj,
| ^
tools/gcmole/gcmole-test.cc:313:9: warning: Possibly stale variable due to GCs.
313 | Print(raw_obj);
| ^
tools/gcmole/gcmole-test.cc:311:3: note: Call might cause unexpected GC.
311 | CauseGCRaw(raw_obj, isolate);
| ^
tools/gcmole/gcmole-test.cc:27:1: note: GC call here.
27 | Tagged<Object> CauseGCRaw(Tagged<Object> obj, Isolate* isolate) {
| ^
tools/gcmole/gcmole-test.cc:332:9: warning: Possibly stale variable due to GCs.
332 | Print(raw_obj);
| ^
tools/gcmole/gcmole-test.cc:328:3: note: Call might cause unexpected GC.
328 | CauseGCRaw(raw_obj, isolate);
| ^
tools/gcmole/gcmole-test.cc:27:1: note: GC call here.
27 | Tagged<Object> CauseGCRaw(Tagged<Object> obj, Isolate* isolate) {
| ^
tools/gcmole/gcmole-test.cc:343:9: warning: Possibly stale variable due to GCs.
343 | Print(raw_obj);
| ^
tools/gcmole/gcmole-test.cc:339:3: note: Call might cause unexpected GC.
339 | CauseGCRaw(raw_obj, isolate);
| ^
tools/gcmole/gcmole-test.cc:27:1: note: GC call here.
27 | Tagged<Object> CauseGCRaw(Tagged<Object> obj, Isolate* isolate) {
| ^
tools/gcmole/gcmole-test.cc:353:9: warning: Possibly stale variable due to GCs.
353 | Print(raw_obj);
| ^
tools/gcmole/gcmole-test.cc:349:3: note: Call might cause unexpected GC.
349 | Safepoint();
| ^
tools/gcmole/gcmole-test.cc:19:1: note: GC call here.
19 | void Safepoint() { LocalHeap::Current()->Safepoint(); }
| ^
25 warnings generated.

View File

@ -0,0 +1,35 @@
# Copyright 2022 the V8 project authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# Test data for gcmole.
some_sources = [
### gcmole(all) ###
"file1.cc",
"file1.h",
"file2.cc",
]
other_sources = [
### gcmole(x64) ###
"x64/file1.cc",
"x64/file1.h",
"x64/file2.cc",
]
yet_more_sources = [
### gcmole(all) ###
"file3.cc",
# Some other comment.
"file4.cc",
"file4.h",
]
the_last_sources = [
### gcmole(arm) ###
"arm/file1.cc",
"arm/file1.h",
"arm/file2.cc",
]

View File

@ -0,0 +1,7 @@
defines = -DUSE_GLIB=1 -DV8_TARGET_ARCH_X64
include_dirs = -I.. -Igen -I../include -Igen/include
cflags = -Wall -Werror
cflags_cc = -isystem../../buildtools/third_party/libc++/trunk/include
label_name = v8_base_without_compiler
target_out_dir = obj
target_output_name = v8_base_without_compiler

View File

@ -0,0 +1,12 @@
# Copyright 2022 the V8 project authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# Test data for gcmole.
some_sources = [
### gcmole(x64) ###
"foo-x64-file1.cc",
"test-x64-file1.cc",
"test-x64-file2.cc",
]