[offthread] Add OffThreadFactory

Introduce OffThreadFactory with initial string construction support.

The OffThreadFactory shares with Factory a new CRTP base class, called
FactoryBase. Methods in FactoryBase return a FactoryHandle<Factory, T>
alias, which is Handle<T> for normal Factory and a new OffThreadHandle<T>
for OffThreadFactory. OffThreadHandle<T> behaves like Handle<T>, except
it stores the object in-line rather than needing external storage.

Any shared factory methods are moved into FactoryBase, which uses CRTP
to call the sub-class's AllocateRaw method (plus a few more customization
points which need Isolate access on the main thread).

Methods that used to take an Isolate or Factory, and are needed off the
main thread, are now expected to be templated on the factory type and
to use the appropriate handle.

Once an OffThreadFactory has finished being used (e.g. off-thread
compilation completed) its pages are "Published" into the main-thread
Heap. To deal with string internalization without creating a bunch of
ThinStrings, this is done in two stages:

  1. 'FinishOffThread': The off-thread pages are walked to
     collect all slots pointing to "internalized" strings. After this is
     called it is invalid to allocate any more objects with the factory.
  2. 'Publish': On the main thread, we transform these slots into
     <Handle to holder, offset> pairs, then for each saved slot
     re-internalize its string and update the slot to point to the
     internalized string.

Bug: chromium:1011762
Change-Id: I008a694da3c357de34362bd86fe7e1f46b535d5e
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1992434
Commit-Queue: Leszek Swirski <leszeks@chromium.org>
Reviewed-by: Ulan Degenbaev <ulan@chromium.org>
Reviewed-by: Toon Verwaest <verwaest@chromium.org>
Cr-Commit-Position: refs/heads/master@{#65787}
This commit is contained in:
Leszek Swirski 2020-01-15 12:47:41 +01:00 committed by Commit Bot
parent e3b27b4aed
commit e659917aa3
22 changed files with 967 additions and 319 deletions

240
BUILD.gn
View File

@ -826,15 +826,9 @@ template("asm_to_inline_asm") {
assert(emit_builtins_as_inline_asm)
script = "tools/snapshot/asm_to_inline_asm.py"
deps = [
":run_mksnapshot_" + name,
]
sources = [
"$target_gen_dir/embedded${suffix}.S",
]
outputs = [
"$target_gen_dir/embedded${suffix}.cc",
]
deps = [ ":run_mksnapshot_" + name ]
sources = [ "$target_gen_dir/embedded${suffix}.S" ]
outputs = [ "$target_gen_dir/embedded${suffix}.cc" ]
args = invoker.args
args += [
rebase_path("$target_gen_dir/embedded${suffix}.S", root_build_dir),
@ -848,9 +842,7 @@ if (is_android && enable_java_templates) {
if (v8_use_external_startup_data) {
# We don't support side-by-side snapshots on Android within Chromium.
assert(!v8_use_multi_snapshots)
deps = [
"//v8",
]
deps = [ "//v8" ]
renaming_sources = [ "$root_out_dir/snapshot_blob.bin" ]
if (current_cpu == "arm" || current_cpu == "x86" ||
current_cpu == "mipsel") {
@ -934,16 +926,12 @@ action("postmortem-metadata") {
"$target_gen_dir/torque-generated/instance-types-tq.h",
]
outputs = [
"$target_gen_dir/debug-support.cc",
]
outputs = [ "$target_gen_dir/debug-support.cc" ]
args = rebase_path(outputs, root_build_dir) +
rebase_path(sources, root_build_dir)
deps = [
":run_torque",
]
deps = [ ":run_torque" ]
}
torque_files = [
@ -1115,9 +1103,7 @@ action("run_torque") {
"test/cctest/:*",
]
deps = [
":torque($v8_generator_toolchain)",
]
deps = [ ":torque($v8_generator_toolchain)" ]
script = "tools/run.py"
@ -1166,9 +1152,7 @@ action("run_torque") {
group("v8_maybe_icu") {
if (v8_enable_i18n_support) {
public_deps = [
"//third_party/icu",
]
public_deps = [ "//third_party/icu" ]
}
}
@ -1180,9 +1164,7 @@ v8_source_set("torque_generated_initializers") {
":run_torque",
]
public_deps = [
":v8_maybe_icu",
]
public_deps = [ ":v8_maybe_icu" ]
sources = [
"$target_gen_dir/torque-generated/csa-types-tq.h",
@ -1210,9 +1192,7 @@ v8_source_set("torque_generated_definitions") {
":run_torque",
]
public_deps = [
":v8_maybe_icu",
]
public_deps = [ ":v8_maybe_icu" ]
sources = [
"$target_gen_dir/torque-generated/class-definitions-tq.cc",
@ -1226,12 +1206,8 @@ v8_source_set("torque_generated_definitions") {
action("generate_bytecode_builtins_list") {
script = "tools/run.py"
outputs = [
"$target_gen_dir/builtins-generated/bytecodes-builtins-list.h",
]
deps = [
":bytecode_builtins_list_generator($v8_generator_toolchain)",
]
outputs = [ "$target_gen_dir/builtins-generated/bytecodes-builtins-list.h" ]
deps = [ ":bytecode_builtins_list_generator($v8_generator_toolchain)" ]
args = [
"./" + rebase_path(
get_label_info(
@ -1263,9 +1239,7 @@ template("run_mksnapshot") {
action("run_mksnapshot_" + name) {
visibility = [ ":*" ] # Only targets in this file can depend on this.
deps = [
":mksnapshot($v8_snapshot_toolchain)",
]
deps = [ ":mksnapshot($v8_snapshot_toolchain)" ]
script = "tools/run.py"
@ -1396,9 +1370,7 @@ if (v8_use_multi_snapshots) {
action("v8_dump_build_config") {
script = "tools/testrunner/utils/dump_build_config.py"
outputs = [
"$root_out_dir/v8_build_config.json",
]
outputs = [ "$root_out_dir/v8_build_config.json" ]
is_gcov_coverage = v8_code_coverage && !is_clang
is_full_debug = v8_enable_debugging_features && !v8_optimized_debug
args = [
@ -1452,9 +1424,7 @@ v8_source_set("v8_snapshot") {
# Do not publicize any header to remove build dependency.
public = []
sources = [
"src/init/setup-isolate-deserialize.cc",
]
sources = [ "src/init/setup-isolate-deserialize.cc" ]
if (emit_builtins_as_inline_asm) {
deps += [ ":asm_to_inline_asm_default" ]
sources += [ "$target_gen_dir/embedded.cc" ]
@ -1494,9 +1464,7 @@ v8_source_set("v8_initializers") {
"test/cctest:*",
]
deps = [
":torque_generated_initializers",
]
deps = [ ":torque_generated_initializers" ]
sources = [
### gcmole(all) ###
@ -1619,18 +1587,14 @@ v8_source_set("v8_initializers") {
v8_source_set("v8_init") {
visibility = [ ":*" ] # Only targets in this file can depend on this.
deps = [
":v8_initializers",
]
deps = [ ":v8_initializers" ]
sources = [
### gcmole(all) ###
"src/init/setup-isolate-full.cc",
]
public_deps = [
":v8_maybe_icu",
]
public_deps = [ ":v8_maybe_icu" ]
configs = [ ":internal_config" ]
}
@ -1667,9 +1631,7 @@ v8_header_set("v8_headers") {
"include/v8-wasm-trap-handler-win.h",
]
deps = [
":v8_version",
]
deps = [ ":v8_version" ]
}
# This is split out to share basic headers with Torque.
@ -1677,13 +1639,9 @@ v8_header_set("v8_shared_internal_headers") {
visibility = [ ":*" ] # Only targets in this file can depend on this.
configs = [ ":internal_config" ]
sources = [
"src/common/globals.h",
]
sources = [ "src/common/globals.h" ]
deps = [
":v8_headers",
]
deps = [ ":v8_headers" ]
}
v8_compiler_sources = [
@ -1964,13 +1922,9 @@ v8_source_set("v8_compiler") {
group("v8_compiler_for_mksnapshot") {
if (is_debug && !v8_optimized_debug && v8_enable_fast_mksnapshot) {
deps = [
":v8_compiler_opt",
]
deps = [ ":v8_compiler_opt" ]
} else {
deps = [
":v8_compiler",
]
deps = [ ":v8_compiler" ]
}
}
@ -2271,6 +2225,8 @@ v8_source_set("v8_base_without_compiler") {
"src/heap/concurrent-marking.h",
"src/heap/embedder-tracing.cc",
"src/heap/embedder-tracing.h",
"src/heap/factory-base.cc",
"src/heap/factory-base.h",
"src/heap/factory-inl.h",
"src/heap/factory.cc",
"src/heap/factory.h",
@ -2316,6 +2272,8 @@ v8_source_set("v8_base_without_compiler") {
"src/heap/objects-visiting-inl.h",
"src/heap/objects-visiting.cc",
"src/heap/objects-visiting.h",
"src/heap/off-thread-factory.cc",
"src/heap/off-thread-factory.h",
"src/heap/read-only-heap-inl.h",
"src/heap/read-only-heap.cc",
"src/heap/read-only-heap.h",
@ -3421,13 +3379,9 @@ v8_source_set("torque_base") {
"src/torque/utils.h",
]
deps = [
":v8_shared_internal_headers",
]
deps = [ ":v8_shared_internal_headers" ]
public_deps = [
":v8_libbase",
]
public_deps = [ ":v8_libbase" ]
# The use of exceptions for Torque in violation of the Chromium style-guide
# is justified by the fact that it is only used from the non-essential
@ -3470,9 +3424,7 @@ v8_source_set("torque_ls_base") {
"src/torque/ls/message.h",
]
public_deps = [
":torque_base",
]
public_deps = [ ":torque_base" ]
# The use of exceptions for Torque in violation of the Chromium style-guide
# is justified by the fact that it is only used from the non-essential
@ -3573,9 +3525,7 @@ v8_component("v8_libbase") {
public_configs = [ ":libbase_config" ]
deps = [
":v8_headers",
]
deps = [ ":v8_headers" ]
public_deps = []
@ -3761,9 +3711,7 @@ v8_source_set("v8_libsampler") {
public_configs = [ ":libsampler_config" ]
deps = [
":v8_libbase",
]
deps = [ ":v8_libbase" ]
}
v8_source_set("fuzzer_support") {
@ -3776,9 +3724,7 @@ v8_source_set("fuzzer_support") {
configs = [ ":internal_config_base" ]
deps = [
":v8",
]
deps = [ ":v8" ]
public_deps = [
":v8_libbase",
@ -3900,9 +3846,7 @@ if (current_toolchain == v8_snapshot_toolchain) {
v8_executable("torque") {
visibility = [ ":*" ] # Only targets in this file can depend on this.
sources = [
"src/torque/torque.cc",
]
sources = [ "src/torque/torque.cc" ]
deps = [
":torque_base",
@ -3932,9 +3876,7 @@ if (current_toolchain == v8_snapshot_toolchain) {
v8_executable("torque-language-server") {
visibility = [ ":*" ] # Only targets in this file can depend on this.
sources = [
"src/torque/ls/torque-language-server.cc",
]
sources = [ "src/torque/ls/torque-language-server.cc" ]
deps = [
":torque_base",
@ -3966,9 +3908,7 @@ if (v8_enable_i18n_support) {
v8_executable("gen-regexp-special-case") {
visibility = [ ":*" ] # Only targets in this file can depend on this.
sources = [
"src/regexp/gen-regexp-special-case.cc",
]
sources = [ "src/regexp/gen-regexp-special-case.cc" ]
deps = [
":v8_libbase",
@ -3985,15 +3925,11 @@ if (v8_enable_i18n_support) {
script = "tools/run.py"
deps = [
":gen-regexp-special-case($v8_generator_toolchain)",
]
deps = [ ":gen-regexp-special-case($v8_generator_toolchain)" ]
output_file = "$target_gen_dir/src/regexp/special-case.cc"
outputs = [
output_file,
]
outputs = [ output_file ]
args = [
"./" + rebase_path(
@ -4039,17 +3975,13 @@ group("gn_all") {
}
group("v8_python_base") {
data = [
".vpython",
]
data = [ ".vpython" ]
}
group("v8_clusterfuzz") {
testonly = true
deps = [
":d8",
]
deps = [ ":d8" ]
if (v8_multi_arch_build) {
deps += [
@ -4065,9 +3997,7 @@ group("v8_clusterfuzz") {
group("v8_archive") {
testonly = true
deps = [
":d8",
]
deps = [ ":d8" ]
if (!is_win) {
# On windows, cctest doesn't link with v8_static_library.
@ -4110,9 +4040,7 @@ group("v8_fuzzers") {
if (is_component_build) {
v8_component("v8") {
sources = [
"src/utils/v8dll-main.cc",
]
sources = [ "src/utils/v8dll-main.cc" ]
public_deps = [
":v8_base",
@ -4127,9 +4055,7 @@ if (is_component_build) {
v8_component("v8_for_testing") {
testonly = true
sources = [
"src/utils/v8dll-main.cc",
]
sources = [ "src/utils/v8dll-main.cc" ]
public_deps = [
":torque_base",
@ -4218,9 +4144,7 @@ v8_executable("d8") {
}
v8_executable("v8_hello_world") {
sources = [
"samples/hello-world.cc",
]
sources = [ "samples/hello-world.cc" ]
configs = [
# Note: don't use :internal_config here because this target will get
@ -4238,9 +4162,7 @@ v8_executable("v8_hello_world") {
}
v8_executable("v8_sample_process") {
sources = [
"samples/process.cc",
]
sources = [ "samples/process.cc" ]
configs = [
# Note: don't use :internal_config here because this target will get
@ -4259,9 +4181,7 @@ v8_executable("v8_sample_process") {
if (want_v8_shell) {
v8_executable("v8_shell") {
sources = [
"samples/shell.cc",
]
sources = [ "samples/shell.cc" ]
configs = [
# Note: don't use :internal_config here because this target will get
@ -4288,22 +4208,16 @@ template("v8_fuzzer") {
"//build/win:default_exe_manifest",
]
sources = [
"test/fuzzer/fuzzer.cc",
]
sources = [ "test/fuzzer/fuzzer.cc" ]
configs = [ ":external_config" ]
}
}
v8_source_set("json_fuzzer") {
sources = [
"test/fuzzer/json.cc",
]
sources = [ "test/fuzzer/json.cc" ]
deps = [
":fuzzer_support",
]
deps = [ ":fuzzer_support" ]
configs = [
":external_config",
@ -4315,13 +4229,9 @@ v8_fuzzer("json_fuzzer") {
}
v8_source_set("multi_return_fuzzer") {
sources = [
"test/fuzzer/multi-return.cc",
]
sources = [ "test/fuzzer/multi-return.cc" ]
deps = [
":fuzzer_support",
]
deps = [ ":fuzzer_support" ]
configs = [
":external_config",
@ -4333,13 +4243,9 @@ v8_fuzzer("multi_return_fuzzer") {
}
v8_source_set("parser_fuzzer") {
sources = [
"test/fuzzer/parser.cc",
]
sources = [ "test/fuzzer/parser.cc" ]
deps = [
":fuzzer_support",
]
deps = [ ":fuzzer_support" ]
configs = [
":external_config",
@ -4356,9 +4262,7 @@ v8_source_set("regexp_builtins_fuzzer") {
"test/fuzzer/regexp_builtins/mjsunit.js.h",
]
deps = [
":fuzzer_support",
]
deps = [ ":fuzzer_support" ]
configs = [
":external_config",
@ -4370,13 +4274,9 @@ v8_fuzzer("regexp_builtins_fuzzer") {
}
v8_source_set("regexp_fuzzer") {
sources = [
"test/fuzzer/regexp.cc",
]
sources = [ "test/fuzzer/regexp.cc" ]
deps = [
":fuzzer_support",
]
deps = [ ":fuzzer_support" ]
configs = [
":external_config",
@ -4398,9 +4298,7 @@ v8_source_set("wasm_module_runner") {
":run_torque",
]
public_deps = [
":v8_maybe_icu",
]
public_deps = [ ":v8_maybe_icu" ]
configs = [
":external_config",
@ -4409,9 +4307,7 @@ v8_source_set("wasm_module_runner") {
}
v8_source_set("wasm_fuzzer") {
sources = [
"test/fuzzer/wasm.cc",
]
sources = [ "test/fuzzer/wasm.cc" ]
deps = [
":fuzzer_support",
@ -4429,9 +4325,7 @@ v8_fuzzer("wasm_fuzzer") {
}
v8_source_set("wasm_async_fuzzer") {
sources = [
"test/fuzzer/wasm-async.cc",
]
sources = [ "test/fuzzer/wasm-async.cc" ]
deps = [
":fuzzer_support",
@ -4480,9 +4374,7 @@ v8_source_set("lib_wasm_fuzzer_common") {
":run_torque",
]
public_deps = [
":v8_maybe_icu",
]
public_deps = [ ":v8_maybe_icu" ]
configs = [
":external_config",
@ -4607,9 +4499,7 @@ if (!build_with_chromium && v8_use_perfetto) {
# This target should be used only by the protoc compiler and by test targets.
source_set("protobuf_full") {
deps = [
":protobuf_lite",
]
deps = [ ":protobuf_lite" ]
sources = [
"third_party/protobuf/src/google/protobuf/any.cc",
"third_party/protobuf/src/google/protobuf/any.pb.cc",
@ -4679,9 +4569,7 @@ if (!build_with_chromium && v8_use_perfetto) {
if (current_toolchain == host_toolchain) {
source_set("protoc_lib") {
deps = [
":protobuf_full",
]
deps = [ ":protobuf_full" ]
sources = [
"third_party/protobuf/src/google/protobuf/compiler/code_generator.cc",
"third_party/protobuf/src/google/protobuf/compiler/command_line_interface.cc",
@ -4720,9 +4608,7 @@ if (!build_with_chromium && v8_use_perfetto) {
":protoc_lib",
"//build/win:default_exe_manifest",
]
sources = [
"src/protobuf/protobuf-compiler-main.cc",
]
sources = [ "src/protobuf/protobuf-compiler-main.cc" ]
configs -= [ "//build/config/compiler:chromium_code" ]
configs += [ "//build/config/compiler:no_chromium_code" ]
}

View File

@ -16,6 +16,7 @@ include_rules = [
"+src/heap/heap-inl.h",
"+src/heap/heap-write-barrier-inl.h",
"+src/heap/heap-write-barrier.h",
"+src/heap/off-thread-factory.h",
"+src/heap/read-only-heap-inl.h",
"+src/heap/read-only-heap.h",
"-src/inspector",

View File

@ -0,0 +1,23 @@
// 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.
#ifndef V8_HANDLES_FACTORY_HANDLES_H_
#define V8_HANDLES_FACTORY_HANDLES_H_
namespace v8 {
namespace internal {
template <typename Impl>
struct FactoryTraits;
template <typename Impl, typename T>
using FactoryHandle = typename FactoryTraits<Impl>::template HandleType<T>;
template <typename Impl, typename T>
using FactoryMaybeHandle =
typename FactoryTraits<Impl>::template MaybeHandleType<T>;
} // namespace internal
} // namespace v8
#endif // V8_HANDLES_FACTORY_HANDLES_H_

View File

@ -38,6 +38,31 @@ V8_INLINE Handle<T> handle(T object, Isolate* isolate) {
return Handle<T>(object, isolate);
}
// Convenience overloads for cases where we want to either create a Handle or an
// OffThreadHandle, depending on whether we have a Factory or an
// OffThreadFactory.
template <typename T>
V8_INLINE Handle<T> handle(T object, Factory* factory) {
return factory->MakeHandle<T>(object);
}
template <typename T>
V8_INLINE OffThreadHandle<T> handle(T object, OffThreadFactory* factory) {
// Convienently, we don't actually need the factory to create this handle.
return OffThreadHandle<T>(object);
}
// Similar convenience overloads for when we already have a Handle, but want
// either a Handle or an OffThreadHandle.
template <typename T>
V8_INLINE Handle<T> handle(Handle<T> handle, Factory* factory) {
return handle;
}
template <typename T>
V8_INLINE OffThreadHandle<T> handle(Handle<T> handle,
OffThreadFactory* factory) {
return OffThreadHandle<T>(*handle);
}
template <typename T>
inline std::ostream& operator<<(std::ostream& os, Handle<T> handle) {
return os << Brief(*handle);

View File

@ -12,6 +12,7 @@
#include "src/base/macros.h"
#include "src/common/checks.h"
#include "src/common/globals.h"
#include "src/handles/factory-handles.h"
#include "src/zone/zone.h"
namespace v8 {
@ -126,6 +127,7 @@ class Handle final : public HandleBase {
// Ex. Handle<JSFunction> can be passed when Handle<Object> is expected.
template <typename S, typename = typename std::enable_if<
std::is_convertible<S*, T*>::value>::type>
// NOLINTNEXTLINE
V8_INLINE Handle(Handle<S> handle) : HandleBase(handle) {}
V8_INLINE ObjectRef operator->() const { return ObjectRef{**this}; }
@ -173,6 +175,51 @@ class Handle final : public HandleBase {
template <typename T>
inline std::ostream& operator<<(std::ostream& os, Handle<T> handle);
// ----------------------------------------------------------------------------
// A fake Handle that simply wraps an object reference. This is used for
// off-thread Objects, where we want a class that behaves like Handle for the
// purposes of operator->, casting, etc., but isn't a GC root and doesn't
// require access to the Isolate.
template <typename T>
class OffThreadHandle {
public:
OffThreadHandle() = default;
template <typename U>
explicit OffThreadHandle(U obj) : obj_(obj) {}
// Constructor for handling automatic up casting. We rely on the compiler
// making sure that the assignment to obj_ is legitimate.
template <typename U>
// NOLINTNEXTLINE
OffThreadHandle<T>(OffThreadHandle<U> other) : obj_(*other) {}
T operator*() const { return obj_; }
T* operator->() { return &obj_; }
const T* operator->() const { return &obj_; }
template <typename U>
static OffThreadHandle<T> cast(OffThreadHandle<U> other) {
return OffThreadHandle<T>(T::cast(*other));
}
// Allow OffThreadHandle to act as a MaybeHandle.
bool is_null() const { return obj_.is_null(); }
bool ToHandle(OffThreadHandle<T>* out) {
if (is_null()) return false;
*out = *this;
return true;
}
OffThreadHandle<T> ToHandleChecked() {
DCHECK(!is_null());
return *this;
}
private:
T obj_;
};
// ----------------------------------------------------------------------------
// A stack-allocated class that governs a number of local handles.
// After a handle scope has been created, all local handles will be

144
src/heap/factory-base.cc Normal file
View File

@ -0,0 +1,144 @@
// 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.
#include "src/heap/factory-base.h"
#include "src/ast/ast.h"
#include "src/handles/handles-inl.h"
#include "src/heap/factory.h"
#include "src/heap/off-thread-factory.h"
#include "src/objects/string-inl.h"
#include "src/utils/memcopy.h"
namespace v8 {
namespace internal {
template <typename Impl>
FactoryHandle<Impl, SeqOneByteString>
FactoryBase<Impl>::NewOneByteInternalizedString(
const Vector<const uint8_t>& str, uint32_t hash_field) {
FactoryHandle<Impl, SeqOneByteString> result =
AllocateRawOneByteInternalizedString(str.length(), hash_field);
DisallowHeapAllocation no_gc;
MemCopy(result->GetChars(no_gc), str.begin(), str.length());
return result;
}
template <typename Impl>
FactoryHandle<Impl, SeqTwoByteString>
FactoryBase<Impl>::NewTwoByteInternalizedString(const Vector<const uc16>& str,
uint32_t hash_field) {
FactoryHandle<Impl, SeqTwoByteString> result =
AllocateRawTwoByteInternalizedString(str.length(), hash_field);
DisallowHeapAllocation no_gc;
MemCopy(result->GetChars(no_gc), str.begin(), str.length() * kUC16Size);
return result;
}
template <typename Impl>
FactoryMaybeHandle<Impl, SeqOneByteString>
FactoryBase<Impl>::NewRawOneByteString(int length, AllocationType allocation) {
if (length > String::kMaxLength || length < 0) {
return impl()->template Throw<SeqOneByteString>(
impl()->NewInvalidStringLengthError());
}
DCHECK_GT(length, 0); // Use Factory::empty_string() instead.
int size = SeqOneByteString::SizeFor(length);
DCHECK_GE(SeqOneByteString::kMaxSize, size);
HeapObject result = AllocateRawWithImmortalMap(
size, allocation, read_only_roots().one_byte_string_map());
FactoryHandle<Impl, SeqOneByteString> string =
handle(SeqOneByteString::cast(result), impl());
string->set_length(length);
string->set_hash_field(String::kEmptyHashField);
DCHECK_EQ(size, string->Size());
return string;
}
template <typename Impl>
FactoryMaybeHandle<Impl, SeqTwoByteString>
FactoryBase<Impl>::NewRawTwoByteString(int length, AllocationType allocation) {
if (length > String::kMaxLength || length < 0) {
return impl()->template Throw<SeqTwoByteString>(
impl()->NewInvalidStringLengthError());
}
DCHECK_GT(length, 0); // Use Factory::empty_string() instead.
int size = SeqTwoByteString::SizeFor(length);
DCHECK_GE(SeqTwoByteString::kMaxSize, size);
HeapObject result = AllocateRawWithImmortalMap(
size, allocation, read_only_roots().string_map());
FactoryHandle<Impl, SeqTwoByteString> string =
handle(SeqTwoByteString::cast(result), impl());
string->set_length(length);
string->set_hash_field(String::kEmptyHashField);
DCHECK_EQ(size, string->Size());
return string;
}
template <typename Impl>
FactoryHandle<Impl, SeqOneByteString>
FactoryBase<Impl>::AllocateRawOneByteInternalizedString(int length,
uint32_t hash_field) {
CHECK_GE(String::kMaxLength, length);
// The canonical empty_string is the only zero-length string we allow.
DCHECK_IMPLIES(length == 0, !impl()->EmptyStringRootIsInitialized());
Map map = read_only_roots().one_byte_internalized_string_map();
int size = SeqOneByteString::SizeFor(length);
HeapObject result = AllocateRawWithImmortalMap(
size,
impl()->CanAllocateInReadOnlySpace() ? AllocationType::kReadOnly
: AllocationType::kOld,
map);
FactoryHandle<Impl, SeqOneByteString> answer =
handle(SeqOneByteString::cast(result), impl());
answer->set_length(length);
answer->set_hash_field(hash_field);
DCHECK_EQ(size, answer->Size());
return answer;
}
template <typename Impl>
FactoryHandle<Impl, SeqTwoByteString>
FactoryBase<Impl>::AllocateRawTwoByteInternalizedString(int length,
uint32_t hash_field) {
CHECK_GE(String::kMaxLength, length);
DCHECK_NE(0, length); // Use Heap::empty_string() instead.
Map map = read_only_roots().internalized_string_map();
int size = SeqTwoByteString::SizeFor(length);
HeapObject result =
AllocateRawWithImmortalMap(size, AllocationType::kOld, map);
FactoryHandle<Impl, SeqTwoByteString> answer =
handle(SeqTwoByteString::cast(result), impl());
answer->set_length(length);
answer->set_hash_field(hash_field);
DCHECK_EQ(size, result.Size());
return answer;
}
template <typename Impl>
HeapObject FactoryBase<Impl>::AllocateRawWithImmortalMap(
int size, AllocationType allocation, Map map,
AllocationAlignment alignment) {
HeapObject result = AllocateRaw(size, allocation, alignment);
result.set_map_after_allocation(map, SKIP_WRITE_BARRIER);
return result;
}
template <typename Impl>
HeapObject FactoryBase<Impl>::AllocateRaw(int size, AllocationType allocation,
AllocationAlignment alignment) {
return impl()->AllocateRaw(size, allocation, alignment);
}
// Instantiate FactoryBase for the two variants we want.
template class EXPORT_TEMPLATE_DEFINE(V8_BASE_EXPORT) FactoryBase<Factory>;
template class EXPORT_TEMPLATE_DEFINE(V8_BASE_EXPORT)
FactoryBase<OffThreadFactory>;
} // namespace internal
} // namespace v8

58
src/heap/factory-base.h Normal file
View File

@ -0,0 +1,58 @@
// 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.
#ifndef V8_HEAP_FACTORY_BASE_H_
#define V8_HEAP_FACTORY_BASE_H_
#include "src/common/globals.h"
#include "src/handles/factory-handles.h"
#include "src/roots/roots.h"
namespace v8 {
namespace internal {
class HeapObject;
class SeqOneByteString;
class SeqTwoByteString;
template <typename Impl>
class V8_EXPORT_PRIVATE FactoryBase {
public:
FactoryHandle<Impl, SeqOneByteString> NewOneByteInternalizedString(
const Vector<const uint8_t>& str, uint32_t hash_field);
FactoryHandle<Impl, SeqTwoByteString> NewTwoByteInternalizedString(
const Vector<const uc16>& str, uint32_t hash_field);
FactoryHandle<Impl, SeqOneByteString> AllocateRawOneByteInternalizedString(
int length, uint32_t hash_field);
FactoryHandle<Impl, SeqTwoByteString> AllocateRawTwoByteInternalizedString(
int length, uint32_t hash_field);
// Allocates and partially initializes an one-byte or two-byte String. The
// characters of the string are uninitialized. Currently used in regexp code
// only, where they are pretenured.
V8_WARN_UNUSED_RESULT FactoryMaybeHandle<Impl, SeqOneByteString>
NewRawOneByteString(int length,
AllocationType allocation = AllocationType::kYoung);
V8_WARN_UNUSED_RESULT FactoryMaybeHandle<Impl, SeqTwoByteString>
NewRawTwoByteString(int length,
AllocationType allocation = AllocationType::kYoung);
protected:
HeapObject AllocateRawWithImmortalMap(
int size, AllocationType allocation, Map map,
AllocationAlignment alignment = kWordAligned);
private:
Impl* impl() { return static_cast<Impl*>(this); }
ReadOnlyRoots read_only_roots() { return impl()->read_only_roots(); }
HeapObject AllocateRaw(int size, AllocationType allocation,
AllocationAlignment alignment = kWordAligned);
};
} // namespace internal
} // namespace v8
#endif // V8_HEAP_FACTORY_BASE_H_

View File

@ -101,6 +101,13 @@ Handle<Object> Factory::NewURIError() {
MessageTemplate::kURIMalformed);
}
template <typename T>
inline MaybeHandle<T> Factory::Throw(Handle<Object> exception) {
return isolate()->Throw<T>(exception);
}
ReadOnlyRoots Factory::read_only_roots() { return ReadOnlyRoots(isolate()); }
} // namespace internal
} // namespace v8

View File

@ -218,14 +218,14 @@ Handle<Code> Factory::CodeBuilder::Build() {
return BuildInternal(true).ToHandleChecked();
}
HeapObject Factory::AllocateRawWithImmortalMap(int size,
AllocationType allocation,
Map map,
AllocationAlignment alignment) {
HeapObject result = isolate()->heap()->AllocateRawWith<Heap::kRetryOrFail>(
void Factory::FatalProcessOutOfHeapMemory(const char* location) {
isolate()->heap()->FatalProcessOutOfMemory(location);
}
HeapObject Factory::AllocateRaw(int size, AllocationType allocation,
AllocationAlignment alignment) {
return isolate()->heap()->AllocateRawWith<Heap::kRetryOrFail>(
size, allocation, AllocationOrigin::kRuntime, alignment);
result.set_map_after_allocation(map, SKIP_WRITE_BARRIER);
return result;
}
HeapObject Factory::AllocateRawWithAllocationSite(
@ -923,57 +923,6 @@ inline void WriteTwoByteData(Handle<String> s, uint16_t* chars, int len) {
} // namespace
Handle<SeqOneByteString> Factory::AllocateRawOneByteInternalizedString(
int length, uint32_t hash_field) {
CHECK_GE(String::kMaxLength, length);
// The canonical empty_string is the only zero-length string we allow.
DCHECK_IMPLIES(
length == 0,
isolate()->roots_table()[RootIndex::kempty_string] == kNullAddress);
Map map = *one_byte_internalized_string_map();
int size = SeqOneByteString::SizeFor(length);
HeapObject result =
AllocateRawWithImmortalMap(size,
isolate()->heap()->CanAllocateInReadOnlySpace()
? AllocationType::kReadOnly
: AllocationType::kOld,
map);
Handle<SeqOneByteString> answer(SeqOneByteString::cast(result), isolate());
answer->set_length(length);
answer->set_hash_field(hash_field);
DCHECK_EQ(size, answer->Size());
return answer;
}
Handle<String> Factory::AllocateTwoByteInternalizedString(
const Vector<const uc16>& str, uint32_t hash_field) {
Handle<SeqTwoByteString> result =
AllocateRawTwoByteInternalizedString(str.length(), hash_field);
DisallowHeapAllocation no_gc;
// Fill in the characters.
MemCopy(result->GetChars(no_gc), str.begin(), str.length() * kUC16Size);
return result;
}
Handle<SeqTwoByteString> Factory::AllocateRawTwoByteInternalizedString(
int length, uint32_t hash_field) {
CHECK_GE(String::kMaxLength, length);
DCHECK_NE(0, length); // Use Heap::empty_string() instead.
Map map = *internalized_string_map();
int size = SeqTwoByteString::SizeFor(length);
HeapObject result =
AllocateRawWithImmortalMap(size, AllocationType::kOld, map);
Handle<SeqTwoByteString> answer(SeqTwoByteString::cast(result), isolate());
answer->set_length(length);
answer->set_hash_field(hash_field);
DCHECK_EQ(size, result.Size());
return answer;
}
template <bool is_one_byte, typename T>
Handle<String> Factory::AllocateInternalizedStringImpl(T t, int chars,
uint32_t hash_field) {
@ -1011,20 +960,6 @@ Handle<String> Factory::AllocateInternalizedStringImpl(T t, int chars,
return answer;
}
Handle<String> Factory::NewOneByteInternalizedString(
const Vector<const uint8_t>& str, uint32_t hash_field) {
Handle<SeqOneByteString> result =
AllocateRawOneByteInternalizedString(str.length(), hash_field);
DisallowHeapAllocation no_allocation;
MemCopy(result->GetChars(no_allocation), str.begin(), str.length());
return result;
}
Handle<String> Factory::NewTwoByteInternalizedString(
const Vector<const uc16>& str, uint32_t hash_field) {
return AllocateTwoByteInternalizedString(str, hash_field);
}
Handle<String> Factory::NewInternalizedStringImpl(Handle<String> string,
int chars,
uint32_t hash_field) {
@ -1084,42 +1019,6 @@ template Handle<ExternalOneByteString>
template Handle<ExternalTwoByteString>
Factory::InternalizeExternalString<ExternalTwoByteString>(Handle<String>);
MaybeHandle<SeqOneByteString> Factory::NewRawOneByteString(
int length, AllocationType allocation) {
if (length > String::kMaxLength || length < 0) {
THROW_NEW_ERROR(isolate(), NewInvalidStringLengthError(), SeqOneByteString);
}
DCHECK_GT(length, 0); // Use Factory::empty_string() instead.
int size = SeqOneByteString::SizeFor(length);
DCHECK_GE(SeqOneByteString::kMaxSize, size);
HeapObject result =
AllocateRawWithImmortalMap(size, allocation, *one_byte_string_map());
Handle<SeqOneByteString> string(SeqOneByteString::cast(result), isolate());
string->set_length(length);
string->set_hash_field(String::kEmptyHashField);
DCHECK_EQ(size, string->Size());
return string;
}
MaybeHandle<SeqTwoByteString> Factory::NewRawTwoByteString(
int length, AllocationType allocation) {
if (length > String::kMaxLength || length < 0) {
THROW_NEW_ERROR(isolate(), NewInvalidStringLengthError(), SeqTwoByteString);
}
DCHECK_GT(length, 0); // Use Factory::empty_string() instead.
int size = SeqTwoByteString::SizeFor(length);
DCHECK_GE(SeqTwoByteString::kMaxSize, size);
HeapObject result =
AllocateRawWithImmortalMap(size, allocation, *string_map());
Handle<SeqTwoByteString> string(SeqTwoByteString::cast(result), isolate());
string->set_length(length);
string->set_hash_field(String::kEmptyHashField);
DCHECK_EQ(size, string->Size());
return string;
}
Handle<String> Factory::LookupSingleCharacterStringFromCode(uint16_t code) {
if (code <= unibrow::Latin1::kMaxChar) {
{
@ -4267,6 +4166,14 @@ Handle<CallHandlerInfo> Factory::NewCallHandlerInfo(bool has_no_side_effect) {
return info;
}
bool Factory::CanAllocateInReadOnlySpace() {
return isolate()->heap()->CanAllocateInReadOnlySpace();
}
bool Factory::EmptyStringRootIsInitialized() {
return isolate()->roots_table()[RootIndex::kempty_string] != kNullAddress;
}
// static
NewFunctionArgs NewFunctionArgs::ForWasm(
Handle<String> name,

View File

@ -12,6 +12,7 @@
#include "src/execution/messages.h"
#include "src/handles/handles.h"
#include "src/handles/maybe-handles.h"
#include "src/heap/factory-base.h"
#include "src/heap/heap.h"
#include "src/objects/code.h"
#include "src/objects/dictionary.h"
@ -105,9 +106,25 @@ enum FunctionMode {
kWithReadonlyPrototypeBit | kWithNameBit,
};
class Factory;
template <>
struct FactoryTraits<Factory> {
template <typename T>
using HandleType = Handle<T>;
template <typename T>
using MaybeHandleType = v8::internal::MaybeHandle<T>;
};
// Interface for handle based allocation.
class V8_EXPORT_PRIVATE Factory {
class V8_EXPORT_PRIVATE Factory : public FactoryBase<Factory> {
public:
inline ReadOnlyRoots read_only_roots();
template <typename T>
Handle<T> MakeHandle(T obj) {
return handle(obj, isolate());
}
Handle<Oddball> NewOddball(Handle<Map> map, const char* to_string,
Handle<Object> to_number, const char* type_of,
byte kind);
@ -311,18 +328,6 @@ class V8_EXPORT_PRIVATE Factory {
Handle<JSStringIterator> NewJSStringIterator(Handle<String> string);
Handle<String> NewOneByteInternalizedString(const Vector<const uint8_t>& str,
uint32_t hash_field);
Handle<SeqOneByteString> AllocateRawOneByteInternalizedString(
int length, uint32_t hash_field);
Handle<String> NewTwoByteInternalizedString(const Vector<const uc16>& str,
uint32_t hash_field);
Handle<SeqTwoByteString> AllocateRawTwoByteInternalizedString(
int length, uint32_t hash_field);
Handle<String> NewInternalizedStringImpl(Handle<String> string, int chars,
uint32_t hash_field);
@ -336,14 +341,6 @@ class V8_EXPORT_PRIVATE Factory {
template <class StringClass>
Handle<StringClass> InternalizeExternalString(Handle<String> string);
// Allocates and partially initializes an one-byte or two-byte String. The
// characters of the string are uninitialized. Currently used in regexp code
// only, where they are pretenured.
V8_WARN_UNUSED_RESULT MaybeHandle<SeqOneByteString> NewRawOneByteString(
int length, AllocationType allocation = AllocationType::kYoung);
V8_WARN_UNUSED_RESULT MaybeHandle<SeqTwoByteString> NewRawTwoByteString(
int length, AllocationType allocation = AllocationType::kYoung);
// Creates a single character string where the character has given code.
// A cache is used for Latin1 codes.
Handle<String> LookupSingleCharacterStringFromCode(uint16_t code);
@ -1009,6 +1006,19 @@ class V8_EXPORT_PRIVATE Factory {
};
private:
friend class FactoryBase<Factory>;
// ------
// Customization points for FactoryBase
HeapObject AllocateRaw(int size, AllocationType allocation,
AllocationAlignment alignment = kWordAligned);
template <typename T>
inline MaybeHandle<T> Throw(Handle<Object> exception);
[[noreturn]] void FatalProcessOutOfHeapMemory(const char* location);
bool CanAllocateInReadOnlySpace();
bool EmptyStringRootIsInitialized();
// ------
Isolate* isolate() {
// Downcast to the privately inherited sub-class using c-style casts to
// avoid undefined behavior (as static_cast cannot cast across private
@ -1017,9 +1027,6 @@ class V8_EXPORT_PRIVATE Factory {
return (Isolate*)this; // NOLINT(readability/casting)
}
HeapObject AllocateRawWithImmortalMap(
int size, AllocationType allocation, Map map,
AllocationAlignment alignment = kWordAligned);
HeapObject AllocateRawWithAllocationSite(
Handle<Map> map, AllocationType allocation,
Handle<AllocationSite> allocation_site);

View File

@ -5221,6 +5221,20 @@ void Heap::NotifyOldGenerationExpansion() {
}
}
void Heap::NotifyOffThreadSpaceMerged() {
// TODO(leszeks): Ideally we would do this check during off-thread page
// allocation too, to proactively do GC. We should also probably do this check
// before merging rather than after.
if (!CanExpandOldGeneration(0)) {
// TODO(leszeks): We should try to invoke the near-heap limit callback and
// do a last-resort GC first.
FatalProcessOutOfMemory("Failed to merge off-thread pages into heap.");
}
StartIncrementalMarkingIfAllocationLimitIsReached(
GCFlagsForIncrementalMarking(), kGCCallbackScheduleIdleGarbageCollection);
NotifyOldGenerationExpansion();
}
void Heap::SetEmbedderHeapTracer(EmbedderHeapTracer* tracer) {
DCHECK_EQ(gc_state_, HeapState::NOT_IN_GC);
local_embedder_heap_tracer()->SetRemoteTracer(tracer);

View File

@ -318,7 +318,7 @@ class Heap {
// writable and reserved to contain unwind information.
static size_t GetCodeRangeReservedAreaSize();
void FatalProcessOutOfMemory(const char* location);
[[noreturn]] void FatalProcessOutOfMemory(const char* location);
// Checks whether the space is valid.
static bool IsValidAllocationSpace(AllocationSpace space);
@ -422,6 +422,9 @@ class Heap {
void NotifyOldGenerationExpansion();
// Notifies the heap that an off-thread space has been merged into it.
void NotifyOffThreadSpaceMerged();
inline Address* NewSpaceAllocationTopAddress();
inline Address* NewSpaceAllocationLimitAddress();
inline Address* OldSpaceAllocationTopAddress();
@ -925,7 +928,8 @@ class Heap {
void FinalizeIncrementalMarkingIfComplete(GarbageCollectionReason gc_reason);
// Synchronously finalizes incremental marking.
void FinalizeIncrementalMarkingAtomically(GarbageCollectionReason gc_reason);
V8_EXPORT_PRIVATE void FinalizeIncrementalMarkingAtomically(
GarbageCollectionReason gc_reason);
void RegisterDeserializedObjectsForBlackAllocation(
Reservation* reservations, const std::vector<HeapObject>& large_objects,

View File

@ -0,0 +1,203 @@
// 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.
#include "src/heap/off-thread-factory.h"
#include "src/ast/ast-value-factory.h"
#include "src/ast/ast.h"
#include "src/base/logging.h"
#include "src/common/globals.h"
#include "src/execution/isolate.h"
#include "src/handles/handles.h"
#include "src/heap/spaces-inl.h"
#include "src/heap/spaces.h"
#include "src/objects/fixed-array.h"
#include "src/objects/heap-object.h"
#include "src/objects/map-inl.h"
#include "src/objects/objects-body-descriptors-inl.h"
#include "src/objects/string.h"
#include "src/objects/visitors.h"
#include "src/roots/roots-inl.h"
#include "src/roots/roots.h"
namespace v8 {
namespace internal {
OffThreadFactory::OffThreadFactory(Isolate* isolate)
: roots_(isolate), space_(isolate->heap()), lo_space_(isolate->heap()) {}
namespace {
class StringSlotCollectingVisitor : public ObjectVisitor {
public:
explicit StringSlotCollectingVisitor(ReadOnlyRoots roots) : roots_(roots) {}
void VisitPointers(HeapObject host, ObjectSlot start,
ObjectSlot end) override {
for (ObjectSlot slot = start; slot != end; ++slot) {
Object obj = *slot;
if (obj.IsInternalizedString() &&
!ReadOnlyHeap::Contains(HeapObject::cast(obj))) {
string_slots.emplace_back(host.ptr(), slot.address() - host.ptr());
}
}
}
void VisitPointers(HeapObject host, MaybeObjectSlot start,
MaybeObjectSlot end) override {
for (MaybeObjectSlot slot = start; slot != end; ++slot) {
MaybeObject maybe_obj = *slot;
HeapObject obj;
if (maybe_obj.GetHeapObjectIfStrong(&obj)) {
if (obj.IsInternalizedString() && !ReadOnlyHeap::Contains(obj)) {
string_slots.emplace_back(host.ptr(), slot.address() - host.ptr());
}
}
}
}
void VisitCodeTarget(Code host, RelocInfo* rinfo) override { UNREACHABLE(); }
void VisitEmbeddedPointer(Code host, RelocInfo* rinfo) override {
UNREACHABLE();
}
std::vector<RelativeSlot> string_slots;
private:
ReadOnlyRoots roots_;
};
} // namespace
void OffThreadFactory::FinishOffThread() {
DCHECK(!is_finished);
StringSlotCollectingVisitor string_slot_collector(read_only_roots());
// First iterate all objects in the spaces to find string slots. At this point
// all string slots have to point to off-thread strings or read-only strings.
{
PagedSpaceObjectIterator it(&space_);
for (HeapObject obj = it.Next(); !obj.is_null(); obj = it.Next()) {
obj.IterateBodyFast(&string_slot_collector);
}
}
{
LargeObjectSpaceObjectIterator it(&lo_space_);
for (HeapObject obj = it.Next(); !obj.is_null(); obj = it.Next()) {
obj.IterateBodyFast(&string_slot_collector);
}
}
string_slots_ = std::move(string_slot_collector.string_slots);
is_finished = true;
}
void OffThreadFactory::Publish(Isolate* isolate) {
DCHECK(is_finished);
HandleScope handle_scope(isolate);
// First, handlify all the string slot holder objects, so that we can keep
// track of them if they move.
//
// TODO(leszeks): We might be able to create a HandleScope-compatible
// structure off-thread and merge it into the current handle scope all in one
// go (DeferredHandles maybe?).
std::vector<Handle<HeapObject>> heap_object_handles;
heap_object_handles.reserve(string_slots_.size());
for (RelativeSlot relative_slot : string_slots_) {
// TODO(leszeks): Group slots in the same parent object to avoid creating
// multiple duplicate handles.
heap_object_handles.push_back(handle(
HeapObject::cast(Object(relative_slot.object_address)), isolate));
// De-internalize the string so that we can re-internalize it later.
ObjectSlot slot(relative_slot.object_address + relative_slot.slot_offset);
String string = String::cast(slot.Acquire_Load());
bool one_byte = string.IsOneByteRepresentation();
Map map = one_byte ? read_only_roots().one_byte_string_map()
: read_only_roots().string_map();
string.set_map_no_write_barrier(map);
}
// Then merge the spaces. At this point, we are allowed to point between (no
// longer) off-thread pages and main-thread heap pages, and objects in the
// previously off-thread page can move.
isolate->heap()->old_space()->MergeLocalSpace(&space_);
isolate->heap()->lo_space()->MergeOffThreadSpace(&lo_space_);
// Iterate the string slots, as an offset from the holders we have handles to.
for (size_t i = 0; i < string_slots_.size(); ++i) {
int slot_offset = string_slots_[i].slot_offset;
// There's currently no cases where the holder object could have been
// resized.
DCHECK_LT(slot_offset, heap_object_handles[i]->Size());
ObjectSlot slot(heap_object_handles[i]->ptr() + slot_offset);
String string = String::cast(slot.Acquire_Load());
if (string.IsThinString()) {
// We may have already internalized this string via another slot.
slot.Release_Store(ThinString::cast(string).GetUnderlying());
} else {
HandleScope handle_scope(isolate);
Handle<String> string_handle = handle(string, isolate);
Handle<String> internalized_string =
isolate->factory()->InternalizeString(string_handle);
// Recalculate the slot in case there was GC and the holder moved.
ObjectSlot slot(heap_object_handles[i]->ptr() +
string_slots_[i].slot_offset);
DCHECK(string_handle->IsThinString() ||
string_handle->IsInternalizedString());
if (*string_handle != *internalized_string) {
slot.Release_Store(*internalized_string);
}
}
}
}
OffThreadHandle<Object> OffThreadFactory::NewInvalidStringLengthError() {
// TODO(leszeks): Implement.
UNREACHABLE();
}
// Hacky method for creating a simple object with a slot pointing to a string.
// TODO(leszeks): Remove once we have full FixedArray support.
OffThreadHandle<FixedArray> OffThreadFactory::StringWrapperForTest(
OffThreadHandle<String> string) {
HeapObject wrapper =
AllocateRaw(FixedArray::SizeFor(1), AllocationType::kOld);
wrapper.set_map_after_allocation(read_only_roots().fixed_array_map());
FixedArray array = FixedArray::cast(wrapper);
array.set_length(1);
array.data_start().Relaxed_Store(*string);
return OffThreadHandle<FixedArray>(array);
}
HeapObject OffThreadFactory::AllocateRaw(int size, AllocationType allocation,
AllocationAlignment alignment) {
DCHECK(!is_finished);
DCHECK_EQ(allocation, AllocationType::kOld);
AllocationResult result;
if (size > kMaxRegularHeapObjectSize) {
result = lo_space_.AllocateRaw(size);
} else {
result = space_.AllocateRaw(size, alignment);
}
return result.ToObjectChecked();
}
void OffThreadFactory::FatalProcessOutOfHeapMemory(const char* location) {
// TODO(leszeks): Do something reasonable.
UNREACHABLE();
}
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,88 @@
// 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.
#ifndef V8_HEAP_OFF_THREAD_FACTORY_H_
#define V8_HEAP_OFF_THREAD_FACTORY_H_
#include <map>
#include <vector>
#include "src/common/globals.h"
#include "src/handles/handles.h"
#include "src/heap/factory-base.h"
#include "src/heap/heap.h"
#include "src/heap/read-only-heap.h"
#include "src/heap/spaces.h"
#include "src/objects/heap-object.h"
#include "src/objects/objects.h"
#include "src/roots/roots.h"
namespace v8 {
namespace internal {
class AstValueFactory;
class AstRawString;
class AstConsString;
class OffThreadFactory;
template <>
struct FactoryTraits<OffThreadFactory> {
template <typename T>
using HandleType = OffThreadHandle<T>;
template <typename T>
using MaybeHandleType = OffThreadHandle<T>;
};
struct RelativeSlot {
RelativeSlot() = default;
RelativeSlot(Address object_address, int slot_offset)
: object_address(object_address), slot_offset(slot_offset) {}
Address object_address;
int slot_offset;
};
class V8_EXPORT_PRIVATE OffThreadFactory
: public FactoryBase<OffThreadFactory> {
public:
explicit OffThreadFactory(Isolate* isolate);
ReadOnlyRoots read_only_roots() const { return roots_; }
void FinishOffThread();
void Publish(Isolate* isolate);
OffThreadHandle<Object> NewInvalidStringLengthError();
OffThreadHandle<FixedArray> StringWrapperForTest(
OffThreadHandle<String> string);
private:
friend class FactoryBase<OffThreadFactory>;
// ------
// Customization points for FactoryBase.
HeapObject AllocateRaw(int size, AllocationType allocation,
AllocationAlignment alignment = kWordAligned);
template <typename T>
OffThreadHandle<T> Throw(OffThreadHandle<Object> exception) {
// TODO(leszeks): Figure out what to do here.
return OffThreadHandle<T>();
}
[[noreturn]] void FatalProcessOutOfHeapMemory(const char* location);
inline bool CanAllocateInReadOnlySpace() { return false; }
inline bool EmptyStringRootIsInitialized() { return true; }
// ------
ReadOnlyRoots roots_;
OffThreadSpace space_;
OffThreadLargeObjectSpace lo_space_;
std::vector<RelativeSlot> string_slots_;
bool is_finished = false;
};
} // namespace internal
} // namespace v8
#endif // V8_HEAP_OFF_THREAD_FACTORY_H_

View File

@ -1616,6 +1616,19 @@ void Space::AllocationStep(int bytes_since_last, Address soon_object,
heap()->set_allocation_step_in_progress(false);
}
void Space::AllocationStepAfterMerge(Address first_object_in_chunk, int size) {
if (!AllocationObserversActive()) {
return;
}
DCHECK(!heap()->allocation_step_in_progress());
heap()->set_allocation_step_in_progress(true);
for (AllocationObserver* observer : allocation_observers_) {
observer->AllocationStep(size, first_object_in_chunk, size);
}
heap()->set_allocation_step_in_progress(false);
}
intptr_t Space::GetNextInlineAllocationStepSize() {
intptr_t next_step = 0;
for (AllocationObserver* observer : allocation_observers_) {
@ -1708,6 +1721,7 @@ void PagedSpace::MergeLocalSpace(LocalSpace* other) {
base::MutexGuard guard(mutex());
DCHECK(identity() == other->identity());
// Unmerged fields:
// area_size_
other->FreeLinearAllocationArea();
@ -1721,11 +1735,20 @@ void PagedSpace::MergeLocalSpace(LocalSpace* other) {
DCHECK_EQ(kNullAddress, other->top());
DCHECK_EQ(kNullAddress, other->limit());
bool merging_from_off_thread = other->is_off_thread_space();
// Move over pages.
for (auto it = other->begin(); it != other->end();) {
Page* p = *(it++);
p->MergeOldToNewRememberedSets();
if (merging_from_off_thread) {
DCHECK_NULL(p->sweeping_slot_set());
if (heap()->incremental_marking()->black_allocation()) {
p->CreateBlackArea(p->area_start(), p->HighWaterMark());
}
} else {
p->MergeOldToNewRememberedSets();
}
// Relinking requires the category to be unlinked.
other->RemovePage(p);
@ -1735,7 +1758,21 @@ void PagedSpace::MergeLocalSpace(LocalSpace* other) {
DCHECK_IMPLIES(
!p->IsFlagSet(Page::NEVER_ALLOCATE_ON_PAGE),
p->AvailableInFreeList() == p->AvailableInFreeListFromAllocatedBytes());
if (merging_from_off_thread) {
// TODO(leszeks): Allocation groups are currently not handled properly by
// the sampling allocation profiler. We'll have to come up with a better
// solution for allocation stepping before shipping.
AllocationStepAfterMerge(
p->area_start(),
static_cast<int>(p->HighWaterMark() - p->area_start()));
}
}
if (merging_from_off_thread) {
heap()->NotifyOffThreadSpaceMerged();
}
DCHECK_EQ(0u, other->Size());
DCHECK_EQ(0u, other->Capacity());
}
@ -4325,10 +4362,21 @@ void OldLargeObjectSpace::MergeOffThreadSpace(
while (!other->memory_chunk_list().Empty()) {
LargePage* page = other->first_page();
int size = page->GetObject().Size();
HeapObject object = page->GetObject();
int size = object.Size();
other->RemovePage(page, size);
AddPage(page, size);
AllocationStepAfterMerge(object.address(), size);
if (heap()->incremental_marking()->black_allocation()) {
heap()->incremental_marking()->marking_state()->WhiteToBlack(object);
}
DCHECK_IMPLIES(
heap()->incremental_marking()->black_allocation(),
heap()->incremental_marking()->marking_state()->IsBlack(object));
}
heap()->NotifyOffThreadSpaceMerged();
}
NewLargeObjectSpace::NewLargeObjectSpace(Heap* heap, size_t capacity)

View File

@ -17,6 +17,7 @@
#include "src/base/export-template.h"
#include "src/base/iterator.h"
#include "src/base/list.h"
#include "src/base/macros.h"
#include "src/base/platform/mutex.h"
#include "src/common/globals.h"
#include "src/flags/flags.h"
@ -430,6 +431,11 @@ class V8_EXPORT_PRIVATE Space : public Malloced {
void AllocationStep(int bytes_since_last, Address soon_object, int size);
// An AllocationStep equivalent to be called after merging a contiguous
// chunk of an off-thread space into this space. The chunk is treated as a
// single allocation-folding group.
void AllocationStepAfterMerge(Address first_object_in_chunk, int size);
// Return the total amount committed memory for this space, i.e., allocatable
// memory and page headers.
virtual size_t CommittedMemory() { return committed_; }
@ -2463,6 +2469,10 @@ class V8_EXPORT_PRIVATE PagedSpace
bool is_local_space() { return local_space_kind_ != LocalSpaceKind::kNone; }
bool is_off_thread_space() {
return local_space_kind_ == LocalSpaceKind::kOffThreadSpace;
}
bool is_compaction_space() {
return base::IsInRange(local_space_kind_,
LocalSpaceKind::kFirstCompactionSpace,
@ -3235,7 +3245,7 @@ class ReadOnlySpace : public PagedSpace {
// managed by the large object space.
// Large objects do not move during garbage collections.
class LargeObjectSpace : public Space {
class V8_EXPORT_PRIVATE LargeObjectSpace : public Space {
public:
using iterator = LargePageIterator;
@ -3259,7 +3269,7 @@ class LargeObjectSpace : public Space {
virtual void FreeUnmarkedObjects();
// Checks whether a heap object is in this space; O(1).
V8_EXPORT_PRIVATE bool Contains(HeapObject obj);
bool Contains(HeapObject obj);
// Checks whether an address is in the object area in this space. Iterates
// all objects in the space. May be slow.
bool ContainsSlow(Address addr);

View File

@ -18,7 +18,7 @@
#include "src/objects/js-collection.h"
#include "src/objects/js-weak-refs.h"
#include "src/objects/oddball.h"
#include "src/objects/ordered-hash-table.h"
#include "src/objects/ordered-hash-table-inl.h"
#include "src/objects/source-text-module.h"
#include "src/objects/synthetic-module.h"
#include "src/objects/transitions.h"

View File

@ -9,6 +9,7 @@
#include "src/execution/isolate.h"
#include "src/handles/handles.h"
#include "src/heap/off-thread-factory.h"
#include "src/heap/read-only-heap.h"
#include "src/objects/api-callbacks.h"
#include "src/objects/descriptor-array.h"
@ -65,6 +66,9 @@ ReadOnlyRoots::ReadOnlyRoots(Isolate* isolate)
: read_only_roots_(reinterpret_cast<Address*>(
isolate->roots_table().read_only_roots_begin().address())) {}
ReadOnlyRoots::ReadOnlyRoots(OffThreadFactory* factory)
: ReadOnlyRoots(factory->read_only_roots()) {}
ReadOnlyRoots::ReadOnlyRoots(Address* ro_roots) : read_only_roots_(ro_roots) {}
// We use unchecked_cast below because we trust our read-only roots to

View File

@ -17,6 +17,7 @@ namespace internal {
// Forward declarations.
enum ElementsKind : uint8_t;
class OffThreadFactory;
template <typename T>
class Handle;
class Heap;
@ -479,6 +480,7 @@ class ReadOnlyRoots {
V8_INLINE explicit ReadOnlyRoots(Heap* heap);
V8_INLINE explicit ReadOnlyRoots(Isolate* isolate);
V8_INLINE explicit ReadOnlyRoots(OffThreadFactory* factory);
#define ROOT_ACCESSOR(Type, name, CamelName) \
V8_INLINE class Type name() const; \

View File

@ -38,9 +38,7 @@ v8_executable("unittests") {
"//testing/gtest",
]
data_deps = [
"../../tools:v8_testrunner",
]
data_deps = [ "../../tools:v8_testrunner" ]
data = [
"testcfg.py",
@ -176,6 +174,7 @@ v8_source_set("unittests_sources") {
"heap/marking-worklist-unittest.cc",
"heap/memory-reducer-unittest.cc",
"heap/object-stats-unittest.cc",
"heap/off-thread-factory-unittest.cc",
"heap/scavenge-job-unittest.cc",
"heap/slot-set-unittest.cc",
"heap/spaces-unittest.cc",

View File

@ -0,0 +1,104 @@
// 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.
#include <cmath>
#include <iostream>
#include <limits>
#include <memory>
#include "src/handles/handles-inl.h"
#include "src/heap/off-thread-factory.h"
#include "src/objects/string.h"
#include "test/unittests/test-utils.h"
namespace v8 {
namespace internal {
class OffThreadFactoryTest : public TestWithIsolateAndZone {
public:
OffThreadFactoryTest()
: TestWithIsolateAndZone(), off_thread_factory_(isolate()) {}
OffThreadFactory* off_thread_factory() { return &off_thread_factory_; }
private:
OffThreadFactory off_thread_factory_;
};
TEST_F(OffThreadFactoryTest, OneByteInternalizedString_IsAddedToStringTable) {
Vector<const uint8_t> string_vector = StaticCharVector("foo");
uint32_t hash_field = StringHasher::HashSequentialString<uint8_t>(
string_vector.begin(), string_vector.length(), HashSeed(isolate()));
OffThreadHandle<String> off_thread_string =
off_thread_factory()->NewOneByteInternalizedString(string_vector,
hash_field);
// We only internalize strings which are referred to in other slots, so create
// a wrapper pointing at the off_thread_string.
// TODO(leszeks): Replace with a different holder (e.g. FixedArray) once
// OffThreadFactory supports it.
OffThreadHandle<FixedArray> off_thread_wrapper =
off_thread_factory()->StringWrapperForTest(off_thread_string);
off_thread_factory()->FinishOffThread();
Handle<FixedArray> wrapper = handle(*off_thread_wrapper, isolate());
off_thread_factory()->Publish(isolate());
Handle<String> string = handle(String::cast(wrapper->get(0)), isolate());
EXPECT_TRUE(string->IsOneByteEqualTo(CStrVector("foo")));
EXPECT_TRUE(string->IsInternalizedString());
Handle<String> same_string = isolate()
->factory()
->NewStringFromOneByte(string_vector)
.ToHandleChecked();
EXPECT_NE(*string, *same_string);
EXPECT_FALSE(same_string->IsInternalizedString());
Handle<String> internalized_string =
isolate()->factory()->InternalizeString(same_string);
EXPECT_EQ(*string, *internalized_string);
}
TEST_F(OffThreadFactoryTest,
OneByteInternalizedString_DuplicateIsDeduplicated) {
Vector<const uint8_t> string_vector = StaticCharVector("foo");
uint32_t hash_field = StringHasher::HashSequentialString<uint8_t>(
string_vector.begin(), string_vector.length(), HashSeed(isolate()));
OffThreadHandle<String> off_thread_string_1 =
off_thread_factory()->NewOneByteInternalizedString(string_vector,
hash_field);
OffThreadHandle<String> off_thread_string_2 =
off_thread_factory()->NewOneByteInternalizedString(string_vector,
hash_field);
// We only internalize strings which are referred to in other slots, so create
// a wrapper pointing at the off_thread_string.
// TODO(leszeks): Replace with a different holder (e.g. FixedArray) once
// OffThreadFactory supports it.
OffThreadHandle<FixedArray> off_thread_wrapper_1 =
off_thread_factory()->StringWrapperForTest(off_thread_string_1);
OffThreadHandle<FixedArray> off_thread_wrapper_2 =
off_thread_factory()->StringWrapperForTest(off_thread_string_2);
off_thread_factory()->FinishOffThread();
Handle<FixedArray> wrapper_1 = handle(*off_thread_wrapper_1, isolate());
Handle<FixedArray> wrapper_2 = handle(*off_thread_wrapper_2, isolate());
off_thread_factory()->Publish(isolate());
Handle<String> string_1 = handle(String::cast(wrapper_1->get(0)), isolate());
Handle<String> string_2 = handle(String::cast(wrapper_2->get(0)), isolate());
EXPECT_TRUE(string_1->IsOneByteEqualTo(CStrVector("foo")));
EXPECT_TRUE(string_1->IsInternalizedString());
EXPECT_EQ(*string_1, *string_2);
}
} // namespace internal
} // namespace v8

View File

@ -8,6 +8,7 @@
#include "src/execution/isolate.h"
#include "src/heap/heap-inl.h"
#include "src/heap/heap-write-barrier-inl.h"
#include "src/heap/heap.h"
#include "src/heap/spaces-inl.h"
#include "test/unittests/test-utils.h"
@ -136,6 +137,42 @@ TEST_F(SpacesTest, OffThreadSpaceMerge) {
old_space->CountTotalPages());
}
TEST_F(SpacesTest, OffThreadSpaceMergeDuringIncrementalMarking) {
Heap* heap = i_isolate()->heap();
OldSpace* old_space = heap->old_space();
EXPECT_TRUE(old_space != nullptr);
static const int kNumThreads = 10;
std::unique_ptr<OffThreadAllocationThread> threads[10];
for (int i = 0; i < kNumThreads; ++i) {
threads[i] = std::make_unique<OffThreadAllocationThread>(heap);
}
for (int i = 0; i < kNumThreads; ++i) {
CHECK(threads[i]->Start());
}
for (int i = 0; i < kNumThreads; ++i) {
threads[i]->Join();
}
heap->StartIncrementalMarking(Heap::kNoGCFlags,
GarbageCollectionReason::kTesting);
int pages_in_old_space = old_space->CountTotalPages();
int expected_merged_pages = 0;
for (int i = 0; i < kNumThreads; ++i) {
int pages_in_off_thread_space = threads[i]->space()->CountTotalPages();
old_space->MergeLocalSpace(threads[i]->space());
expected_merged_pages += pages_in_off_thread_space;
}
heap->FinalizeIncrementalMarkingAtomically(GarbageCollectionReason::kTesting);
EXPECT_EQ(pages_in_old_space + expected_merged_pages,
old_space->CountTotalPages());
}
class LargeOffThreadAllocationThread final : public base::Thread {
public:
explicit LargeOffThreadAllocationThread(Heap* heap)
@ -212,6 +249,36 @@ TEST_F(SpacesTest, OffThreadLargeObjectSpaceMerge) {
EXPECT_EQ(pages_in_old_space + expected_merged_pages, lo_space->PageCount());
}
TEST_F(SpacesTest, OffThreadLargeObjectSpaceMergeDuringIncrementalMarking) {
Heap* heap = i_isolate()->heap();
OldLargeObjectSpace* lo_space = heap->lo_space();
EXPECT_TRUE(lo_space != nullptr);
static const int kNumThreads = 10;
std::unique_ptr<LargeOffThreadAllocationThread> threads[10];
for (int i = 0; i < kNumThreads; ++i) {
threads[i] = std::make_unique<LargeOffThreadAllocationThread>(heap);
}
for (int i = 0; i < kNumThreads; ++i) {
CHECK(threads[i]->Start());
}
for (int i = 0; i < kNumThreads; ++i) {
threads[i]->Join();
}
int pages_in_old_space = lo_space->PageCount();
int expected_merged_pages = 0;
for (int i = 0; i < kNumThreads; ++i) {
int pages_in_off_thread_space = threads[i]->space()->PageCount();
lo_space->MergeOffThreadSpace(threads[i]->space());
expected_merged_pages += pages_in_off_thread_space;
}
EXPECT_EQ(pages_in_old_space + expected_merged_pages, lo_space->PageCount());
}
TEST_F(SpacesTest, WriteBarrierFromHeapObject) {
constexpr Address address1 = Page::kPageSize;
HeapObject object1 = HeapObject::unchecked_cast(Object(address1));